[
  {
    "path": ".gitattributes",
    "content": "# Set the default behavior, in case people don't have core.autocrlf set.\n* text eol=lf\n\n# Explicitly declare text files you want to always be normalized and converted\n# to native line endings on checkout.\n# *.c text\n# *.h text\n\n# Declare files that will always have CRLF line endings on checkout.\n*.cmd text eol=crlf\n*.bat text eol=crlf\n\n# Denote all files that are truly binary and should not be modified.\ntools/** binary\n*.jar binary\n*.exe binary\n*.apk binary\n*.png binary\n*.jpg binary\n*.ttf binary\n*.so binary\n*.wav binary\n\n# Help GitHub detect languages\nnative/jni/external/** linguist-vendored\nnative/jni/systemproperties/** linguist-language=C++\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: Bug report | 反馈 Bug\ndescription: Report bugs or unexpected behavior | 报告错误或未预料的行为\nlabels: [bug]\n\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thanks for reporting issues of APatch!\n        To better assist you, please provide the following information.\n        To avoid duplicate issues, please use English in the title.\n\n        感谢给 FolkPatch 汇报问题！\n        为了使我们更好地帮助你，请提供以下信息。\n        为了防止重复汇报，标题请务必使用英文。\n\n  - type: checkboxes\n    attributes:\n       label: Please check before submitting an issue | 在提交 Issue 前请检查\n       options:\n          - label: I searched the issues and didn't found anything relevant | 我已经搜索了 Issues 列表，没有发现于本问题相关内容\n            required: true\n\n          - label: If the patch fails or the image cannot be booted after flashing the new boot.img, visit KernelPatch to clarify your doubts | 修复失败或刷入修补后镜像不能启动，请前往 KernelPatch 提问\n            required: true\n\n          - label: I will upload the bug report file in APatch Manager > Settings > Send logs | 我会上传 Bug Report 文件从 APatch 管理器 > 设置 > 发送日志\n            required: true\n\n          - label: I know how to reproduce the issue, which might not be specific to my device | 我知道如何重新复现这个问题\n            required: false\n\n  - type: checkboxes\n    id: latest\n    attributes:\n      label: Version requirements | 版本要求\n      options:\n        - label: I'm using the latest CI version of APatch Manager | 我正在使用最新 CI 版本\n          required: true\n\n  - type: textarea\n    attributes:\n        label: Bug description | 描述 Bug\n        description: |\n          Please enter a clear and concise description of the bug.  \n          对 Bug 的清晰简洁的描述。\n    validations:\n        required: true\n\n  - type: textarea\n    attributes:\n        label: Reproduce method | 复现方法\n        description: |\n          Steps to reproduce the bug.  \n          复现的步骤。\n        placeholder: |\n          - 1. Go to...\n          - 2. Click on...\n          - 3. Scroll down to...\n          - 4. See error\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n        label: Expected behavior | 预期行为\n        description: |\n          Please enter a clear and concise description of what you expected to happen.  \n          对你期望发生的行为进行清晰简洁的描述。\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n        label: Actual behavior | 实际行为\n        description: |\n          Tell us what actually happened.  \n          告诉我们实际发生了什么。\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n        label: Screenshots | 截图\n        description: |\n          If possible, add screenshots to help explain your issue.  \n          如果可以的话，添加截图可以帮你解释问题。\n\n  - type: textarea\n    attributes:\n        label: Logs | 日志\n        description: |\n          If possible, add the crash log to help us find your issue.  \n          如果可以的话，添加崩溃日志可以帮助我们找到问题。\n\n  - type: input\n    attributes:\n      label: Device name | 设备名称\n    validations:\n      required: true\n\n  - type: input\n    attributes:\n      label: OS version | 系统版本\n    validations:\n      required: true\n\n  - type: input\n    attributes:\n      label: APatch version | FolkPatch 版本\n    validations:\n      required: true\n\n  - type: input\n    attributes:\n      label: Kernel version | 内核版本\n    validations:\n      required: true\n\n  - type: input\n    attributes:\n      label: KernelPatch version | KernelPatch 版本\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n        label: Other information | 其他信息\n        description: |\n          Add any information about the issue.  \n          添加关于问题的任何信息。\n        placeholder: |\n          Upload logs in .zip format by clicking the bottom bar. Uploading logs to other websites or using external links isn't allowed  \n          点击文本框底栏上传日志压缩包，禁止上传到其它网站或使用外链提供日志\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Ask a question | 提问\n    url: https://github.com/bmax121/APatch/discussions/new?category=Q-A\n    about: If you've any questions, ask them here | 如果有任何疑问请在这里提问\n  - name: Official Telegram channel | 官方 Telegram 频道\n    url: https://t.me/FolkPatch\n    about: Subscribe to receive releases and announcements | 可以订阅通知和发行版\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "---\nname: Feature request | 新特性请求\ndescription: Suggest an idea for this project | 提出建议\nlabels: [enhancement]\n\nbody:\n  - type: textarea\n    attributes:\n      label: Is your request related to a specific issue? | 你的请求是否与某个问题相关？\n      description: |\n        Please enter a clear and concise description of the issue.  \n        请清晰准确表述该问题。\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: Describe the solution you'd like | 描述你想要的解决方案\n      description: |\n        Please enter a clear and concise description of what you'd like.  \n        请清晰准确描述新特性的预期行为。\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: Describe the alternatives you've considered | 描述您考虑过的备选方案\n      description: |\n        Please enter a clear and concise description of any alternative solutions or features you've considered.  \n        对您考虑过的任何替代解决方案或功能的清晰简洁的描述。\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: Other information | 其他信息\n      description: |\n        Add any information or screenshots about the feature request.  \n        其他关于新特性的信息或者截图。\n    validations:\n      required: false\n"
  },
  {
    "path": ".github/actions/setup-build-env/action.yml",
    "content": "name: Setup Build Environment\ndescription: Install all build dependencies (Java, Android SDK, Gradle, Rust toolchain)\n\nruns:\n  using: composite\n  steps:\n    - uses: actions/setup-java@v4\n      with:\n        distribution: temurin\n        java-version: '21'\n\n    - uses: android-actions/setup-android@v3\n\n    - uses: gradle/actions/setup-gradle@v3\n\n    - uses: actions-rs/toolchain@v1\n      with:\n        toolchain: stable\n        target: aarch64-linux-android\n        override: true\n\n    - name: Install cargo-ndk\n      shell: bash\n      run: cargo install cargo-ndk\n\n    - name: Grant execute permission\n      shell: bash\n      run: chmod +x gradlew\n\n    - uses: actions/cache@v4\n      with:\n        path: |\n          ~/.gradle/caches\n          ~/.gradle/wrapper\n        key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}\n        restore-keys: ${{ runner.os }}-gradle-\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: gradle\n    directory: \"/\"\n    schedule:\n      interval: daily\n    target-branch: main\n    registries:\n      - maven-google\n      - gradle-plugin\n    groups:\n      maven-dependencies:\n        patterns:\n          - \"*\"\n\n  - package-ecosystem: github-actions\n    target-branch: main\n    directory: /\n    schedule:\n      interval: daily\n    groups:\n      action-dependencies:\n        patterns:\n          - \"*\"\n\n  - package-ecosystem: cargo\n    target-branch: main\n    directory: apd/\n    schedule:\n      interval: daily\n    allow:\n      - dependency-type: \"all\"\n    groups:\n      rust-dependencies:\n        patterns:\n          - \"*\"\n\nregistries:\n  maven-google:\n    type: maven-repository\n    url: \"https://dl.google.com/dl/android/maven2/\"\n  gradle-plugin:\n    type: maven-repository\n    url: \"https://plugins.gradle.org/m2/\"\n"
  },
  {
    "path": ".github/workflows/CI_up.yml",
    "content": "name: CI Upload to Telegram\n\non:\n  workflow_call:\n    inputs:\n      artifact_names:\n        required: true\n        type: string\n        description: 'Comma-separated artifact names to download'\n      message:\n        required: false\n        type: string\n        default: 'FolkPatch Build'\n        description: 'Telegram message caption'\n\njobs:\n  upload:\n    name: Upload to Telegram\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Check Telegram config\n        id: tg_check\n        run: |\n          if [ -n \"${{ secrets.TELEGRAM_BOT_TOKEN }}\" ] && [ -n \"${{ secrets.TELEGRAM_CHAT_ID }}\" ]; then\n            echo \"enabled=true\" >> \"$GITHUB_OUTPUT\"\n          else\n            echo \"::notice::Telegram secrets not configured, skipping upload\"\n          fi\n\n      - name: Checkout repository\n        if: steps.tg_check.outputs.enabled == 'true'\n        uses: actions/checkout@v5\n\n      - name: Download artifacts\n        if: steps.tg_check.outputs.enabled == 'true'\n        uses: actions/download-artifact@v4\n        with:\n          pattern: folkpatch-*\n          path: artifacts\n          merge-multiple: true\n\n      - name: Send to Telegram\n        if: steps.tg_check.outputs.enabled == 'true'\n        run: |\n          FILES=$(find artifacts -type f 2>/dev/null | sort)\n          FILE_COUNT=$(echo \"$FILES\" | grep -c . || true)\n          \n          if [ \"$FILE_COUNT\" -eq 0 ]; then\n            echo \"::error::No files found in artifacts\"\n            exit 1\n          fi\n          \n          CAPTION=\"${{ inputs.message }}\n          Branch: ${{ github.ref_name }}\n          Commit: ${{ github.sha }}\n          Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"\n          \n          if [ \"$FILE_COUNT\" -eq 1 ]; then\n            curl -sf -X POST \"https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendDocument\" \\\n              -F \"chat_id=${{ secrets.TELEGRAM_CHAT_ID }}\" \\\n              -F \"document=@$FILES\" \\\n              -F \"caption=${CAPTION}\"\n          else\n            FILE_ARR=()\n            while IFS= read -r f; do\n              FILE_ARR+=(\"$f\")\n            done <<< \"$FILES\"\n            \n            TOTAL=${#FILE_ARR[@]}\n            MEDIA_JSON=\"[\"\n            for i in \"${!FILE_ARR[@]}\"; do\n              FNAME=$(basename \"${FILE_ARR[$i]}\")\n              if [ $i -gt 0 ]; then\n                MEDIA_JSON+=\",\"\n              fi\n              if [ $((i + 1)) -eq $TOTAL ]; then\n                MEDIA_JSON+=\"{\\\"type\\\":\\\"document\\\",\\\"media\\\":\\\"attach://$FNAME\\\",\\\"caption\\\":\\\"${CAPTION}\\\"}\"\n              else\n                MEDIA_JSON+=\"{\\\"type\\\":\\\"document\\\",\\\"media\\\":\\\"attach://$FNAME\\\"}\"\n              fi\n            done\n            MEDIA_JSON+=\"]\"\n            \n            ARGS=(-X POST \"https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMediaGroup\")\n            ARGS+=(-F \"chat_id=${{ secrets.TELEGRAM_CHAT_ID }}\")\n            ARGS+=(-F \"media=${MEDIA_JSON}\")\n            \n            while IFS= read -r f; do\n              FNAME=$(basename \"$f\")\n              ARGS+=(-F \"$FNAME=@$f\")\n            done <<< \"$FILES\"\n            \n            curl -sf \"${ARGS[@]}\"\n          fi\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: Build FolkPatch\n\non:\n  push:\n    branches: [ main, develop ]\n    paths: [ 'app/**', 'fpd/**', '.github/workflows/build.yml' ]\n  pull_request:\n    branches: [ main, develop ]\n    paths: [ 'app/**', 'fpd/**', '.github/workflows/build.yml' ]\n  workflow_dispatch:\n    inputs:\n      build_type:\n        description: 'Build type'\n        required: true\n        default: 'both'\n        type: choice\n        options: [ both, debug, release ]\n\nenv:\n  KEYSTORE_FILE: debug.keystore\n  KEYSTORE_PASSWORD: android\n  KEY_ALIAS: androiddebugkey\n  KEY_PASSWORD: android\n\njobs:\n  prepare:\n    runs-on: ubuntu-latest\n    outputs:\n      build_debug: ${{ steps.set.outputs.debug }}\n      build_release: ${{ steps.set.outputs.release }}\n      commit_msg: ${{ steps.msg.outputs.text }}\n      commit_author: ${{ steps.msg.outputs.author }}\n      artifact_list: ${{ steps.set.outputs.artifact_list }}\n\n    steps:\n      - id: set\n        run: |\n          TYPE=\"${{ inputs.build_type }}\"\n          [ \"${{ github.event_name }}\" != \"workflow_dispatch\" ] && TYPE=\"both\"\n\n          DEBUG=\"false\"; RELEASE=\"false\"; LIST=\"\"\n          [ \"$TYPE\" = \"both\" -o \"$TYPE\" = \"debug\" ] && { DEBUG=\"true\"; LIST=\"folkpatch-debug-${{ github.sha }}\"; }\n          [ \"$TYPE\" = \"both\" -o \"$TYPE\" = \"release\" ] && {\n            RELEASE=\"true\"\n            [ -n \"$LIST\" ] && LIST=\"$LIST,\" || true\n            LIST=\"${LIST}folkpatch-release-${{ github.sha }}\"\n          }\n\n          echo \"debug=$DEBUG\" >> \"$GITHUB_OUTPUT\"\n          echo \"release=$RELEASE\" >> \"$GITHUB_OUTPUT\"\n          echo \"artifact_list=$LIST\" >> \"$GITHUB_OUTPUT\"\n\n      - id: msg\n        run: |\n          case \"${{ github.event_name }}\" in\n            push)         TEXT=\"${{ github.event.head_commit.message }}\"\n                           AUTHOR=\"${{ github.event.head_commit.author.name }}\" ;;\n            pull_request) TEXT=\"PR: ${{ github.event.pull_request.title }}\"\n                           AUTHOR=\"${{ github.event.pull_request.user.login }}\" ;;\n            *)            TEXT=\"FolkPatch Build\"\n                           AUTHOR=\"${{ github.actor }}\" ;;\n          esac\n          TEXT=\"${TEXT%%$'\\n'*}\"\n          echo \"text=$TEXT\" >> \"$GITHUB_OUTPUT\"\n          echo \"author=$AUTHOR\" >> \"$GITHUB_OUTPUT\"\n\n  build_debug:\n    name: Build Debug\n    needs: prepare\n    if: needs.prepare.outputs.build_debug == 'true'\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v5\n        with:\n          submodules: recursive\n\n      - uses: ./.github/actions/setup-build-env\n\n      - run: ./gradlew assembleDebug --no-daemon\n\n      - name: Rename APK\n        run: |\n          SHORT=\"${GITHUB_SHA::7}\"\n          APK=$(find app/build/outputs/apk/debug -name \"*.apk\" -type f | head -1)\n          [ -n \"$APK\" ] && mv \"$APK\" \"FolkPatch-Debug-${SHORT}.apk\"\n\n      - uses: actions/upload-artifact@v4\n        with:\n          name: folkpatch-debug-${{ github.sha }}\n          path: FolkPatch-Debug-*.apk\n          retention-days: 30\n\n  build_release:\n    name: Build Release\n    needs: prepare\n    if: needs.prepare.outputs.build_release == 'true'\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v5\n        with:\n          submodules: recursive\n\n      - uses: ./.github/actions/setup-build-env\n\n      - name: Decode keystore\n        run: |\n          echo \"${{ secrets.KEYSTORE_BASE64 }}\" | base64 -d > FolkPatch.jks\n\n      - name: Create keystore.properties\n        run: |\n          cat > keystore.properties << 'EOF'\n          KEYSTORE_FILE=../FolkPatch.jks\n          KEYSTORE_PASSWORD=${{ secrets.KEYSTORE_PASSWORD }}\n          KEY_ALIAS=${{ secrets.KEY_ALIAS }}\n          KEY_PASSWORD=${{ secrets.KEY_PASSWORD }}\n          EOF\n\n      - run: ./gradlew assembleRelease --no-daemon\n\n      - name: Debug APK location\n        run: find app/build/outputs -name \"*.apk\" -type f || echo \"No APK found\"\n\n      - name: Rename APK\n        run: |\n          SHORT=\"${GITHUB_SHA::7}\"\n          APK=$(find app/build/outputs/apk/release -name \"*.apk\" -type f | head -1)\n          [ -n \"$APK\" ] && mv \"$APK\" \"FolkPatch-Release-${SHORT}.apk\"\n\n      - uses: actions/upload-artifact@v4\n        with:\n          name: folkpatch-release-${{ github.sha }}\n          path: FolkPatch-Release-*.apk\n          retention-days: 30\n\n  upload:\n    name: Upload to Telegram\n    needs: [prepare, build_debug, build_release]\n    if: always() && needs.prepare.result == 'success' && (needs.build_debug.result == 'success' || needs.build_release.result == 'success') && github.actor != 'dependabot[bot]'\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Check Telegram config\n        id: tg_check\n        run: |\n          if [ -n \"${{ secrets.TELEGRAM_BOT_TOKEN }}\" ] && [ -n \"${{ secrets.TELEGRAM_CHAT_ID }}\" ]; then\n            echo \"enabled=true\" >> \"$GITHUB_OUTPUT\"\n          else\n            echo \"::notice::Telegram secrets not configured, skipping upload\"\n          fi\n\n      - name: Download artifacts\n        if: steps.tg_check.outputs.enabled == 'true'\n        uses: actions/download-artifact@v4\n        with:\n          pattern: folkpatch-*\n          path: artifacts\n          merge-multiple: true\n\n      - name: Send to Telegram\n        if: steps.tg_check.outputs.enabled == 'true'\n        run: |\n          FILES=$(find artifacts -type f 2>/dev/null | sort)\n          FILE_COUNT=$(echo \"$FILES\" | grep -c . || true)\n          \n          if [ \"$FILE_COUNT\" -eq 0 ]; then\n            echo \"::error::No files found in artifacts\"\n            exit 1\n          fi\n          \n          CAPTION=\"${{ needs.prepare.outputs.commit_msg }}\\nBranch: ${{ github.ref_name }}\\nCommit: ${{ github.sha }}\\nAuthor: ${{ needs.prepare.outputs.commit_author }}\\nRun: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"\n          \n          if [ \"$FILE_COUNT\" -eq 1 ]; then\n            curl -sf -X POST \"https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendDocument\" \\\n              -F \"chat_id=${{ secrets.TELEGRAM_CHAT_ID }}\" \\\n              -F \"document=@$FILES\" \\\n              -F \"caption=${CAPTION}\"\n          else\n            FILE_ARR=()\n            while IFS= read -r f; do\n              FILE_ARR+=(\"$f\")\n            done <<< \"$FILES\"\n            \n            TOTAL=${#FILE_ARR[@]}\n            MEDIA_JSON=\"[\"\n            for i in \"${!FILE_ARR[@]}\"; do\n              FNAME=$(basename \"${FILE_ARR[$i]}\")\n              if [ $i -gt 0 ]; then\n                MEDIA_JSON+=\",\"\n              fi\n              if [ $((i + 1)) -eq $TOTAL ]; then\n                MEDIA_JSON+=\"{\\\"type\\\":\\\"document\\\",\\\"media\\\":\\\"attach://$FNAME\\\",\\\"caption\\\":\\\"${CAPTION}\\\"}\"\n              else\n                MEDIA_JSON+=\"{\\\"type\\\":\\\"document\\\",\\\"media\\\":\\\"attach://$FNAME\\\"}\"\n              fi\n            done\n            MEDIA_JSON+=\"]\"\n            \n            ARGS=(-X POST \"https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMediaGroup\")\n            ARGS+=(-F \"chat_id=${{ secrets.TELEGRAM_CHAT_ID }}\")\n            ARGS+=(-F \"media=${MEDIA_JSON}\")\n            \n            while IFS= read -r f; do\n              FNAME=$(basename \"$f\")\n              ARGS+=(-F \"$FNAME=@$f\")\n            done <<< \"$FILES\"\n            \n            curl -sf \"${ARGS[@]}\"\n          fi\n"
  },
  {
    "path": ".gitignore",
    "content": "*.iml\n.gradle\n.idea\n.DS_Store\nbuild\ncaptures\n.cxx\n*.keystore\n*.jks\nkeystore.properties\nlocal.properties\nauth.properties\n\n# FolkPatch specific signing files\napp/folkpatch-release*.jks\nkeystore.properties\n.vscode\n.kotlin\napp/src/main/resources/\nprivate\n.claude/\n.opencode\n\n# Built native binaries\napp/src/main/assets/Service/\nfpd/target/\nfpd/.cargo/\n\n# demo project\ndemo\nKernelPatch"
  },
  {
    "path": "Build-Debug.bat",
    "content": "@echo off\r\nchcp 65001 > nul 2>&1\r\nsetlocal enabledelayedexpansion\r\n\r\necho [1/4] Entering apd directory...\r\ncd /d apd\r\nif errorlevel 1 (\r\n    echo Error: Failed to enter apd directory, please check if the directory exists!\r\n    pause\r\n    exit /b 1\r\n)\r\n\r\necho [2/4] Executing cargo clean...\r\ncargo clean\r\nif errorlevel 1 (\r\n    echo Error: cargo clean execution failed!\r\n    pause\r\n    exit /b 1\r\n)\r\n\r\necho [3/4] Returning to parent directory...\r\ncd ..\r\nif errorlevel 1 (\r\n    echo Error: Failed to return to parent directory!\r\n    pause\r\n    exit /b 1\r\n)\r\n\r\necho [4/4] Executing gradlew.bat assembleDebug...\r\n.\\gradlew.bat assembleDebug\r\nif errorlevel 1 (\r\n    echo Error: gradlew.bat assembleDebug execution failed!\r\n    pause\r\n    exit /b 1\r\n)\r\n\r\necho.\r\necho All commands executed successfully!\r\npause\r\nendlocal"
  },
  {
    "path": "Build-Release.bat",
    "content": "@echo off\r\nchcp 65001 > nul 2>&1\r\nsetlocal enabledelayedexpansion\r\n\r\necho [1/4] Entering apd directory...\r\ncd /d apd\r\nif errorlevel 1 (\r\n    echo Error: Failed to enter apd directory, please check if the directory exists!\r\n    pause\r\n    exit /b 1\r\n)\r\n\r\necho [2/4] Executing cargo clean...\r\ncargo clean\r\nif errorlevel 1 (\r\n    echo Error: cargo clean execution failed!\r\n    pause\r\n    exit /b 1\r\n)\r\n\r\necho [3/4] Returning to parent directory...\r\ncd ..\r\nif errorlevel 1 (\r\n    echo Error: Failed to return to parent directory!\r\n    pause\r\n    exit /b 1\r\n)\r\n\r\necho [4/4] Executing gradlew.bat assembleRelease...\r\n.\\gradlew.bat assembleRelease\r\nif errorlevel 1 (\r\n    echo Error: gradlew.bat assembleDebug execution failed!\r\n    pause\r\n    exit /b 1\r\n)\r\n\r\necho.\r\necho All commands executed successfully!\r\npause\r\nendlocal"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<https://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<https://www.gnu.org/licenses/why-not-lgpl.html>.\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n<img src='logo.png' width='500px' alt=\"FolkPatch logo\">\n\n[![Latest Release](https://img.shields.io/github/v/release/matsuzaka-yuki/FolkPatch?label=Release&logo=github)](https://github.com/LyraVoid/FolkPatch/releases/latest)\n[![Channel](https://img.shields.io/badge/Follow-Telegram-blue.svg?logo=telegram)](https://t.me/FolkPatch)\n[![GitHub License](https://img.shields.io/github/license/matsuzaka-yuki/FolkPatch?logo=gnu)](/LICENSE)\n\n</div>\n\n🌏 **README 语言:** [**English**](./README_EN.md) / [**中文**](./README.md) / [**日本語**](./README_JA.md)\n\nFolkPatch - 专注界面优化与功能扩展的Root管理工具\n\n通过我们的综合文档快速开始。无论是安装使用、模块管理，还是自定义设置，文档涵盖了您成功使用FolkPatch所需的所有内容。\n\n[📚 阅读完整文档](https://fp.mysqil.com/) →\n\n<table>\n  <tr>\n    <td><img alt=\"\" src=\"docs/1.png\"></td>\n    <td><img alt=\"\" src=\"docs/2.png\"></td>\n    <td><img alt=\"\" src=\"docs/3.png\"></td>\n  <tr>\n  <tr>\n    <td><img alt=\"\" src=\"docs/4.png\"></td>\n    <td><img alt=\"\" src=\"docs/5.png\"></td>\n    <td><img alt=\"\" src=\"docs/6.png\"></td>\n  <tr>\n</table>\n\n---\n\n## ✨ 介绍\n\n### 🎨 核心功能\n- [x] 基于 KernelPatch 的 Root 实现\n- [x] 无需重新编译内核即可 Hook 内核函数\n\n### 📱 前置要求\n\n- **必须：** 基于 ARM64 架构且 Linux 内核版本 3.18 至 6.15 的 Android 设备\n\n### 🎨 管理器的界面与设计\n- [x] 全新的 UI 与交互体验优化\n- [x] 个性化壁纸支持\n- [x] 国际化支持\n- [x] 动画性能与交互流畅度优化\n- [x] 界面视觉细节与动态效果提升\n- [x] 支持手动关闭自动更新检查，将版本升级的主导权交还给用户\n\n### 📦 模块相关\n- [x] APM: 类 Magisk 模块系统 , 支持批量刷入与全量备份\n- [x] KPM: 内核模块系统(支持 inline-hook 与 syscall-table-hook) , 支持自动加载\n- [x] 通过商店可以下载热门的 APM 或 KPM\n\n### ⚡ 技术特性\n- [x] 基于 [KernelPatch](https://github.com/bmax121/KernelPatch/)\n\n## 🚀 下载安装\n\n### 📦 使用指导\n\n1. **下载安装：**\n   从 [发布页面](https://github.com/LyraVoid/FolkPatch/releases/latest) 下载最新版安装包\n\n2. **安装应用：**\n   安装最新版安装包到你的 Android 设备\n\n3. **开始使用：**\n  阅读 https://fp.mysqil.com/\n\n## 🙏 开源致谢\n\n本项目基于以下开源项目：\n\n- [KernelPatch](https://github.com/bmax121/KernelPatch/) - 核心组件\n- [Magisk](https://github.com/topjohnwu/Magisk) - magiskpolicy\n- [KernelSU](https://github.com/tiann/KernelSU) - 应用UI和类似Magisk的模块支持\n- [Sukisu-Ultra](https://github.com/SukiSU-Ultra/SukiSU-Ultra) - 参考一些界面的设计\n- [APatch](https://github.com/bmax121/APatch) - 上游分支\n\n## 📄 许可证\n\n- FolkPatch 遵循 [GNU General Public License v3 (GPL-3)](http://www.gnu.org/copyleft/gpl.html) 许可证开源 , 作为二改者或分发者 , 您需遵守以下标准:\n- 若您修改了代码或在项目中集成了 FolkPatch 并向第三方分发 , 您的整个项目必须同样采用 GPLv3 协议开源\n- 分发二进制文件时 , 必须主动提供或承诺提供完整且可读的源代码\n- 严禁对软件授权本身收取许可费 , 您可以针对分发、技术支持或定制开发收费\n- 分发行为即代表您授予所有用户使用该项目涉及的您的相关专利\n- 本软件“按原样”提供 , 不含任何担保 , 原作者不对因使用本软件造成的任何损失负责\n- 任何违反上述条款的行为将导致您的 GPLv3 授权自动终止 , 届时 , 您将失去分发 FolkPatch 的合法权利 , 原作者保留依法追究著作权侵权责任(包括但不限于申请停止侵权禁令、经济赔偿及下架违规项目)的权利\n## 💬 社区交流\n\n### FolkPatch讨论交流\n- Telegram 频道: [@FolkPatch](https://t.me/FolkPatch)\n"
  },
  {
    "path": "README_EN.md",
    "content": "<div align=\"center\">\n<img src='logo.png' width='500px' alt=\"FolkPatch logo\">\n\n[![Latest Release](https://img.shields.io/github/v/release/matsuzaka-yuki/FolkPatch?label=Release&logo=github)](https://github.com/LyraVoid/FolkPatch/releases/latest)\n[![Channel](https://img.shields.io/badge/Follow-Telegram-blue.svg?logo=telegram)](https://t.me/FolkPatch)\n[![GitHub License](https://img.shields.io/github/license/matsuzaka-yuki/FolkPatch?logo=gnu)](/LICENSE)\n\n</div>\n\n🌏 **README Language:** [**English**](./README_EN.md) / [**中文**](./README.md) / [**日本語**](./README_JA.md)\n\nFolkPatch - A Root management tool focused on interface optimization and feature extension\n\nGet started quickly with our comprehensive documentation. Whether it's installation, module management, or custom settings, the documentation covers everything you need to successfully use FolkPatch.\n\n[📚 Read Full Documentation](https://fp.mysqil.com/) →\n\n<table>\n  <tr>\n    <td><img alt=\"\" src=\"docs/1.png\"></td>\n    <td><img alt=\"\" src=\"docs/2.png\"></td>\n    <td><img alt=\"\" src=\"docs/3.png\"></td>\n  <tr>\n  <tr>\n    <td><img alt=\"\" src=\"docs/4.png\"></td>\n    <td><img alt=\"\" src=\"docs/5.png\"></td>\n    <td><img alt=\"\" src=\"docs/6.png\"></td>\n  <tr>\n</table>\n\n---\n\n## ✨ Introduction\n\n### 🎨 Core Features\n- [x] Root implementation based on KernelPatch\n- [x] Hook kernel functions without recompiling the kernel\n\n### 📱 Prerequisites\n\n- **Required:** ARM64 architecture Android device with Linux kernel version 3.18 to 6.15\n\n### 🎨 Manager Interface & Design\n- [x] Brand new UI and interaction experience optimization\n- [x] Personalized wallpaper support\n- [x] Internationalization support\n- [x] Animation performance and interaction fluency optimization\n- [x] Interface visual details and dynamic effects enhancement\n- [x] Support for manually disabling automatic update checks, giving users control over version upgrades\n\n### 📦 Module Related\n- [x] APM: Magisk-like module system, supports batch flashing and full backup\n- [x] KPM: Kernel module system (supports inline-hook and syscall-table-hook), supports automatic loading\n- [x] Download popular APM or KPM through the store\n\n### ⚡ Technical Features\n- [x] Based on [KernelPatch](https://github.com/bmax121/KernelPatch/)\n\n## 🚀 Download & Install\n\n### 📦 Installation Guide\n\n1. **Download & Install:**\n   Download the latest installation package from the [Releases page](https://github.com/LyraVoid/FolkPatch/releases/latest)\n\n2. **Install App:**\n   Install the latest installation package to your Android device\n\n3. **Get Started:**\n   Read https://fp.mysqil.com/\n\n## 🙏 Open Source Credits\n\nThis project is based on the following open source projects:\n\n- [KernelPatch](https://github.com/bmax121/KernelPatch/) - Core component\n- [Magisk](https://github.com/topjohnwu/Magisk) - magiskpolicy\n- [KernelSU](https://github.com/tiann/KernelSU) - App UI and Magisk-like module support\n- [Sukisu-Ultra](https://github.com/SukiSU-Ultra/SukiSU-Ultra) - Referenced some interface designs\n- [APatch](https://github.com/bmax121/APatch) - Upstream branch\n\n## 📄 License\n\n- FolkPatch is open sourced under the [GNU General Public License v3 (GPL-3)](http://www.gnu.org/copyleft/gpl.html) license. As a modifier or distributor, you must comply with the following standards:\n- If you modify the code or integrate FolkPatch into your project and distribute it to a third party, your entire project must also be open sourced under the GPLv3 license\n- When distributing binary files, you must actively provide or promise to provide complete and readable source code\n- Strictly prohibit charging licensing fees for the software license itself. You may charge for distribution, technical support, or customized development\n- Distribution implies that you grant all users the relevant patents involved in the use of the project\n- This software is provided \"as is\", without any warranty. The original author is not responsible for any losses caused by using this software\n- Any violation of the above terms will automatically terminate your GPLv3 license. At that time, you will lose the legal right to distribute FolkPatch. The original author reserves the right to pursue copyright infringement liability (including but not limited to applying for injunctions to stop infringement, economic compensation, and removing infringing projects)\n## 💬 Community & Discussion\n\n### FolkPatch Discussion & Communication\n- Telegram Channel: [@FolkPatch](https://t.me/FolkPatch)\n"
  },
  {
    "path": "README_JA.md",
    "content": "<div align=\"center\">\n<img src='logo.png' width='500px' alt=\"FolkPatch logo\">\n\n[![Latest Release](https://img.shields.io/github/v/release/matsuzaka-yuki/FolkPatch?label=Release&logo=github)](https://github.com/LyraVoid/FolkPatch/releases/latest)\n[![Channel](https://img.shields.io/badge/Follow-Telegram-blue.svg?logo=telegram)](https://t.me/FolkPatch)\n[![GitHub License](https://img.shields.io/github/license/matsuzaka-yuki/FolkPatch?logo=gnu)](/LICENSE)\n\n</div>\n\n🌏 **README の言語:** [**English**](./README_EN.md) / [**中文**](./README.md) / [**日本語**](./README_JA.md)\n\nFolkPatch - インターフェースの最適化と拡張機能に重視した Root 管理ツール\n\n包括的なドキュメントですぐに開始しましょう。インストール、モジュールの管理、カスタム設定など FolkPatch を快適に使用するための情報はドキュメントに網羅しています。\n\n[📚 完全なドキュメントを読む](https://fp.mysqil.com/) →\n\n<table>\n  <tr>\n    <td><img alt=\"\" src=\"docs/1.png\"></td>\n    <td><img alt=\"\" src=\"docs/2.png\"></td>\n    <td><img alt=\"\" src=\"docs/3.png\"></td>\n  <tr>\n  <tr>\n    <td><img alt=\"\" src=\"docs/4.png\"></td>\n    <td><img alt=\"\" src=\"docs/5.png\"></td>\n    <td><img alt=\"\" src=\"docs/6.png\"></td>\n  <tr>\n</table>\n\n---\n\n## ✨ 紹介\n\n### 🎨 コア機能\n- [x] KernelPatch ベースの Root 実装\n- [x] カーネルの再コンパイルなしでカーネル関数をフック可能\n\n### 📱 前提条件\n\n- **必須：** ARM64 アーキテクチャベースで Linux カーネルバージョン 3.18 から 6.15 の Android デバイス\n\n### 🎨 マネージャーのインターフェースとデザイン\n- [x] 全く新しい UI とインタラクションエクスペリエンスの最適化\n- [x] パーソナライズされた壁紙サポート\n- [x] 国際化サポート\n- [x] アニメーションパフォーマンスとインタラクションの滑らかさの最適化\n- [x] インターフェースの視覚的詳細と動的効果の向上\n- [x] 自動更新チェックの手動無効化をサポートし、バージョンアップグレードの主導権をユーザーに返還\n\n### 📦 モジュール関連\n- [x] APM: Magisk ライクなモジュールシステム、一括フラッシュとフルバックアップをサポート\n- [x] KPM: カーネルモジュールシステム（inline-hook と syscall-table-hook をサポート）、自動ロードをサポート\n- [x] ストアから人気のある APM または KPM をダウンロード可能\n\n### ⚡ 技術的特徴\n- [x] [KernelPatch](https://github.com/bmax121/KernelPatch/) に基づいています\n\n## 🚀 ダウンロードとインストール\n\n### 📦 インストールガイド\n\n1. **ダウンロードとインストール:**\n   [リリースページ](https://github.com/LyraVoid/FolkPatch/releases/latest)から最新のインストールパッケージをダウンロード\n\n2. **アプリをインストール:**\n   最新のインストールパッケージをあなたの Android デバイスにインストール\n\n3. **使用開始:**\n   https://fp.mysqil.com/ を読んでください\n\n## 🙏 オープンソースクレジット\n\nこのプロジェクトは以下のオープンソースプロジェクトに基づいています:\n\n- [KernelPatch](https://github.com/bmax121/KernelPatch/) - コアコンポーネント\n- [Magisk](https://github.com/topjohnwu/Magisk) - magiskpolicy\n- [KernelSU](https://github.com/tiann/KernelSU) - アプリ UI と Magisk ライクなモジュールサポート\n- [Sukisu-Ultra](https://github.com/SukiSU-Ultra/SukiSU-Ultra) - 一部のインターフェースデザインを参照\n- [APatch](https://github.com/bmax121/APatch) - 上流ブランチ\n\n## 📄 ライセンス\n\n- FolkPatch は [GNU General Public License v3 (GPL-3)](http://www.gnu.org/copyleft/gpl.html) ライセンスの下でオープンソースされています。変更者または配布者として、以下の基準を遵守する必要があります:\n- コードを変更した場合、またはプロジェクトに FolkPatch を統合して第三者に配布する場合、プロジェクト全体も GPLv3 ライセンスの下でオープンソースする必要があります\n- バイナリファイルを配布する場合、完全かつ読み取り可能なソースコードを積極的に提供するか、提供することを約束する必要があります\n- ソフトウェアライセンス自体に対するライセンス料の徴収を厳禁します。配布、技術サポート、カスタム開発に対して料金を請求できます\n- 配布行為は、プロジェクトに関連するすべてのユーザーにあなたの関連特許の使用権を付与することを意味します\n- 本ソフトウェアは「現状のまま」提供され、いかなる保証もありません。原作者は本ソフトウェアの使用による損失について責任を負いません\n- 上記の条項に違反すると GPLv3 ライセンスは自動的に終了します。その際、FolkPatch を配布する正当な権利を失い、原作者は著作権侵害の責任を追求する権利（侵害停止命令の申請、経済的賠償、違反プロジェクトの削除を含むがこれらに限定されない）を留保します\n## 💬 コミュニティとディスカッション\n\n### FolkPatch ディスカッションとコミュニケーション\n- Telegram チャンネル: [@FolkPatch](https://t.me/FolkPatch)\n"
  },
  {
    "path": "apd/.gitignore",
    "content": "/target\n.cargo/"
  },
  {
    "path": "apd/Cargo.toml",
    "content": "[package]\nname = \"apd\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\nmlua = { version = \"0.11.5\", features = [\"lua54\",\"vendored\"] }\nanyhow = \"1\"\ncsv = \"1.3.1\"\nclap = { version = \"4\", features = [\"derive\"] }\nconst_format = \"0.2\"\nzip = { version = \"7.2.0\",features = [\n    \"deflate\",\n    \"deflate64\",\n    \"time\",\n    \"lzma\",\n    \"xz\",\n], default-features = false }\nzip-extensions = { git = \"https://github.com/AndroidPatch/zip-extensions-rs.git\", branch = \"master\", features = [\n    \"deflate\",\n    \"lzma\",\n    \"xz\",\n], default-features = false }\njava-properties = { git = \"https://github.com/AndroidPatch/java-properties.git\", branch = \"master\", default-features = false }\nlog = \"0.4\"\nenv_logger = \"0.11\"\nserde = { version = \"1\", features = [\"derive\"] }\nserde_json = \"1\"\nencoding_rs = \"0.8\"\nwalkdir=\"2.4\"\nretry = \"2\"\nlibc = \"0.2\"\nextattr = \"1\"\njwalk = \"0.8\"\nis_executable = \"1\"\nnom = \"8\"\nderive-new = \"0.7.0\"\nwhich = \"8\"\ngetopts = \"0.2\"\nerrno = \"0.3.14\"\nnotify = \"8.2\"\nsignal-hook = \"0.4\"\nregex-lite = \"0.1.9\"\n\n[target.'cfg(any(target_os = \"android\", target_os = \"linux\"))'.dependencies]\nrustix = { version = \"1\", features = [\"all-apis\"] }\n# some android specific dependencies which compiles under unix are also listed here for convenience of coding\nandroid-properties = { version = \"0.2.2\", features = [\"bionic-deprecated\"] }\nprocfs = \"0.18\"\nloopdev = { git = \"https://github.com/AndroidPatch/loopdev\" }\nprop-rs-android = { git = \"https://github.com/Kernel-SU/ksu_props\" }\npolicy = {git = \"https://github.com/AndroidPatch/ap_policy\"}\n[target.'cfg(target_os = \"android\")'.dependencies]\nandroid_logger = { version = \"0.15\", default-features = false }\n\n[profile.release]\nstrip = true\noverflow-checks = false\nrpath = false\nopt-level = 3\ncodegen-units = 1\npanic = \"abort\"\nlto = \"fat\"\n"
  },
  {
    "path": "apd/build.rs",
    "content": "use std::{env, fs::File, io::Write, path::Path, process::Command};\n\nfn get_git_version() -> Result<(u32, String), std::io::Error> {\n    // Try to get version code from environment variable first\n    let version_code: u32 = if let Ok(env_version_code) = env::var(\"APATCH_VERSION_CODE\") {\n        env_version_code.parse().map_err(|_| {\n            std::io::Error::new(std::io::ErrorKind::Other, \"Failed to parse {version_code}\")\n        })?\n    } else {\n        // Fallback to git-based calculation\n        let output = Command::new(\"git\")\n            .args([\"rev-list\", \"--count\", \"HEAD\"])\n            .output()?;\n\n        let output = output.stdout;\n        let git_count = String::from_utf8(output).expect(\"Failed to read git count stdout\");\n        let git_count: u32 = git_count.trim().parse().map_err(|_| {\n            std::io::Error::new(std::io::ErrorKind::Other, \"Failed to parse git count\")\n        })?;\n        std::cmp::max(11000 + 200 + git_count, 10762) // For historical reasons and ensure minimum version\n    };\n\n    let version_name = if let Ok(env_version_name) = env::var(\"APATCH_VERSION_NAME\") {\n        env_version_name\n    } else {\n        \"113005-Matsuzaka-yuki\".to_string()\n    };\n\n    Ok((version_code, version_name))\n}\n\nfn main() {\n    // update VersionCode when git repository change\n    println!(\"cargo:rerun-if-changed=../.git/HEAD\");\n    println!(\"cargo:rerun-if-changed=../.git/refs/\");\n\n    let (code, name) = match get_git_version() {\n        Ok((code, name)) => (code, name),\n        Err(_) => {\n            // show warning if git is not installed\n            println!(\"cargo:warning=Failed to get git version, using 0.0.0\");\n            (0, \"0.0.0\".to_string())\n        }\n    };\n    let out_dir = env::var(\"OUT_DIR\").expect(\"Failed to get $OUT_DIR\");\n    println!(\"out_dir: ${out_dir}\");\n    println!(\"code: ${code}\");\n    let out_dir = Path::new(&out_dir);\n    File::create(Path::new(out_dir).join(\"VERSION_CODE\"))\n        .expect(\"Failed to create VERSION_CODE\")\n        .write_all(code.to_string().as_bytes())\n        .expect(\"Failed to write VERSION_CODE\");\n\n    File::create(Path::new(out_dir).join(\"VERSION_NAME\"))\n        .expect(\"Failed to create VERSION_NAME\")\n        .write_all(name.trim().as_bytes())\n        .expect(\"Failed to write VERSION_NAME\");\n}\n"
  },
  {
    "path": "apd/src/apd.rs",
    "content": "#[cfg(unix)]\nuse std::os::unix::process::CommandExt;\nuse std::{env, ffi::CStr, path::PathBuf, process::Command};\n\nuse anyhow::{Ok, Result};\n#[cfg(unix)]\nuse getopts::Options;\nuse rustix::thread::{Gid, Uid, set_thread_res_gid, set_thread_res_uid};\n\n#[cfg(any(target_os = \"linux\", target_os = \"android\"))]\nuse crate::pty::prepare_pty;\nuse crate::{\n    defs,\n    utils::{self, umask},\n};\n\nfn print_usage(opts: Options) {\n    let brief = \"APatch\\n\\nUsage: <command> [options] [-] [user [argument...]]\".to_string();\n    print!(\"{}\", opts.usage(&brief));\n}\n\nfn set_identity(uid: u32, gid: u32) {\n    #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n    let gid = Gid::from_raw(gid);\n    let uid = Uid::from_raw(uid);\n    set_thread_res_gid(gid, gid, gid).ok();\n    set_thread_res_uid(uid, uid, uid).ok();\n}\n\n#[cfg(not(unix))]\npub fn root_shell() -> Result<()> {\n    unimplemented!()\n}\n\n#[cfg(unix)]\npub fn root_shell() -> Result<()> {\n    // we are root now, this was set in kernel!\n    let env_args: Vec<String> = env::args().collect();\n    let args = env_args\n        .iter()\n        .position(|arg| arg == \"-c\")\n        .map(|i| {\n            let rest = env_args[i + 1..].to_vec();\n            let mut new_args = env_args[..i].to_vec();\n            new_args.push(\"-c\".to_string());\n            if !rest.is_empty() {\n                new_args.push(rest.join(\" \"));\n            }\n            new_args\n        })\n        .unwrap_or_else(|| env_args.clone());\n\n    let mut opts = Options::new();\n    opts.optopt(\n        \"c\",\n        \"command\",\n        \"pass COMMAND to the invoked shell\",\n        \"COMMAND\",\n    );\n    opts.optflag(\"h\", \"help\", \"display this help message and exit\");\n    opts.optflag(\"l\", \"login\", \"pretend the shell to be a login shell\");\n    opts.optflag(\n        \"p\",\n        \"preserve-environment\",\n        \"preserve the entire environment\",\n    );\n    opts.optopt(\n        \"s\",\n        \"shell\",\n        \"use SHELL instead of the default /system/bin/sh\",\n        \"SHELL\",\n    );\n    opts.optflag(\"v\", \"version\", \"display version number and exit\");\n    opts.optflag(\"V\", \"\", \"display version code and exit\");\n    opts.optflag(\n        \"M\",\n        \"mount-master\",\n        \"force run in the global mount namespace\",\n    );\n    opts.optflag(\"\", \"no-pty\", \"Do not allocate a new pseudo terminal.\");\n\n    // Replace -cn with -z, -mm with -M for supporting getopt_long\n    let args = args\n        .into_iter()\n        .map(|e| {\n            if e == \"-mm\" {\n                \"-M\".to_string()\n            } else if e == \"-cn\" {\n                \"-z\".to_string()\n            } else {\n                e\n            }\n        })\n        .collect::<Vec<String>>();\n\n    let matches = match opts.parse(&args[1..]) {\n        Result::Ok(m) => m,\n        Err(f) => {\n            println!(\"{f}\");\n            print_usage(opts);\n            std::process::exit(-1);\n        }\n    };\n\n    if matches.opt_present(\"h\") {\n        print_usage(opts);\n        return Ok(());\n    }\n\n    if matches.opt_present(\"v\") {\n        println!(\"{}:APatch(FolkPatch)\", defs::VERSION_NAME);\n        return Ok(());\n    }\n\n    if matches.opt_present(\"V\") {\n        println!(\"{}\", defs::VERSION_CODE);\n        return Ok(());\n    }\n\n    let shell = matches.opt_str(\"s\").unwrap_or(\"/system/bin/sh\".to_string());\n    let mut is_login = matches.opt_present(\"l\");\n    let preserve_env = matches.opt_present(\"p\");\n    let mount_master = matches.opt_present(\"M\");\n\n    // we've made sure that -c is the last option and it already contains the whole command, no need to construct it again\n    let args = matches\n        .opt_str(\"c\")\n        .map(|cmd| vec![\"-c\".to_string(), cmd])\n        .unwrap_or_default();\n\n    let mut free_idx = 0;\n    if !matches.free.is_empty() && matches.free[free_idx] == \"-\" {\n        is_login = true;\n        free_idx += 1;\n    }\n\n    // use current uid if no user specified, these has been done in kernel!\n    let mut uid = unsafe { libc::getuid() };\n    let gid = unsafe { libc::getgid() };\n    if free_idx < matches.free.len() {\n        let name = &matches.free[free_idx];\n        uid = {\n            #[cfg(target_arch = \"aarch64\")]\n            let pw = unsafe { libc::getpwnam(name.as_ptr()).as_ref() };\n            #[cfg(target_arch = \"x86_64\")]\n            let pw = unsafe { libc::getpwnam(name.as_ptr() as *const i8).as_ref() };\n\n            match pw {\n                Some(pw) => pw.pw_uid,\n                None => name.parse::<u32>().unwrap_or(0),\n            }\n        }\n    }\n\n    // https://github.com/topjohnwu/Magisk/blob/master/native/src/core/su/su_daemon.cpp#L408\n    let arg0 = if is_login { \"-\" } else { &shell };\n\n    let mut command = &mut Command::new(&shell);\n\n    if !preserve_env {\n        // This is actually incorrect, I don't know why.\n        // command = command.env_clear();\n\n        let pw = unsafe { libc::getpwuid(uid).as_ref() };\n\n        if let Some(pw) = pw {\n            let home = unsafe { CStr::from_ptr(pw.pw_dir) };\n            let pw_name = unsafe { CStr::from_ptr(pw.pw_name) };\n\n            let home = home.to_string_lossy();\n            let pw_name = pw_name.to_string_lossy();\n\n            command = command\n                .env(\"HOME\", home.as_ref() as &str)\n                .env(\"USER\", pw_name.as_ref() as &str)\n                .env(\"LOGNAME\", pw_name.as_ref() as &str)\n                .env(\"SHELL\", &shell);\n        }\n    }\n\n    // add /data/adb/ap/bin to PATH\n    #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n    add_path_to_env(defs::BINARY_DIR)?;\n\n    // when AP_RC_PATH exists and ENV is not set, set ENV to AP_RC_PATH\n    if PathBuf::from(defs::AP_RC_PATH).exists() && env::var(\"ENV\").is_err() {\n        command = command.env(\"ENV\", defs::AP_RC_PATH);\n    }\n    #[cfg(target_os = \"android\")]\n    if !matches.opt_present(\"no-pty\") {\n        if let Err(e) = prepare_pty() {\n            log::error!(\"failed to prepare pty: {:?}\", e);\n        }\n    }\n    // escape from the current cgroup and become session leader\n    // WARNING!!! This cause some root shell hang forever!\n    // command = command.process_group(0);\n    command = unsafe {\n        command.pre_exec(move || {\n            umask(0o22);\n            utils::switch_cgroups();\n\n            // switch to global mount namespace\n            #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n            let global_namespace_enable =\n                std::fs::read_to_string(defs::GLOBAL_NAMESPACE_FILE).unwrap_or(\"0\".to_string());\n            if global_namespace_enable.trim() == \"1\" || mount_master {\n                let _ = utils::switch_mnt_ns(1);\n            }\n\n            set_identity(uid, gid);\n\n            Result::Ok(())\n        })\n    };\n\n    command = command.args(args).arg0(arg0);\n    Err(command.exec().into())\n}\n\nfn add_path_to_env(path: &str) -> Result<()> {\n    let mut paths =\n        env::var_os(\"PATH\").map_or(Vec::new(), |val| env::split_paths(&val).collect::<Vec<_>>());\n    let new_path = PathBuf::from(path.trim_end_matches('/'));\n    paths.push(new_path);\n    let new_path_env = env::join_paths(paths)?;\n    unsafe { env::set_var(\"PATH\", new_path_env) };\n    Ok(())\n}\n"
  },
  {
    "path": "apd/src/assets.rs",
    "content": "use anyhow::Result;\nuse const_format::concatcp;\n\nuse crate::{defs::BINARY_DIR, utils};\n\npub const RESETPROP_PATH: &str = concatcp!(BINARY_DIR, \"resetprop\");\npub const BUSYBOX_PATH: &str = concatcp!(BINARY_DIR, \"busybox\");\npub const MAGISKPOLICY_PATH: &str = concatcp!(BINARY_DIR, \"magiskpolicy\");\n\npub fn ensure_binaries() -> Result<()> {\n    utils::ensure_binary(BUSYBOX_PATH)?;\n    let resetprop_link = RESETPROP_PATH;\n    let _ = std::fs::remove_file(resetprop_link);\n    std::os::unix::fs::symlink(\"/data/adb/apd\", resetprop_link)?;\n\n    let magiskpolicy_link = MAGISKPOLICY_PATH;\n    let _ = std::fs::remove_file(magiskpolicy_link);\n    std::os::unix::fs::symlink(\"/data/adb/apd\", magiskpolicy_link)?;\n\n    Ok(())\n}\n"
  },
  {
    "path": "apd/src/banner",
    "content": "   ___  ___  __          ___  _   _____  ___        \n  / __\\/___\\/ /   /\\ /\\ / _ \\/_\\ /__   \\/ __\\ /\\  /\\\n / _\\ //  // /   / //_// /_)//_\\\\  / /\\/ /   / /_/ /\n/ /  / \\_// /___/ __ \\/ ___/  _  \\/ / / /___/ __  / \n\\/   \\___/\\____/\\/  \\/\\/   \\_/ \\_/\\/  \\____/\\/ /_/  \n                                                    "
  },
  {
    "path": "apd/src/cli.rs",
    "content": "use crate::{defs, event, lua, module, module_config, supercall, utils};\n#[cfg(target_os = \"android\")]\nuse android_logger::Config;\nuse anyhow::{Context, Result};\nuse clap::Parser;\n#[cfg(target_os = \"android\")]\nuse log::LevelFilter;\n\n/// APatch cli\n#[derive(Parser, Debug)]\n#[command(author, version = defs::VERSION_CODE, about, long_about = None)]\nstruct Args {\n    #[arg(\n        short,\n        long,\n        value_name = \"KEY\",\n        help = \"Super key for authentication root\"\n    )]\n    superkey: Option<String>,\n    #[command(subcommand)]\n    command: Commands,\n}\n\n#[derive(clap::Subcommand, Debug)]\nenum Commands {\n    /// Manage APatch modules\n    Module {\n        #[command(subcommand)]\n        command: Module,\n    },\n\n    /// Trigger `post-fs-data` event\n    PostFsData,\n\n    /// Trigger `service` event\n    Services,\n\n    /// Trigger `boot-complete` event\n    BootCompleted,\n\n    /// Start uid listener for synchronizing root list\n    UidListener,\n\n    /// Resetprop - Magisk-compatible system property tool\n    Resetprop(crate::resetprop::Args),\n\n    /// MagiskPolicy - SELinux Policy Patch Tool\n    Sepolicy(crate::sepolicy::Args),\n}\n\n#[derive(clap::Subcommand, Debug)]\nenum Module {\n    /// Install module <ZIP>\n    Install {\n        /// module zip file path\n        zip: String,\n    },\n\n    /// Uninstall module <id>\n    Uninstall {\n        /// module id\n        id: String,\n    },\n\n    /// Undo uninstall module <id>\n    UndoUninstall {\n        /// module id\n        id: String,\n    },\n\n    /// enable module <id>\n    Enable {\n        /// module id\n        id: String,\n    },\n\n    /// disable module <id>\n    Disable {\n        // module id\n        id: String,\n    },\n\n    /// run action for module <id>\n    Action {\n        // module id\n        id: String,\n    },\n    /// module lua runner\n    Lua {\n        // module id\n        id: String,\n        // lua function\n        function: String,\n    },\n    /// list all modules\n    List,\n\n    /// manage module configuration\n    Config {\n        /// target internal module name (resolved as internal.<name>)\n        #[arg(long)]\n        internal: Option<String>,\n        #[command(subcommand)]\n        command: ModuleConfigCmd,\n    },\n}\n\n#[derive(clap::Subcommand, Debug)]\nenum ModuleConfigCmd {\n    /// Get a config value\n    Get {\n        /// config key\n        key: String,\n    },\n\n    /// Set a config value\n    Set {\n        /// config key\n        key: String,\n        /// config value (omit to read from stdin)\n        value: Option<String>,\n        /// read value from stdin (default if value not provided)\n        #[arg(long)]\n        stdin: bool,\n        /// use temporary config (cleared on reboot)\n        #[arg(short, long)]\n        temp: bool,\n    },\n\n    /// List all config entries\n    List,\n\n    /// Delete a config entry\n    Delete {\n        /// config key\n        key: String,\n        /// delete from temporary config\n        #[arg(short, long)]\n        temp: bool,\n    },\n\n    /// Clear all config entries\n    Clear {\n        /// clear temporary config\n        #[arg(short, long)]\n        temp: bool,\n    },\n}\n\npub fn run() -> Result<()> {\n    #[cfg(target_os = \"android\")]\n    android_logger::init_once(\n        Config::default()\n            .with_max_level(LevelFilter::Trace) // limit log level\n            .with_tag(\"APatchD\")\n            .with_filter(\n                android_logger::FilterBuilder::new()\n                    .filter_level(LevelFilter::Trace)\n                    .filter_module(\"notify\", LevelFilter::Warn)\n                    .build(),\n            ),\n    );\n\n    #[cfg(not(target_os = \"android\"))]\n    env_logger::init();\n\n    // the kernel executes su with argv[0] = \"/system/bin/kp\" or \"/system/bin/su\" or \"su\" or \"kp\" and replace it with us\n    let arg0 = std::env::args().next().unwrap_or_default();\n    if arg0.ends_with(\"kp\") || arg0.ends_with(\"su\") {\n        return crate::apd::root_shell();\n    }\n    if arg0.ends_with(\"resetprop\") {\n        let all_args: Vec<String> = std::env::args().collect();\n        crate::resetprop::resetprop_main(&all_args)\n    }\n    if arg0.ends_with(\"magiskpolicy\") {\n        let all_args: Vec<String> = std::env::args().collect();\n        crate::sepolicy::policy_main(&all_args)\n    }\n\n    let cli = Args::parse();\n\n    log::info!(\"command: {:?}\", cli.command);\n\n    if let Some(ref _superkey) = cli.superkey {\n        supercall::privilege_apd_profile(&cli.superkey);\n    }\n\n    let result = match cli.command {\n        Commands::PostFsData => event::on_post_data_fs(cli.superkey),\n\n        Commands::BootCompleted => event::on_boot_completed(cli.superkey),\n\n        Commands::UidListener => event::start_uid_listener(),\n\n        Commands::Module { command } => {\n            #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n            {\n                utils::switch_mnt_ns(1)?;\n            }\n            match command {\n                Module::Install { zip } => module::install_module(&zip),\n                Module::Uninstall { id } => module::uninstall_module(&id),\n                Module::UndoUninstall { id } => module::undo_uninstall_module(&id),\n                Module::Action { id } => module::run_action(&id),\n                Module::Lua { id, function } => {\n                    lua::run_lua(&id, &function, false, true).map_err(|e| anyhow::anyhow!(\"{}\", e))\n                }\n                Module::Enable { id } => module::enable_module(&id),\n                Module::Disable { id } => module::disable_module(&id),\n                Module::List => module::list_modules(),\n                Module::Config { internal, command } => {\n                    let module_id = match internal {\n                        Some(internal_name) => format!(\"internal.{internal_name}\"),\n                        None => std::env::var(\"AP_MODULE\").map_err(|_| {\n                            anyhow::anyhow!(\n                                \"This command must be run in the context of a module or passed --internal <name>\"\n                            )\n                        })?,\n                    };\n\n                    match command {\n                        ModuleConfigCmd::Get { key } => {\n                            // Use merge_configs to respect priority (temp overrides persist)\n                            let config = module_config::merge_configs(&module_id)?;\n                            match config.get(&key) {\n                                Some(value) => {\n                                    println!(\"{value}\");\n                                    Ok(())\n                                }\n                                None => anyhow::bail!(\"Key '{key}' not found\"),\n                            }\n                        }\n                        ModuleConfigCmd::Set {\n                            key,\n                            value,\n                            stdin,\n                            temp,\n                        } => {\n                            // Validate key at CLI layer for better user experience\n                            module_config::validate_config_key(&key)?;\n\n                            // Read value from stdin or argument\n                            let value_str = match value {\n                                Some(v) if !stdin => v,\n                                _ => {\n                                    // Read from stdin\n                                    use std::io::Read;\n                                    let mut buffer = String::new();\n                                    std::io::stdin()\n                                        .read_to_string(&mut buffer)\n                                        .context(\"Failed to read from stdin\")?;\n                                    buffer\n                                }\n                            };\n\n                            // Validate value\n                            module_config::validate_config_value(&value_str)?;\n\n                            let config_type = if temp {\n                                module_config::ConfigType::Temp\n                            } else {\n                                module_config::ConfigType::Persist\n                            };\n                            module_config::set_config_value(\n                                &module_id,\n                                &key,\n                                &value_str,\n                                config_type,\n                            )\n                        }\n                        ModuleConfigCmd::List => {\n                            let config = module_config::merge_configs(&module_id)?;\n                            if config.is_empty() {\n                                println!(\"No config entries found\");\n                            } else {\n                                for (key, value) in config {\n                                    println!(\"{key}={value}\");\n                                }\n                            }\n                            Ok(())\n                        }\n                        ModuleConfigCmd::Delete { key, temp } => {\n                            let config_type = if temp {\n                                module_config::ConfigType::Temp\n                            } else {\n                                module_config::ConfigType::Persist\n                            };\n                            module_config::delete_config_value(&module_id, &key, config_type)\n                        }\n                        ModuleConfigCmd::Clear { temp } => {\n                            let config_type = if temp {\n                                module_config::ConfigType::Temp\n                            } else {\n                                module_config::ConfigType::Persist\n                            };\n                            module_config::clear_config(&module_id, config_type)\n                        }\n                    }\n                }\n            }\n        }\n\n        Commands::Services => event::on_services(cli.superkey),\n\n        Commands::Resetprop(resetprop_args) => crate::resetprop::execute(&resetprop_args)\n            .inspect_err(|e| {\n                if e.downcast_ref::<crate::resetprop::WaitTimeoutError>()\n                    .is_some()\n                {\n                    std::process::exit(2);\n                }\n            }),\n\n        Commands::Sepolicy(sepolicy_args) => crate::sepolicy::execute(&sepolicy_args),\n    };\n\n    if let Err(e) = &result {\n        log::error!(\"Error: {:?}\", e);\n    }\n    result\n}\n"
  },
  {
    "path": "apd/src/defs.rs",
    "content": "use const_format::concatcp;\n\npub const ADB_DIR: &str = \"/data/adb/\";\npub const WORKING_DIR: &str = concatcp!(ADB_DIR, \"ap/\");\npub const BINARY_DIR: &str = concatcp!(WORKING_DIR, \"bin/\");\npub const APATCH_LOG_FOLDER: &str = concatcp!(WORKING_DIR, \"log/\");\n\npub const AP_RC_PATH: &str = concatcp!(WORKING_DIR, \".aprc\");\npub const GLOBAL_NAMESPACE_FILE: &str = concatcp!(ADB_DIR, \".global_namespace_enable\");\npub const MAGIC_MOUNT_FILE: &str = concatcp!(ADB_DIR, \".magic_mount_enable\");\npub const HIDE_SERVICE_FILE: &str = concatcp!(ADB_DIR, \".hide_service_enable\");\npub const HIDE_BINARY_PATH: &str = concatcp!(ADB_DIR, \"fp/bin/fpd\");\npub const UMOUNT_SERVICE_FILE: &str = concatcp!(ADB_DIR, \".umount_service_enable\");\npub const UMOUNT_BINARY_PATH: &str = concatcp!(ADB_DIR, \"fp/bin/fpd\");\npub const UTS_SPOOF_ENABLE_FILE: &str = concatcp!(ADB_DIR, \".uts_spoof_enable\");\npub const UTS_SPOOF_CONFIG_FILE: &str = concatcp!(ADB_DIR, \".uts_spoof_config\");\npub const UTS_SPOOF_BOOT_PENDING: &str = concatcp!(ADB_DIR, \".uts_spoof_boot_pending\");\npub const DAEMON_PATH: &str = concatcp!(ADB_DIR, \"apd\");\npub const AUTO_EXCLUDE_KNOWN_PACKAGES_FILE: &str = concatcp!(WORKING_DIR, \"auto_exclude_known_packages\");\n\npub const PATHHIDE_DIR: &str = concatcp!(ADB_DIR, \"fp/pathhide/\");\npub const PATHHIDE_ENABLE_FILE: &str = concatcp!(ADB_DIR, \"fp/pathhide/enabled\");\npub const PATHHIDE_PATHS_FILE: &str = concatcp!(ADB_DIR, \"fp/pathhide/paths\");\npub const PATHHIDE_UIDS_FILE: &str = concatcp!(ADB_DIR, \"fp/pathhide/uids\");\npub const PATHHIDE_UID_MODE_FILE: &str = concatcp!(ADB_DIR, \"fp/pathhide/uid_mode\");\npub const PATHHIDE_FILTER_SYSTEM_FILE: &str = concatcp!(ADB_DIR, \"fp/pathhide/filter_system\");\n\npub const NETISOLATE_DIR: &str = concatcp!(ADB_DIR, \"fp/netisolate/\");\npub const NETISOLATE_ENABLE_FILE: &str = concatcp!(ADB_DIR, \"fp/netisolate/enabled\");\npub const NETISOLATE_UIDS_FILE: &str = concatcp!(ADB_DIR, \"fp/netisolate/uids\");\n\npub const MODULE_DIR: &str = concatcp!(ADB_DIR, \"modules/\");\npub const AP_MAGIC_MOUNT_SOURCE: &str = concatcp!(WORKING_DIR, \"magic_mount\");\n\n// warning: this directory should not change, or you need to change the code in module_installer.sh!!!\npub const MODULE_UPDATE_DIR: &str = concatcp!(ADB_DIR, \"modules_update/\");\n\npub const TEMP_DIR: &str = \"/debug_ramdisk\";\npub const TEMP_DIR_LEGACY: &str = \"/sbin\";\n\npub const MODULE_WEB_DIR: &str = \"webroot\";\npub const MODULE_ACTION_SH: &str = \"action.sh\";\npub const DISABLE_FILE_NAME: &str = \"disable\";\npub const SKIP_MOUNT_FILE_NAME: &str = \"skip_mount\";\npub const UPDATE_FILE_NAME: &str = \"update\";\npub const REMOVE_FILE_NAME: &str = \"remove\";\n\n// Metamodule support\npub const METAMODULE_MOUNT_SCRIPT: &str = \"metamount.sh\";\npub const METAMODULE_METAINSTALL_SCRIPT: &str = \"metainstall.sh\";\npub const METAMODULE_METAUNINSTALL_SCRIPT: &str = \"metauninstall.sh\";\npub const METAMODULE_DIR: &str = concatcp!(ADB_DIR, \"metamodule/\");\n\npub const FP_KPMS_DIR: &str = concatcp!(ADB_DIR, \"fp/kpms/\");\npub const FP_KPMS_AUTOLOAD_DIR: &str = concatcp!(ADB_DIR, \"fp/kpms/autoload/\");\npub const KPM_AUTOLOAD_CONFIG: &str = concatcp!(ADB_DIR, \"fp/kpms/kpm_autoload_config.json\");\n\n// Module config\npub const MODULE_CONFIG_DIR: &str = concatcp!(WORKING_DIR, \"module_configs/\");\npub const PERSIST_CONFIG_NAME: &str = \"persist.config\";\npub const TEMP_CONFIG_NAME: &str = \"tmp.config\";\n\npub const PTS_NAME: &str = \"pts\";\n\npub const VERSION_CODE: &str = include_str!(concat!(env!(\"OUT_DIR\"), \"/VERSION_CODE\"));\npub const VERSION_NAME: &str = include_str!(concat!(env!(\"OUT_DIR\"), \"/VERSION_NAME\"));\n"
  },
  {
    "path": "apd/src/event.rs",
    "content": "use crate::sepolicy::get_policy_main;\nuse anyhow::{Context, Result};\nuse libc::SIGPWR;\nuse log::{info, warn};\nuse notify::{\n    Config, Event, EventKind, INotifyWatcher, RecursiveMode, Watcher,\n    event::{ModifyKind, RenameMode},\n};\nuse signal_hook::{consts::signal::*, iterator::Signals};\nuse std::process::Stdio;\nuse std::{\n    env,\n    ffi::CStr,\n    fs,\n    os::unix::{fs::PermissionsExt, process::CommandExt},\n    path::{Path, PathBuf},\n    process::Command,\n    sync::{Arc, Mutex},\n    thread,\n    time::Duration,\n};\n\nuse crate::{\n    assets, defs, lua, metamodule, module, restorecon, supercall,\n    supercall::{init_load_su_path, refresh_ap_package_list},\n    utils::{self, switch_cgroups},\n};\n\npub fn report_kernel(superkey: Option<String>, event: &str, state: &str) -> Result<()> {\n    let args = vec![\n        superkey.unwrap_or_default(),\n        \"event\".to_string(),\n        event.to_string(),\n        state.to_string(),\n    ];\n    let args_ref: Vec<&str> = args.iter().map(|s| s.as_str()).collect();\n    let _ = utils::run_command(\"truncate\", &args_ref, None)?.wait()?;\n    Ok(())\n}\n\nfn setup_fp_directories() -> Result<()> {\n    utils::ensure_dir_with_perms(\n        Path::new(\"/data/adb/fp/bin\"),\n        Path::new(\"/data/adb/fp\"),\n        0o755,\n    )?;\n    utils::ensure_dir_with_perms(\n        Path::new(defs::FP_KPMS_AUTOLOAD_DIR),\n        Path::new(defs::FP_KPMS_DIR),\n        0o755,\n    )?;\n    Ok(())\n}\n\nfn setup_logging() -> Result<()> {\n    let log_dir = Path::new(defs::APATCH_LOG_FOLDER);\n    if !log_dir.exists() {\n        fs::create_dir(log_dir).expect(\"Failed to create log folder\");\n        let permissions = fs::Permissions::from_mode(0o700);\n        fs::set_permissions(log_dir, permissions).expect(\"Failed to set permissions\");\n    }\n\n    let command_string = format!(\n        \"cd {}; rm -f *.last; [ -f dmesg.log ] && mv dmesg.log dmesg.last; [ -f logcat.log ] && mv logcat.log logcat.last; [ -f locat.log ] && mv locat.log logcat.last; rm -f *.log *.old.log\",\n        defs::APATCH_LOG_FOLDER\n    );\n    let result = utils::run_command(\"sh\", &[\"-c\", &command_string], None)?.wait()?;\n    if result.success() {\n        info!(\"Successfully rotated logs.\");\n    } else {\n        info!(\"Failed to rotate logs.\");\n    }\n\n    let logcat_path = format!(\"{}logcat.log\", defs::APATCH_LOG_FOLDER);\n    let dmesg_path = format!(\"{}dmesg.log\", defs::APATCH_LOG_FOLDER);\n    let bootlog = fs::File::create(&dmesg_path)?;\n\n    let _ = unsafe {\n        Command::new(\"timeout\")\n            .process_group(0)\n            .pre_exec(|| {\n                switch_cgroups();\n                Ok(())\n            })\n            .args(vec![\n                \"-s\", \"9\", \"45s\", \"logcat\", \"-b\", \"main,system,crash\",\n                \"DrmLibFs:S\", \"-f\", &logcat_path, \"logcatcher-bootlog:S\", \"&\",\n            ])\n            .spawn()\n    };\n    let _ = unsafe {\n        Command::new(\"timeout\")\n            .process_group(0)\n            .pre_exec(|| {\n                switch_cgroups();\n                Ok(())\n            })\n            .args([\"-s\", \"9\", \"120s\", \"dmesg\", \"-w\"])\n            .stdout(Stdio::from(bootlog))\n            .spawn()\n    };\n\n    Ok(())\n}\n\nfn disable_all_modules_safe() {\n    if let Err(e) = module::disable_all_modules() {\n        warn!(\"disable all modules failed: {e}\");\n    }\n}\n\npub fn on_post_data_fs(superkey: Option<String>) -> Result<()> {\n    utils::umask(0);\n    report_kernel(superkey.clone(), \"post-fs-data\", \"before\")?;\n\n    setup_fp_directories()?;\n\n    supercall::autoload_kpm_modules(&superkey, \"post-fs-data\");\n\n    init_load_su_path(&superkey);\n\n    let mut sepol = get_policy_main(&[\"magiskpolicy\".to_string(), \"--live\".to_string()])?;\n    sepol.magisk_rules();\n    sepol\n        .to_file(\"/sys/fs/selinux/load\")\n        .context(\"Cannot apply policy\")?;\n\n    info!(\"Re-privilege apd profile after injecting sepolicy\");\n    supercall::privilege_apd_profile(&superkey);\n\n    // Apply UTS namespace spoofing if configured\n    supercall::apply_uts_spoof(&superkey);\n\n    // Apply pathhide config if enabled\n    supercall::apply_pathhide(&superkey);\n\n    // Apply netisolate config if enabled\n    supercall::apply_netisolate(&superkey);\n\n    // Clear all temporary module configs early\n    if let Err(e) = crate::module_config::clear_all_temp_configs() {\n        warn!(\"clear temp configs failed: {e}\");\n    }\n\n    if utils::has_magisk() {\n        warn!(\"Magisk detected, skip post-fs-data!\");\n        report_kernel(superkey.clone(), \"post-fs-data\", \"after\")?;\n        return Ok(());\n    }\n\n    setup_logging()?;\n\n    for key in [\"KERNELPATCH_VERSION\", \"KERNEL_VERSION\"] {\n        match env::var(key) {\n            Ok(value) => println!(\"{key}: {value}\"),\n            Err(_) => println!(\"{key} not found\"),\n        }\n    }\n\n    let safe_mode = utils::is_safe_mode(superkey.clone());\n\n    if safe_mode {\n        // we should still mount modules.img to `/data/adb/modules` in safe mode\n        // becuase we may need to operate the module dir in safe mode\n        warn!(\"safe mode, skip common post-fs-data.d scripts\");\n        disable_all_modules_safe();\n    } else {\n        // Then exec common post-fs-data scripts\n        if let Err(e) = module::exec_common_scripts(\"post-fs-data.d\", true) {\n            warn!(\"exec common post-fs-data scripts failed: {}\", e);\n        }\n    }\n    let module_update_dir = defs::MODULE_UPDATE_DIR; //save module place\n    let module_dir = defs::MODULE_DIR; // run modules place\n    let module_update_flag = Path::new(defs::WORKING_DIR).join(defs::UPDATE_FILE_NAME); // if update ,there will be renewed modules file\n    assets::ensure_binaries().with_context(|| \"binary missing\")?;\n\n    if Path::new(defs::MODULE_UPDATE_DIR).exists() {\n        module::handle_updated_modules()?;\n        fs::remove_dir_all(module_update_dir)?;\n    }\n\n    if safe_mode {\n        warn!(\"safe mode, skip post-fs-data scripts and disable all modules!\");\n        disable_all_modules_safe();\n        return Ok(());\n    }\n\n    if let Err(e) = module::prune_modules() {\n        warn!(\"prune modules failed: {}\", e);\n    }\n\n    if let Err(e) = restorecon::restorecon() {\n        warn!(\"restorecon failed: {}\", e);\n    }\n\n    // load sepolicy.rule\n    if module::load_sepolicy_rule().is_err() {\n        warn!(\"load sepolicy.rule failed\");\n    }\n    if Path::new(defs::MAGIC_MOUNT_FILE).exists() {\n        info!(\"Magic Mount mode enabled\");\n        if let Err(e) = crate::magic_mount::magic_mount() {\n            log::error!(\"Magic Mount failed: {}\", e);\n        }\n    } else {\n        info!(\"Magic Mount disabled\");\n        if let Err(e) = metamodule::exec_mount_script(module_dir) {\n            warn!(\"execute metamodule mount failed: {e}\");\n        }\n    }\n\n    // Execute Hide Service if enabled\n    if Path::new(defs::HIDE_SERVICE_FILE).exists() {\n        info!(\"Hide Service enabled, executing fpd -hide...\");\n        if Path::new(defs::HIDE_BINARY_PATH).exists() {\n            let result = Command::new(defs::HIDE_BINARY_PATH).arg(\"-hide\").status();\n            match result {\n                Ok(status) => {\n                    if status.success() {\n                        info!(\"fpd -hide executed successfully\");\n                    } else {\n                        warn!(\"fpd -hide exited with status: {:?}\", status.code());\n                    }\n                }\n                Err(e) => {\n                    warn!(\"Failed to execute fpd -hide: {}\", e);\n                }\n            }\n        } else {\n            warn!(\n                \"fpd binary not found at {}, please copy it manually\",\n                defs::HIDE_BINARY_PATH\n            );\n        }\n    } else {\n        info!(\"Hide Service disabled\");\n    }\n\n    // exec modules post-fs-data scripts\n    // TODO: Add timeout\n    if let Err(e) = module::exec_stage_script(\"post-fs-data\", true) {\n        warn!(\"exec post-fs-data scripts failed: {}\", e);\n    }\n    if let Err(e) = lua::exec_stage_lua(\"post-fs-data\", true, superkey.as_deref().unwrap_or(\"\")) {\n        warn!(\"Failed to exec post-fs-data lua: {}\", e);\n    }\n    // load system.prop\n    if let Err(e) = module::load_system_prop() {\n        warn!(\"load system.prop failed: {}\", e);\n    }\n\n    info!(\"remove update flag\");\n    let _ = fs::remove_file(module_update_flag);\n\n    run_stage(\"post-mount\", superkey.clone(), true);\n\n    report_kernel(superkey, \"post-fs-data\", \"after\")?;\n\n    env::set_current_dir(\"/\").with_context(|| \"failed to chdir to /\")?;\n\n    Ok(())\n}\n\nfn run_stage(stage: &str, superkey: Option<String>, block: bool) {\n    utils::umask(0);\n\n    if utils::has_magisk() {\n        warn!(\"Magisk detected, skip {stage}\");\n        return;\n    }\n\n    if utils::is_safe_mode(superkey.clone()) {\n        warn!(\"safe mode, skip {stage} scripts\");\n        disable_all_modules_safe();\n        return;\n    }\n\n    // execute metamodule stage script first (priority)\n    if let Err(e) = metamodule::exec_stage_script(stage, block) {\n        warn!(\"Failed to exec metamodule {stage} script: {e}\");\n    }\n\n    if let Err(e) = module::exec_common_scripts(&format!(\"{stage}.d\"), block) {\n        warn!(\"Failed to exec common {stage} scripts: {e}\");\n    }\n    if let Err(e) = module::exec_stage_script(stage, block) {\n        warn!(\"Failed to exec {stage} scripts: {e}\");\n    }\n    if let Err(e) = lua::exec_stage_lua(stage, block, superkey.as_deref().unwrap_or(\"\")) {\n        warn!(\"Failed to exec {stage} lua: {e}\");\n    }\n}\n\npub fn on_services(superkey: Option<String>) -> Result<()> {\n    info!(\"on_services triggered!\");\n\n    supercall::autoload_kpm_modules(&superkey, \"service\");\n\n    run_stage(\"service\", superkey, false);\n\n    Ok(())\n}\n\nfn run_uid_monitor() {\n    info!(\"Trigger run_uid_monitor!\");\n\n    let mut command = &mut Command::new(\"/data/adb/apd\");\n    {\n        command = command.process_group(0);\n        command = unsafe {\n            command.pre_exec(|| {\n                // ignore the error?\n                switch_cgroups();\n                Ok(())\n            })\n        };\n    }\n    command = command.arg(\"uid-listener\");\n\n    command\n        .spawn()\n        .map(|_| ())\n        .expect(\"[run_uid_monitor] Failed to run uid monitor\");\n}\n\npub fn on_boot_completed(superkey: Option<String>) -> Result<()> {\n    info!(\"on_boot_completed triggered!\");\n\n    // Clear UTS spoof boot safety flag — boot completed successfully\n    if Path::new(defs::UTS_SPOOF_BOOT_PENDING).exists() {\n        let _ = std::fs::remove_file(defs::UTS_SPOOF_BOOT_PENDING);\n        info!(\"UTS spoof boot safety flag cleared\");\n    }\n\n    run_stage(\"boot-completed\", superkey, false);\n\n    // Execute Umount Service if enabled\n    // Run at boot-completed (latest possible stage) to ensure all mount\n    // points — including those created by system_server and Zygote hooks\n    // (e.g. ReZygisk module.prop bind mounts) — are fully established.\n    if Path::new(defs::UMOUNT_SERVICE_FILE).exists() {\n        info!(\"Umount Service enabled, executing fpd -umount...\");\n        if Path::new(defs::UMOUNT_BINARY_PATH).exists() {\n            let result = unsafe {\n                Command::new(defs::UMOUNT_BINARY_PATH)\n                    .arg(\"-umount\")\n                    .stdout(Stdio::piped())\n                    .stderr(Stdio::piped())\n                    .pre_exec(|| {\n                        let _ = utils::switch_mnt_ns(1);\n                        Ok(())\n                    })\n                    .output()\n            };\n            match result {\n                Ok(output) => {\n                    let stdout = String::from_utf8_lossy(&output.stdout);\n                    let stderr = String::from_utf8_lossy(&output.stderr);\n                    if output.status.success() {\n                        info!(\"fpd -umount executed successfully\");\n                    } else {\n                        warn!(\"fpd -umount exited with status: {:?}\", output.status.code());\n                    }\n                    if !stdout.trim().is_empty() {\n                        info!(\"fpd -umount stdout: {}\", stdout.trim());\n                    }\n                    if !stderr.trim().is_empty() {\n                        info!(\"fpd -umount stderr: {}\", stderr.trim());\n                    }\n                }\n                Err(e) => {\n                    warn!(\"Failed to execute fpd -umount: {}\", e);\n                }\n            }\n        } else {\n            warn!(\n                \"fpd binary not found at {}, please copy it manually\",\n                defs::UMOUNT_BINARY_PATH\n            );\n        }\n    } else {\n        info!(\"Umount Service disabled\");\n    }\n\n    run_uid_monitor();\n    Ok(())\n}\n\npub fn start_uid_listener() -> Result<()> {\n    info!(\"start_uid_listener triggered!\");\n    println!(\"[start_uid_listener] Registering...\");\n\n    // create inotify instance\n    const SYS_PACKAGES_LIST_TMP: &str = \"/data/system/packages.list.tmp\";\n    let sys_packages_list_tmp = PathBuf::from(&SYS_PACKAGES_LIST_TMP);\n    let dir: PathBuf = sys_packages_list_tmp.parent().unwrap().into();\n\n    let (tx, rx) = std::sync::mpsc::channel();\n    let tx_clone = tx.clone();\n    let mutex = Arc::new(Mutex::new(()));\n\n    {\n        let mutex_clone = mutex.clone();\n        thread::spawn(move || {\n            let mut signals = Signals::new(&[SIGTERM, SIGINT, SIGPWR]).unwrap();\n            for sig in signals.forever() {\n                log::warn!(\"[shutdown] Caught signal {sig}, refreshing package list...\");\n                let skey = CStr::from_bytes_with_nul(b\"su\\0\")\n                    .expect(\"[shutdown_listener] CStr::from_bytes_with_nul failed\");\n                refresh_ap_package_list(&skey, &mutex_clone);\n                break;\n            }\n        });\n    }\n\n    let mut watcher = INotifyWatcher::new(\n        move |ev: notify::Result<Event>| match ev {\n            Ok(Event {\n                kind: EventKind::Modify(ModifyKind::Name(RenameMode::Both)),\n                paths,\n                ..\n            }) => {\n                if paths.contains(&sys_packages_list_tmp) {\n                    info!(\"[uid_monitor] System packages list changed, sending to tx...\");\n                    tx_clone.send(false).unwrap()\n                }\n            }\n            Err(err) => warn!(\"inotify error: {err}\"),\n            _ => (),\n        },\n        Config::default(),\n    )?;\n\n    watcher.watch(dir.as_ref(), RecursiveMode::NonRecursive)?;\n\n    {\n        let skey = CStr::from_bytes_with_nul(b\"su\\0\")\n            .expect(\"[start_uid_listener] CStr::from_bytes_with_nul failed\");\n        info!(\"[uid_monitor] Performing initial refresh on startup...\");\n        refresh_ap_package_list(&skey, &mutex);\n    }\n\n    let mut debounce = false;\n    while let Ok(delayed) = rx.recv() {\n        if delayed {\n            debounce = false;\n            let skey = CStr::from_bytes_with_nul(b\"su\\0\")\n                .expect(\"[start_uid_listener] CStr::from_bytes_with_nul failed\");\n            refresh_ap_package_list(&skey, &mutex);\n            report_kernel(None, \"uid_listener\", \"package-list-updated\").unwrap_or_else(|e| {\n                warn!(\"Failed to report kernel about package list update: {e}\");\n            });\n        } else if !debounce {\n            thread::sleep(Duration::from_secs(1));\n            debounce = true;\n            tx.send(true)?;\n        }\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "apd/src/install_jq.sh",
    "content": "#!/system/bin/sh\n# Install jq binary to /data/adb/jq\n# This script is called during APatch installation\n\nJQ_DIR=\"/data/adb\"\nJQ_BIN=\"$JQ_DIR/jq\"\n\n# Check if jq already exists and is up to date\nif [ -f \"$JQ_BIN\" ]; then\n    # jq already installed, skip\n    exit 0\nfi\n\n# Extract jq from assets\nif [ -f \"$APATCH_ASSETS_DIR/jq/jq\" ]; then\n    cp \"$APATCH_ASSETS_DIR/jq/jq\" \"$JQ_BIN\"\n    chmod 755 \"$JQ_BIN\"\n    echo \"jq installed to $JQ_BIN\"\nelse\n    echo \"jq binary not found in assets\"\n    exit 1\nfi\n"
  },
  {
    "path": "apd/src/installer.sh",
    "content": "#!/system/bin/sh\n############################################\n# APatch Module installer script\n# mostly from module_installer.sh\n# and util_functions.sh in Magisk\n############################################\n\numask 022\n\nui_print() {\n  if $BOOTMODE; then\n    echo \"$1\"\n  else\n    echo -e \"ui_print $1\\nui_print\" >> /proc/self/fd/$OUTFD\n  fi\n}\n\ntoupper() {\n  echo \"$@\" | tr '[:lower:]' '[:upper:]'\n}\n\ngrep_cmdline() {\n  local REGEX=\"s/^$1=//p\"\n  { echo $(cat /proc/cmdline)$(sed -e 's/[^\"]//g' -e 's/\"\"//g' /proc/cmdline) | xargs -n 1; \\\n    sed -e 's/ = /=/g' -e 's/, /,/g' -e 's/\"//g' /proc/bootconfig; \\\n  } 2>/dev/null | sed -n \"$REGEX\"\n}\n\ngrep_prop() {\n  local REGEX=\"s/^$1=//p\"\n  shift\n  local FILES=$@\n  [ -z \"$FILES\" ] && FILES='/system/build.prop'\n  cat $FILES 2>/dev/null | dos2unix | sed -n \"$REGEX\" | head -n 1\n}\n\ngrep_get_prop() {\n  local result=$(grep_prop $@)\n  if [ -z \"$result\" ]; then\n    # Fallback to getprop\n    getprop \"$1\"\n  else\n    echo $result\n  fi\n}\n\nis_mounted() {\n  grep -q \" $(readlink -f $1) \" /proc/mounts 2>/dev/null\n  return $?\n}\n\nabort() {\n  ui_print \"$1\"\n  $BOOTMODE || recovery_cleanup\n  [ ! -z $MODPATH ] && rm -rf $MODPATH\n  rm -rf $TMPDIR\n  exit 1\n}\n\nprint_title() {\n  local len line1len line2len bar\n  line1len=$(echo -n $1 | wc -c)\n  line2len=$(echo -n $2 | wc -c)\n  len=$line2len\n  [ $line1len -gt $line2len ] && len=$line1len\n  len=$((len + 2))\n  bar=$(printf \"%${len}s\" | tr ' ' '*')\n  ui_print \"$bar\"\n  ui_print \" $1 \"\n  [ \"$2\" ] && ui_print \" $2 \"\n  ui_print \"$bar\"\n}\n\n######################\n# Environment Related\n######################\n\nsetup_flashable() {\n  ensure_bb\n  $BOOTMODE && return\n  if [ -z $OUTFD ] || readlink /proc/$$/fd/$OUTFD | grep -q /tmp; then\n    # We will have to manually find out OUTFD\n    for FD in `ls /proc/$$/fd`; do\n      if readlink /proc/$$/fd/$FD | grep -q pipe; then\n        if ps | grep -v grep | grep -qE \" 3 $FD |status_fd=$FD\"; then\n          OUTFD=$FD\n          break\n        fi\n      fi\n    done\n  fi\n  recovery_actions\n}\n\nensure_bb() {\n  :\n}\n\nrecovery_actions() {\n  :\n}\n\nrecovery_cleanup() {\n  :\n}\n\n#######################\n# Installation Related\n#######################\n\n# find_block [partname...]\nfind_block() {\n  local BLOCK DEV DEVICE DEVNAME PARTNAME UEVENT\n  for BLOCK in \"$@\"; do\n    DEVICE=`find /dev/block \\( -type b -o -type c -o -type l \\) -iname $BLOCK | head -n 1` 2>/dev/null\n    if [ ! -z $DEVICE ]; then\n      readlink -f $DEVICE\n      return 0\n    fi\n  done\n  # Fallback by parsing sysfs uevents\n  for UEVENT in /sys/dev/block/*/uevent; do\n    DEVNAME=`grep_prop DEVNAME $UEVENT`\n    PARTNAME=`grep_prop PARTNAME $UEVENT`\n    for BLOCK in \"$@\"; do\n      if [ \"$(toupper $BLOCK)\" = \"$(toupper $PARTNAME)\" ]; then\n        echo /dev/block/$DEVNAME\n        return 0\n      fi\n    done\n  done\n  # Look just in /dev in case we're dealing with MTD/NAND without /dev/block devices/links\n  for DEV in \"$@\"; do\n    DEVICE=`find /dev \\( -type b -o -type c -o -type l \\) -maxdepth 1 -iname $DEV | head -n 1` 2>/dev/null\n    if [ ! -z $DEVICE ]; then\n      readlink -f $DEVICE\n      return 0\n    fi\n  done\n  return 1\n}\n\n# setup_mntpoint <mountpoint>\nsetup_mntpoint() {\n  local POINT=$1\n  [ -L $POINT ] && mv -f $POINT ${POINT}_link\n  if [ ! -d $POINT ]; then\n    rm -f $POINT\n    mkdir -p $POINT\n  fi\n}\n\n# mount_name <partname(s)> <mountpoint> <flag>\nmount_name() {\n  local PART=$1\n  local POINT=$2\n  local FLAG=$3\n  setup_mntpoint $POINT\n  is_mounted $POINT && return\n  # First try mounting with fstab\n  mount $FLAG $POINT 2>/dev/null\n  if ! is_mounted $POINT; then\n    local BLOCK=$(find_block $PART)\n    mount $FLAG $BLOCK $POINT || return\n  fi\n  ui_print \"- Mounting $POINT\"\n}\n\n# mount_ro_ensure <partname(s)> <mountpoint>\nmount_ro_ensure() {\n  # We handle ro partitions only in recovery\n  $BOOTMODE && return\n  local PART=$1\n  local POINT=$2\n  mount_name \"$PART\" $POINT '-o ro'\n  is_mounted $POINT || abort \"! Cannot mount $POINT\"\n}\n\nmount_partitions() {\n  # Check A/B slot\n  SLOT=`grep_cmdline androidboot.slot_suffix`\n  if [ -z $SLOT ]; then\n    SLOT=`grep_cmdline androidboot.slot`\n    [ -z $SLOT ] || SLOT=_${SLOT}\n  fi\n  [ -z $SLOT ] || ui_print \"- Current boot slot: $SLOT\"\n\n  # Mount ro partitions\n  if is_mounted /system_root; then\n    umount /system 2&>/dev/null\n    umount /system_root 2&>/dev/null\n  fi\n  mount_ro_ensure \"system$SLOT app$SLOT\" /system\n  if [ -f /system/init -o -L /system/init ]; then\n    SYSTEM_ROOT=true\n    setup_mntpoint /system_root\n    if ! mount --move /system /system_root; then\n      umount /system\n      umount -l /system 2>/dev/null\n      mount_ro_ensure \"system$SLOT app$SLOT\" /system_root\n    fi\n    mount -o bind /system_root/system /system\n  else\n    SYSTEM_ROOT=false\n    grep ' / ' /proc/mounts | grep -qv 'rootfs' || grep -q ' /system_root ' /proc/mounts && SYSTEM_ROOT=true\n  fi\n  # /vendor is used only on some older devices for recovery AVBv1 signing so is not critical if fails\n  [ -L /system/vendor ] && mount_name vendor$SLOT /vendor '-o ro'\n  $SYSTEM_ROOT && ui_print \"- Device is system-as-root\"\n\n  # Mount sepolicy rules dir locations in recovery (best effort)\n  if ! $BOOTMODE; then\n    mount_name \"cache cac\" /cache\n    mount_name metadata /metadata\n    mount_name persist /persist\n  fi\n}\n\napi_level_arch_detect() {\n  API=$(grep_get_prop ro.build.version.sdk)\n  ABI=$(grep_get_prop ro.product.cpu.abi)\n  if [ \"$ABI\" = \"x86\" ]; then\n    ARCH=x86\n    ABI32=x86\n    IS64BIT=false\n  elif [ \"$ABI\" = \"arm64-v8a\" ]; then\n    ARCH=arm64\n    ABI32=armeabi-v7a\n    IS64BIT=true\n  elif [ \"$ABI\" = \"x86_64\" ]; then\n    ARCH=x64\n    ABI32=x86\n    IS64BIT=true\n  else\n    ARCH=arm\n    ABI=armeabi-v7a\n    ABI32=armeabi-v7a\n    IS64BIT=false\n  fi\n}\n\n#################\n# Module Related\n#################\n\nset_perm() {\n  chown $2:$3 $1 || return 1\n  chmod $4 $1 || return 1\n  local CON=$5\n  [ -z $CON ] && CON=u:object_r:system_file:s0\n  chcon $CON $1 || return 1\n}\n\nset_perm_recursive() {\n  find $1 -type d 2>/dev/null | while read dir; do\n    set_perm $dir $2 $3 $4 $6\n  done\n  find $1 -type f -o -type l 2>/dev/null | while read file; do\n    set_perm $file $2 $3 $5 $6\n  done\n}\n\nmktouch() {\n  mkdir -p ${1%/*} 2>/dev/null\n  [ -z $2 ] && touch $1 || echo $2 > $1\n  chmod 644 $1\n}\n\nmark_remove() {\n  mkdir -p ${1%/*} 2>/dev/null\n  mknod $1 c 0 0\n  chmod 644 $1\n}\n\nmark_replace() {\n  # REPLACE must be directory!!!\n  # https://docs.kernel.org/filesystems/overlayfs.html#whiteouts-and-opaque-directories\n  mkdir -p $1 2>/dev/null\n  setfattr -n trusted.overlay.opaque -v y $1\n  chmod 644 $1\n}\n\nrequest_size_check() {\n  reqSizeM=`du -ms \"$1\" | cut -f1`\n}\n\nrequest_zip_size_check() {\n  reqSizeM=`unzip -l \"$1\" | tail -n 1 | awk '{ print int(($1 - 1) / 1048576 + 1) }'`\n}\n\nboot_actions() {\n  if [ ! -f \"$NVBASE/jq\" ]; then\n    local apk_path=$(find /data/app -name \"base.apk\" -path \"*/me.yuki.folk-*\" 2>/dev/null | head -n 1)\n    if [ -n \"$apk_path\" ] && [ -f \"$apk_path\" ]; then\n      # Extract jq from APK assets\n      mkdir -p /data/local/tmp/jq_extract\n      if unzip -o \"$apk_path\" \"jq/jq\" -d /data/local/tmp/jq_extract >&2; then\n        if [ -f \"/data/local/tmp/jq_extract/jq/jq\" ]; then\n          cp /data/local/tmp/jq_extract/jq/jq \"$NVBASE/jq\"\n          chmod 755 \"$NVBASE/jq\"\n          rm -rf /data/local/tmp/jq_extract\n        fi\n      else\n        rm -rf /data/local/tmp/jq_extract\n      fi\n    fi\n  fi\n  return\n}\n\n# Require ZIPFILE to be set\nis_legacy_script() {\n  unzip -l \"$ZIPFILE\" install.sh | grep -q install.sh\n  return $?\n}\n\nhandle_partition() {\n    # if /system/vendor is a symlink, we need to move it out of $MODPATH/system, otherwise it will be overlayed\n    # if /system/vendor is a normal directory, it is ok to overlay it and we don't need to overlay it separately.\n    if [ ! -e $MODPATH/system/$1 ]; then\n        # no partition found\n        return;\n    fi\n\n    if [ -L \"/system/$1\" ] && [ \"$(readlink -f /system/$1)\" = \"/$1\" ]; then\n        ui_print \"- Handle partition /$1\"\n        ln -sf \"./system/$1\" \"$MODPATH/$1\"\n    fi\n}\n\n# Require OUTFD, ZIPFILE to be set\ninstall_module() {\n  rm -rf $TMPDIR\n  mkdir -p $TMPDIR\n  chcon u:object_r:system_file:s0 $TMPDIR\n  cd $TMPDIR\n\n  mount_partitions\n  api_level_arch_detect\n\n  # Setup busybox and binaries\n  if $BOOTMODE; then\n    boot_actions\n  else\n    recovery_actions\n  fi\n\n  # Extract prop file\n  unzip -o \"$ZIPFILE\" module.prop -d $TMPDIR >&2\n  [ ! -f $TMPDIR/module.prop ] && abort \"! Unable to extract zip file!\"\n\n  local MODDIRNAME=modules\n  $BOOTMODE && MODDIRNAME=modules_update\n  local MODULEROOT=$NVBASE/$MODDIRNAME\n  MODID=`grep_prop id $TMPDIR/module.prop`\n  MODNAME=`grep_prop name $TMPDIR/module.prop`\n  MODAUTH=`grep_prop author $TMPDIR/module.prop`\n  MODPATH=$MODULEROOT/$MODID\n\n  # Create mod paths\n  rm -rf $MODPATH\n  mkdir -p $MODPATH\n\n  if is_legacy_script; then\n    unzip -oj \"$ZIPFILE\" module.prop install.sh uninstall.sh 'common/*' -d $TMPDIR >&2\n\n    # Load install script\n    . $TMPDIR/install.sh\n\n    # Callbacks\n    print_modname\n    on_install\n\n    [ -f $TMPDIR/uninstall.sh ] && cp -af $TMPDIR/uninstall.sh $MODPATH/uninstall.sh\n    $SKIPMOUNT && touch $MODPATH/skip_mount\n    $PROPFILE && cp -af $TMPDIR/system.prop $MODPATH/system.prop\n    cp -af $TMPDIR/module.prop $MODPATH/module.prop\n    $POSTFSDATA && cp -af $TMPDIR/post-fs-data.sh $MODPATH/post-fs-data.sh\n    $LATESTARTSERVICE && cp -af $TMPDIR/service.sh $MODPATH/service.sh\n\n    ui_print \"- Setting permissions\"\n    set_permissions\n  else\n    print_title \"$MODNAME\" \"by $MODAUTH\"\n    print_title \"Powered by APatch\"\n\n    unzip -o \"$ZIPFILE\" customize.sh -d $MODPATH >&2\n\n    if ! grep -q '^SKIPUNZIP=1$' $MODPATH/customize.sh 2>/dev/null; then\n      ui_print \"- Extracting module files\"\n      unzip -o \"$ZIPFILE\" -x 'META-INF/*' -d $MODPATH >&2\n\n      # Default permissions\n      set_perm_recursive $MODPATH 0 0 0755 0644\n      set_perm_recursive $MODPATH/system/bin 0 2000 0755 0755\n      set_perm_recursive $MODPATH/system/xbin 0 2000 0755 0755\n      set_perm_recursive $MODPATH/system/system_ext/bin 0 2000 0755 0755\n      set_perm_recursive $MODPATH/system/vendor 0 2000 0755 0755 u:object_r:vendor_file:s0\n    fi\n\n    # Load customization script\n    [ -f $MODPATH/customize.sh ] && . $MODPATH/customize.sh\n  fi\n\n  # Handle replace folders\n  for TARGET in $REPLACE; do\n    ui_print \"- Replace target: $TARGET\"\n    mark_replace $MODPATH$TARGET\n  done\n\n  # Handle remove files\n  for TARGET in $REMOVE; do\n    ui_print \"- Remove target: $TARGET\"\n    mark_remove $MODPATH$TARGET\n  done\n\n  handle_partition vendor\n  handle_partition system_ext\n  handle_partition product\n\n  if $BOOTMODE; then\n    mktouch $NVBASE/modules/$MODID/update\n    rm -rf $NVBASE/modules/$MODID/remove 2>/dev/null\n    rm -rf $NVBASE/modules/$MODID/disable 2>/dev/null\n    cp -af $MODPATH/module.prop $NVBASE/modules/$MODID/module.prop\n  fi\n\n  # Remove stuff that doesn't belong to modules and clean up any empty directories\n  rm -rf \\\n  $MODPATH/system/placeholder $MODPATH/customize.sh \\\n  $MODPATH/README.md $MODPATH/.git*\n  rmdir -p $MODPATH 2>/dev/null\n\n  cd /\n  $BOOTMODE || recovery_cleanup\n  rm -rf $TMPDIR\n\n  ui_print \"- Done\"\n}\n\n##########\n# Presets\n##########\n\n# Detect whether in boot mode\n[ -z $BOOTMODE ] && ps | grep zygote | grep -qv grep && BOOTMODE=true\n[ -z $BOOTMODE ] && ps -A 2>/dev/null | grep zygote | grep -qv grep && BOOTMODE=true\n[ -z $BOOTMODE ] && BOOTMODE=false\n\nNVBASE=/data/adb\nTMPDIR=/dev/tmp\nPOSTFSDATAD=$NVBASE/post-fs-data.d\nSERVICED=$NVBASE/service.d\n\n# Some modules dependents on this\nexport MAGISK_VER=27.0\nexport MAGISK_VER_CODE=27000\n"
  },
  {
    "path": "apd/src/installer_bind.sh",
    "content": "#!/system/bin/sh\n############################################\n# APatch Module installer script\n# mostly from module_installer.sh\n# and util_functions.sh in Magisk\n############################################\n\numask 022\n\nui_print() {\n  if $BOOTMODE; then\n    echo \"$1\"\n  else\n    echo -e \"ui_print $1\\nui_print\" >> /proc/self/fd/$OUTFD\n  fi\n}\n\ntoupper() {\n  echo \"$@\" | tr '[:lower:]' '[:upper:]'\n}\n\ngrep_cmdline() {\n  local REGEX=\"s/^$1=//p\"\n  { echo $(cat /proc/cmdline)$(sed -e 's/[^\"]//g' -e 's/\"\"//g' /proc/cmdline) | xargs -n 1; \\\n    sed -e 's/ = /=/g' -e 's/, /,/g' -e 's/\"//g' /proc/bootconfig; \\\n  } 2>/dev/null | sed -n \"$REGEX\"\n}\n\ngrep_prop() {\n  local REGEX=\"s/$1=//p\"\n  shift\n  local FILES=$@\n  [ -z \"$FILES\" ] && FILES='/system/build.prop'\n  cat $FILES 2>/dev/null | dos2unix | sed -n \"$REGEX\" | head -n 1 | xargs\n}\n\ngrep_get_prop() {\n  local result=$(grep_prop $@)\n  if [ -z \"$result\" ]; then\n    # Fallback to getprop\n    getprop \"$1\"\n  else\n    echo $result\n  fi\n}\n\nis_mounted() {\n  grep -q \" $(readlink -f $1) \" /proc/mounts 2>/dev/null\n  return $?\n}\n\nabort() {\n  ui_print \"$1\"\n  $BOOTMODE || recovery_cleanup\n  [ ! -z $MODPATH ] && rm -rf $MODPATH\n  rm -rf $TMPDIR\n  exit 1\n}\n\nprint_title() {\n  local len line1len line2len bar\n  line1len=$(echo -n $1 | wc -c)\n  line2len=$(echo -n $2 | wc -c)\n  len=$line2len\n  [ $line1len -gt $line2len ] && len=$line1len\n  len=$((len + 2))\n  bar=$(printf \"%${len}s\" | tr ' ' '*')\n  ui_print \"$bar\"\n  ui_print \" $1 \"\n  [ \"$2\" ] && ui_print \" $2 \"\n  ui_print \"$bar\"\n}\n\ncheck_sepolicy() {\n    /data/adb/apd sepolicy check \"$1\"\n    return $?\n}\n\n######################\n# Environment Related\n######################\n\nsetup_flashable() {\n  ensure_bb\n  $BOOTMODE && return\n  if [ -z $OUTFD ] || readlink /proc/$$/fd/$OUTFD | grep -q /tmp; then\n    # We will have to manually find out OUTFD\n    for FD in /proc/$$/fd/*; do\n      if readlink /proc/$$/fd/$FD | grep -q pipe; then\n        if ps | grep -v grep | grep -qE \" 3 $FD |status_fd=$FD\"; then\n          OUTFD=$FD\n          break\n        fi\n      fi\n    done\n  fi\n  recovery_actions\n}\n\nensure_bb() {\n  :\n}\n\nrecovery_actions() {\n  :\n}\n\nrecovery_cleanup() {\n  :\n}\n\n#######################\n# Installation Related\n#######################\n\n# find_block [partname...]\nfind_block() {\n  local BLOCK DEV DEVICE DEVNAME PARTNAME UEVENT\n  for BLOCK in \"$@\"; do\n    DEVICE=`find /dev/block \\( -type b -o -type c -o -type l \\) -iname $BLOCK | head -n 1` 2>/dev/null\n    if [ ! -z $DEVICE ]; then\n      readlink -f $DEVICE\n      return 0\n    fi\n  done\n  # Fallback by parsing sysfs uevents\n  for UEVENT in /sys/dev/block/*/uevent; do\n    DEVNAME=`grep_prop DEVNAME $UEVENT`\n    PARTNAME=`grep_prop PARTNAME $UEVENT`\n    for BLOCK in \"$@\"; do\n      if [ \"$(toupper $BLOCK)\" = \"$(toupper $PARTNAME)\" ]; then\n        echo /dev/block/$DEVNAME\n        return 0\n      fi\n    done\n  done\n  # Look just in /dev in case we're dealing with MTD/NAND without /dev/block devices/links\n  for DEV in \"$@\"; do\n    DEVICE=`find /dev \\( -type b -o -type c -o -type l \\) -maxdepth 1 -iname $DEV | head -n 1` 2>/dev/null\n    if [ ! -z $DEVICE ]; then\n      readlink -f $DEVICE\n      return 0\n    fi\n  done\n  return 1\n}\n\n# setup_mntpoint <mountpoint>\nsetup_mntpoint() {\n  local POINT=$1\n  [ -L $POINT ] && mv -f $POINT ${POINT}_link\n  if [ ! -d $POINT ]; then\n    rm -f $POINT\n    mkdir -p $POINT\n  fi\n}\n\n# mount_name <partname(s)> <mountpoint> <flag>\nmount_name() {\n  local PART=$1\n  local POINT=$2\n  local FLAG=$3\n  setup_mntpoint $POINT\n  is_mounted $POINT && return\n  # First try mounting with fstab\n  mount $FLAG $POINT 2>/dev/null\n  if ! is_mounted $POINT; then\n    local BLOCK=$(find_block $PART)\n    mount $FLAG $BLOCK $POINT || return\n  fi\n  ui_print \"- Mounting $POINT\"\n}\n\n# mount_ro_ensure <partname(s)> <mountpoint>\nmount_ro_ensure() {\n  # We handle ro partitions only in recovery\n  $BOOTMODE && return\n  local PART=$1\n  local POINT=$2\n  mount_name \"$PART\" $POINT '-o ro'\n  is_mounted $POINT || abort \"! Cannot mount $POINT\"\n}\n\nmount_partitions() {\n  # Check A/B slot\n  SLOT=`grep_cmdline androidboot.slot_suffix`\n  if [ -z $SLOT ]; then\n    SLOT=`grep_cmdline androidboot.slot`\n    [ -z $SLOT ] || SLOT=_${SLOT}\n  fi\n  [ -z $SLOT ] || ui_print \"- Current boot slot: $SLOT\"\n\n  # Mount ro partitions\n  if is_mounted /system_root; then\n    umount /system 2&>/dev/null\n    umount /system_root 2&>/dev/null\n  fi\n  mount_ro_ensure \"system$SLOT app$SLOT\" /system\n  if [ -f /system/init -o -L /system/init ]; then\n    SYSTEM_ROOT=true\n    setup_mntpoint /system_root\n    if ! mount --move /system /system_root; then\n      umount /system\n      umount -l /system 2>/dev/null\n      mount_ro_ensure \"system$SLOT app$SLOT\" /system_root\n    fi\n    mount -o bind /system_root/system /system\n  else\n    SYSTEM_ROOT=false\n    grep ' / ' /proc/mounts | grep -qv 'rootfs' || grep -q ' /system_root ' /proc/mounts && SYSTEM_ROOT=true\n  fi\n  # /vendor is used only on some older devices for recovery AVBv1 signing so is not critical if fails\n  [ -L /system/vendor ] && mount_name vendor$SLOT /vendor '-o ro'\n  $SYSTEM_ROOT && ui_print \"- Device is system-as-root\"\n\n  # Mount sepolicy rules dir locations in recovery (best effort)\n  if ! $BOOTMODE; then\n    mount_name \"cache cac\" /cache\n    mount_name metadata /metadata\n    mount_name persist /persist\n  fi\n}\n\napi_level_arch_detect() {\n  API=$(grep_get_prop ro.build.version.sdk)\n  ABI=$(grep_get_prop ro.product.cpu.abi)\n  if [ \"$ABI\" = \"x86\" ]; then\n    ARCH=x86\n    ABI32=x86\n    IS64BIT=false\n  elif [ \"$ABI\" = \"arm64-v8a\" ]; then\n    ARCH=arm64\n    ABI32=armeabi-v7a\n    IS64BIT=true\n  elif [ \"$ABI\" = \"x86_64\" ]; then\n    ARCH=x64\n    ABI32=x86\n    IS64BIT=true\n  else\n    ARCH=arm\n    ABI=armeabi-v7a\n    ABI32=armeabi-v7a\n    IS64BIT=false\n  fi\n}\n\n#################\n# Module Related\n#################\n\nset_perm() {\n  chown $2:$3 $1 || return 1\n  chmod $4 $1 || return 1\n  local CON=$5\n  [ -z $CON ] && CON=u:object_r:system_file:s0\n  chcon $CON $1 || return 1\n}\n\nset_perm_recursive() {\n  find $1 -type d 2>/dev/null | while read dir; do\n    set_perm $dir $2 $3 $4 $6\n  done\n  find $1 -type f -o -type l 2>/dev/null | while read file; do\n    set_perm $file $2 $3 $5 $6\n  done\n}\n\nmktouch() {\n  mkdir -p ${1%/*} 2>/dev/null\n  [ -z $2 ] && touch $1 || echo $2 > $1\n  chmod 644 $1\n}\n\nmark_remove() {\n  mkdir -p ${1%/*} 2>/dev/null\n  mknod $1 c 0 0\n  chmod 644 $1\n}\n\nmark_replace() {\n  # REPLACE must be directory!!!\n  # https://docs.kernel.org/filesystems/overlayfs.html#whiteouts-and-opaque-directories\n  mkdir -p $1 2>/dev/null\n  setfattr -n trusted.overlay.opaque -v y $1\n  chmod 644 $1\n}\n\nrequest_size_check() {\n  reqSizeM=`du -ms \"$1\" | cut -f1`\n}\n\nrequest_zip_size_check() {\n  reqSizeM=`unzip -l \"$1\" | tail -n 1 | awk '{ print int(($1 - 1) / 1048576 + 1) }'`\n}\n\nboot_actions() { return; }\n\n# Require ZIPFILE to be set\nis_legacy_script() {\n  unzip -l \"$ZIPFILE\" install.sh | grep -q install.sh\n  return $?\n}\n\nhandle_partition() {\n    PARTITION=\"$1\"\n    REQUIRE_SYMLINK=\"$2\"\n    if [ ! -e \"$MODPATH/system/$PARTITION\" ]; then\n        # no partition found\n        return;\n    fi\n\n    if [ \"$REQUIRE_SYMLINK\" = \"false\" ] || [ -L \"/system/$PARTITION\" ] && [ \"$(readlink -f \"/system/$PARTITION\")\" = \"/$PARTITION\" ]; then\n        ui_print \"- Handle partition /$PARTITION\"\n        ln -sf \"./system/$PARTITION\" \"$MODPATH/$PARTITION\"\n    fi\n}\n\n# Require OUTFD, ZIPFILE to be set\ninstall_module() {\n  rm -rf $TMPDIR\n  mkdir -p $TMPDIR\n  chcon u:object_r:system_file:s0 $TMPDIR\n  cd $TMPDIR\n\n  mount_partitions\n  api_level_arch_detect\n\n  # Setup busybox and binaries\n  if $BOOTMODE; then\n    boot_actions\n  else\n    recovery_actions\n  fi\n\n  # Extract prop file\n  unzip -o \"$ZIPFILE\" module.prop -d $TMPDIR >&2\n  [ ! -f $TMPDIR/module.prop ] && abort \"! Unable to extract zip file!\"\n\n  local MODDIRNAME=modules\n  $BOOTMODE && MODDIRNAME=modules_update\n  local MODULEROOT=$NVBASE/$MODDIRNAME\n  MODID=`grep_prop id $TMPDIR/module.prop`\n  MODNAME=`grep_prop name $TMPDIR/module.prop`\n  MODAUTH=`grep_prop author $TMPDIR/module.prop`\n  MODPATH=$MODULEROOT/$MODID\n\n  # Create mod paths\n  rm -rf $MODPATH\n  mkdir -p $MODPATH\n\n  if is_legacy_script; then\n    unzip -oj \"$ZIPFILE\" module.prop install.sh uninstall.sh 'common/*' -d $TMPDIR >&2\n\n    # Load install script\n    . $TMPDIR/install.sh\n\n    # Callbacks\n    print_modname\n    on_install\n\n    [ -f $TMPDIR/uninstall.sh ] && cp -af $TMPDIR/uninstall.sh $MODPATH/uninstall.sh\n    $SKIPMOUNT && touch $MODPATH/skip_mount\n    $PROPFILE && cp -af $TMPDIR/system.prop $MODPATH/system.prop\n    cp -af $TMPDIR/module.prop $MODPATH/module.prop\n    $POSTFSDATA && cp -af $TMPDIR/post-fs-data.sh $MODPATH/post-fs-data.sh\n    $LATESTARTSERVICE && cp -af $TMPDIR/service.sh $MODPATH/service.sh\n\n    ui_print \"- Setting permissions\"\n    set_permissions\n  else\n    print_title \"$MODNAME\" \"by $MODAUTH\"\n    print_title \"Powered by APatch\"\n\n    unzip -o \"$ZIPFILE\" customize.sh -d $MODPATH >&2\n\n    if ! grep -q '^SKIPUNZIP=1$' $MODPATH/customize.sh 2>/dev/null; then\n      ui_print \"- Extracting module files\"\n      unzip -o \"$ZIPFILE\" -x 'META-INF/*' -d $MODPATH >&2\n\n      # Default permissions\n      set_perm_recursive $MODPATH 0 0 0755 0644\n      set_perm_recursive $MODPATH/system/bin 0 2000 0755 0755\n      set_perm_recursive $MODPATH/system/xbin 0 2000 0755 0755\n      set_perm_recursive $MODPATH/system/system_ext/bin 0 2000 0755 0755\n      set_perm_recursive $MODPATH/system/vendor 0 2000 0755 0755 u:object_r:vendor_file:s0\n    fi\n\n    # Load customization script\n    [ -f $MODPATH/customize.sh ] && . $MODPATH/customize.sh\n  fi\n\n  handle_partition vendor true\n  handle_partition system_ext true\n  handle_partition product true\n  handle_partition odm false\n\n  # Handle replace folders\n  for TARGET in $REPLACE; do\n    ui_print \"- Replace target: $TARGET\"\n    mark_replace \"$MODPATH$TARGET\"\n  done\n\n  # Handle remove files\n  for TARGET in $REMOVE; do\n    ui_print \"- Remove target: $TARGET\"\n    mark_remove \"$MODPATH$TARGET\"\n  done\n\n  if $BOOTMODE; then\n    mktouch $NVBASE/modules/$MODID/update\n    rm -rf $NVBASE/modules/$MODID/remove 2>/dev/null\n    rm -rf $NVBASE/modules/$MODID/disable 2>/dev/null\n    cp -af $MODPATH/module.prop $NVBASE/modules/$MODID/module.prop\n  fi\n\n  # Remove stuff that doesn't belong to modules and clean up any empty directories\n  rm -rf \\\n  $MODPATH/system/placeholder $MODPATH/customize.sh \\\n  $MODPATH/README.md $MODPATH/.git*\n  rmdir -p $MODPATH 2>/dev/null\n\n  cd /\n  $BOOTMODE || recovery_cleanup\n  rm -rf $TMPDIR\n\n  ui_print \"- Done\"\n}\n\n##########\n# Presets\n##########\n\n# Detect whether in boot mode\n[ -z $BOOTMODE ] && ps | grep zygote | grep -qv grep && BOOTMODE=true\n[ -z $BOOTMODE ] && ps -A 2>/dev/null | grep zygote | grep -qv grep && BOOTMODE=true\n[ -z $BOOTMODE ] && BOOTMODE=false\n\nNVBASE=/data/adb\nTMPDIR=/dev/tmp\nPOSTFSDATAD=$NVBASE/post-fs-data.d\nSERVICED=$NVBASE/service.d\n\n# Some modules dependents on this\nexport MAGISK_VER=27.0\nexport MAGISK_VER_CODE=27000"
  },
  {
    "path": "apd/src/lua.rs",
    "content": "use crate::module::*;\nuse crate::utils::*;\nuse anyhow::Result;\nuse log::{info, warn};\nuse mlua::{Function, Lua, Result as LuaResult, Table};\nuse std::{fs, path::Path};\n\npub fn save_text<P: AsRef<Path>>(filename: P, content: &str) -> std::io::Result<()> {\n    let _ = ensure_dir_exists(\"/data/adb/config\");\n    let path = format!(\"/data/adb/config/{}\", filename.as_ref().display());\n    fs::write(&path, content)?;\n    Ok(())\n}\n\npub fn load_text<P: AsRef<Path>>(filename: P) -> std::io::Result<String> {\n    let _ = ensure_dir_exists(\"/data/adb/config\");\n    let path = format!(\"/data/adb/config/{}\", filename.as_ref().display());\n    fs::read_to_string(path)\n}\n\npub fn load_all_lua_modules(lua: &Lua) -> LuaResult<()> {\n    let modules_dir = Path::new(\"/data/adb/modules\");\n\n    let modules: Table = match lua.globals().get(\"modules\") {\n        Ok(t) => t,\n        Err(_) => {\n            let t = lua.create_table()?;\n            lua.globals().set(\"modules\", t.clone())?;\n            t\n        }\n    };\n\n    if modules_dir.exists() {\n        for entry in\n            fs::read_dir(modules_dir).unwrap_or_else(|_| fs::read_dir(\"/dev/null\").unwrap())\n        {\n            if let Ok(entry) = entry {\n                let path = entry.path();\n                if path.is_dir() {\n                    let id = path.file_name().unwrap().to_string_lossy().to_string();\n                    let package: Table = lua.globals().get(\"package\")?;\n                    let old_cpath: String = package.get(\"cpath\")?;\n                    let new_cpath = format!(\"{}/?.so;{}\", path.to_string_lossy(), old_cpath);\n                    package.set(\"cpath\", new_cpath)?;\n\n                    let lua_file = path.join(format!(\"{}.lua\", id));\n\n                    if lua_file.exists() {\n                        match fs::read_to_string(&lua_file) {\n                            Ok(code) => {\n                                match lua\n                                    .load(&code)\n                                    .set_name(&*lua_file.to_string_lossy())\n                                    .eval::<Table>()\n                                {\n                                    Ok(module) => {\n                                        modules.set(id.clone(), module.clone())?;\n                                    }\n                                    Err(e) => {\n                                        eprintln!(\n                                            \"Failed to eval Lua {}: {}\",\n                                            lua_file.display(),\n                                            e\n                                        );\n                                    }\n                                }\n                            }\n                            Err(e) => {\n                                eprintln!(\"Failed to read Lua {}: {}\", lua_file.display(), e);\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    Ok(())\n}\n\npub fn info_lua(lua: &Lua) -> LuaResult<Function> {\n    lua.create_function(|_, msg: String| {\n        info!(\"[Lua] {}\", msg);\n        Ok(())\n    })\n}\n\npub fn warn_lua(lua: &Lua) -> LuaResult<Function> {\n    lua.create_function(|_, msg: String| {\n        warn!(\"[Lua] {}\", msg);\n        Ok(())\n    })\n}\n\npub fn install_module_lua(lua: &Lua) -> LuaResult<Function> {\n    lua.create_function(|_, zip: String| {\n        install_module(&zip)\n            .map_err(|e| mlua::Error::external(format!(\"install_module failed: {}\", e)))\n    })\n}\npub fn save_text_lua(lua: &Lua) -> LuaResult<Function> {\n    lua.create_function(|_, (filename, content): (String, String)| {\n        save_text(&filename, &content)\n            .map_err(|e| mlua::Error::external(format!(\"save filed: {}\", e)))?;\n        Ok(())\n    })\n}\npub fn read_text_lua(lua: &Lua) -> LuaResult<Function> {\n    lua.create_function(|_, filename: String| {\n        let content = match load_text(&filename) {\n            Ok(s) => s,\n            Err(ref e) if e.kind() == std::io::ErrorKind::NotFound => String::new(),\n            Err(e) => return Err(mlua::Error::external(format!(\"read failed: {}\", e))),\n        };\n        Ok(content)\n    })\n}\n\npub fn exec_stage_lua(stage: &str, wait: bool, superkey: &str) -> Result<()> {\n    let stage_safe = stage.replace('-', \"_\");\n    run_lua(&superkey, &stage_safe, true, wait).map_err(|e| anyhow::anyhow!(\"{}\", e))?;\n    Ok(())\n}\n\npub fn run_lua(id: &str, function: &str, on_each_module: bool, _wait: bool) -> mlua::Result<()> {\n    let lua = unsafe { Lua::unsafe_new() };\n\n    let func = install_module_lua(&lua)?;\n    lua.globals().set(\"install_module\", func)?;\n    lua.globals().set(\"info\", info_lua(&lua)?)?;\n    lua.globals().set(\"warn\", warn_lua(&lua)?)?;\n    lua.globals().set(\"setConfig\", save_text_lua(&lua)?)?;\n    lua.globals().set(\"getConfig\", read_text_lua(&lua)?)?;\n\n    load_all_lua_modules(&lua)?;\n\n    let modules: mlua::Table = lua.globals().get(\"modules\")?;\n    if on_each_module {\n        for pair in modules.pairs::<String, mlua::Table>() {\n            let (_, module_table) = pair?;\n            if let Ok(func_obj) = module_table.get::<mlua::Function>(function) {\n                func_obj.call::<()>(id)?;\n            }\n        }\n    } else {\n        let module_table: mlua::Table = modules.get(id)?;\n        let func_obj: mlua::Function = module_table.get(function)?;\n        func_obj.call::<()>(())?;\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "apd/src/magic_mount.rs",
    "content": "use std::{\n    cmp::PartialEq,\n    collections::{HashMap, hash_map::Entry},\n    fs,\n    fs::{DirEntry, FileType, create_dir, create_dir_all, read_dir, read_link},\n    os::unix::fs::{FileTypeExt, symlink},\n    path::{Path, PathBuf},\n};\n\nuse anyhow::{Context, Result, bail};\nuse extattr::lgetxattr;\nuse rustix::{\n    fs::{Gid, MetadataExt, Mode, Uid, chmod, chown},\n    mount::{\n        MountFlags, MountPropagationFlags, UnmountFlags, mount, mount_bind, mount_change,\n        mount_move, unmount,\n    },\n};\n\nuse crate::{\n    defs::{\n        AP_MAGIC_MOUNT_SOURCE, DISABLE_FILE_NAME, MODULE_DIR, REMOVE_FILE_NAME,\n        SKIP_MOUNT_FILE_NAME,\n    },\n    magic_mount::NodeFileType::{Directory, RegularFile, Symlink, Whiteout},\n    restorecon::{lgetfilecon, lsetfilecon},\n    utils::ensure_dir_exists,\n};\n\nconst REPLACE_DIR_FILE_NAME: &str = \".replace\";\nconst REPLACE_DIR_XATTR: &str = \"trusted.overlay.opaque\";\n\n#[derive(PartialEq, Eq, Hash, Clone, Debug)]\nenum NodeFileType {\n    RegularFile,\n    Directory,\n    Symlink,\n    Whiteout,\n}\n\nimpl NodeFileType {\n    fn from_file_type(file_type: FileType) -> Self {\n        if file_type.is_file() {\n            RegularFile\n        } else if file_type.is_dir() {\n            Directory\n        } else if file_type.is_symlink() {\n            Symlink\n        } else {\n            Whiteout\n        }\n    }\n\n    /// Check if mounting this node type over `real_path` requires a tmpfs overlay\n    /// due to type mismatch or missing file.\n    fn needs_tmpfs_vs_real(&self, real_path: &Path) -> bool {\n        match self {\n            Symlink => true,\n            Whiteout => real_path.exists(),\n            _ => match real_path.symlink_metadata() {\n                Ok(metadata) => {\n                    let real_type = Self::from_file_type(metadata.file_type());\n                    real_type != *self || real_type == Symlink\n                }\n                Err(_) => true,\n            },\n        }\n    }\n}\n\n#[derive(Debug, Clone)]\nstruct Node {\n    name: String,\n    file_type: NodeFileType,\n    children: HashMap<String, Node>,\n    // the module that owned this node\n    module_path: Option<PathBuf>,\n    replace: bool,\n    skip: bool,\n}\n\nimpl Node {\n    fn collect_module_files<P>(&mut self, module_dir: P) -> Result<bool>\n    where\n        P: AsRef<Path>,\n    {\n        let dir = module_dir.as_ref();\n        let mut has_file = false;\n        for entry in dir.read_dir()?.flatten() {\n            let name = entry.file_name().to_string_lossy().to_string();\n\n            let node = match self.children.entry(name.clone()) {\n                Entry::Occupied(o) => Some(o.into_mut()),\n                Entry::Vacant(v) => Self::new_module(&name, &entry).map(|it| v.insert(it)),\n            };\n\n            if let Some(node) = node {\n                has_file |= if node.file_type == NodeFileType::Directory {\n                    node.collect_module_files(dir.join(&node.name))? || node.replace\n                } else {\n                    true\n                }\n            }\n        }\n\n        Ok(has_file)\n    }\n\n    fn dir_is_replace<P>(path: P) -> bool\n    where\n        P: AsRef<Path>,\n    {\n        if let Ok(v) = lgetxattr(&path, REPLACE_DIR_XATTR)\n            && String::from_utf8_lossy(&v) == \"y\"\n        {\n            return true;\n        }\n\n        path.as_ref().join(REPLACE_DIR_FILE_NAME).exists()\n    }\n\n    fn new_root<T: ToString>(name: T) -> Self {\n        Node {\n            name: name.to_string(),\n            file_type: Directory,\n            children: Default::default(),\n            module_path: None,\n            replace: false,\n            skip: false,\n        }\n    }\n\n    fn new_module<S>(name: &S, entry: &DirEntry) -> Option<Self>\n    where\n        S: ToString,\n    {\n        if let Ok(metadata) = entry.metadata() {\n            let path = entry.path();\n            let file_type = if metadata.file_type().is_char_device() && metadata.rdev() == 0 {\n                NodeFileType::Whiteout\n            } else {\n                NodeFileType::from_file_type(metadata.file_type())\n            };\n            let replace = file_type == NodeFileType::Directory && Self::dir_is_replace(&path);\n            if replace {\n                log::debug!(\"{} need replace\", path.display());\n            }\n            return Some(Self {\n                name: name.to_string(),\n                file_type,\n                children: HashMap::default(),\n                module_path: Some(path),\n                replace,\n                skip: false,\n            });\n        }\n\n        None\n    }\n}\n\nfn collect_module_files() -> Result<Option<Node>> {\n    let mut root = Node::new_root(\"\");\n    let mut system = Node::new_root(\"system\");\n    let module_root = Path::new(MODULE_DIR);\n    let mut has_file = false;\n\n    log::debug!(\"begin collect module files: {}\", module_root.display());\n\n    for entry in module_root.read_dir()?.flatten() {\n        if !entry.file_type()?.is_dir() {\n            continue;\n        }\n\n        let id = entry.file_name().to_str().unwrap().to_string();\n        log::debug!(\"processing new module: {id}\");\n\n        let prop = entry.path().join(\"module.prop\");\n        if !prop.exists() {\n            log::debug!(\"skipped module {id}, because not found module.prop\");\n            continue;\n        }\n\n        if entry.path().join(DISABLE_FILE_NAME).exists()\n            || entry.path().join(REMOVE_FILE_NAME).exists()\n            || entry.path().join(SKIP_MOUNT_FILE_NAME).exists()\n        {\n            log::debug!(\"skipped module {id}, due to disable/remove/skip_mount\");\n            continue;\n        }\n\n        let mod_system = entry.path().join(\"system\");\n\n        if !mod_system.is_dir() {\n            continue;\n        }\n\n        log::debug!(\"collecting {}\", entry.path().display());\n\n        has_file |= system.collect_module_files(mod_system)?;\n    }\n\n    if has_file {\n        const BUILTIN_PARTITIONS: [(&str, bool); 5] = [\n            (\"vendor\", true),\n            (\"system_ext\", true),\n            (\"product\", true),\n            (\"odm\", false),\n            (\"oem\", false),\n        ];\n\n        for (partition, require_symlink) in BUILTIN_PARTITIONS {\n            let path_of_root = Path::new(\"/\").join(partition);\n            let path_of_system = Path::new(\"/system\").join(partition);\n            if path_of_root.is_dir() && (!require_symlink || path_of_system.is_symlink()) {\n                let name = partition.to_string();\n                if let Some(node) = system.children.remove(&name) {\n                    root.children.insert(name, node);\n                }\n            }\n        }\n\n        root.children.insert(\"system\".to_string(), system);\n        Ok(Some(root))\n    } else {\n        Ok(None)\n    }\n}\n\nfn clone_symlink<Src: AsRef<Path>, Dst: AsRef<Path>>(src: Src, dst: Dst) -> Result<()> {\n    let src_symlink = read_link(src.as_ref())?;\n    symlink(&src_symlink, dst.as_ref())?;\n    lsetfilecon(dst.as_ref(), lgetfilecon(src.as_ref())?.as_str())?;\n    log::debug!(\n        \"clone symlink {} -> {}({})\",\n        dst.as_ref().display(),\n        dst.as_ref().display(),\n        src_symlink.display()\n    );\n    Ok(())\n}\n\nfn mount_mirror<P: AsRef<Path>, WP: AsRef<Path>>(\n    path: P,\n    work_dir_path: WP,\n    entry: &DirEntry,\n) -> Result<()> {\n    let path = path.as_ref().join(entry.file_name());\n    let work_dir_path = work_dir_path.as_ref().join(entry.file_name());\n    let file_type = entry.file_type()?;\n\n    if file_type.is_file() {\n        log::debug!(\n            \"mount mirror file {} -> {}\",\n            path.display(),\n            work_dir_path.display()\n        );\n        fs::File::create(&work_dir_path)?;\n        mount_bind(&path, &work_dir_path)?;\n    } else if file_type.is_dir() {\n        log::debug!(\n            \"mount mirror dir {} -> {}\",\n            path.display(),\n            work_dir_path.display()\n        );\n        create_dir(&work_dir_path)?;\n        let metadata = entry.metadata()?;\n        chmod(&work_dir_path, Mode::from_raw_mode(metadata.mode()))?;\n        chown(\n            &work_dir_path,\n            Some(Uid::from_raw(metadata.uid())),\n            Some(Gid::from_raw(metadata.gid())),\n        )?;\n        lsetfilecon(&work_dir_path, lgetfilecon(&path)?.as_str())?;\n        for entry in read_dir(&path)?.flatten() {\n            mount_mirror(&path, &work_dir_path, &entry)?;\n        }\n    } else if file_type.is_symlink() {\n        log::debug!(\n            \"create mirror symlink {} -> {}\",\n            path.display(),\n            work_dir_path.display()\n        );\n        clone_symlink(&path, &work_dir_path)?;\n    }\n\n    Ok(())\n}\n\nfn should_create_tmpfs(path: &Path, current: &mut Node, has_tmpfs: bool) -> bool {\n    if has_tmpfs {\n        return false;\n    }\n    if current.replace && current.module_path.is_some() {\n        return true;\n    }\n    for (name, node) in &mut current.children {\n        let real_path = path.join(name);\n        if node.file_type.needs_tmpfs_vs_real(&real_path) {\n            if current.module_path.is_none() {\n                log::error!(\"cannot create tmpfs on {}, ignore: {name}\", path.display());\n                node.skip = true;\n                continue;\n            }\n            return true;\n        }\n    }\n    false\n}\n\nfn prepare_tmpfs_skeleton(\n    path: &Path,\n    work_dir_path: &Path,\n    module_path: Option<&PathBuf>,\n) -> Result<()> {\n    log::debug!(\n        \"creating tmpfs skeleton for {} at {}\",\n        path.display(),\n        work_dir_path.display()\n    );\n    create_dir_all(work_dir_path)?;\n    let source: &Path = if path.exists() {\n        path\n    } else if let Some(mp) = module_path {\n        mp\n    } else {\n        bail!(\"cannot mount root dir {}!\", path.display());\n    };\n    let metadata = source.metadata()?;\n    chmod(work_dir_path, Mode::from_raw_mode(metadata.mode()))?;\n    chown(\n        work_dir_path,\n        Some(Uid::from_raw(metadata.uid())),\n        Some(Gid::from_raw(metadata.gid())),\n    )?;\n    lsetfilecon(work_dir_path, lgetfilecon(source)?.as_str())?;\n    Ok(())\n}\n\nfn handle_mount_result(result: Result<()>, path: &Path, name: &str, has_tmpfs: bool) -> Result<()> {\n    if let Err(e) = result {\n        if has_tmpfs {\n            return Err(e);\n        }\n        log::error!(\"mount child {}/{} failed: {}\", path.display(), name, e);\n    }\n    Ok(())\n}\n\nfn process_existing_entries(\n    path: &Path,\n    work_dir_path: &Path,\n    children: &mut HashMap<String, Node>,\n    has_tmpfs: bool,\n) -> Result<()> {\n    for entry in path.read_dir()?.flatten() {\n        let name = entry.file_name().to_string_lossy().to_string();\n        let result = if let Some(node) = children.remove(&name) {\n            if node.skip {\n                continue;\n            }\n            do_magic_mount(path, work_dir_path, node, has_tmpfs)\n                .with_context(|| format!(\"magic mount {}/{name}\", path.display()))\n        } else if has_tmpfs {\n            mount_mirror(path, work_dir_path, &entry)\n                .with_context(|| format!(\"mount mirror {}/{name}\", path.display()))\n        } else {\n            Ok(())\n        };\n        handle_mount_result(result, path, &name, has_tmpfs)?;\n    }\n    Ok(())\n}\n\nfn process_remaining_children(\n    path: &Path,\n    work_dir_path: &Path,\n    children: HashMap<String, Node>,\n    has_tmpfs: bool,\n) -> Result<()> {\n    for (name, node) in children {\n        if node.skip {\n            continue;\n        }\n        let result = do_magic_mount(path, work_dir_path, node, has_tmpfs)\n            .with_context(|| format!(\"magic mount {}/{name}\", path.display()));\n        handle_mount_result(result, path, &name, has_tmpfs)?;\n    }\n    Ok(())\n}\n\nfn move_tmpfs_to_target(work_dir_path: &Path, target: &Path) -> Result<()> {\n    log::debug!(\n        \"moving tmpfs {} -> {}\",\n        work_dir_path.display(),\n        target.display()\n    );\n    mount_move(work_dir_path, target).context(\"move self\")?;\n    mount_change(target, MountPropagationFlags::PRIVATE).context(\"make self private\")?;\n    Ok(())\n}\n\nfn do_magic_mount<P: AsRef<Path>, WP: AsRef<Path>>(\n    path: P,\n    work_dir_path: WP,\n    mut current: Node,\n    has_tmpfs: bool,\n) -> Result<()> {\n    let path = path.as_ref().join(&current.name);\n    let work_dir_path = work_dir_path.as_ref().join(&current.name);\n    match current.file_type {\n        RegularFile => {\n            let target_path = if has_tmpfs {\n                fs::File::create(&work_dir_path)?;\n                &work_dir_path\n            } else {\n                &path\n            };\n            if let Some(module_path) = &current.module_path {\n                log::debug!(\n                    \"mount module file {} -> {}\",\n                    module_path.display(),\n                    work_dir_path.display()\n                );\n                mount_bind(module_path, target_path)?;\n            } else {\n                bail!(\"cannot mount root file {}!\", path.display());\n            }\n        }\n        Symlink => {\n            if let Some(module_path) = &current.module_path {\n                log::debug!(\n                    \"create module symlink {} -> {}\",\n                    module_path.display(),\n                    work_dir_path.display()\n                );\n                clone_symlink(module_path, &work_dir_path)?;\n            } else {\n                bail!(\"cannot mount root symlink {}!\", path.display());\n            }\n        }\n        Directory => {\n            let create_tmpfs = should_create_tmpfs(&path, &mut current, has_tmpfs);\n            let has_tmpfs = has_tmpfs || create_tmpfs;\n\n            if has_tmpfs {\n                prepare_tmpfs_skeleton(&path, &work_dir_path, current.module_path.as_ref())?;\n            }\n            if create_tmpfs {\n                log::debug!(\n                    \"creating tmpfs for {} at {}\",\n                    path.display(),\n                    work_dir_path.display()\n                );\n                mount_bind(&work_dir_path, &work_dir_path).context(\"bind self\")?;\n            }\n            if path.exists() && !current.replace {\n                process_existing_entries(\n                    &path,\n                    &work_dir_path,\n                    &mut current.children,\n                    has_tmpfs,\n                )?;\n            }\n            if current.replace {\n                if current.module_path.is_none() {\n                    bail!(\"dir {} is declared as replaced but it is root!\", path.display());\n                }\n                log::debug!(\"dir {} is replaced\", path.display());\n            }\n            process_remaining_children(&path, &work_dir_path, current.children, has_tmpfs)?;\n            if create_tmpfs {\n                move_tmpfs_to_target(&work_dir_path, &path)?;\n            }\n        }\n        Whiteout => {\n            log::debug!(\"file {} is removed\", path.display());\n        }\n    }\n    Ok(())\n}\n\npub fn magic_mount() -> Result<()> {\n    if let Some(root) = collect_module_files()? {\n        log::debug!(\"collected: {:#?}\", root);\n        let tmp_dir = PathBuf::from(AP_MAGIC_MOUNT_SOURCE);\n        ensure_dir_exists(&tmp_dir)?;\n        mount(\"tmpfs\", &tmp_dir, \"tmpfs\", MountFlags::empty(), None).context(\"mount tmp\")?;\n        mount_change(&tmp_dir, MountPropagationFlags::PRIVATE).context(\"make tmp private\")?;\n        let result = do_magic_mount(\"/\", &tmp_dir, root, false);\n        if let Err(e) = unmount(&tmp_dir, UnmountFlags::DETACH) {\n            log::error!(\"failed to unmount tmp {}\", e);\n        }\n        fs::remove_dir(tmp_dir).ok();\n        result\n    } else {\n        log::info!(\"no modules to mount, skipping!\");\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "apd/src/main.rs",
    "content": "mod apd;\nmod assets;\nmod cli;\nmod defs;\nmod event;\nmod lua;\nmod magic_mount;\nmod metamodule;\nmod module;\nmod module_config;\nmod package;\n#[cfg(any(target_os = \"linux\", target_os = \"android\"))]\nmod pty;\nmod resetprop;\nmod restorecon;\nmod sepolicy;\nmod supercall;\nmod utils;\nfn main() -> anyhow::Result<()> {\n    cli::run()\n}\n"
  },
  {
    "path": "apd/src/metamodule.rs",
    "content": "//! Metamodule management\n//!\n//! This module handles all metamodule-related functionality.\n//! Metamodules are special modules that manage how regular modules are mounted\n//! and provide hooks for module installation/uninstallation.\n\nuse std::{\n    collections::HashMap,\n    path::{Path, PathBuf},\n    process::Command,\n};\n\nuse anyhow::{Context, Result, ensure};\nuse log::{info, warn};\n\nuse crate::{assets, defs, module::ModuleType::All};\n\n/// Determine whether the provided module properties mark it as a metamodule\npub fn is_metamodule(props: &HashMap<String, String>) -> bool {\n    props.get(\"metamodule\").is_some_and(|s| {\n        let trimmed = s.trim();\n        trimmed == \"1\" || trimmed.eq_ignore_ascii_case(\"true\")\n    })\n}\n\n/// Get metamodule path if it exists\n/// The metamodule is stored in /data/adb/modules/{id} with a symlink at /data/adb/metamodule\npub fn get_metamodule_path() -> Option<PathBuf> {\n    let path = Path::new(defs::METAMODULE_DIR);\n\n    // Check if symlink exists and resolve it\n    if path.is_symlink()\n        && let Ok(target) = std::fs::read_link(path)\n    {\n        // If target is relative, resolve it\n        let resolved = if target.is_absolute() {\n            target\n        } else {\n            path.parent()?.join(target)\n        };\n\n        if resolved.exists() && resolved.is_dir() {\n            return Some(resolved);\n        }\n        warn!(\n            \"Metamodule symlink points to non-existent path: {}\",\n            resolved.display()\n        );\n    }\n\n    // Fallback: search for metamodule=1 in modules directory\n    let mut result = None;\n    let _ = crate::module::foreach_module(All, |module_path| {\n        if let Ok(props) = crate::module::read_module_prop(module_path)\n            && is_metamodule(&props)\n        {\n            info!(\n                \"Found metamodule in modules directory: {}\",\n                module_path.display()\n            );\n            result = Some(module_path.to_path_buf());\n        }\n        Ok(())\n    });\n\n    result\n}\n\n/// Check if metamodule exists\npub fn has_metamodule() -> bool {\n    get_metamodule_path().is_some()\n}\n\n/// Check if it's safe to install a regular module\n/// Returns Ok(()) if safe, Err(is_disabled) if blocked\n/// - Err(true) means metamodule is disabled\n/// - Err(false) means metamodule is in other unstable state\npub fn check_install_safety() -> Result<(), bool> {\n    // No metamodule → safe\n    let Some(metamodule_path) = get_metamodule_path() else {\n        return Ok(());\n    };\n\n    // No metainstall.sh → safe (uses default installer)\n    // The staged update directory may contain the latest scripts, so check both locations\n    let has_metainstall = metamodule_path\n        .join(defs::METAMODULE_METAINSTALL_SCRIPT)\n        .exists()\n        || metamodule_path.file_name().is_some_and(|module_id| {\n            Path::new(defs::MODULE_UPDATE_DIR)\n                .join(module_id)\n                .join(defs::METAMODULE_METAINSTALL_SCRIPT)\n                .exists()\n        });\n    if !has_metainstall {\n        return Ok(());\n    }\n\n    // Check for marker files\n    let has_update = metamodule_path.join(defs::UPDATE_FILE_NAME).exists();\n    let has_remove = metamodule_path.join(defs::REMOVE_FILE_NAME).exists();\n    let has_disable = metamodule_path.join(defs::DISABLE_FILE_NAME).exists();\n\n    // Stable state (no markers) → safe\n    if !has_update && !has_remove && !has_disable {\n        return Ok(());\n    }\n\n    // Return true if disabled, false for other unstable states\n    Err(has_disable && !has_update && !has_remove)\n}\n\n/// Create or update the metamodule symlink\n/// Points /data/adb/metamodule -> /data/adb/modules/{module_id}\npub fn ensure_symlink<P>(module_path: P) -> Result<()>\nwhere\n    P: AsRef<Path>,\n{\n    // METAMODULE_DIR might have trailing slash, so we need to trim it\n    let symlink_path = Path::new(defs::METAMODULE_DIR.trim_end_matches('/'));\n    let module_path = module_path.as_ref();\n\n    info!(\n        \"Creating metamodule symlink: {} -> {}\",\n        symlink_path.display(),\n        module_path.display()\n    );\n\n    // Remove existing symlink if it exists\n    if symlink_path.exists() || symlink_path.is_symlink() {\n        info!(\"Removing old metamodule symlink/path\");\n        if symlink_path.is_symlink() {\n            std::fs::remove_file(symlink_path).with_context(|| \"Failed to remove old symlink\")?;\n        } else {\n            // Could be a directory, remove it\n            std::fs::remove_dir_all(symlink_path)\n                .with_context(|| \"Failed to remove old directory\")?;\n        }\n    }\n\n    // Create symlink\n    #[cfg(unix)]\n    std::os::unix::fs::symlink(module_path, symlink_path)\n        .with_context(|| format!(\"Failed to create symlink to {}\", module_path.display()))?;\n\n    info!(\"Metamodule symlink created successfully\");\n    Ok(())\n}\n\n/// Remove the metamodule symlink\npub fn remove_symlink() -> Result<()> {\n    let symlink_path = Path::new(defs::METAMODULE_DIR.trim_end_matches('/'));\n\n    if symlink_path.is_symlink() {\n        std::fs::remove_file(symlink_path)\n            .with_context(|| \"Failed to remove metamodule symlink\")?;\n        info!(\"Metamodule symlink removed\");\n    }\n\n    Ok(())\n}\n\n/// Get the install script content, using metainstall.sh from metamodule if available\n/// Returns the script content to be executed\npub fn get_install_script(\n    is_metamodule: bool,\n    installer_content: &str,\n    install_module_script: &str,\n) -> Result<String> {\n    // Check if there's a metamodule with metainstall.sh\n    // Only apply this logic for regular modules (not when installing metamodule itself)\n    let install_script = if is_metamodule {\n        info!(\"Installing metamodule, using default installer\");\n        install_module_script.to_string()\n    } else if let Some(metamodule_path) = get_metamodule_path() {\n        if metamodule_path.join(defs::DISABLE_FILE_NAME).exists() {\n            info!(\"Metamodule is disabled, using default installer\");\n            install_module_script.to_string()\n        } else {\n            let metainstall_path = metamodule_path.join(defs::METAMODULE_METAINSTALL_SCRIPT);\n\n            if metainstall_path.exists() {\n                info!(\"Using metainstall.sh from metamodule\");\n                let metamodule_content = std::fs::read_to_string(&metainstall_path)\n                    .with_context(|| \"Failed to read metamodule metainstall.sh\")?;\n                format!(\"{installer_content}\\n{metamodule_content}\\nexit 0\\n\")\n            } else {\n                info!(\"Metamodule exists but has no metainstall.sh, using default installer\");\n                install_module_script.to_string()\n            }\n        }\n    } else {\n        info!(\"No metamodule found, using default installer\");\n        install_module_script.to_string()\n    };\n\n    Ok(install_script)\n}\n\n/// Check if metamodule script exists and is ready to execute\n/// Returns None if metamodule doesn't exist, is disabled, or script is missing\n/// Returns Some(script_path) if script is ready to execute\nfn check_metamodule_script(script_name: &str) -> Option<PathBuf> {\n    // Check if metamodule exists\n    let metamodule_path = get_metamodule_path()?;\n\n    // Check if metamodule is disabled\n    if metamodule_path.join(defs::DISABLE_FILE_NAME).exists() {\n        info!(\"Metamodule is disabled, skipping {script_name}\");\n        return None;\n    }\n\n    // Check if script exists\n    let script_path = metamodule_path.join(script_name);\n    if !script_path.exists() {\n        return None;\n    }\n\n    Some(script_path)\n}\n\n/// Execute metamodule's metauninstall.sh for a specific module\npub fn exec_metauninstall_script(module_id: &str) -> Result<()> {\n    let Some(metauninstall_path) = check_metamodule_script(defs::METAMODULE_METAUNINSTALL_SCRIPT)\n    else {\n        return Ok(());\n    };\n\n    info!(\"Executing metamodule metauninstall.sh for module: {module_id}\",);\n\n    let result = Command::new(assets::BUSYBOX_PATH)\n        .args([\"sh\", metauninstall_path.to_str().unwrap()])\n        .current_dir(metauninstall_path.parent().unwrap())\n        .envs(crate::module::get_common_script_envs())\n        .env(\"MODULE_ID\", module_id)\n        .status()?;\n\n    ensure!(\n        result.success(),\n        \"Metamodule metauninstall.sh failed for module {module_id}: {:?}\",\n        result\n    );\n\n    info!(\"Metamodule metauninstall.sh executed successfully for {module_id}\",);\n    Ok(())\n}\n\n/// Execute metamodule mount script\npub fn exec_mount_script(module_dir: &str) -> Result<()> {\n    let Some(mount_script) = check_metamodule_script(defs::METAMODULE_MOUNT_SCRIPT) else {\n        return Ok(());\n    };\n\n    info!(\"Executing mount script for metamodule\");\n\n    let result = Command::new(assets::BUSYBOX_PATH)\n        .args([\"sh\", mount_script.to_str().unwrap()])\n        .envs(crate::module::get_common_script_envs())\n        .env(\"MODULE_DIR\", module_dir)\n        .status()?;\n\n    ensure!(\n        result.success(),\n        \"Metamodule mount script failed with status: {:?}\",\n        result\n    );\n\n    info!(\"Metamodule mount script executed successfully\");\n    Ok(())\n}\n\n/// Execute metamodule script for a specific stage\npub fn exec_stage_script(stage: &str, block: bool) -> Result<()> {\n    let Some(script_path) = check_metamodule_script(&format!(\"{stage}.sh\")) else {\n        return Ok(());\n    };\n\n    info!(\"Executing metamodule {stage}.sh\");\n    crate::module::exec_script(&script_path, block)?;\n    info!(\"Metamodule {stage}.sh executed successfully\");\n    Ok(())\n}\n"
  },
  {
    "path": "apd/src/module.rs",
    "content": "use crate::sepolicy::get_policy_main;\nuse crate::{lua, module_config};\nuse anyhow::{Context, Result, anyhow, bail, ensure};\nuse const_format::concatcp;\nuse is_executable::is_executable;\nuse java_properties::PropertiesIter;\nuse log::{debug, info, warn};\n#[cfg(unix)]\nuse std::os::unix::{prelude::PermissionsExt, process::CommandExt};\nuse std::{\n    collections::HashMap,\n    env::var as env_var,\n    fs::{self, remove_dir_all},\n    io::Cursor,\n    path::{Path, PathBuf, Component},\n    process::Command,\n    str::FromStr,\n};\nuse zip_extensions::zip_extract_file_to_memory;\n\n#[allow(clippy::wildcard_imports)]\nuse crate::utils::*;\nuse crate::{\n    assets,\n    defs::{self, MODULE_DIR, MODULE_UPDATE_DIR},\n    metamodule, restorecon,\n};\n\nconst INSTALLER_CONTENT: &str = include_str!(\"./installer.sh\");\nconst INSTALL_MODULE_SCRIPT: &str = concatcp!(\n    INSTALLER_CONTENT,\n    \"\\n\",\n    \"install_module\",\n    \"\\n\",\n    \"exit 0\",\n    \"\\n\"\n);\n\n#[derive(PartialEq, Eq)]\npub enum ModuleType {\n    All,\n    Active,\n    Updated,\n}\n\nfn exec_install_script(module_file: &str, is_metamodule: bool) -> Result<()> {\n    let realpath = std::fs::canonicalize(module_file)\n        .with_context(|| format!(\"realpath: {module_file} failed\"))?;\n\n    // Get install script from metamodule module\n    let install_script =\n        metamodule::get_install_script(is_metamodule, INSTALLER_CONTENT, INSTALL_MODULE_SCRIPT)?;\n\n    let result = Command::new(assets::BUSYBOX_PATH)\n        .args([\"sh\", \"-c\", &install_script])\n        .envs(get_common_script_envs())\n        .env(\"OUTFD\", \"1\")\n        .env(\"ZIPFILE\", realpath)\n        .status()?;\n    ensure!(result.success(), \"Failed to install module script\");\n    Ok(())\n}\n\npub fn handle_updated_modules() -> Result<()> {\n    let modules_root = Path::new(MODULE_DIR);\n    foreach_module(ModuleType::Updated, |updated_module| {\n        if !updated_module.is_dir() {\n            return Ok(());\n        }\n\n        if let Some(name) = updated_module.file_name() {\n            let module_dir = modules_root.join(name);\n            let mut disabled = false;\n            let mut removed = false;\n            if module_dir.exists() {\n                // If the old module is disabled, we need to also disable the new one\n                disabled = module_dir.join(defs::DISABLE_FILE_NAME).exists();\n                removed = module_dir.join(defs::REMOVE_FILE_NAME).exists();\n                remove_dir_all(&module_dir)?;\n            }\n            std::fs::rename(updated_module, &module_dir)?;\n            if removed {\n                let path = module_dir.join(defs::REMOVE_FILE_NAME);\n                if let Err(e) = ensure_file_exists(&path) {\n                    warn!(\"Failed to create {}: {e}\", path.display());\n                }\n            } else if disabled {\n                let path = module_dir.join(defs::DISABLE_FILE_NAME);\n                if let Err(e) = ensure_file_exists(&path) {\n                    warn!(\"Failed to create {}: {e}\", path.display());\n                }\n            }\n        }\n        Ok(())\n    })?;\n    Ok(())\n}\n\n/// Get common environment variables for script execution\npub fn get_common_script_envs() -> Vec<(&'static str, String)> {\n    vec![\n        (\"ASH_STANDALONE\", \"1\".to_string()),\n        (\"APATCH\", \"true\".to_string()),\n        (\"APATCH_VER\", defs::VERSION_NAME.to_string()),\n        (\"APATCH_VER_CODE\", defs::VERSION_CODE.to_string()),\n        (\n            \"PATH\",\n            format!(\n                \"/data/adb:{}:{}\",\n                defs::BINARY_DIR.trim_end_matches('/'),\n                env_var(\"PATH\").unwrap_or_default()\n            ),\n        ),\n    ]\n}\n\n// because we use something like A-B update\n// we need to update the module state after the boot_completed\n// if someone(such as the module) install a module before the boot_completed\n// then it may cause some problems, just forbid it\nfn ensure_boot_completed() -> Result<()> {\n    // ensure getprop sys.boot_completed == 1\n    if getprop(\"sys.boot_completed\").as_deref() != Some(\"1\") {\n        bail!(\"Android is Booting!\");\n    }\n    Ok(())\n}\n\nfn mark_update() -> Result<()> {\n    ensure_file_exists(concatcp!(defs::WORKING_DIR, defs::UPDATE_FILE_NAME))\n}\n\nfn mark_module_state(module: &str, flag_file: &str, create_or_delete: bool) -> Result<()> {\n    let module_state_file = Path::new(defs::MODULE_DIR).join(module).join(flag_file);\n    if create_or_delete {\n        ensure_file_exists(module_state_file)\n    } else {\n        if module_state_file.exists() {\n            fs::remove_file(module_state_file)?;\n        }\n        Ok(())\n    }\n}\npub fn foreach_module(\n    module_type: ModuleType,\n    mut f: impl FnMut(&Path) -> Result<()>,\n) -> Result<()> {\n    let modules_dir = Path::new(match module_type {\n        ModuleType::Updated => MODULE_UPDATE_DIR,\n        _ => defs::MODULE_DIR,\n    });\n    let dir = std::fs::read_dir(modules_dir)?;\n    for entry in dir.flatten() {\n        let path = entry.path();\n        if !path.is_dir() {\n            warn!(\"{} is not a directory, skip\", path.display());\n            continue;\n        }\n\n        if module_type == ModuleType::Active && path.join(defs::DISABLE_FILE_NAME).exists() {\n            info!(\"{} is disabled, skip\", path.display());\n            continue;\n        }\n        if module_type == ModuleType::Active && path.join(defs::REMOVE_FILE_NAME).exists() {\n            warn!(\"{} is removed, skip\", path.display());\n            continue;\n        }\n\n        f(&path)?;\n    }\n\n    Ok(())\n}\n\nfn foreach_active_module(f: impl FnMut(&Path) -> Result<()>) -> Result<()> {\n    foreach_module(ModuleType::Active, f)\n}\n\npub fn load_sepolicy_rule() -> Result<()> {\n    foreach_active_module(|path| {\n        let rule_file = path.join(\"sepolicy.rule\");\n        if !rule_file.exists() {\n            return Ok(());\n        }\n\n        info!(\"load policy: {}\", &rule_file.display());\n        let mut _sepol = get_policy_main(&[\n            \"magiskpolicy\".to_string(),\n            \"--live\".to_string(),\n            \"--apply\".to_string(),\n            rule_file.display().to_string(),\n        ])?;\n\n        Ok(())\n    })?;\n\n    Ok(())\n}\n\npub fn exec_script<T: AsRef<Path>>(path: T, wait: bool) -> Result<()> {\n    info!(\"exec {}\", path.as_ref().display());\n\n    let is_module_script = path.as_ref().starts_with(defs::MODULE_DIR);\n    // Extract module_id from path if it matches /data/adb/modules/{id}/...\n    let module_id = if is_module_script {\n        path.as_ref()\n            .strip_prefix(defs::MODULE_DIR)\n            .ok()\n            .and_then(|p| p.components().next())\n            .and_then(|c| c.as_os_str().to_str())\n            .map(ToString::to_string)\n    } else {\n        None\n    };\n\n    if is_module_script && module_id.is_none() {\n        debug!(\n            \"Failed to extract module_id from script path '{}'. Script will run without AP_MODULE environment variable.\",\n            path.as_ref().display()\n        );\n    }\n\n    let is_elf = fs::read(path.as_ref())\n        .ok()\n        .and_then(|bytes| bytes.get(..4).map(|b| b.to_vec()))\n        .map_or(false, |magic| magic == [0x7f, b'E', b'L', b'F']);\n\n    let mut command = Command::new(if is_elf {\n        path.as_ref().as_os_str().to_owned()\n    } else {\n        assets::BUSYBOX_PATH.into()\n    });\n    #[cfg(unix)]\n    {\n        command.process_group(0);\n        unsafe {\n            command.pre_exec(|| {\n                switch_cgroups();\n                Ok(())\n            });\n        }\n    }\n    command\n        .current_dir(path.as_ref().parent().unwrap())\n        .env(\"APATCH\", \"true\")\n        .env(\"APATCH_VER\", defs::VERSION_NAME)\n        .env(\"APATCH_VER_CODE\", defs::VERSION_CODE)\n        .env(\n            \"PATH\",\n            format!(\n                \"{}:{}\",\n                env_var(\"PATH\")?,\n                defs::BINARY_DIR.trim_end_matches('/')\n            ),\n        );\n    if !is_elf {\n        command\n            .arg(\"sh\")\n            .arg(path.as_ref())\n            .env(\"ASH_STANDALONE\", \"1\");\n    }\n    if let Some(id) = module_id {\n        command.env(\"AP_MODULE\", id);\n    }\n\n    let result = if wait {\n        command.status().map(|_| ())\n    } else {\n        command.spawn().map(|_| ())\n    };\n    result.map_err(|err| anyhow!(\"Failed to exec {}: {}\", path.as_ref().display(), err))\n}\n\npub fn exec_stage_script(stage: &str, block: bool) -> Result<()> {\n    foreach_active_module(|module| {\n        let script_path = module.join(format!(\"{stage}.sh\"));\n        if !script_path.exists() {\n            return Ok(());\n        }\n\n        exec_script(&script_path, block)\n    })?;\n    Ok(())\n}\n\npub fn exec_common_scripts(dir: &str, wait: bool) -> Result<()> {\n    let script_dir = Path::new(defs::ADB_DIR).join(dir);\n    if !script_dir.exists() {\n        info!(\"{} not exists, skip\", script_dir.display());\n        return Ok(());\n    }\n\n    let dir = fs::read_dir(&script_dir)?;\n    for entry in dir.flatten() {\n        let path = entry.path();\n\n        if !is_executable(&path) {\n            warn!(\"{} is not executable, skip\", path.display());\n            continue;\n        }\n\n        exec_script(path, wait)?;\n    }\n\n    Ok(())\n}\n\npub fn load_system_prop() -> Result<()> {\n    foreach_active_module(|module| {\n        let system_prop = module.join(\"system.prop\");\n        if !system_prop.exists() {\n            return Ok(());\n        }\n        info!(\"load {} system.prop\", module.display());\n\n        crate::resetprop::load_system_prop_file(&system_prop)?;\n\n        Ok(())\n    })?;\n\n    Ok(())\n}\n\npub fn prune_modules() -> Result<()> {\n    foreach_module(ModuleType::All, |module| {\n        fs::remove_file(module.join(defs::UPDATE_FILE_NAME)).ok();\n        if !module.join(defs::REMOVE_FILE_NAME).exists() {\n            return Ok(());\n        }\n\n        info!(\"remove module: {}\", module.display());\n\n        // Execute metamodule's metauninstall.sh first\n        let module_id = module.file_name().and_then(|n| n.to_str()).unwrap_or(\"\");\n\n        // Check if this is a metamodule\n        let is_metamodule = read_module_prop(module)\n            .map(|props| metamodule::is_metamodule(&props))\n            .unwrap_or(false);\n\n        if is_metamodule {\n            info!(\"Removing metamodule symlink\");\n            if let Err(e) = metamodule::remove_symlink() {\n                warn!(\"Failed to remove metamodule symlink: {e}\");\n            }\n        } else if let Err(e) = metamodule::exec_metauninstall_script(module_id) {\n            warn!(\"Failed to exec metamodule uninstall for {module_id}: {e}\",);\n        }\n\n        // Then execute module's own uninstall.sh\n        let uninstaller = module.join(\"uninstall.sh\");\n        if uninstaller.exists()\n            && let Err(e) = exec_script(uninstaller, true)\n        {\n            warn!(\"Failed to exec uninstaller: {e}\");\n        }\n\n        // Clear module configs before removing module directory\n        if let Err(e) = module_config::clear_module_configs(module_id) {\n            warn!(\"Failed to clear configs for {module_id}: {e}\");\n        }\n\n        // Finally remove the module directory\n        if let Err(e) = remove_dir_all(module) {\n            warn!(\"Failed to remove {}: {e}\", module.display());\n        }\n\n        Ok(())\n    })?;\n\n    // collect remaining modules, if none, clean up metamodule record\n    let remaining_modules: Vec<_> = std::fs::read_dir(defs::MODULE_DIR)?\n        .filter_map(std::result::Result::ok)\n        .filter(|entry| entry.path().join(\"module.prop\").exists())\n        .collect();\n\n    if remaining_modules.is_empty() {\n        info!(\"no remaining modules.\");\n    }\n\n    Ok(())\n}\n\nfn _install_module(zip: &str) -> Result<()> {\n    ensure_boot_completed()?;\n\n    // print banner\n    println!(include_str!(\"banner\"));\n\n    assets::ensure_binaries().with_context(|| \"binary missing\")?;\n\n    // first check if workding dir is usable\n    ensure_dir_exists(defs::WORKING_DIR).with_context(|| \"Failed to create working dir\")?;\n    ensure_dir_exists(defs::BINARY_DIR).with_context(|| \"Failed to create bin dir\")?;\n\n    // read the module_id from zip\n    let mut buffer: Vec<u8> = Vec::new();\n    let entry_path = PathBuf::from_str(\"module.prop\")?;\n    let zip_path = PathBuf::from_str(zip)?;\n    let zip_path = zip_path.canonicalize()?;\n    zip_extract_file_to_memory(&zip_path, &entry_path, &mut buffer)?;\n    let mut module_prop = HashMap::new();\n    PropertiesIter::new_with_encoding(Cursor::new(buffer), encoding_rs::UTF_8).read_into(\n        |k, v| {\n            module_prop.insert(k, v);\n        },\n    )?;\n    info!(\"module prop: {:?}\", module_prop);\n\n    let Some(module_id) = module_prop.get(\"id\") else {\n        bail!(\"module id not found in module.prop!\");\n    };\n    let module_id = module_id.trim();\n\n    // Check if this module is a metamodule\n    let is_metamodule = metamodule::is_metamodule(&module_prop);\n\n    // Check if it's safe to install regular module\n    if !is_metamodule && let Err(is_disabled) = metamodule::check_install_safety() {\n        println!(\"\\n❌ Installation Blocked\");\n        println!(\"┌────────────────────────────────\");\n        println!(\"│ A metamodule with custom installer is active\");\n        println!(\"│\");\n        if is_disabled {\n            println!(\"│ Current state: Disabled\");\n            println!(\"│ Action required: Re-enable or uninstall it, then reboot\");\n        } else {\n            println!(\"│ Current state: Pending changes\");\n            println!(\"│ Action required: Reboot to apply changes first\");\n        }\n        println!(\"└─────────────────────────────────\\n\");\n        bail!(\"Metamodule installation blocked\");\n    }\n\n    let modules_dir = Path::new(defs::MODULE_DIR);\n    let modules_update_dir = Path::new(defs::MODULE_UPDATE_DIR);\n    if !Path::new(modules_dir).exists() {\n        fs::create_dir(modules_dir).expect(\"Failed to create modules folder\");\n        let permissions = fs::Permissions::from_mode(0o700);\n        fs::set_permissions(modules_dir, permissions).expect(\"Failed to set permissions\");\n    }\n\n    if is_metamodule {\n        info!(\"Installing metamodule: {module_id}\");\n\n        // Check if there's already a metamodule installed\n        if metamodule::has_metamodule()\n            && let Some(existing_path) = metamodule::get_metamodule_path()\n        {\n            let existing_id = read_module_prop(&existing_path)\n                .ok()\n                .and_then(|m| m.get(\"id\").cloned())\n                .unwrap_or_else(|| \"unknown\".to_string());\n\n            if existing_id != module_id {\n                println!(\"\\n❌ Installation Failed\");\n                println!(\"┌────────────────────────────────\");\n                println!(\"│ A metamodule is already installed\");\n                println!(\"│   Current metamodule: {existing_id}\");\n                println!(\"│\");\n                println!(\"│ Only one metamodule can be active at a time.\");\n                println!(\"│\");\n                println!(\"│ To install this metamodule:\");\n                println!(\"│   1. Uninstall the current metamodule\");\n                println!(\"│   2. Reboot your device\");\n                println!(\"│   3. Install the new metamodule\");\n                println!(\"└─────────────────────────────────\\n\");\n                bail!(\"Cannot install multiple metamodules\");\n            }\n        }\n    }\n\n    let module_dir = format!(\"{}{}\", modules_dir.display(), module_id);\n    let _module_update_dir = format!(\"{}{}\", modules_update_dir.display(), module_id);\n    info!(\"module dir: {}\", module_dir);\n    if !Path::new(&module_dir.clone()).exists() {\n        fs::create_dir(&module_dir.clone()).expect(\"Failed to create module folder\");\n        let permissions = fs::Permissions::from_mode(0o700);\n        fs::set_permissions(module_dir.clone(), permissions).expect(\"Failed to set permissions\");\n    }\n    // unzip the image and move it to modules_update/<id> dir\n    let file = fs::File::open(zip)?;\n    let mut archive = zip::ZipArchive::new(file)?;\n    archive.extract(&_module_update_dir)?;\n    \n    // Set SELinux context for module root directory and special files\n    // This is critical for .img files that need to be loop-mounted\n    #[cfg(unix)]\n    {\n        let module_update_path = Path::new(&_module_update_dir);\n        if module_update_path.exists() {\n            // Set adb_data_file context for the module root directory\n            restorecon::lsetfilecon(&_module_update_dir, restorecon::ADB_CON)?;\n            \n            // Process special files like .img that need proper permissions for mounting\n            if let Ok(entries) = fs::read_dir(&_module_update_dir) {\n                for entry in entries.flatten() {\n                    let path = entry.path();\n                    if let Some(extension) = path.extension() {\n                        if extension == \"img\" {\n                            // Set proper permissions for image files (readable by all)\n                            fs::set_permissions(&path, fs::Permissions::from_mode(0o644))?;\n                            // Set SELinux context to allow loop mounting\n                            restorecon::lsetfilecon(&path, restorecon::ADB_CON)?;\n                            info!(\"Set permissions and SELinux context for: {:?}\", path);\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    println!(\"- Running module installer\");\n    exec_install_script(zip, is_metamodule)?;\n\n    // set permission and selinux context for $MOD/system\n    let module_system_dir = PathBuf::from(module_dir.clone()).join(\"system\");\n    if module_system_dir.exists() {\n        #[cfg(unix)]\n        fs::set_permissions(&module_system_dir, fs::Permissions::from_mode(0o755))?;\n        restorecon::restore_syscon(&module_system_dir)?;\n    }\n\n    // Create symlink for metamodule\n    if is_metamodule {\n        println!(\"- Creating metamodule symlink\");\n        metamodule::ensure_symlink(&module_dir)?;\n    }\n\n    mark_update()?;\n    Ok(())\n}\n\npub fn install_module(zip: &str) -> Result<()> {\n    let result = _install_module(zip);\n    result\n}\n\npub fn _uninstall_module(id: &str, update_dir: &str) -> Result<()> {\n    let dir = Path::new(update_dir);\n    ensure!(dir.exists(), \"No module installed\");\n\n    // iterate the modules_update dir, find the module to be removed\n    let dir = fs::read_dir(dir)?;\n    for entry in dir.flatten() {\n        let path = entry.path();\n        let module_prop = path.join(\"module.prop\");\n        if !module_prop.exists() {\n            continue;\n        }\n        let content = fs::read(module_prop)?;\n        let mut module_id: String = String::new();\n        PropertiesIter::new_with_encoding(Cursor::new(content), encoding_rs::UTF_8).read_into(\n            |k, v| {\n                if k.eq(\"id\") {\n                    module_id = v;\n                }\n            },\n        )?;\n        if module_id.eq(id) {\n            let remove_file = path.join(defs::REMOVE_FILE_NAME);\n            fs::File::create(remove_file).with_context(|| \"Failed to create remove file.\")?;\n            break;\n        }\n    }\n\n    // santity check\n    let target_module_path = format!(\"{update_dir}/{id}\");\n    let target_module = Path::new(&target_module_path);\n    if target_module.exists() {\n        let remove_file = target_module.join(defs::REMOVE_FILE_NAME);\n        if !remove_file.exists() {\n            fs::File::create(remove_file).with_context(|| \"Failed to create remove file.\")?;\n        }\n    }\n\n    let _ = mark_module_state(id, defs::REMOVE_FILE_NAME, true);\n    Ok(())\n}\npub fn uninstall_module(id: &str) -> Result<()> {\n    _uninstall_module(id, defs::MODULE_DIR)?;\n    mark_update()?;\n    Ok(())\n}\n\npub fn undo_uninstall_module(id: &str) -> Result<()> {\n    let module_path = Path::new(defs::MODULE_DIR).join(id);\n    ensure!(module_path.exists(), \"Module {id} not found\");\n\n    // Remove the remove mark\n    let remove_file = module_path.join(defs::REMOVE_FILE_NAME);\n    if remove_file.exists() {\n        fs::remove_file(&remove_file)\n            .with_context(|| format!(\"Failed to delete remove file for module '{id}'\"))?;\n        info!(\"Removed the remove mark for module {id}\");\n    }\n\n    Ok(())\n}\n\n/// Read module.prop from the given module path and return as a HashMap\npub fn read_module_prop(module_path: &Path) -> Result<HashMap<String, String>> {\n    let module_prop = module_path.join(\"module.prop\");\n    ensure!(\n        module_prop.exists(),\n        \"module.prop not found in {}\",\n        module_path.display()\n    );\n\n    let content = std::fs::read(&module_prop)\n        .with_context(|| format!(\"Failed to read module.prop: {}\", module_prop.display()))?;\n\n    let mut prop_map: HashMap<String, String> = HashMap::new();\n    PropertiesIter::new_with_encoding(Cursor::new(content), encoding_rs::UTF_8)\n        .read_into(|k, v| {\n            prop_map.insert(k, v);\n        })\n        .with_context(|| format!(\"Failed to parse module.prop: {}\", module_prop.display()))?;\n\n    Ok(prop_map)\n}\n\npub fn run_action(id: &str) -> Result<()> {\n    let action_script_path = format!(\"/data/adb/modules/{}/action.sh\", id);\n    if Path::new(&action_script_path).exists() {\n        let _ = exec_script(&action_script_path, true);\n    } else {\n        //if no action.sh, try to run lua action\n        lua::run_lua(&id, \"action\", false, true).map_err(|e| anyhow::anyhow!(\"{}\", e))?;\n    }\n    Ok(())\n}\n\nfn _change_module_state(module_dir: &str, mid: &str, enable: bool) -> Result<()> {\n    let src_module_path = format!(\"{module_dir}/{mid}\");\n    let src_module = Path::new(&src_module_path);\n    ensure!(src_module.exists(), \"module: {} not found!\", mid);\n\n    let disable_path = src_module.join(defs::DISABLE_FILE_NAME);\n    if enable {\n        if disable_path.exists() {\n            fs::remove_file(&disable_path).with_context(|| {\n                format!(\"Failed to remove disable file: {}\", &disable_path.display())\n            })?;\n        }\n    } else {\n        ensure_file_exists(disable_path)?;\n    }\n\n    let _ = mark_module_state(mid, defs::DISABLE_FILE_NAME, !enable);\n\n    Ok(())\n}\n\npub fn _enable_module(id: &str, update_dir: &Path) -> Result<()> {\n    if let Some(module_dir_str) = update_dir.to_str() {\n        _change_module_state(module_dir_str, id, true)\n    } else {\n        info!(\"Enable module failed: Invalid path\");\n        Err(anyhow::anyhow!(\"Invalid module directory\"))\n    }\n}\n\npub fn enable_module(id: &str) -> Result<()> {\n    let update_dir = Path::new(defs::MODULE_DIR);\n    _enable_module(id, update_dir)?;\n    Ok(())\n}\n\npub fn _disable_module(id: &str, update_dir: &Path) -> Result<()> {\n    if let Some(module_dir_str) = update_dir.to_str() {\n        _change_module_state(module_dir_str, id, false)\n    } else {\n        info!(\"Disable module failed: Invalid path\");\n        Err(anyhow::anyhow!(\"Invalid module directory\"))\n    }\n}\n\npub fn disable_module(id: &str) -> Result<()> {\n    let module_dir = Path::new(defs::MODULE_DIR);\n    _disable_module(id, module_dir)?;\n\n    Ok(())\n}\n\npub fn _disable_all_modules(dir: &str) -> Result<()> {\n    let dir = fs::read_dir(dir)?;\n    for entry in dir.flatten() {\n        let path = entry.path();\n        let disable_flag = path.join(defs::DISABLE_FILE_NAME);\n        if let Err(e) = ensure_file_exists(disable_flag) {\n            warn!(\"Failed to disable module: {}: {}\", path.display(), e);\n        }\n    }\n    Ok(())\n}\n\npub fn disable_all_modules() -> Result<()> {\n    // Skip disabling modules since boot completed\n    if getprop(\"sys.boot_completed\").as_deref() == Some(\"1\") {\n        info!(\"System boot completed, no need to disable all modules\");\n        return Ok(());\n    }\n    mark_update()?;\n    _disable_all_modules(defs::MODULE_DIR)?;\n    Ok(())\n}\n\n// Resolve a module icon path to an absolute on-disk path\nfn resolve_module_icon_path(\n    module_prop_map: &mut HashMap<String, String>,\n    key: &str,\n    module_path: &Path,\n) {\n    let module_id = module_prop_map.get(\"id\").map(|s| s.as_str()).unwrap_or(\"\");\n\n    if let Some(icon_value) = module_prop_map.get(key).map(|v| v.trim()).filter(|v| !v.is_empty()) {\n        let path = Path::new(icon_value);\n\n        if path.is_absolute() || path.components().any(|c| matches!(c, Component::ParentDir)) {\n            log::warn!(\"Rejected {} (invalid path) for module {}: {}\", key, module_id, icon_value);\n            return;\n        }\n\n        let candidate = module_path.join(path);\n\n        if candidate.exists() && candidate.is_file() {\n            if let Some(full_path) = candidate.to_str() {\n                module_prop_map.insert(key.to_string(), full_path.to_string());\n            }\n        } else {\n            log::debug!(\"{} not found for module {}: {}\", key, module_id, candidate.display());\n        }\n    }\n}\n\nfn _list_modules(path: &str) -> Vec<HashMap<String, String>> {\n    // Load all module configs once to minimize I/O overhead\n    let all_configs = match module_config::get_all_module_configs() {\n        Ok(configs) => configs,\n        Err(e) => {\n            warn!(\"Failed to load module configs: {e}\");\n            HashMap::new()\n        }\n    };\n\n    // first check enabled modules\n    let dir = fs::read_dir(path);\n    let Ok(dir) = dir else {\n        return Vec::new();\n    };\n\n    let mut modules: Vec<HashMap<String, String>> = Vec::new();\n\n    for entry in dir.flatten() {\n        let path = entry.path();\n        info!(\"path: {}\", path.display());\n        let module_prop = path.join(\"module.prop\");\n        if !module_prop.exists() {\n            continue;\n        }\n        let content = fs::read(&module_prop);\n        let Ok(content) = content else {\n            warn!(\"Failed to read file: {}\", module_prop.display());\n            continue;\n        };\n        let mut module_prop_map: HashMap<String, String> = HashMap::new();\n        let encoding = encoding_rs::UTF_8;\n\n        if PropertiesIter::new_with_encoding(Cursor::new(content), encoding)\n            .read_into(|k, v| {\n                module_prop_map.insert(k, v);\n            })\n            .is_err()\n        {\n            warn!(\"Failed to parse module.prop: {}\", module_prop.display());\n            continue;\n        }\n\n        if !module_prop_map.contains_key(\"id\") || module_prop_map[\"id\"].is_empty() {\n            match entry.file_name().to_str() {\n                Some(id) => {\n                    info!(\"Use dir name as module id: {}\", id);\n                    module_prop_map.insert(\"id\".to_owned(), id.to_owned());\n                }\n                _ => {\n                    info!(\"Failed to get module id: {:?}\", module_prop);\n                    continue;\n                }\n            }\n        }\n\n        // Add enabled, update, remove flags\n        let enabled = !path.join(defs::DISABLE_FILE_NAME).exists();\n        let update = path.join(defs::UPDATE_FILE_NAME).exists();\n        let remove = path.join(defs::REMOVE_FILE_NAME).exists();\n        let web = path.join(defs::MODULE_WEB_DIR).exists();\n        let id = module_prop_map.get(\"id\").map(|s| s.as_str()).unwrap_or(\"\");\n        let id_lua_file = format!(\"{}.lua\", id);\n        let action = path.join(defs::MODULE_ACTION_SH).exists() || path.join(&id_lua_file).exists();\n\n        module_prop_map.insert(\"enabled\".to_owned(), enabled.to_string());\n        module_prop_map.insert(\"update\".to_owned(), update.to_string());\n        module_prop_map.insert(\"remove\".to_owned(), remove.to_string());\n        module_prop_map.insert(\"web\".to_owned(), web.to_string());\n        module_prop_map.insert(\"action\".to_owned(), action.to_string());\n\n        // Resolve and validate module icon paths for action and webui icons\n        resolve_module_icon_path(&mut module_prop_map, \"actionIcon\", &path);\n        resolve_module_icon_path(&mut module_prop_map, \"webuiIcon\", &path);\n\n        // Apply module config overrides and extract managed features\n        if let Some(module_id) = module_prop_map.get(\"id\")\n            && let Some(config) = all_configs.get(module_id.as_str())\n        {\n            // Apply override.description\n            if let Some(desc) = config.get(\"override.description\") {\n                module_prop_map.insert(\"description\".to_owned(), desc.clone());\n            }\n        }\n\n        modules.push(module_prop_map);\n    }\n\n    modules\n}\n\npub fn list_modules() -> Result<()> {\n    let modules = _list_modules(defs::MODULE_DIR);\n    println!(\"{}\", serde_json::to_string_pretty(&modules)?);\n    Ok(())\n}\n"
  },
  {
    "path": "apd/src/module_config.rs",
    "content": "use std::{\n    collections::HashMap,\n    fs::{self, File},\n    io::{Read, Write},\n    path::{Path, PathBuf},\n};\n\nuse anyhow::{Context, Result, bail};\nuse log::{debug, warn};\n\nuse crate::{defs, utils::ensure_dir_exists};\n\n#[allow(clippy::unreadable_literal)]\nconst MODULE_CONFIG_MAGIC: u32 = 0x4150544D; // \"APTM\"\nconst MODULE_CONFIG_VERSION: u32 = 1;\n\n// Validation limits\npub const MAX_CONFIG_KEY_LEN: usize = 256;\npub const MAX_CONFIG_VALUE_LEN: usize = 1024 * 1024; // 1MB\npub const MAX_CONFIG_COUNT: usize = 32;\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum ConfigType {\n    Persist,\n    Temp,\n}\n\nimpl ConfigType {\n    const fn filename(self) -> &'static str {\n        match self {\n            Self::Persist => defs::PERSIST_CONFIG_NAME,\n            Self::Temp => defs::TEMP_CONFIG_NAME,\n        }\n    }\n}\n\n/// Validate config key\n/// Uses the same validation rules as module_id: ^[a-zA-Z][a-zA-Z0-9._-]+$\n/// - Must start with a letter (a-zA-Z)\n/// - Followed by one or more alphanumeric, dot, underscore, or hyphen characters\n/// - Minimum length: 2 characters\npub fn validate_config_key(key: &str) -> Result<()> {\n    if key.is_empty() {\n        bail!(\"Config key cannot be empty\");\n    }\n\n    if key.len() > MAX_CONFIG_KEY_LEN {\n        bail!(\n            \"Config key too long: {} bytes (max: {MAX_CONFIG_KEY_LEN})\",\n            key.len(),\n        );\n    }\n\n    // Use same pattern as module_id for consistency\n    let re = regex_lite::Regex::new(r\"^[a-zA-Z][a-zA-Z0-9._-]+$\")?;\n    if !re.is_match(key) {\n        bail!(\"Invalid config key: '{key}'. Must match /^[a-zA-Z][a-zA-Z0-9._-]+$/\");\n    }\n\n    Ok(())\n}\n\n/// Validate config value\n/// Only enforces maximum length - no character restrictions\n/// Values are stored in binary format with length prefix, so any UTF-8 data is safe\npub fn validate_config_value(value: &str) -> Result<()> {\n    if value.len() > MAX_CONFIG_VALUE_LEN {\n        bail!(\n            \"Config value too long: {} bytes (max: {})\",\n            value.len(),\n            MAX_CONFIG_VALUE_LEN\n        );\n    }\n\n    // No character restrictions - binary storage format handles all UTF-8 safely\n    Ok(())\n}\n\n/// Validate config count\nfn validate_config_count(config: &HashMap<String, String>) -> Result<()> {\n    if config.len() > MAX_CONFIG_COUNT {\n        bail!(\n            \"Too many config entries: {} (max: {MAX_CONFIG_COUNT})\",\n            config.len(),\n        );\n    }\n    Ok(())\n}\n\n/// Get the config directory path for a module\nfn get_config_dir(module_id: &str) -> PathBuf {\n    Path::new(defs::MODULE_CONFIG_DIR).join(module_id)\n}\n\n/// Get the config file path for a module\nfn get_config_path(module_id: &str, config_type: ConfigType) -> PathBuf {\n    get_config_dir(module_id).join(config_type.filename())\n}\n\n/// Ensure the config directory exists\nfn ensure_config_dir(module_id: &str) -> Result<PathBuf> {\n    let dir = get_config_dir(module_id);\n    ensure_dir_exists(&dir)?;\n    Ok(dir)\n}\n\n/// Load config from binary file\npub fn load_config(module_id: &str, config_type: ConfigType) -> Result<HashMap<String, String>> {\n    let config_path = get_config_path(module_id, config_type);\n\n    if !config_path.exists() {\n        debug!(\"Config file not found: {}\", config_path.display());\n        return Ok(HashMap::new());\n    }\n\n    let mut file = File::open(&config_path)\n        .with_context(|| format!(\"Failed to open config file: {}\", config_path.display()))?;\n\n    // Read magic\n    let mut magic_buf = [0u8; 4];\n    file.read_exact(&mut magic_buf)\n        .with_context(|| \"Failed to read magic\")?;\n    let magic = u32::from_le_bytes(magic_buf);\n\n    if magic != MODULE_CONFIG_MAGIC {\n        bail!(\"Invalid config magic: expected 0x{MODULE_CONFIG_MAGIC:08x}, got 0x{magic:08x}\");\n    }\n\n    // Read version\n    let mut version_buf = [0u8; 4];\n    file.read_exact(&mut version_buf)\n        .with_context(|| \"Failed to read version\")?;\n    let version = u32::from_le_bytes(version_buf);\n\n    if version != MODULE_CONFIG_VERSION {\n        bail!(\"Unsupported config version: expected {MODULE_CONFIG_VERSION}, got {version}\");\n    }\n\n    // Read count\n    let mut count_buf = [0u8; 4];\n    file.read_exact(&mut count_buf)\n        .with_context(|| \"Failed to read count\")?;\n    let count = u32::from_le_bytes(count_buf);\n\n    // Read entries\n    let mut config = HashMap::new();\n    for i in 0..count {\n        // Read key length\n        let mut key_len_buf = [0u8; 4];\n        file.read_exact(&mut key_len_buf)\n            .with_context(|| format!(\"Failed to read key length for entry {i}\"))?;\n        let key_len = u32::from_le_bytes(key_len_buf) as usize;\n\n        // Read key data\n        let mut key_buf = vec![0u8; key_len];\n        file.read_exact(&mut key_buf)\n            .with_context(|| format!(\"Failed to read key data for entry {i}\"))?;\n        let key = String::from_utf8(key_buf)\n            .with_context(|| format!(\"Invalid UTF-8 in key for entry {i}\"))?;\n\n        // Read value length\n        let mut value_len_buf = [0u8; 4];\n        file.read_exact(&mut value_len_buf)\n            .with_context(|| format!(\"Failed to read value length for entry {i}\"))?;\n        let value_len = u32::from_le_bytes(value_len_buf) as usize;\n\n        // Read value data\n        let mut value_buf = vec![0u8; value_len];\n        file.read_exact(&mut value_buf)\n            .with_context(|| format!(\"Failed to read value data for entry {i}\"))?;\n        let value = String::from_utf8(value_buf)\n            .with_context(|| format!(\"Invalid UTF-8 in value for entry {i}\"))?;\n\n        config.insert(key, value);\n    }\n\n    debug!(\n        \"Loaded {} entries from {}\",\n        config.len(),\n        config_path.display()\n    );\n    Ok(config)\n}\n\n/// Save config to binary file\npub fn save_config(\n    module_id: &str,\n    config_type: ConfigType,\n    config: &HashMap<String, String>,\n) -> Result<()> {\n    // Validate config count\n    validate_config_count(config)?;\n\n    // Validate all keys and values\n    for (key, value) in config {\n        validate_config_key(key).with_context(|| format!(\"Invalid config key: '{key}'\"))?;\n        validate_config_value(value)\n            .with_context(|| format!(\"Invalid config value for key '{key}'\"))?;\n    }\n\n    ensure_config_dir(module_id)?;\n\n    let config_path = get_config_path(module_id, config_type);\n    let temp_path = config_path.with_extension(\"tmp\");\n\n    // Write to temporary file first\n    let mut file = File::create(&temp_path)\n        .with_context(|| format!(\"Failed to create temp config file: {}\", temp_path.display()))?;\n\n    // Write magic\n    file.write_all(&MODULE_CONFIG_MAGIC.to_le_bytes())\n        .with_context(|| \"Failed to write magic\")?;\n\n    // Write version\n    file.write_all(&MODULE_CONFIG_VERSION.to_le_bytes())\n        .with_context(|| \"Failed to write version\")?;\n\n    // Write count\n    let count = config.len() as u32;\n    file.write_all(&count.to_le_bytes())\n        .with_context(|| \"Failed to write count\")?;\n\n    // Write entries\n    for (key, value) in config {\n        // Write key length\n        let key_bytes = key.as_bytes();\n        let key_len = key_bytes.len() as u32;\n        file.write_all(&key_len.to_le_bytes())\n            .with_context(|| format!(\"Failed to write key length for '{key}'\"))?;\n\n        // Write key data\n        file.write_all(key_bytes)\n            .with_context(|| format!(\"Failed to write key data for '{key}'\"))?;\n\n        // Write value length\n        let value_bytes = value.as_bytes();\n        let value_len = value_bytes.len() as u32;\n        file.write_all(&value_len.to_le_bytes())\n            .with_context(|| format!(\"Failed to write value length for '{key}'\"))?;\n\n        // Write value data\n        file.write_all(value_bytes)\n            .with_context(|| format!(\"Failed to write value data for '{key}'\"))?;\n    }\n\n    file.sync_all()\n        .with_context(|| \"Failed to sync config file\")?;\n\n    // Atomic rename\n    fs::rename(&temp_path, &config_path).with_context(|| {\n        format!(\n            \"Failed to rename config file: {} -> {}\",\n            temp_path.display(),\n            config_path.display()\n        )\n    })?;\n\n    debug!(\n        \"Saved {} entries to {}\",\n        config.len(),\n        config_path.display()\n    );\n    Ok(())\n}\n\n/// Get a single config value\n#[allow(dead_code)]\npub fn get_config_value(\n    module_id: &str,\n    key: &str,\n    config_type: ConfigType,\n) -> Result<Option<String>> {\n    let config = load_config(module_id, config_type)?;\n    Ok(config.get(key).cloned())\n}\n\n/// Set a single config value\npub fn set_config_value(\n    module_id: &str,\n    key: &str,\n    value: &str,\n    config_type: ConfigType,\n) -> Result<()> {\n    // Validate input early for better error messages\n    validate_config_key(key)?;\n    validate_config_value(value)?;\n\n    let mut config = load_config(module_id, config_type)?;\n    config.insert(key.to_string(), value.to_string());\n\n    // Note: save_config will also validate, but this provides earlier feedback\n    save_config(module_id, config_type, &config)?;\n    Ok(())\n}\n\n/// Delete a single config value\npub fn delete_config_value(module_id: &str, key: &str, config_type: ConfigType) -> Result<()> {\n    let mut config = load_config(module_id, config_type)?;\n\n    if config.remove(key).is_none() {\n        bail!(\"Key '{key}' not found in config\");\n    }\n\n    save_config(module_id, config_type, &config)?;\n    Ok(())\n}\n\n/// Clear all config values\npub fn clear_config(module_id: &str, config_type: ConfigType) -> Result<()> {\n    let config_path = get_config_path(module_id, config_type);\n\n    if config_path.exists() {\n        fs::remove_file(&config_path)\n            .with_context(|| format!(\"Failed to remove config file: {}\", config_path.display()))?;\n        debug!(\"Cleared config: {}\", config_path.display());\n    }\n\n    Ok(())\n}\n\n/// Merge persist and temp configs (temp takes priority)\npub fn merge_configs(module_id: &str) -> Result<HashMap<String, String>> {\n    let mut merged = match load_config(module_id, ConfigType::Persist) {\n        Ok(config) => config,\n        Err(e) => {\n            warn!(\"Failed to load persist config for module '{module_id}': {e}\");\n            HashMap::new()\n        }\n    };\n\n    let temp = match load_config(module_id, ConfigType::Temp) {\n        Ok(config) => config,\n        Err(e) => {\n            warn!(\"Failed to load temp config for module '{module_id}': {e}\");\n            HashMap::new()\n        }\n    };\n\n    // Temp config overrides persist config\n    for (key, value) in temp {\n        merged.insert(key, value);\n    }\n\n    Ok(merged)\n}\n\n/// Get all module configs (for iteration)\n/// Loads all configs in a single pass to minimize I/O overhead\npub fn get_all_module_configs() -> Result<HashMap<String, HashMap<String, String>>> {\n    let config_root = Path::new(defs::MODULE_CONFIG_DIR);\n\n    if !config_root.exists() {\n        return Ok(HashMap::new());\n    }\n\n    let mut all_configs = HashMap::new();\n\n    for entry in fs::read_dir(config_root)\n        .with_context(|| format!(\"Failed to read config directory: {}\", config_root.display()))?\n    {\n        let entry = entry?;\n        let path = entry.path();\n\n        if !path.is_dir() {\n            continue;\n        }\n\n        if let Some(module_id) = path.file_name().and_then(|n| n.to_str()) {\n            match merge_configs(module_id) {\n                Ok(config) => {\n                    if !config.is_empty() {\n                        all_configs.insert(module_id.to_string(), config);\n                    }\n                }\n                Err(e) => {\n                    warn!(\"Failed to load config for module '{module_id}': {e}\");\n                    // Continue processing other modules\n                }\n            }\n        }\n    }\n\n    Ok(all_configs)\n}\n\n/// Clear all temporary configs (called during post-fs-data)\npub fn clear_all_temp_configs() -> Result<()> {\n    let config_root = Path::new(defs::MODULE_CONFIG_DIR);\n\n    if !config_root.exists() {\n        debug!(\"Config directory does not exist, nothing to clear\");\n        return Ok(());\n    }\n\n    let mut cleared_count = 0;\n\n    for entry in fs::read_dir(config_root)\n        .with_context(|| format!(\"Failed to read config directory: {}\", config_root.display()))?\n    {\n        let entry = entry?;\n        let path = entry.path();\n\n        if !path.is_dir() {\n            continue;\n        }\n\n        let temp_config = path.join(defs::TEMP_CONFIG_NAME);\n        if temp_config.exists() {\n            match fs::remove_file(&temp_config) {\n                Ok(()) => {\n                    debug!(\"Cleared temp config: {}\", temp_config.display());\n                    cleared_count += 1;\n                }\n                Err(e) => {\n                    warn!(\"Failed to clear temp config {}: {e}\", temp_config.display());\n                }\n            }\n        }\n    }\n\n    if cleared_count > 0 {\n        debug!(\"Cleared {cleared_count} temp config file(s)\");\n    }\n\n    Ok(())\n}\n\n/// Clear all configs for a module (called during uninstall)\npub fn clear_module_configs(module_id: &str) -> Result<()> {\n    let config_dir = get_config_dir(module_id);\n\n    if config_dir.exists() {\n        fs::remove_dir_all(&config_dir).with_context(|| {\n            format!(\n                \"Failed to remove config directory: {}\",\n                config_dir.display()\n            )\n        })?;\n        debug!(\"Cleared all configs for module: {module_id}\");\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "apd/src/package.rs",
    "content": "use std::{\n    collections::{HashMap, HashSet},\n    fs::File,\n    io::{self, BufRead},\n    path::Path,\n    process::Command,\n    thread,\n    time::Duration,\n};\n\nuse log::{info, warn};\nuse serde::{Deserialize, Serialize};\n\nuse crate::defs;\n\nconst DEFAULT_SCONTEXT: &str = \"u:r:untrusted_app:s0\";\nconst MAGISK_SCONTEXT: &str = \"u:r:magisk:s0\";\n\n#[derive(Deserialize, Serialize, Clone)]\npub struct PackageConfig {\n    pub pkg: String,\n    pub exclude: i32,\n    pub allow: i32,\n    pub uid: i32,\n    pub to_uid: i32,\n    pub sctx: String,\n}\n\nfn read_known_user_packages() -> HashSet<String> {\n    std::fs::read_to_string(defs::AUTO_EXCLUDE_KNOWN_PACKAGES_FILE)\n        .map(|content| {\n            content\n                .lines()\n                .map(str::trim)\n                .filter(|line| !line.is_empty())\n                .map(ToOwned::to_owned)\n                .collect()\n        })\n        .unwrap_or_default()\n}\n\nfn write_known_user_packages(packages: &HashSet<String>) -> io::Result<()> {\n    let mut sorted_packages: Vec<_> = packages.iter().cloned().collect();\n    sorted_packages.sort();\n    let mut content = sorted_packages.join(\"\\n\");\n    if !content.is_empty() {\n        content.push('\\n');\n    }\n    std::fs::write(defs::AUTO_EXCLUDE_KNOWN_PACKAGES_FILE, content)\n}\n\nfn list_user_packages() -> HashSet<String> {\n    let commands: [(&str, &[&str]); 2] = [\n        (\"cmd\", &[\"package\", \"list\", \"packages\", \"-3\"]),\n        (\"pm\", &[\"list\", \"packages\", \"-3\"]),\n    ];\n\n    for (program, args) in commands {\n        let output = match Command::new(program).args(args).output() {\n            Ok(output) if output.status.success() => output,\n            Ok(output) => {\n                warn!(\"User package query {} {:?} failed: {:?}\", program, args, output.status.code());\n                continue;\n            }\n            Err(e) => {\n                warn!(\"User package query {} {:?} failed: {}\", program, args, e);\n                continue;\n            }\n        };\n\n        return String::from_utf8_lossy(&output.stdout)\n            .lines()\n            .filter_map(|line| line.strip_prefix(\"package:\"))\n            .map(str::trim)\n            .filter(|pkg| !pkg.is_empty())\n            .map(ToOwned::to_owned)\n            .collect();\n    }\n\n    HashSet::new()\n}\n\npub fn sync_auto_exclude_new_apps(\n    package_configs: &mut Vec<PackageConfig>,\n    uid_map: &HashMap<String, i32>,\n    mode: i32,\n) -> io::Result<bool> {\n    let current_user_packages = list_user_packages();\n    if current_user_packages.is_empty() {\n        return Ok(false);\n    }\n\n    let known_user_packages = read_known_user_packages();\n    let known_initialized = Path::new(defs::AUTO_EXCLUDE_KNOWN_PACKAGES_FILE).exists();\n    let mut changed = false;\n\n    if mode != 0 && known_initialized {\n        let new_packages: Vec<_> = current_user_packages\n            .difference(&known_user_packages)\n            .cloned()\n            .collect();\n\n        for pkg in new_packages {\n            let Some(&uid) = uid_map.get(&pkg) else {\n                warn!(\"[auto_exclude] Missing uid for package {}, skip\", pkg);\n                continue;\n            };\n\n            let exists = package_configs.iter().any(|config| config.pkg == pkg || config.uid == uid);\n            if exists {\n                continue;\n            }\n\n            let (allow, exclude, sctx, mode_name) = match mode {\n                1 => (1, 0, MAGISK_SCONTEXT.to_string(), \"root\"),\n                2 => (0, 1, DEFAULT_SCONTEXT.to_string(), \"exclude\"),\n                _ => continue,\n            };\n\n            info!(\n                \"[new_app_profile] New package detected, apply {} by default: {} ({})\",\n                mode_name, pkg, uid\n            );\n            package_configs.push(PackageConfig {\n                pkg,\n                exclude,\n                allow,\n                uid,\n                to_uid: 0,\n                sctx,\n            });\n            changed = true;\n        }\n    }\n\n    write_known_user_packages(&current_user_packages)?;\n    Ok(changed || !known_initialized)\n}\n\npub fn read_ap_package_config() -> Vec<PackageConfig> {\n    let max_retry = 5;\n    for _ in 0..max_retry {\n        let file = match File::open(\"/data/adb/ap/package_config\") {\n            Ok(file) => file,\n            Err(e) => {\n                warn!(\"Error opening file: {}\", e);\n                thread::sleep(Duration::from_secs(1));\n                continue;\n            }\n        };\n\n        let mut reader = csv::Reader::from_reader(file);\n        let mut package_configs = Vec::new();\n        let mut success = true;\n\n        for record in reader.deserialize() {\n            match record {\n                Ok(config) => package_configs.push(config),\n                Err(e) => {\n                    warn!(\"Error deserializing record: {}\", e);\n                    success = false;\n                    break;\n                }\n            }\n        }\n\n        if success {\n            return package_configs;\n        }\n        thread::sleep(Duration::from_secs(1));\n    }\n    Vec::new()\n}\n\npub fn write_ap_package_config(package_configs: &[PackageConfig]) -> io::Result<()> {\n    let max_retry = 5;\n    for _ in 0..max_retry {\n        let temp_path = \"/data/adb/ap/package_config.tmp\";\n        let file = match File::create(temp_path) {\n            Ok(file) => file,\n            Err(e) => {\n                warn!(\"Error creating temp file: {}\", e);\n                thread::sleep(Duration::from_secs(1));\n                continue;\n            }\n        };\n\n        let mut writer = csv::Writer::from_writer(file);\n        let mut success = true;\n\n        for config in package_configs {\n            if let Err(e) = writer.serialize(config) {\n                warn!(\"Error serializing record: {}\", e);\n                success = false;\n                break;\n            }\n        }\n\n        if !success {\n            thread::sleep(Duration::from_secs(1));\n            continue;\n        }\n\n        if let Err(e) = writer.flush() {\n            warn!(\"Error flushing writer: {}\", e);\n            thread::sleep(Duration::from_secs(1));\n            continue;\n        }\n\n        if let Err(e) = std::fs::rename(temp_path, \"/data/adb/ap/package_config\") {\n            warn!(\"Error renaming temp file: {}\", e);\n            thread::sleep(Duration::from_secs(1));\n            continue;\n        }\n        return Ok(());\n    }\n    Err(io::Error::new(\n        io::ErrorKind::Other,\n        \"Failed after max retries\",\n    ))\n}\n\nfn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>\nwhere\n    P: AsRef<Path>,\n{\n    File::open(filename).map(|file| io::BufReader::new(file).lines())\n}\n\npub fn synchronize_package_uid() -> io::Result<()> {\n    info!(\"[synchronize_package_uid] Start synchronizing root list with system packages...\");\n\n    let max_retry = 5;\n    for _ in 0..max_retry {\n        match read_lines(\"/data/system/packages.list\") {\n            Ok(lines) => {\n                let lines: Vec<_> = lines.filter_map(|line| line.ok()).collect();\n\n                let mut package_configs = read_ap_package_config();\n                let uid_map: HashMap<String, i32> = lines\n                    .iter()\n                    .filter_map(|line| {\n                        let words: Vec<&str> = line.split_whitespace().collect();\n                        if words.len() < 2 {\n                            return None;\n                        }\n                        words[1]\n                            .parse::<i32>()\n                            .ok()\n                            .map(|uid| (words[0].to_string(), uid))\n                    })\n                    .collect();\n\n                let system_packages: Vec<String> = lines\n                    .iter()\n                    .filter_map(|line| line.split_whitespace().next())\n                    .map(|pkg| pkg.to_string())\n                    .collect();\n\n                let original_len = package_configs.len();\n                package_configs.retain(|config| system_packages.contains(&config.pkg));\n                let removed_count = original_len - package_configs.len();\n\n                if removed_count > 0 {\n                    info!(\n                        \"Removed {} uninstalled package configurations\",\n                        removed_count\n                    );\n                }\n\n                let mut updated = false;\n\n                let new_app_profile_mode = crate::supercall::get_new_app_profile_mode();\n                if sync_auto_exclude_new_apps(&mut package_configs, &uid_map, new_app_profile_mode)? {\n                    updated = true;\n                }\n\n                for line in &lines {\n                    let words: Vec<&str> = line.split_whitespace().collect();\n                    if words.len() >= 2 {\n                        let pkg_name = words[0];\n                        if let Ok(uid) = words[1].parse::<i32>() {\n                            if let Some(config) = package_configs\n                                .iter_mut()\n                                .find(|config| config.pkg == pkg_name)\n                            {\n                                if config.uid % 100000 != uid % 100000 {\n                                    let uid = config.uid / 100000 * 100000 + uid % 100000;\n                                    info!(\n                                        \"Updating uid for package {}: {} -> {}\",\n                                        pkg_name, config.uid, uid\n                                    );\n                                    config.uid = uid;\n                                    updated = true;\n                                }\n                            }\n                        } else {\n                            warn!(\"Error parsing uid: {}\", words[1]);\n                        }\n                    }\n                }\n\n                if updated || removed_count > 0 {\n                    write_ap_package_config(&package_configs)?;\n                }\n                return Ok(());\n            }\n            Err(e) => {\n                warn!(\"Error reading packages.list: {}\", e);\n                thread::sleep(Duration::from_secs(1));\n            }\n        }\n    }\n    Err(io::Error::new(\n        io::ErrorKind::Other,\n        \"Failed after max retries\",\n    ))\n}\n"
  },
  {
    "path": "apd/src/pty.rs",
    "content": "use std::{\n    ffi::c_int,\n    fs::File,\n    io::{Read, Write, stderr, stdin, stdout},\n    mem::MaybeUninit,\n    os::fd::{AsFd, AsRawFd, OwnedFd, RawFd},\n    process::exit,\n    ptr::null_mut,\n    sync::Mutex,\n    thread,\n};\n\nuse anyhow::{Ok, Result, bail};\nuse libc::{\n    EINTR, SIG_BLOCK, SIG_UNBLOCK, SIGWINCH, TIOCGWINSZ, TIOCSWINSZ, fork,\n    pthread_sigmask, sigaddset, sigemptyset, sigset_t, sigwait, waitpid, winsize,\n};\nuse rustix::{\n    fs::{Mode, OFlags, open},\n    io::dup,\n    ioctl::{Getter, Opcode, ioctl, opcode},\n    process::setsid,\n    pty::{grantpt, unlockpt},\n    stdio::{dup2_stderr, dup2_stdin, dup2_stdout},\n    termios::{OptionalActions, Termios, isatty, tcgetattr, tcsetattr},\n};\n\nuse crate::{defs::PTS_NAME, utils::get_tmp_path};\n\n// https://github.com/topjohnwu/Magisk/blob/5627053b7481618adfdf8fa3569b48275589915b/native/src/core/su/pts.cpp\n\nfn get_pty_num<F: AsFd>(fd: F) -> Result<u32> {\n    // TIOCGPTN: Get the PTY number\n    const TIOCGPTN: Opcode = opcode::read::<u32>(b'T', 0x30);\n    Ok(unsafe {\n        let tiocgptn = Getter::<TIOCGPTN, u32>::new();\n        ioctl(fd, tiocgptn)?\n    })\n}\n\nstatic OLD_STDIN: Mutex<Option<Termios>> = Mutex::new(None);\n\nfn watch_sigwinch_async(slave: RawFd) {\n    let mut winch = MaybeUninit::<sigset_t>::uninit();\n    unsafe {\n        sigemptyset(winch.as_mut_ptr());\n        sigaddset(winch.as_mut_ptr(), SIGWINCH);\n        pthread_sigmask(SIG_BLOCK, winch.as_mut_ptr(), null_mut());\n    }\n\n    thread::spawn(move || unsafe {\n        let mut winch = MaybeUninit::<sigset_t>::uninit();\n        sigemptyset(winch.as_mut_ptr());\n        sigaddset(winch.as_mut_ptr(), SIGWINCH);\n        pthread_sigmask(SIG_UNBLOCK, winch.as_mut_ptr(), null_mut());\n        let mut sig: c_int = 0;\n        loop {\n            let mut w = MaybeUninit::<winsize>::uninit();\n            if libc::ioctl(1, TIOCGWINSZ, w.as_mut_ptr()) < 0 {\n                continue;\n            }\n            libc::ioctl(slave, TIOCSWINSZ, w.as_mut_ptr());\n            if sigwait(winch.as_mut_ptr(), &mut sig) != 0 {\n                break;\n            }\n        }\n    });\n}\n\nfn set_stdin_raw() -> rustix::io::Result<()> {\n    let mut termios = tcgetattr(stdin())?;\n\n    let mut guard = OLD_STDIN.lock().unwrap();\n    *guard = Some(termios.clone());\n    drop(guard);\n\n    termios.make_raw();\n    tcsetattr(stdin(), OptionalActions::Flush, &termios)\n}\n\nfn restore_stdin() -> Result<()> {\n    let mut guard = OLD_STDIN.lock().unwrap();\n\n    if let Some(original_termios) = guard.take() {\n        tcsetattr(stdin(), OptionalActions::Flush, &original_termios)?;\n    }\n\n    Ok(())\n}\n\nfn pump<R: Read, W: Write>(mut from: R, mut to: W) {\n    let mut buf = [0u8; 4096];\n    loop {\n        match from.read(&mut buf) {\n            Result::Ok(len) => {\n                if len == 0 {\n                    return;\n                }\n                if to.write_all(&buf[0..len]).is_err() {\n                    return;\n                }\n                if to.flush().is_err() {\n                    return;\n                }\n            }\n            Err(_) => {\n                return;\n            }\n        }\n    }\n}\n\nfn pump_stdin_async(mut ptmx: File) {\n    let _ = set_stdin_raw();\n\n    thread::spawn(move || {\n        let mut stdin = stdin();\n        pump(&mut stdin, &mut ptmx);\n    });\n}\n\nfn pump_stdout_blocking(mut ptmx: File) {\n    let mut stdout = stdout();\n    pump(&mut ptmx, &mut stdout);\n\n    let _ = restore_stdin();\n}\n\nfn create_transfer(ptmx: OwnedFd) -> Result<()> {\n    let pid = unsafe { fork() };\n    match pid {\n        d if d < 0 => bail!(\"fork\"),\n        0 => return Ok(()),\n        _ => {}\n    }\n\n    let ptmx_r = ptmx;\n    let ptmx_w = dup(&ptmx_r)?;\n\n    let ptmx_r = File::from(ptmx_r);\n    let ptmx_w = File::from(ptmx_w);\n\n    watch_sigwinch_async(ptmx_w.as_raw_fd());\n    pump_stdin_async(ptmx_r);\n    pump_stdout_blocking(ptmx_w);\n\n    let mut status: c_int = -1;\n\n    unsafe {\n        loop {\n            if waitpid(pid, &mut status, 0) == -1 && std::io::Error::last_os_error().raw_os_error() != Some(EINTR) {\n                continue;\n            }\n            break;\n        }\n    }\n\n    exit(status)\n}\n\npub fn prepare_pty() -> Result<()> {\n    let tty_in = isatty(stdin());\n    let tty_out = isatty(stdout());\n    let tty_err = isatty(stderr());\n    if !tty_in && !tty_out && !tty_err {\n        return Ok(());\n    }\n\n    let mut pts_path = format!(\"{}/{}\", get_tmp_path(), PTS_NAME);\n    if !std::path::Path::new(&pts_path).exists() {\n        pts_path = \"/dev/pts\".to_string();\n    }\n    let ptmx_path = format!(\"{}/ptmx\", pts_path);\n    let ptmx_fd = open(ptmx_path, OFlags::RDWR, Mode::empty())?;\n    grantpt(&ptmx_fd)?;\n    unlockpt(&ptmx_fd)?;\n    let pty_num = get_pty_num(&ptmx_fd)?;\n    create_transfer(ptmx_fd)?;\n    setsid()?;\n    let pty_fd = open(format!(\"{pts_path}/{pty_num}\"), OFlags::RDWR, Mode::empty())?;\n    if tty_in {\n        dup2_stdin(&pty_fd)?;\n    }\n    if tty_out {\n        dup2_stdout(&pty_fd)?;\n    }\n    if tty_err {\n        dup2_stderr(&pty_fd)?;\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "apd/src/resetprop.rs",
    "content": "use anyhow::{bail, Context, Result};\nuse clap::error::ErrorKind;\nuse clap::Parser;\nuse log::info;\nuse prop_rs_android::resetprop::ResetProp;\nuse prop_rs_android::sys_prop;\nuse std::fmt;\nuse std::fs::File;\nuse std::io::{BufRead, BufReader};\nuse std::path::Path;\nuse std::time::Duration;\n\n#[derive(Debug)]\npub struct WaitTimeoutError {\n    name: String,\n}\n\nimpl fmt::Display for WaitTimeoutError {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"timeout waiting for {}\", self.name)\n    }\n}\n\nimpl std::error::Error for WaitTimeoutError {}\n\n/// Magisk-compatible Android system property tool.\n#[derive(Debug, clap::Args)]\n#[allow(clippy::struct_excessive_bools)]\npub struct Args {\n    /// Skip property_service (force direct mmap operation).\n    #[arg(short = 'n', long = \"skip-svc\")]\n    skip_svc: bool,\n\n    /// Also operate on persistent property storage (persist.* files).\n    #[arg(short = 'p', long = \"persistent\")]\n    persistent: bool,\n\n    /// Only read persistent properties from storage.\n    #[arg(short = 'P')]\n    persist_only: bool,\n\n    /// Delete the named property.\n    #[arg(short = 'd', long = \"delete\")]\n    delete: bool,\n\n    /// Verbose output.\n    #[arg(short = 'v', long = \"verbose\")]\n    verbose: bool,\n\n    /// Wait for a property to exist or match a value.\n    #[arg(short = 'w', long = \"wait\")]\n    wait: bool,\n\n    /// Timeout in seconds for --wait (default: wait forever).\n    #[arg(long = \"timeout\")]\n    timeout: Option<f64>,\n\n    /// Load and set properties from FILE.\n    #[arg(short = 'f', long = \"file\")]\n    file: Option<String>,\n\n    /// Compact property area memory (reclaim holes left by deleted properties).\n    /// Optionally pass a SELinux context name to compact only that area.\n    #[arg(short = 'c', long = \"compact\")]\n    compact: bool,\n\n    /// Show SELinux context when listing properties.\n    #[arg(short = 'Z')]\n    show_context: bool,\n\n    /// Property name.\n    name: Option<String>,\n\n    /// Property value (for set or wait-for-value).\n    value: Option<String>,\n}\n#[derive(Parser)]\n#[command(\n    name = \"resetprop\",\n    version,\n    about = \"Magisk-compatible system property tool\",\n    disable_help_subcommand = true\n)]\nstruct ResetPropParser {\n    #[command(flatten)]\n    arg: Args,\n}\n\npub fn resetprop_main(args: &[String]) -> ! {\n    if let Err(err) = run_from_args(args) {\n        let code = if err.downcast_ref::<WaitTimeoutError>().is_some() {\n            2\n        } else {\n            1\n        };\n        eprintln!(\"resetprop: {err:#}\");\n        std::process::exit(code);\n    }\n    std::process::exit(0);\n}\n\n/// Entry point for resetprop multicall.\n///\n/// `args` should include argv[0] (the program name).\nfn run_from_args(args: &[String]) -> Result<()> {\n    let parser = match ResetPropParser::try_parse_from(args) {\n        Ok(cli) => cli,\n        Err(err) => {\n            if matches!(\n                err.kind(),\n                ErrorKind::DisplayHelp | ErrorKind::DisplayVersion\n            ) {\n                err.print()?;\n                return Ok(());\n            }\n            return Err(anyhow::anyhow!(\"{err}\"));\n        }\n    };\n    execute(&parser.arg)\n}\n/// Execute resetprop logic\n/// Subcommand will direct call that, skip run_from_args\npub fn execute(cli: &Args) -> Result<()> {\n    sys_prop::init().context(\"Failed to initialize system property API\")?;\n\n    let rp = ResetProp {\n        skip_svc: cli.skip_svc,\n        persistent: cli.persistent,\n        persist_only: cli.persist_only,\n        verbose: cli.verbose,\n        show_context: cli.show_context,\n    };\n\n    // Validate: at most one special mode\n    let special_modes = u8::from(cli.wait)\n        + u8::from(cli.delete)\n        + u8::from(cli.compact)\n        + u8::from(cli.file.is_some());\n    if special_modes > 1 {\n        bail!(\"multiple operation modes detected\");\n    }\n\n    // -w: wait mode\n    if cli.wait {\n        let name = cli\n            .name\n            .as_deref()\n            .context(\"--wait requires a property name\")?;\n        let timeout = cli.timeout.map(Duration::from_secs_f64);\n        let ok = rp\n            .wait(name, cli.value.as_deref(), timeout)\n            .context(\"wait failed\")?;\n        if !ok {\n            return Err(WaitTimeoutError {\n                name: name.to_owned(),\n            }\n            .into());\n        }\n        return Ok(());\n    }\n\n    // -c: compact property area memory\n    // When a positional argument is given, treat it as a SELinux context name.\n    if cli.compact {\n        let context = cli.name.as_deref();\n        let compacted = sys_prop::compact(context).context(\"compact failed\")?;\n        if !compacted {\n            bail!(\"nothing to compact\");\n        }\n        return Ok(());\n    }\n\n    // -f: load from file\n    if let Some(path) = &cli.file {\n        let file = File::open(path).with_context(|| format!(\"Failed to open {path}\"))?;\n        let reader = BufReader::new(file);\n        rp.load_props(reader.lines())\n            .context(\"Failed to load properties from file\")?;\n        return Ok(());\n    }\n\n    // -d: delete\n    if cli.delete {\n        let name = cli\n            .name\n            .as_deref()\n            .context(\"--delete requires a property name\")?;\n        let deleted = rp.delete(name).context(\"delete failed\")?;\n        if !deleted {\n            bail!(\"{name} not found\");\n        }\n        return Ok(());\n    }\n\n    match (&cli.name, &cli.value) {\n        // resetprop name value (set)\n        (Some(name), Some(value)) => {\n            rp.set(name, value)\n                .with_context(|| format!(\"Failed to set {name}\"))?;\n        }\n\n        // resetprop name (get)\n        (Some(name), None) => match rp.get(name) {\n            Some(val) => println!(\"{val}\"),\n            None => bail!(\"{name} not found\"),\n        },\n\n        // resetprop (list all)\n        (None, None) => {\n            let props = rp.list_all().context(\"Failed to list properties\")?;\n            for (name, value) in &props {\n                println!(\"[{name}]: [{value}]\");\n            }\n        }\n\n        // resetprop <no name> <value> — invalid\n        (None, Some(_)) => {\n            bail!(\"property name is required\");\n        }\n    }\n\n    Ok(())\n}\n\n/// Load system.prop file using internal resetprop API.\n///\n/// Equivalent to `resetprop -n --file <path>`.\n#[allow(dead_code)]\npub fn load_system_prop_file(path: &Path) -> Result<()> {\n    sys_prop::init().context(\"Failed to initialize system property API\")?;\n\n    let rp = ResetProp {\n        skip_svc: true,\n        persistent: false,\n        persist_only: false,\n        verbose: false,\n        show_context: false,\n    };\n\n    let file = File::open(path).with_context(|| format!(\"Failed to open {}\", path.display()))?;\n    let reader = BufReader::new(file);\n    rp.load_props(reader.lines())\n        .with_context(|| format!(\"Failed to load properties from {}\", path.display()))?;\n\n    info!(\"Loaded system.prop from {}\", path.display());\n    Ok(())\n}\n"
  },
  {
    "path": "apd/src/restorecon.rs",
    "content": "use std::path::Path;\n\nuse anyhow::Result;\n#[cfg(any(target_os = \"linux\", target_os = \"android\"))]\nuse anyhow::{Context, Ok};\n#[cfg(any(target_os = \"linux\", target_os = \"android\"))]\nuse extattr::{Flags as XattrFlags, lsetxattr};\nuse jwalk::{Parallelism::Serial, WalkDir};\n\nuse crate::defs;\n\npub const SYSTEM_CON: &str = \"u:object_r:system_file:s0\";\npub const ADB_CON: &str = \"u:object_r:adb_data_file:s0\";\npub const UNLABEL_CON: &str = \"u:object_r:unlabeled:s0\";\n\nconst SELINUX_XATTR: &str = \"security.selinux\";\n\npub fn lsetfilecon<P: AsRef<Path>>(path: P, con: &str) -> Result<()> {\n    #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n    lsetxattr(&path, SELINUX_XATTR, con, XattrFlags::empty()).with_context(|| {\n        format!(\n            \"Failed to change SELinux context for {}\",\n            path.as_ref().display()\n        )\n    })?;\n    Ok(())\n}\n\n#[cfg(any(target_os = \"linux\", target_os = \"android\"))]\npub fn lgetfilecon<P: AsRef<Path>>(path: P) -> Result<String> {\n    let con = extattr::lgetxattr(&path, SELINUX_XATTR).with_context(|| {\n        format!(\n            \"Failed to get SELinux context for {}\",\n            path.as_ref().display()\n        )\n    })?;\n    let con = String::from_utf8_lossy(&con);\n    Ok(con.to_string())\n}\n\n#[cfg(any(target_os = \"linux\", target_os = \"android\"))]\npub fn setsyscon<P: AsRef<Path>>(path: P) -> Result<()> {\n    lsetfilecon(path, SYSTEM_CON)\n}\n\n#[cfg(not(any(target_os = \"linux\", target_os = \"android\")))]\npub fn setsyscon<P: AsRef<Path>>(path: P) -> Result<()> {\n    unimplemented!()\n}\n\n#[cfg(not(any(target_os = \"linux\", target_os = \"android\")))]\npub fn lgetfilecon<P: AsRef<Path>>(path: P) -> Result<String> {\n    unimplemented!()\n}\n\npub fn restore_syscon<P: AsRef<Path>>(dir: P) -> Result<()> {\n    for dir_entry in WalkDir::new(dir).parallelism(Serial) {\n        if let Some(path) = dir_entry.ok().map(|dir_entry| dir_entry.path()) {\n            setsyscon(&path)?;\n        }\n    }\n    Ok(())\n}\n\nfn restore_syscon_if_unlabeled<P: AsRef<Path>>(dir: P) -> Result<()> {\n    for dir_entry in WalkDir::new(dir).parallelism(Serial) {\n        if let Some(path) = dir_entry.ok().map(|dir_entry| dir_entry.path()) {\n            if let Result::Ok(con) = lgetfilecon(&path) {\n                if con == UNLABEL_CON || con.is_empty() {\n                    lsetfilecon(&path, SYSTEM_CON)?;\n                }\n            }\n        }\n    }\n    Ok(())\n}\n\npub fn restorecon() -> Result<()> {\n    lsetfilecon(defs::DAEMON_PATH, ADB_CON)?;\n    restore_syscon_if_unlabeled(defs::MODULE_DIR)?;\n    Ok(())\n}\n"
  },
  {
    "path": "apd/src/sepolicy.rs",
    "content": "use anyhow::{Context, Result, bail};\nuse clap::Parser;\nuse policy::{SePolicy, format_statement_help};\nuse std::io::{self, Write};\nuse std::path::PathBuf;\n\n/// Write adapter for formatting\nstruct WriteAdapter<T>(T);\n\nimpl<T: Write> std::fmt::Write for WriteAdapter<T> {\n    fn write_str(&mut self, s: &str) -> std::fmt::Result {\n        self.0.write_all(s.as_bytes()).map_err(|_| std::fmt::Error)\n    }\n}\n\n/// MagiskPolicy - SELinux Policy Patch Tool\n#[derive(Debug, clap::Args)]\n#[allow(clippy::struct_excessive_bools)]\npub struct Args {\n    /// Load monolithic sepolicy from FILE\n    #[arg(long = \"load\", value_name = \"FILE\")]\n    load: Option<PathBuf>,\n\n    /// Load from precompiled sepolicy or compile split cil policies\n    #[arg(long = \"load-split\")]\n    load_split: bool,\n\n    /// Compile split cil policies\n    #[arg(long = \"compile-split\")]\n    compile_split: bool,\n\n    /// Dump monolithic sepolicy to FILE\n    #[arg(long = \"save\", value_name = \"FILE\")]\n    save: Option<PathBuf>,\n\n    /// Immediately load sepolicy into the kernel\n    #[arg(long = \"live\")]\n    live: bool,\n\n    /// Apply built-in Magisk sepolicy rules\n    #[arg(long = \"magisk\")]\n    magisk: bool,\n\n    /// Apply rules from FILE, read and parsed line by line as policy statements\n    #[arg(long = \"apply\", value_name = \"FILE\")]\n    apply: Vec<PathBuf>,\n\n    /// Print all rules in the loaded sepolicy\n    #[arg(long = \"print-rules\")]\n    print_rules: bool,\n\n    /// Policy statements to apply\n    #[arg(required = false)]\n    policies: Vec<String>,\n}\n\n#[derive(Parser)]\n#[command(\n    name = \"magiskpolicy\",\n    version,\n    about = \"SELinux Policy Patch Tool\",\n    disable_help_subcommand = true\n)]\nstruct MagiskPolicyParser {\n    #[command(flatten)]\n    arg: Args,\n}\n\npub fn policy_main(args: &[String]) -> ! {\n    if let Err(err) = run_from_args(args) {\n        eprintln!(\"magiskpolicy: {err:#}\");\n        std::process::exit(1);\n    }\n    std::process::exit(0);\n}\n\n/// Entry point for magiskpolicy multicall.\n///\n/// `args` should include argv[0] (the program name).\nfn run_from_args(args: &[String]) -> Result<()> {\n    let parser = match MagiskPolicyParser::try_parse_from(args) {\n        Ok(cli) => cli,\n        Err(err) => {\n            if err.kind() == clap::error::ErrorKind::DisplayHelp {\n                print_usage(args.first().map(|s| s.as_str()).unwrap_or(\"magiskpolicy\"));\n                return Ok(());\n            }\n            if err.kind() == clap::error::ErrorKind::DisplayVersion {\n                err.print()?;\n                return Ok(());\n            }\n            return Err(anyhow::anyhow!(\"{err}\"));\n        }\n    };\n    execute(&parser.arg)\n}\n\npub fn get_policy_main(args: &[String]) -> Result<SePolicy> {\n    let parser = MagiskPolicyParser::try_parse_from(args)?;\n    let cli = parser.arg;\n\n    // Validate mutually exclusive options\n    let load_count = cli.load.iter().count() + cli.compile_split as usize + cli.load_split as usize;\n    if load_count > 1 {\n        bail!(\"Multiple load source supplied\");\n    }\n\n    // Load policy\n    let mut sepol = if let Some(ref file) = cli.load {\n        SePolicy::from_file(file)\n            .with_context(|| format!(\"Cannot load policy from {}\", file.display()))?\n    } else if cli.load_split {\n        SePolicy::from_split().context(\"Cannot load split policy\")?\n    } else if cli.compile_split {\n        SePolicy::compile_split().context(\"Cannot compile split policy\")?\n    } else {\n        SePolicy::from_file(\"/sys/fs/selinux/policy\").context(\"Cannot load live policy\")?\n    };\n    execute_next(&cli, &mut sepol)?;\n    Ok(sepol)\n}\n\n/// Execute magiskpolicy logic\n/// Subcommand will direct call that, skip run_from_args\npub fn execute(cli: &Args) -> Result<()> {\n    // Validate mutually exclusive options\n    let load_count = cli.load.iter().count() + cli.compile_split as usize + cli.load_split as usize;\n    if load_count > 1 {\n        bail!(\"Multiple load source supplied\");\n    }\n\n    // Load policy\n    let mut sepol = if let Some(ref file) = cli.load {\n        SePolicy::from_file(file)\n            .with_context(|| format!(\"Cannot load policy from {}\", file.display()))?\n    } else if cli.load_split {\n        SePolicy::from_split().context(\"Cannot load split policy\")?\n    } else if cli.compile_split {\n        SePolicy::compile_split().context(\"Cannot compile split policy\")?\n    } else {\n        SePolicy::from_file(\"/sys/fs/selinux/policy\").context(\"Cannot load live policy\")?\n    };\n\n    execute_next(cli, &mut sepol)?;\n    Ok(())\n}\n\nfn execute_next(cli: &Args, sepol: &mut SePolicy) -> Result<()> {\n    if cli.print_rules {\n        if cli.magisk\n            || !cli.apply.is_empty()\n            || !cli.policies.is_empty()\n            || cli.live\n            || cli.save.is_some()\n        {\n            bail!(\"Cannot print rules with other options\");\n        }\n        sepol.print_rules();\n        return Ok(());\n    }\n\n    if cli.magisk {\n        sepol.magisk_rules();\n    }\n\n    for file in &cli.apply {\n        sepol\n            .load_rule_file(file)\n            .with_context(|| format!(\"Cannot load rule file {}\", file.display()))?;\n    }\n\n    for statement in &cli.policies {\n        sepol.load_rules(statement);\n    }\n\n    if cli.live {\n        sepol\n            .to_file(\"/sys/fs/selinux/load\")\n            .context(\"Cannot apply policy\")?;\n    }\n\n    if let Some(ref file) = cli.save {\n        sepol\n            .to_file(file)\n            .with_context(|| format!(\"Cannot dump policy to {}\", file.display()))?;\n    }\n    Ok(())\n}\n\n/// Print usage information\nfn print_usage(cmd: &str) {\n    eprintln!(\n        r#\"MagiskPolicy - SELinux Policy Patch Tool\n\nUsage: {cmd} [--options...] [policy statements...]\n\nOptions:\n   --help            show help message for policy statements\n   --load FILE       load monolithic sepolicy from FILE\n   --load-split      load from precompiled sepolicy or compile\n                     split cil policies\n   --compile-split   compile split cil policies\n   --save FILE       dump monolithic sepolicy to FILE\n   --live            immediately load sepolicy into the kernel\n   --magisk          apply built-in Magisk sepolicy rules\n   --apply FILE      apply rules from FILE, read and parsed\n                     line by line as policy statements\n                     (multiple --apply are allowed)\n   --print-rules     print all rules in the loaded sepolicy\n\nIf neither --load, --load-split, nor --compile-split is specified,\nit will load from current live policies (/sys/fs/selinux/policy)\n\"#\n    );\n\n    let _ = format_statement_help(&mut WriteAdapter(io::stderr()));\n    eprintln!();\n}\n"
  },
  {
    "path": "apd/src/supercall.rs",
    "content": "use std::{\n    ffi::{CStr, CString},\n    fs::File,\n    io::{self, Read},\n    process,\n    sync::{Arc, Mutex},\n};\n\nuse libc::{c_long, c_void, syscall, uid_t, EINVAL};\nuse log::{error, info, warn};\n\nuse crate::package::{read_ap_package_config, synchronize_package_uid};\n\nconst MAJOR: c_long = 0;\nconst MINOR: c_long = 13;\nconst PATCH: c_long = 1;\n\nconst KSTORAGE_EXCLUDE_LIST_GROUP: i32 = 1;\nconst KSTORAGE_AUTO_EXCLUDE_GROUP: i32 = 3;\n\nconst __NR_SUPERCALL: c_long = 45;\nconst SUPERCALL_SU: c_long = 0x1010;\nconst SUPERCALL_KSTORAGE_WRITE: c_long = 0x1041;\nconst SUPERCALL_KSTORAGE_READ: c_long = 0x1042;\nconst SUPERCALL_SU_GRANT_UID: c_long = 0x1100;\nconst SUPERCALL_SU_REVOKE_UID: c_long = 0x1101;\nconst SUPERCALL_SU_NUMS: c_long = 0x1102;\nconst SUPERCALL_SU_LIST: c_long = 0x1103;\nconst SUPERCALL_SU_RESET_PATH: c_long = 0x1111;\nconst SUPERCALL_SU_GET_SAFEMODE: c_long = 0x1112;\n\nconst SUPERCALL_KPM_LOAD: c_long = 0x1020;\n\nconst SUPERCALL_UTS_SET: c_long = 0x1050;\nconst SUPERCALL_UTS_RESET: c_long = 0x1051;\n\nconst SUPERCALL_PATHHIDE_ENABLE: c_long = 0x1064;\nconst SUPERCALL_PATHHIDE_ADD: c_long = 0x1060;\nconst SUPERCALL_PATHHIDE_CLEAR: c_long = 0x1063;\nconst SUPERCALL_PATHHIDE_UID_MODE: c_long = 0x106A;\nconst SUPERCALL_PATHHIDE_UID_ADD: c_long = 0x1066;\nconst SUPERCALL_PATHHIDE_UID_CLEAR: c_long = 0x1069;\nconst SUPERCALL_PATHHIDE_FILTER_SYSTEM: c_long = 0x106B;\n\nconst SUPERCALL_NETISOLATE_ENABLE: c_long = 0x1070;\nconst SUPERCALL_NETISOLATE_UID_ADD: c_long = 0x1072;\nconst SUPERCALL_NETISOLATE_UID_REMOVE: c_long = 0x1073;\nconst SUPERCALL_NETISOLATE_UID_LIST: c_long = 0x1074;\nconst SUPERCALL_NETISOLATE_UID_CLEAR: c_long = 0x1075;\n\nconst SUPERCALL_SCONTEXT_LEN: usize = 0x60;\n\n#[repr(C)]\nstruct SuProfile {\n    uid: i32,\n    to_uid: i32,\n    scontext: [u8; SUPERCALL_SCONTEXT_LEN],\n}\n\nfn ver_and_cmd(cmd: c_long) -> c_long {\n    let version_code: u32 = ((MAJOR << 16) + (MINOR << 8) + PATCH).try_into().unwrap();\n    ((version_code as c_long) << 32) | (0x1158 << 16) | (cmd & 0xFFFF)\n}\n\nfn sc_su_revoke_uid(key: &CStr, uid: uid_t) -> c_long {\n    if key.to_bytes().is_empty() {\n        return (-EINVAL).into();\n    }\n    unsafe {\n        syscall(\n            __NR_SUPERCALL,\n            key.as_ptr(),\n            ver_and_cmd(SUPERCALL_SU_REVOKE_UID),\n            uid,\n        ) as c_long\n    }\n}\n\nfn sc_su_grant_uid(key: &CStr, profile: &SuProfile) -> c_long {\n    if key.to_bytes().is_empty() {\n        return (-EINVAL).into();\n    }\n    unsafe {\n        syscall(\n            __NR_SUPERCALL,\n            key.as_ptr(),\n            ver_and_cmd(SUPERCALL_SU_GRANT_UID),\n            profile,\n        ) as c_long\n    }\n}\n\nfn sc_kstorage_write(\n    key: &CStr,\n    gid: i32,\n    did: i64,\n    data: *mut c_void,\n    offset: i32,\n    dlen: i32,\n) -> c_long {\n    if key.to_bytes().is_empty() {\n        return (-EINVAL).into();\n    }\n    unsafe {\n        syscall(\n            __NR_SUPERCALL,\n            key.as_ptr(),\n            ver_and_cmd(SUPERCALL_KSTORAGE_WRITE),\n            gid as c_long,\n            did as c_long,\n            data,\n            (((offset as i64) << 32) | (dlen as i64)) as c_long,\n        ) as c_long\n    }\n}\n\nfn sc_kstorage_read(\n    key: &CStr,\n    gid: i32,\n    did: i64,\n    out_data: *mut c_void,\n    offset: i32,\n    dlen: i32,\n) -> c_long {\n    if key.to_bytes().is_empty() {\n        return (-EINVAL).into();\n    }\n    unsafe {\n        syscall(\n            __NR_SUPERCALL,\n            key.as_ptr(),\n            ver_and_cmd(SUPERCALL_KSTORAGE_READ),\n            gid as c_long,\n            did as c_long,\n            out_data,\n            (((offset as i64) << 32) | (dlen as i64)) as c_long,\n        ) as c_long\n    }\n}\n\nfn sc_set_ap_mod_exclude(key: &CStr, uid: i64, exclude: i32) -> c_long {\n    sc_kstorage_write(\n        key,\n        KSTORAGE_EXCLUDE_LIST_GROUP,\n        uid,\n        &exclude as *const i32 as *mut c_void,\n        0,\n        size_of::<i32>() as i32,\n    )\n}\n\npub fn get_new_app_profile_mode() -> i32 {\n    let key = CStr::from_bytes_with_nul(b\"su\\0\").expect(\"auto exclude key init failed\");\n    let mut enabled = 0_i32;\n    let rc = sc_kstorage_read(\n        key,\n        KSTORAGE_AUTO_EXCLUDE_GROUP,\n        0,\n        &mut enabled as *mut i32 as *mut c_void,\n        0,\n        size_of::<i32>() as i32,\n    );\n    if rc < 0 {\n        return 0;\n    }\n    enabled\n}\n\npub fn sc_su_get_safemode(key: &CStr) -> c_long {\n    if key.to_bytes().is_empty() {\n        warn!(\"[sc_su_get_safemode] null superkey, tell apd we are not in safemode!\");\n        return 0;\n    }\n\n    let key_ptr = key.as_ptr();\n    if key_ptr.is_null() {\n        warn!(\"[sc_su_get_safemode] superkey pointer is null!\");\n        return 0;\n    }\n\n    unsafe {\n        syscall(\n            __NR_SUPERCALL,\n            key_ptr,\n            ver_and_cmd(SUPERCALL_SU_GET_SAFEMODE),\n        ) as c_long\n    }\n}\n\nfn sc_su(key: &CStr, profile: &SuProfile) -> c_long {\n    if key.to_bytes().is_empty() {\n        return (-EINVAL).into();\n    }\n    unsafe {\n        syscall(\n            __NR_SUPERCALL,\n            key.as_ptr(),\n            ver_and_cmd(SUPERCALL_SU),\n            profile,\n        ) as c_long\n    }\n}\n\nfn sc_su_reset_path(key: &CStr, path: &CStr) -> c_long {\n    if key.to_bytes().is_empty() || path.to_bytes().is_empty() {\n        return (-EINVAL).into();\n    }\n    unsafe {\n        syscall(\n            __NR_SUPERCALL,\n            key.as_ptr(),\n            ver_and_cmd(SUPERCALL_SU_RESET_PATH),\n            path.as_ptr(),\n        ) as c_long\n    }\n}\n\nfn sc_kpm_load(key: &CStr, path: &CStr, args: &CStr) -> c_long {\n    if key.to_bytes().is_empty() || path.to_bytes().is_empty() {\n        return (-EINVAL).into();\n    }\n    unsafe {\n        syscall(\n            __NR_SUPERCALL,\n            key.as_ptr(),\n            ver_and_cmd(SUPERCALL_KPM_LOAD),\n            path.as_ptr(),\n            args.as_ptr(),\n            std::ptr::null::<c_void>(),\n        ) as c_long\n    }\n}\n\nfn sc_su_uid_nums(key: &CStr) -> c_long {\n    if key.to_bytes().is_empty() {\n        return (-EINVAL).into();\n    }\n    unsafe { syscall(__NR_SUPERCALL, key.as_ptr(), ver_and_cmd(SUPERCALL_SU_NUMS)) as c_long }\n}\n\nfn sc_su_allow_uids(key: &CStr, buf: &mut [uid_t]) -> c_long {\n    if key.to_bytes().is_empty() {\n        return (-EINVAL).into();\n    }\n    if buf.is_empty() {\n        return (-EINVAL).into();\n    }\n    unsafe {\n        syscall(\n            __NR_SUPERCALL,\n            key.as_ptr(),\n            ver_and_cmd(SUPERCALL_SU_LIST),\n            buf.as_mut_ptr(),\n            buf.len() as i32,\n        ) as c_long\n    }\n}\n\nfn read_file_to_string(path: &str) -> io::Result<String> {\n    let mut file = File::open(path)?;\n    let mut content = String::new();\n    file.read_to_string(&mut content)?;\n    Ok(content)\n}\n\nfn convert_string_to_u8_array(s: &str) -> [u8; SUPERCALL_SCONTEXT_LEN] {\n    let mut u8_array = [0u8; SUPERCALL_SCONTEXT_LEN];\n    let bytes = s.as_bytes();\n    let len = usize::min(SUPERCALL_SCONTEXT_LEN, bytes.len());\n    u8_array[..len].copy_from_slice(&bytes[..len]);\n    u8_array\n}\n\nfn convert_superkey(s: &Option<String>) -> Option<CString> {\n    s.as_ref().and_then(|s| CString::new(s.clone()).ok())\n}\n\npub fn refresh_ap_package_list(skey: &CStr, mutex: &Arc<Mutex<()>>) {\n    let _lock = mutex.lock().unwrap();\n\n    if let Err(e) = synchronize_package_uid() {\n        error!(\"Failed to synchronize package UIDs: {}\", e);\n    }\n\n    let package_configs = read_ap_package_config();\n\n    let num = sc_su_uid_nums(skey);\n    if num < 0 {\n        error!(\"[refresh_su_list] Error getting number of UIDs: {}\", num);\n        return;\n    }\n    let num = num as usize;\n    let mut uids = vec![0 as uid_t; num];\n    let n = sc_su_allow_uids(skey, &mut uids);\n    if n < 0 {\n        error!(\"[refresh_su_list] Error getting su list\");\n        return;\n    }\n\n    let granted_uids: std::collections::HashSet<uid_t> = package_configs\n        .iter()\n        .filter(|c| c.allow == 1 && c.exclude == 0)\n        .map(|c| c.uid as uid_t)\n        .collect();\n\n    for uid in &uids {\n        if *uid == 0 || *uid == 2000 {\n            continue;\n        }\n        if granted_uids.contains(uid) {\n            continue;\n        }\n        info!(\n            \"[refresh_ap_package_list] Revoking {} root permission...\",\n            uid\n        );\n        let rc = sc_su_revoke_uid(skey, *uid);\n        if rc != 0 {\n            error!(\"[refresh_ap_package_list] Error revoking UID: {}\", rc);\n        }\n    }\n\n    for config in &package_configs {\n        if config.allow == 1 && config.exclude == 0 {\n            let profile = SuProfile {\n                uid: config.uid,\n                to_uid: config.to_uid,\n                scontext: convert_string_to_u8_array(&config.sctx),\n            };\n            let result = sc_su_grant_uid(skey, &profile);\n            info!(\n                \"[refresh_ap_package_list] Loading {}: result = {}\",\n                config.pkg, result\n            );\n        }\n        if config.allow == 0 && config.exclude == 1 {\n            let result = sc_set_ap_mod_exclude(skey, config.uid as i64, 1);\n            info!(\n                \"[refresh_ap_package_list] Loading exclude {}: result = {}\",\n                config.pkg, result\n            );\n        }\n    }\n}\n\npub fn privilege_apd_profile(superkey: &Option<String>) {\n    let key = convert_superkey(superkey);\n\n    let all_allow_ctx = \"u:r:magisk:s0\";\n    let profile = SuProfile {\n        uid: process::id().try_into().expect(\"PID conversion failed\"),\n        to_uid: 0,\n        scontext: convert_string_to_u8_array(all_allow_ctx),\n    };\n    if let Some(ref key) = key {\n        let result = sc_su(key, &profile);\n        info!(\"[privilege_apd_profile] result = {}\", result);\n    }\n}\n\npub fn init_load_su_path(superkey: &Option<String>) {\n    let su_path_file = \"/data/adb/ap/su_path\";\n\n    match read_file_to_string(su_path_file) {\n        Ok(su_path) => {\n            let superkey_cstr = convert_superkey(superkey);\n\n            match superkey_cstr {\n                Some(superkey_cstr) => match CString::new(su_path.trim()) {\n                    Ok(su_path_cstr) => {\n                        let result = sc_su_reset_path(&superkey_cstr, &su_path_cstr);\n                        if result == 0 {\n                            info!(\"suPath load successfully\");\n                        } else {\n                            warn!(\"Failed to load su path, error code: {}\", result);\n                        }\n                    }\n                    Err(e) => {\n                        warn!(\"Failed to convert su_path: {}\", e);\n                    }\n                },\n                _ => {\n                    warn!(\"Superkey is None, skipping...\");\n                }\n            }\n        }\n        Err(e) => {\n            warn!(\"Failed to read su_path file: {}\", e);\n        }\n    }\n}\npub fn autoload_kpm_modules(superkey: &Option<String>, event_filter: &str) {\n    use serde::Deserialize;\n\n    #[derive(Deserialize, Default)]\n    struct KpmAutoLoadEntry {\n        path: String,\n        #[serde(default = \"default_event\")]\n        event: String,\n        #[serde(default)]\n        args: String,\n    }\n\n    fn default_event() -> String {\n        \"service\".to_string()\n    }\n\n    #[derive(Deserialize, Default)]\n    struct KpmAutoLoadConfig {\n        enabled: bool,\n        #[serde(default, rename = \"kpmEntries\")]\n        kpm_entries: Vec<KpmAutoLoadEntry>,\n    }\n\n    let config_path = crate::defs::KPM_AUTOLOAD_CONFIG;\n    let content = match std::fs::read_to_string(config_path) {\n        Ok(c) => c,\n        Err(e) => {\n            info!(\"[kpm_autoload] config not found or unreadable ({}): {}\", config_path, e);\n            return;\n        }\n    };\n\n    let config: KpmAutoLoadConfig = match serde_json::from_str(&content) {\n        Ok(c) => c,\n        Err(e) => {\n            warn!(\"[kpm_autoload] failed to parse config: {}\", e);\n            return;\n        }\n    };\n\n    if !config.enabled || config.kpm_entries.is_empty() {\n        info!(\"[kpm_autoload] disabled or no entries configured, skipping\");\n        return;\n    }\n\n    let key = convert_superkey(superkey);\n    let key = match key {\n        Some(k) => k,\n        None => {\n            warn!(\"[kpm_autoload] no superkey available\");\n            return;\n        }\n    };\n\n    const MAX_KPM_MODULES: usize = 64;\n\n    if config.kpm_entries.len() > MAX_KPM_MODULES {\n        warn!(\n            \"[kpm_autoload] too many entries ({}), truncating to {}\",\n            config.kpm_entries.len(),\n            MAX_KPM_MODULES\n        );\n    }\n\n    let mut success = 0u32;\n    let mut fail = 0u32;\n    for entry in config.kpm_entries.iter().take(MAX_KPM_MODULES) {\n        if entry.event != event_filter {\n            info!(\"[kpm_autoload] skipping '{}' (event='{}', expected='{}')\", entry.path, entry.event, event_filter);\n            continue;\n        }\n\n        let path_str = &entry.path;\n\n        if !std::path::Path::new(path_str).exists() {\n            warn!(\"[kpm_autoload] file not found: {}\", path_str);\n            fail += 1;\n            continue;\n        }\n\n        let canonical = match std::fs::canonicalize(path_str) {\n            Ok(p) => p,\n            Err(e) => {\n                warn!(\"[kpm_autoload] cannot canonicalize '{}': {}\", path_str, e);\n                fail += 1;\n                continue;\n            }\n        };\n\n        let allowed_dir = std::path::Path::new(crate::defs::FP_KPMS_AUTOLOAD_DIR);\n        if !canonical.starts_with(allowed_dir) {\n            warn!(\n                \"[kpm_autoload] path '{}' outside allowed directory '{}', skipping\",\n                path_str,\n                crate::defs::FP_KPMS_AUTOLOAD_DIR\n            );\n            fail += 1;\n            continue;\n        }\n\n        let path_cstr = match CString::new(canonical.to_string_lossy().into_owned()) {\n            Ok(c) => c,\n            Err(e) => {\n                warn!(\"[kpm_autoload] invalid canonical path: {}\", e);\n                fail += 1;\n                continue;\n            }\n        };\n        let args_cstr = match CString::new(entry.args.clone()) {\n            Ok(c) => c,\n            Err(e) => {\n                warn!(\"[kpm_autoload] invalid args for '{}': {}\", path_str, e);\n                fail += 1;\n                continue;\n            }\n        };\n        info!(\"[kpm_autoload] loading '{}' with event='{}' args='{}'\", path_str, entry.event, entry.args);\n        let rc = sc_kpm_load(&key, &path_cstr, &args_cstr);\n        if rc == 0 {\n            success += 1;\n            info!(\"[kpm_autoload] loaded: {}\", path_str);\n        } else {\n            fail += 1;\n            warn!(\"[kpm_autoload] failed to load '{}', rc={}\", path_str, rc);\n        }\n    }\n    info!(\"[kpm_autoload] done: success={}, fail={}\", success, fail);\n}\n\nfn sc_pathhide_enable(key: &CStr, enable: bool) -> c_long {\n    if key.to_bytes().is_empty() {\n        return (-EINVAL).into();\n    }\n    unsafe {\n        syscall(\n            __NR_SUPERCALL,\n            key.as_ptr(),\n            ver_and_cmd(SUPERCALL_PATHHIDE_ENABLE),\n            if enable { 1i64 } else { 0i64 },\n        ) as c_long\n    }\n}\n\nfn sc_pathhide_add(key: &CStr, path: &CStr) -> c_long {\n    if key.to_bytes().is_empty() || path.to_bytes().is_empty() {\n        return (-EINVAL).into();\n    }\n    unsafe {\n        syscall(\n            __NR_SUPERCALL,\n            key.as_ptr(),\n            ver_and_cmd(SUPERCALL_PATHHIDE_ADD),\n            path.as_ptr(),\n        ) as c_long\n    }\n}\n\nfn sc_pathhide_clear(key: &CStr) -> c_long {\n    if key.to_bytes().is_empty() {\n        return (-EINVAL).into();\n    }\n    unsafe {\n        syscall(\n            __NR_SUPERCALL,\n            key.as_ptr(),\n            ver_and_cmd(SUPERCALL_PATHHIDE_CLEAR),\n        ) as c_long\n    }\n}\n\nfn sc_pathhide_uid_mode(key: &CStr, enable: bool) -> c_long {\n    if key.to_bytes().is_empty() {\n        return (-EINVAL).into();\n    }\n    unsafe {\n        syscall(\n            __NR_SUPERCALL,\n            key.as_ptr(),\n            ver_and_cmd(SUPERCALL_PATHHIDE_UID_MODE),\n            if enable { 1i64 } else { 0i64 },\n        ) as c_long\n    }\n}\n\nfn sc_pathhide_filter_system(key: &CStr, enable: bool) -> c_long {\n    if key.to_bytes().is_empty() {\n        return (-EINVAL).into();\n    }\n    unsafe {\n        syscall(\n            __NR_SUPERCALL,\n            key.as_ptr(),\n            ver_and_cmd(SUPERCALL_PATHHIDE_FILTER_SYSTEM),\n            if enable { 1i64 } else { 0i64 },\n        ) as c_long\n    }\n}\n\nfn sc_pathhide_uid_add(key: &CStr, uid: i32) -> c_long {\n    if key.to_bytes().is_empty() {\n        return (-EINVAL).into();\n    }\n    unsafe {\n        syscall(\n            __NR_SUPERCALL,\n            key.as_ptr(),\n            ver_and_cmd(SUPERCALL_PATHHIDE_UID_ADD),\n            uid,\n        ) as c_long\n    }\n}\n\nfn sc_pathhide_uid_clear(key: &CStr) -> c_long {\n    if key.to_bytes().is_empty() {\n        return (-EINVAL).into();\n    }\n    unsafe {\n        syscall(\n            __NR_SUPERCALL,\n            key.as_ptr(),\n            ver_and_cmd(SUPERCALL_PATHHIDE_UID_CLEAR),\n        ) as c_long\n    }\n}\n\nfn sc_netisolate_enable(key: &CStr, enable: bool) -> c_long {\n    if key.to_bytes().is_empty() {\n        return (-EINVAL).into();\n    }\n    unsafe {\n        syscall(\n            __NR_SUPERCALL,\n            key.as_ptr(),\n            ver_and_cmd(SUPERCALL_NETISOLATE_ENABLE),\n            if enable { 1i64 } else { 0i64 },\n        ) as c_long\n    }\n}\n\nfn sc_netisolate_uid_add(key: &CStr, uid: i32) -> c_long {\n    if key.to_bytes().is_empty() {\n        return (-EINVAL).into();\n    }\n    unsafe {\n        syscall(\n            __NR_SUPERCALL,\n            key.as_ptr(),\n            ver_and_cmd(SUPERCALL_NETISOLATE_UID_ADD),\n            uid,\n        ) as c_long\n    }\n}\n\nfn sc_netisolate_uid_clear(key: &CStr) -> c_long {\n    if key.to_bytes().is_empty() {\n        return (-EINVAL).into();\n    }\n    unsafe {\n        syscall(\n            __NR_SUPERCALL,\n            key.as_ptr(),\n            ver_and_cmd(SUPERCALL_NETISOLATE_UID_CLEAR),\n        ) as c_long\n    }\n}\n\npub fn apply_netisolate(superkey: &Option<String>) {\n    if !std::path::Path::new(crate::defs::NETISOLATE_ENABLE_FILE).exists() {\n        info!(\"[netisolate] disabled, skipping\");\n        return;\n    }\n\n    let key = convert_superkey(superkey);\n    let key = match key {\n        Some(k) => k,\n        None => {\n            warn!(\"[netisolate] no superkey available\");\n            return;\n        }\n    };\n\n    // Step 1: Clear and populate UID blocklist (netisolate not yet enabled)\n    match std::fs::read_to_string(crate::defs::NETISOLATE_UIDS_FILE) {\n        Ok(uids) => {\n            sc_netisolate_uid_clear(&key);\n            let mut count = 0u32;\n            for uid_str in uids.lines() {\n                let uid_str = uid_str.trim();\n                if uid_str.is_empty() {\n                    continue;\n                }\n                match uid_str.parse::<i32>() {\n                    Ok(uid) => {\n                        let rc = sc_netisolate_uid_add(&key, uid);\n                        if rc < 0 {\n                            warn!(\"[netisolate] add uid {} failed: {}\", uid, rc);\n                        } else {\n                            count += 1;\n                        }\n                    }\n                    Err(_) => {\n                        warn!(\"[netisolate] invalid uid: '{}'\", uid_str);\n                    }\n                }\n            }\n            info!(\"[netisolate] {} uids restored\", count);\n        }\n        Err(_) => {\n            info!(\"[netisolate] no uids file\");\n        }\n    }\n\n    // Step 2: Enable netisolate LAST\n    let rc = sc_netisolate_enable(&key, true);\n    if rc < 0 {\n        warn!(\"[netisolate] enable failed: {}\", rc);\n        return;\n    }\n\n    info!(\"[netisolate] auto-apply completed\");\n}\n\npub fn apply_pathhide(superkey: &Option<String>) {\n    if !std::path::Path::new(crate::defs::PATHHIDE_ENABLE_FILE).exists() {\n        info!(\"[pathhide] disabled, skipping\");\n        return;\n    }\n\n    let key = convert_superkey(superkey);\n    let key = match key {\n        Some(k) => k,\n        None => {\n            warn!(\"[pathhide] no superkey available\");\n            return;\n        }\n    };\n\n    // Step 1: Clear and populate blocklist (pathhide not yet enabled, hooks are no-ops)\n    match std::fs::read_to_string(crate::defs::PATHHIDE_PATHS_FILE) {\n        Ok(paths) => {\n            sc_pathhide_clear(&key);\n            let mut count = 0u32;\n            for path in paths.lines() {\n                let path = path.trim();\n                if path.is_empty() {\n                    continue;\n                }\n                match CString::new(path) {\n                    Ok(path_cstr) => {\n                        let rc = sc_pathhide_add(&key, &path_cstr);\n                        if rc < 0 {\n                            warn!(\"[pathhide] add path '{}' failed: {}\", path, rc);\n                        } else {\n                            count += 1;\n                        }\n                    }\n                    Err(e) => {\n                        warn!(\"[pathhide] invalid path '{}': {}\", path, e);\n                    }\n                }\n            }\n            info!(\"[pathhide] {} paths restored\", count);\n        }\n        Err(_) => {\n            info!(\"[pathhide] no paths file, clearing blocklist\");\n            sc_pathhide_clear(&key);\n        }\n    }\n\n    // Step 2: Configure UID whitelist BEFORE enabling (so filters are ready)\n    if std::path::Path::new(crate::defs::PATHHIDE_UID_MODE_FILE).exists() {\n        match std::fs::read_to_string(crate::defs::PATHHIDE_UIDS_FILE) {\n            Ok(uids) => {\n                sc_pathhide_uid_clear(&key);\n                let mut count = 0u32;\n                for uid_str in uids.lines() {\n                    let uid_str = uid_str.trim();\n                    if uid_str.is_empty() {\n                        continue;\n                    }\n                    match uid_str.parse::<i32>() {\n                        Ok(uid) => {\n                            let rc = sc_pathhide_uid_add(&key, uid);\n                            if rc < 0 {\n                                warn!(\"[pathhide] add uid {} failed: {}\", uid, rc);\n                            } else {\n                                count += 1;\n                            }\n                        }\n                        Err(_) => {\n                            warn!(\"[pathhide] invalid uid: '{}'\", uid_str);\n                        }\n                    }\n                }\n                info!(\"[pathhide] {} uids restored\", count);\n            }\n            Err(_) => {\n                info!(\"[pathhide] no uids file\");\n            }\n        }\n\n        let rc = sc_pathhide_uid_mode(&key, true);\n        if rc < 0 {\n            warn!(\"[pathhide] uid mode enable failed: {}\", rc);\n        }\n    }\n\n    // Step 2.5: Configure filter_system (allow hiding from system/root UIDs)\n    if std::path::Path::new(crate::defs::PATHHIDE_FILTER_SYSTEM_FILE).exists() {\n        let rc = sc_pathhide_filter_system(&key, true);\n        if rc < 0 {\n            warn!(\"[pathhide] filter_system enable failed: {}\", rc);\n        }\n    }\n\n    // Step 3: Enable pathhide LAST (all config is now in place)\n    let rc = sc_pathhide_enable(&key, true);\n    if rc < 0 {\n        warn!(\"[pathhide] enable failed: {}\", rc);\n        return;\n    }\n\n    info!(\"[pathhide] auto-apply completed\");\n}\n\nfn sc_uts_set(key: &CStr, release: Option<&CStr>, version: Option<&CStr>) -> c_long {\n    if key.to_bytes().is_empty() {\n        return (-EINVAL).into();\n    }\n    let release_ptr = match release {\n        Some(r) => r.as_ptr(),\n        None => std::ptr::null(),\n    };\n    let version_ptr = match version {\n        Some(v) => v.as_ptr(),\n        None => std::ptr::null(),\n    };\n    unsafe {\n        syscall(\n            __NR_SUPERCALL,\n            key.as_ptr(),\n            ver_and_cmd(SUPERCALL_UTS_SET),\n            release_ptr,\n            version_ptr,\n        ) as c_long\n    }\n}\n\nfn sc_uts_reset(key: &CStr) -> c_long {\n    if key.to_bytes().is_empty() {\n        return (-EINVAL).into();\n    }\n    unsafe {\n        syscall(\n            __NR_SUPERCALL,\n            key.as_ptr(),\n            ver_and_cmd(SUPERCALL_UTS_RESET),\n        ) as c_long\n    }\n}\n\npub fn apply_uts_spoof(superkey: &Option<String>) {\n    use std::path::Path;\n\n    const MAX_BOOT_RETRIES: u32 = 3;\n\n    if !Path::new(crate::defs::UTS_SPOOF_ENABLE_FILE).exists() {\n        info!(\"[uts_spoof] disabled, skipping\");\n        return;\n    }\n\n    let config_content = match std::fs::read_to_string(crate::defs::UTS_SPOOF_CONFIG_FILE) {\n        Ok(c) => c,\n        Err(e) => {\n            warn!(\"[uts_spoof] failed to read config: {}\", e);\n            return;\n        }\n    };\n\n    let config: serde_json::Value = match serde_json::from_str(&config_content) {\n        Ok(v) => v,\n        Err(e) => {\n            warn!(\"[uts_spoof] failed to parse config: {}\", e);\n            return;\n        }\n    };\n\n    let release = config.get(\"release\").and_then(|v| v.as_str()).unwrap_or(\"\");\n    let version = config.get(\"version\").and_then(|v| v.as_str()).unwrap_or(\"\");\n\n    let key = convert_superkey(superkey);\n    let key = match key {\n        Some(k) => k,\n        None => {\n            warn!(\"[uts_spoof] no superkey available\");\n            return;\n        }\n    };\n\n    let _ = sc_uts_reset(&key);\n\n    let retries = match std::fs::read_to_string(crate::defs::UTS_SPOOF_BOOT_PENDING) {\n        Ok(s) => s.trim().parse::<u32>().unwrap_or(0),\n        Err(_) => 0,\n    };\n\n    if retries >= MAX_BOOT_RETRIES {\n        warn!(\n            \"[uts_spoof] boot pending retries ({}) >= max ({}), skipping spoof to prevent bootloop\",\n            retries, MAX_BOOT_RETRIES\n        );\n        let _ = std::fs::remove_file(crate::defs::UTS_SPOOF_BOOT_PENDING);\n        return;\n    }\n\n    if let Err(e) = std::fs::write(crate::defs::UTS_SPOOF_BOOT_PENDING, (retries + 1).to_string()) {\n        warn!(\"[uts_spoof] failed to write boot pending flag: {}\", e);\n    }\n\n    // Only set if we have values to spoof\n    let release_cstr = if !release.is_empty() {\n        match CString::new(release) {\n            Ok(c) => Some(c),\n            Err(e) => {\n                warn!(\"[uts_spoof] invalid release string: {}\", e);\n                None\n            }\n        }\n    } else {\n        None\n    };\n    let version_cstr = if !version.is_empty() {\n        match CString::new(version) {\n            Ok(c) => Some(c),\n            Err(e) => {\n                warn!(\"[uts_spoof] invalid version string: {}\", e);\n                None\n            }\n        }\n    } else {\n        None\n    };\n\n    if release_cstr.is_some() || version_cstr.is_some() {\n        let rc = sc_uts_set(\n            &key,\n            release_cstr.as_deref(),\n            version_cstr.as_deref(),\n        );\n        if rc == 0 {\n            info!(\"[uts_spoof] applied: release='{}' version='{}'\", release, version);\n        } else {\n            warn!(\"[uts_spoof] set failed: {}\", rc);\n        }\n    } else {\n        info!(\"[uts_spoof] config has empty values, skipping set\");\n    }\n}\n"
  },
  {
    "path": "apd/src/utils.rs",
    "content": "#[allow(unused_imports)]\nuse std::fs::{Permissions, set_permissions};\n#[cfg(unix)]\nuse std::os::unix::prelude::PermissionsExt;\nuse std::{\n    ffi::CString,\n    fs::{File, OpenOptions, create_dir_all, metadata},\n    io::{ErrorKind::AlreadyExists, Write},\n    path::Path,\n    process::{Command, Stdio},\n};\n\nuse anyhow::{Context, Error, Ok, Result, bail};\nuse log::{info, warn};\n\nuse crate::{defs, supercall::sc_su_get_safemode};\n\npub fn ensure_file_exists<T: AsRef<Path>>(file: T) -> Result<()> {\n    match File::options().write(true).create_new(true).open(&file) {\n        Result::Ok(_) => Ok(()),\n        Err(err) => {\n            if err.kind() == AlreadyExists && file.as_ref().is_file() {\n                Ok(())\n            } else {\n                Err(Error::from(err))\n                    .with_context(|| format!(\"{} is not a regular file\", file.as_ref().display()))\n            }\n        }\n    }\n}\n\npub fn ensure_dir_exists<T: AsRef<Path>>(dir: T) -> Result<()> {\n    let result = create_dir_all(&dir).map_err(Error::from);\n    if dir.as_ref().is_dir() {\n        result\n    } else if result.is_ok() {\n        bail!(\"{} is not a regular directory\", dir.as_ref().display())\n    } else {\n        result\n    }\n}\n\npub fn ensure_dir_with_perms(dir: &Path, parent: &Path, mode: u32) -> Result<()> {\n    if dir.exists() {\n        return Ok(());\n    }\n    create_dir_all(dir)\n        .with_context(|| format!(\"Failed to create {} directory\", dir.display()))?;\n    let permissions = Permissions::from_mode(mode);\n    set_permissions(parent, permissions.clone())\n        .with_context(|| format!(\"Failed to set permissions for {}\", parent.display()))?;\n    set_permissions(dir, permissions)\n        .with_context(|| format!(\"Failed to set permissions for {}\", dir.display()))?;\n    info!(\"Created directory: {}\", dir.display());\n    Ok(())\n}\n\n// todo: ensure\npub fn ensure_binary<T: AsRef<Path>>(path: T) -> Result<()> {\n    set_permissions(&path, Permissions::from_mode(0o755))?;\n    Ok(())\n}\n\npub fn get_work_dir() -> &'static str {\n    defs::WORKING_DIR\n}\n\n#[cfg(any(target_os = \"linux\", target_os = \"android\"))]\npub fn getprop(prop: &str) -> Option<String> {\n    android_properties::getprop(prop).value()\n}\n\n#[cfg(not(any(target_os = \"linux\", target_os = \"android\")))]\npub fn getprop(_prop: &str) -> Option<String> {\n    unimplemented!()\n}\npub fn run_command(\n    command: &str,\n    args: &[&str],\n    stdout: Option<Stdio>,\n) -> Result<std::process::Child> {\n    let mut command_builder = Command::new(command);\n    command_builder.args(args);\n    if let Some(out) = stdout {\n        command_builder.stdout(out);\n    }\n    let child = command_builder.spawn()?;\n    Ok(child)\n}\npub fn is_safe_mode(superkey: Option<String>) -> bool {\n    let safemode = getprop(\"persist.sys.safemode\")\n        .filter(|prop| prop == \"1\")\n        .is_some()\n        || getprop(\"ro.sys.safemode\")\n            .filter(|prop| prop == \"1\")\n            .is_some();\n    info!(\"safemode: {}\", safemode);\n    if safemode {\n        return true;\n    }\n    let safemode = superkey\n        .as_ref()\n        .and_then(|key_str| CString::new(key_str.as_str()).ok())\n        .map_or_else(\n            || {\n                warn!(\"[is_safe_mode] No valid superkey provided, assuming safemode as false.\");\n                false\n            },\n            |cstr| sc_su_get_safemode(&cstr) == 1,\n        );\n    info!(\"kernel_safemode: {}\", safemode);\n    safemode\n}\n\n#[cfg(any(target_os = \"linux\", target_os = \"android\"))]\npub fn switch_mnt_ns(pid: i32) -> Result<()> {\n    use std::os::fd::AsRawFd;\n\n    use anyhow::ensure;\n    let path = format!(\"/proc/{pid}/ns/mnt\");\n    let fd = File::open(path)?;\n    let current_dir = std::env::current_dir();\n    let ret = unsafe { libc::setns(fd.as_raw_fd(), libc::CLONE_NEWNS) };\n    if let Result::Ok(current_dir) = current_dir {\n        let _ = std::env::set_current_dir(current_dir);\n    }\n    ensure!(ret == 0, \"switch mnt ns failed\");\n    Ok(())\n}\n\nfn switch_cgroup(grp: &str, pid: u32) {\n    let path = Path::new(grp).join(\"cgroup.procs\");\n    if !path.exists() {\n        return;\n    }\n\n    let fp = OpenOptions::new().append(true).open(path);\n    if let Result::Ok(mut fp) = fp {\n        let _ = write!(fp, \"{pid}\");\n    }\n}\n\npub fn switch_cgroups() {\n    let pid = std::process::id();\n    switch_cgroup(\"/acct\", pid);\n    switch_cgroup(\"/dev/cg2_bpf\", pid);\n    switch_cgroup(\"/sys/fs/cgroup\", pid);\n\n    if getprop(\"ro.config.per_app_memcg\")\n        .filter(|prop| prop == \"false\")\n        .is_none()\n    {\n        switch_cgroup(\"/dev/memcg/apps\", pid);\n    }\n}\n\n#[cfg(any(target_os = \"linux\", target_os = \"android\"))]\npub fn umask(mask: u32) {\n    unsafe { libc::umask(mask) };\n}\n\n#[cfg(not(any(target_os = \"linux\", target_os = \"android\")))]\npub fn umask(_mask: u32) {\n    unimplemented!(\"umask is not supported on this platform\")\n}\n\npub fn has_magisk() -> bool {\n    which::which(\"magisk\").is_ok()\n}\npub fn get_tmp_path() -> &'static str {\n    if metadata(defs::TEMP_DIR_LEGACY).is_ok() {\n        return defs::TEMP_DIR_LEGACY;\n    }\n    if metadata(defs::TEMP_DIR).is_ok() {\n        return defs::TEMP_DIR;\n    }\n    \"\"\n}\n"
  },
  {
    "path": "app/.gitignore",
    "content": "/build\n/release/\n*.jks\n*.keystore\nkeystore.properties\n"
  },
  {
    "path": "app/build.gradle.kts",
    "content": "@file:Suppress(\"UnstableApiUsage\")\n\nimport com.android.build.gradle.tasks.PackageAndroidArtifact\nimport org.jetbrains.kotlin.gradle.dsl.JvmTarget\nimport java.net.URI\nimport java.util.Properties\nimport java.io.File\nimport java.io.FileInputStream\n\nplugins {\n    alias(libs.plugins.agp.app)\n    alias(libs.plugins.kotlin)\n    alias(libs.plugins.kotlin.compose.compiler)\n    alias(libs.plugins.ksp)\n    alias(libs.plugins.lsplugin.apksign)\n    alias(libs.plugins.lsplugin.resopt)\n    alias(libs.plugins.lsplugin.cmaker)\n    id(\"kotlin-parcelize\")\n}\n\nval androidCompileSdkVersion: Int by rootProject.extra\nval androidCompileNdkVersion: String by rootProject.extra\nval androidBuildToolsVersion: String by rootProject.extra\nval androidMinSdkVersion: Int by rootProject.extra\nval androidTargetSdkVersion: Int by rootProject.extra\nval androidSourceCompatibility: JavaVersion by rootProject.extra\nval androidTargetCompatibility: JavaVersion by rootProject.extra\nval managerVersionCode: Int by rootProject.extra\nval managerVersionName: String by rootProject.extra\nval branchName: String by rootProject.extra\nval kernelPatchVersion: String by rootProject.extra\n\n// Load keystore properties\nval keystoreProperties = Properties()\nval keystorePropertiesFile = rootProject.file(\"keystore.properties\")\nif (keystorePropertiesFile.exists()) {\n    keystoreProperties.load(FileInputStream(keystorePropertiesFile))\n}\n\n// Load local properties\nval localProperties = Properties()\nval localPropertiesFile = rootProject.file(\"local.properties\")\nif (localPropertiesFile.exists()) {\n    localProperties.load(FileInputStream(localPropertiesFile))\n}\n\napksign {\n    storeFileProperty = \"KEYSTORE_FILE\"\n    storePasswordProperty = \"KEYSTORE_PASSWORD\"\n    keyAliasProperty = \"KEY_ALIAS\"\n    keyPasswordProperty = \"KEY_PASSWORD\"\n}\n\nval ccache = System.getenv(\"PATH\")?.split(File.pathSeparator)\n    ?.map { File(it, \"ccache\") }?.firstOrNull { it.exists() }?.absolutePath\n\nval baseFlags = listOf(\n    \"-Wall\", \"-Qunused-arguments\", \"-fno-rtti\", \"-fvisibility=hidden\",\n    \"-fvisibility-inlines-hidden\", \"-fno-exceptions\", \"-fno-stack-protector\",\n    \"-fomit-frame-pointer\", \"-Wno-builtin-macro-redefined\", \"-Wno-unused-value\",\n    \"-D__FILE__=__FILE_NAME__\",\n    \"-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON\", \"-Wno-unused\", \"-Wno-unused-parameter\",\n    \"-Wno-unused-command-line-argument\", \"-Wno-incompatible-function-pointer-types\",\n    \"-U_FORTIFY_SOURCE\", \"-D_FORTIFY_SOURCE=0\"\n)\n\nval baseArgs = mutableListOf(\n    \"-DANDROID_STL=none\", \"-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON\",\n    \"-DCMAKE_CXX_STANDARD=23\", \"-DCMAKE_C_STANDARD=23\",\n    \"-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON\", \"-DCMAKE_VISIBILITY_INLINES_HIDDEN=ON\",\n    \"-DCMAKE_CXX_VISIBILITY_PRESET=hidden\", \"-DCMAKE_C_VISIBILITY_PRESET=hidden\"\n).apply { if (ccache != null) add(\"-DANDROID_CCACHE=$ccache\") }\n\nandroid {\n    namespace = \"me.bmax.apatch\"\n    signingConfigs {\n        create(\"release\") {\n            storeFile = file(keystoreProperties.getProperty(\"KEYSTORE_FILE\") ?: \"debug.keystore\")\n            storePassword = keystoreProperties.getProperty(\"KEYSTORE_PASSWORD\") ?: \"android\"\n            keyAlias = keystoreProperties.getProperty(\"KEY_ALIAS\") ?: \"androiddebugkey\"\n            keyPassword = keystoreProperties.getProperty(\"KEY_PASSWORD\") ?: \"android\"\n            enableV1Signing = true\n            enableV2Signing = true\n            enableV3Signing = true\n            enableV4Signing = true\n        }\n    }\n\n    buildTypes {\n        debug {\n            isDebuggable = true\n            isMinifyEnabled = false\n            isShrinkResources = false\n            proguardFiles(\n                getDefaultProguardFile(\"proguard-android-optimize.txt\"),\n                \"proguard-rules.pro\"\n            )\n            externalNativeBuild {\n                cmake {\n                    arguments += listOf(\"-DCMAKE_CXX_FLAGS_DEBUG=-Og\", \"-DCMAKE_C_FLAGS_DEBUG=-Og\")\n                }\n            }\n        }\n        release {\n            isMinifyEnabled = true\n            isShrinkResources = true\n            isDebuggable = false\n            multiDexEnabled = true\n            vcsInfo.include = false\n            if (keystorePropertiesFile.exists()) {\n                signingConfig = signingConfigs.getByName(\"release\")\n            }\n            proguardFiles(\n                getDefaultProguardFile(\"proguard-android-optimize.txt\"),\n                \"proguard-rules.pro\"\n            )\n            externalNativeBuild {\n                cmake {\n                    val relFlags = listOf(\n                        \"-flto\", \"-ffunction-sections\", \"-fdata-sections\", \"-Wl,--gc-sections\",\n                        \"-fno-unwind-tables\", \"-fno-asynchronous-unwind-tables\", \"-Wl,--exclude-libs,ALL\",\n                        \"-Ofast\", \"-fmerge-all-constants\", \"-flto=full\", \"-ffat-lto-objects\",\n                        \"-fno-semantic-interposition\", \"-fno-threadsafe-statics\"\n                    )\n                    cppFlags += relFlags\n                    cFlags += relFlags\n                    arguments += listOf(\"-DCMAKE_BUILD_TYPE=Release\", \"-DCMAKE_CXX_FLAGS_RELEASE=-O3 -DNDEBUG\", \"-DCMAKE_C_FLAGS_RELEASE=-O3 -DNDEBUG\")\n                }\n            }\n        }\n    }\n\n    dependenciesInfo.includeInApk = false\n\n    buildFeatures {\n        aidl = true\n        buildConfig = true\n        compose = true\n        prefab = true\n    }\n\n    defaultConfig {\n        applicationId = \"me.yuki.folk\"\n        minSdk = androidMinSdkVersion\n        targetSdk = androidTargetSdkVersion\n        versionCode = managerVersionCode\n        versionName = managerVersionName\n        buildConfigField(\"String\", \"buildKPV\", \"\\\"$kernelPatchVersion\\\"\")\n        buildConfigField(\"boolean\", \"DEBUG_FAKE_ROOT\", localProperties.getProperty(\"debug.fake_root\", \"false\"))\n\n        base.archivesName = \"FolkPatch_${managerVersionCode}_${managerVersionName}_on_${branchName}\"\n\n        ndk.abiFilters.addAll(arrayOf(\"arm64-v8a\"))\n        externalNativeBuild {\n            cmake {\n                cppFlags += baseFlags + \"-std=c++2b\"\n                cFlags += baseFlags + \"-std=c2x\"\n                arguments += baseArgs\n                \n                // Pass Token and Signature Hash to CMake\n                val authProps = Properties()\n                val authFile = rootProject.file(\"auth.properties\")\n                if (authFile.exists()) {\n                    authProps.load(FileInputStream(authFile))\n                }\n                val token = authProps.getProperty(\"api.token\", \"\")\n                val signatureHash = authProps.getProperty(\"app.signature.hash\", \"\")\n\n                // Pass to C++ compiler directly via flags\n                // Only add flags if values are non-empty to avoid compiler errors\n                if (token.isNotEmpty()) {\n                    cppFlags += \"-DAPI_TOKEN=\\\"$token\\\"\"\n                }\n                if (signatureHash.isNotEmpty()) {\n                    cppFlags += \"-DAPP_SIGNATURE_HASH=\\\"$signatureHash\\\"\"\n                }\n                cppFlags += \"-DAPP_PACKAGE_NAME=\\\"$applicationId\\\"\"\n                \n                abiFilters(\"arm64-v8a\")\n            }\n        }\n        \n    }\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_21\n        targetCompatibility = JavaVersion.VERSION_21\n    }\n\n    packaging {\n        jniLibs {\n            useLegacyPackaging = true\n        }\n        resources {\n            excludes += \"**\"\n            merges += \"META-INF/com/google/android/**\"\n        }\n    }\n\n    externalNativeBuild {\n        cmake {\n            version = \"3.28.0+\"\n            path(\"src/main/cpp/CMakeLists.txt\")\n        }\n    }\n\n    androidResources {\n        generateLocaleConfig = true\n    }\n\n    compileSdk = androidCompileSdkVersion\n    ndkVersion = androidCompileNdkVersion\n    buildToolsVersion = androidBuildToolsVersion\n\n    lint {\n        abortOnError = false\n        checkReleaseBuilds = false\n    }\n\n    android.sourceSets.named(\"main\") {\n        kotlin.directories += \"build/generated/ksp/$name/kotlin\"\n        jniLibs.directories += \"libs\"\n    }\n}\n\n// https://stackoverflow.com/a/77745844\ntasks.withType<PackageAndroidArtifact> {\n    doFirst { appMetadata.asFile.orNull?.writeText(\"\") }\n}\n\njava {\n    toolchain {\n        languageVersion = JavaLanguageVersion.of(21)\n    }\n}\n\nkotlin {\n    jvmToolchain(21)\n    compilerOptions {\n        jvmTarget = JvmTarget.JVM_21\n    }\n}\n\nfun registerDownloadTask(\n    taskName: String, srcUrl: String, destPath: String, project: Project, version: String? = null\n) {\n    project.tasks.register(taskName) {\n        val destFile = File(destPath)\n        val versionFile = File(\"$destPath.version\")\n\n        doLast {\n            var forceDownload = false\n            if (version != null) {\n                if (!versionFile.exists() || versionFile.readText().trim() != version) {\n                    forceDownload = true\n                }\n            }\n\n            if (!destFile.exists() || forceDownload || isFileUpdated(srcUrl, destFile)) {\n                println(\" - Downloading $srcUrl to ${destFile.absolutePath}\")\n                downloadFile(srcUrl, destFile)\n                if (version != null) {\n                    versionFile.writeText(version)\n                }\n                println(\" - Download completed.\")\n            } else {\n                println(\" - File is up-to-date, skipping download.\")\n            }\n        }\n    }\n}\n\nfun isFileUpdated(url: String, localFile: File): Boolean {\n    val connection = URI.create(url).toURL().openConnection()\n    val remoteLastModified = connection.getHeaderFieldDate(\"Last-Modified\", 0L)\n    return remoteLastModified > localFile.lastModified()\n}\n\nfun downloadFile(url: String, destFile: File) {\n    URI.create(url).toURL().openStream().use { input ->\n        destFile.outputStream().use { output ->\n            input.copyTo(output)\n        }\n    }\n}\n\nregisterDownloadTask(\n    taskName = \"downloadKpimg\",\n    srcUrl = \"https://github.com/LyraVoid/KernelPatch/releases/download/$kernelPatchVersion/kpimg-android\",\n    destPath = \"${project.projectDir}/src/main/assets/kpimg\",\n    project = project,\n    version = kernelPatchVersion\n)\n\nregisterDownloadTask(\n    taskName = \"downloadKptools\",\n    srcUrl = \"https://github.com/LyraVoid/KernelPatch/releases/download/$kernelPatchVersion/kptools-android\",\n    destPath = \"${project.projectDir}/libs/arm64-v8a/libkptools.so\",\n    project = project,\n    version = kernelPatchVersion\n)\n\n// Compat kp version less than 0.10.7\n// TODO: Remove in future\nregisterDownloadTask(\n    taskName = \"downloadCompatKpatch\",\n    srcUrl = \"https://github.com/bmax121/KernelPatch/releases/download/0.10.7/kpatch-android\",\n    destPath = \"${project.projectDir}/libs/arm64-v8a/libkpatch.so\",\n    project = project,\n    version = \"0.10.7\"\n)\n\ntasks.register<Copy>(\"mergeScripts\") {\n    into(\"${project.projectDir}/src/main/resources/META-INF/com/google/android\")\n    from(rootProject.file(\"${project.rootDir}/scripts/update_binary.sh\")) {\n        rename { \"update-binary\" }\n    }\n    from(rootProject.file(\"${project.rootDir}/scripts/update_script.sh\")) {\n        rename { \"updater-script\" }\n    }\n}\n\n// Build fpd (FolkPatch service binary) for arm64\ntasks.register<Exec>(\"buildFpd\") {\n    executable(\"cargo\")\n    args(\"ndk\", \"-t\", \"arm64-v8a\", \"build\", \"--release\")\n    workingDir(\"${project.rootDir}/fpd\")\n    doFirst {\n        println(\"Building fpd for arm64...\")\n    }\n    doLast {\n        val fpdBinary = file(\"${project.rootDir}/fpd/target/aarch64-linux-android/release/fpd\")\n        val serviceDir = file(\"src/main/assets/Service\")\n        serviceDir.mkdirs()\n        fpdBinary.copyTo(file(\"${serviceDir}/fpd\"), overwrite = true)\n        println(\"fpd binary built and copied to Service/fpd\")\n    }\n}\n\ntasks.getByName(\"preBuild\").dependsOn(\n    \"downloadKpimg\",\n    \"downloadKptools\",\n    \"downloadCompatKpatch\",\n    \"mergeScripts\",\n    \"buildFpd\",\n)\n\n// https://github.com/bbqsrc/cargo-ndk\n// cargo ndk -t arm64-v8a build --release\ntasks.register<Exec>(\"cargoBuild\") {\n    executable(\"cargo\")\n    args(\"ndk\", \"-t\", \"arm64-v8a\", \"build\", \"--release\")\n    workingDir(\"${project.rootDir}/apd\")\n    environment(\"APATCH_VERSION_CODE\", \"${managerVersionCode}\")\n    environment(\"APATCH_VERSION_NAME\", \"${managerVersionCode}-Matsuzaka-yuki\")\n}\n\ntasks.register<Copy>(\"buildApd\") {\n    dependsOn(\"cargoBuild\")\n    from(\"${project.rootDir}/apd/target/aarch64-linux-android/release/apd\")\n    into(\"${project.projectDir}/libs/arm64-v8a\")\n    rename(\"apd\", \"libapd.so\")\n}\n\ntasks.configureEach {\n    if (name == \"mergeDebugJniLibFolders\" || name == \"mergeReleaseJniLibFolders\") {\n        dependsOn(\"buildApd\")\n    }\n}\n\ntasks.register<Exec>(\"cargoClean\") {\n    executable(\"cargo\")\n    args(\"clean\")\n    workingDir(\"${project.rootDir}/apd\")\n}\n\ntasks.register<Delete>(\"apdClean\") {\n    dependsOn(\"cargoClean\")\n    delete(file(\"${project.projectDir}/libs/arm64-v8a/libapd.so\"))\n}\n\ntasks.clean {\n    dependsOn(\"apdClean\")\n}\n\nksp {\n    arg(\"compose-destinations.defaultTransitions\", \"none\")\n}\n\ndependencies {\n    implementation(libs.androidx.appcompat)\n    implementation(libs.androidx.activity.compose)\n    implementation(libs.androidx.core.splashscreen)\n    implementation(libs.androidx.webkit)\n    implementation(libs.androidx.biometric)\n\n    implementation(platform(libs.androidx.compose.bom))\n    implementation(libs.androidx.compose.material.icons.extended)\n    implementation(libs.androidx.compose.material)\n    implementation(libs.androidx.compose.material3)\n\n    implementation(\"androidx.compose.material3:material3-android:1.5.0-alpha17\")\n    implementation(libs.androidx.compose.ui)\n    implementation(libs.androidx.compose.ui.tooling.preview)\n    implementation(libs.androidx.compose.runtime.livedata)\n\n    debugImplementation(libs.androidx.compose.ui.test.manifest)\n    debugImplementation(libs.androidx.compose.ui.tooling)\n\n    implementation(libs.androidx.lifecycle.runtime.compose)\n    implementation(libs.androidx.lifecycle.runtime.ktx)\n    implementation(libs.androidx.lifecycle.viewmodel.compose)\n    implementation(\"androidx.lifecycle:lifecycle-process:2.8.7\")\n\n    implementation(libs.compose.destinations.core)\n    ksp(libs.compose.destinations.ksp)\n\n    implementation(libs.com.github.topjohnwu.libsu.core)\n    implementation(libs.com.github.topjohnwu.libsu.service)\n    implementation(libs.com.github.topjohnwu.libsu.nio)\n    implementation(libs.com.github.topjohnwu.libsu.io)\n\n    implementation(libs.dev.rikka.rikkax.parcelablelist)\n\n    implementation(libs.io.coil.kt.coil.compose)\n    implementation(libs.io.coil.kt.coil.gif)\n\n    implementation(libs.kotlinx.coroutines.core)\n\n    implementation(libs.me.zhanghai.android.appiconloader.coil)\n\n    implementation(libs.sheet.compose.dialogs.core)\n    implementation(libs.sheet.compose.dialogs.list)\n    implementation(libs.sheet.compose.dialogs.input)\n\n    implementation(libs.markdown)\n\n    implementation(libs.ini4j)\n\n    implementation(libs.google.code.gson)\n\n    implementation(libs.liquid)\n\n    compileOnly(libs.cxx)\n}\n"
  },
  {
    "path": "app/libs/arm64-v8a/.gitignore",
    "content": "libkptools.so\nlibapjni.so\nlibkpatch.so\nlibapd.so"
  },
  {
    "path": "app/libs/arm64-v8a/libkpatch.so.version",
    "content": "0.10.7"
  },
  {
    "path": "app/libs/arm64-v8a/libkptools.so.version",
    "content": "0.13.1"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "-dontwarn org.bouncycastle.jsse.BCSSLParameters\n-dontwarn org.bouncycastle.jsse.BCSSLSocket\n-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider\n-dontwarn org.conscrypt.Conscrypt$Version\n-dontwarn org.conscrypt.Conscrypt\n-dontwarn org.conscrypt.ConscryptHostnameVerifier\n-dontwarn org.openjsse.javax.net.ssl.SSLParameters\n-dontwarn org.openjsse.javax.net.ssl.SSLSocket\n-dontwarn org.openjsse.net.ssl.OpenJSSE\n-dontwarn java.beans.Introspector\n-dontwarn java.beans.VetoableChangeListener\n-dontwarn java.beans.VetoableChangeSupport\n-dontwarn java.beans.BeanInfo\n-dontwarn java.beans.IntrospectionException\n-dontwarn java.beans.PropertyDescriptor\n\n# Keep ini4j Service Provider Interface\n-keep,allowobfuscation,allowoptimization class org.ini4j.spi.** { *; }\n\n# Keep native methods and JNI classes\n-keep class me.bmax.apatch.Natives {\n    *;\n}\n\n-keepclasseswithmembernames class * {\n    native <methods>;\n}\n\n-keep class me.bmax.apatch.Natives$Profile { *; }\n-keep class me.bmax.apatch.Natives$KPMCtlRes { *; }\n\n# Keep RootServices\n-keep class me.bmax.apatch.services.RootServices { *; }\n\n# Keep AIDL interfaces\n-keep class me.bmax.apatch.IAPRootService { *; }\n-keep class me.bmax.apatch.IAPRootService$Stub { *; }\n-keep class rikka.parcelablelist.ParcelableListSlice { *; }\n# Keep ScriptInfo for Gson serialization in release\n-keep class me.bmax.apatch.data.ScriptInfo { *; }\n-keepclassmembers class me.bmax.apatch.data.ScriptInfo { *; }\n\n# Gson\n-keepattributes Signature\n-keepattributes *Annotation*\n-keep class sun.misc.Unsafe { *; }\n-keep class com.google.gson.** { *; }\n-keep class * extends com.google.gson.reflect.TypeToken\n\n# Kotlin\n-assumenosideeffects class kotlin.jvm.internal.Intrinsics {\n    public static void check*(...);\n    public static void throw*(...);\n}\n\n# Keep Umount configuration classes\n-keep class me.bmax.apatch.ui.component.UmountConfig { *; }\n-keep class me.bmax.apatch.ui.component.UmountConfigManager { *; }\n-keep class me.bmax.apatch.ui.screen.UmountConfigScreen { *; }\n\n-keepclassmembers class me.bmax.apatch.ui.component.UmountConfigManager {\n    public static *;\n}\n\n-keepclassmembers class me.bmax.apatch.ui.component.UmountConfig {\n    public <init>(boolean, java.lang.String);\n}\n\n# Keep Umount destination\n-keep class me.bmax.apatch.ui.screen.destinations.UmountConfigScreenDestination { *; }\n\n-repackageclasses\n-allowaccessmodification\n-overloadaggressively\n-renamesourcefileattribute SourceFile\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.VIBRATE\" />\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.MANAGE_EXTERNAL_STORAGE\" />\n    <!-- 用于访问外部存储上的媒体文件 -->\n    <uses-permission android:name=\"android.permission.READ_MEDIA_IMAGES\" />\n    <uses-permission\n        android:name=\"android.permission.QUERY_ALL_PACKAGES\"\n        tools:ignore=\"QueryAllPackagesPermission\" />\n\n    <application\n        android:name=\".APApplication\"\n        android:allowBackup=\"true\"\n        android:dataExtractionRules=\"@xml/data_extraction_rules\"\n        android:enableOnBackInvokedCallback=\"true\"\n        android:fullBackupContent=\"@xml/backup_rules\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"FolkPatch\"\n        android:networkSecurityConfig=\"@xml/network_security_config\"\n        android:requestLegacyExternalStorage=\"true\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/Theme.APatch\"\n        android:usesCleartextTraffic=\"true\"\n        tools:targetApi=\"34\">\n\n        <activity\n            android:name=\".ui.MainActivity\"\n            android:exported=\"true\"\n            android:theme=\"@style/Theme.Splash\" />\n\n        <activity-alias\n            android:name=\".ui.MainActivityFileHandler\"\n            android:enabled=\"true\"\n            android:exported=\"true\"\n            android:icon=\"@mipmap/ic_launcher\"\n            android:label=\"@string/app_name\"\n            android:targetActivity=\".ui.MainActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.VIEW\" />\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <category android:name=\"android.intent.category.BROWSABLE\" />\n                <data android:mimeType=\"application/zip\" />\n                <data android:scheme=\"content\" />\n            </intent-filter>\n\n            <intent-filter>\n                <action android:name=\"android.intent.action.VIEW\" />\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <category android:name=\"android.intent.category.BROWSABLE\" />\n                <data android:scheme=\"file\" />\n                <data android:scheme=\"content\" />\n                <data android:host=\"*\" />\n                <data android:mimeType=\"*/*\" />\n                <data android:pathPattern=\".*\\\\.fpt\" />\n            </intent-filter>\n\n            <intent-filter>\n                <action android:name=\"android.intent.action.VIEW\" />\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <category android:name=\"android.intent.category.BROWSABLE\" />\n                <data android:scheme=\"file\" />\n                <data android:scheme=\"content\" />\n                <data android:host=\"*\" />\n                <data android:pathPattern=\".*\\\\.fpt\" />\n            </intent-filter>\n\n            <intent-filter>\n                <action android:name=\"android.intent.action.SEND\" />\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <data android:mimeType=\"application/zip\" />\n                <data android:mimeType=\"application/octet-stream\" />\n            </intent-filter>\n\n            <intent-filter>\n                <action android:name=\"android.intent.action.SEND_MULTIPLE\" />\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <data android:mimeType=\"application/zip\" />\n                <data android:mimeType=\"application/octet-stream\" />\n            </intent-filter>\n        </activity-alias>\n\n        <activity-alias\n            android:name=\".ui.MainActivityDefault\"\n            android:enabled=\"true\"\n            android:exported=\"true\"\n            android:icon=\"@mipmap/ic_launcher\"\n            android:label=\"@string/app_name\"\n            android:targetActivity=\".ui.MainActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity-alias>\n\n        <activity-alias\n            android:name=\".ui.MainActivityAlias\"\n            android:enabled=\"false\"\n            android:exported=\"true\"\n            android:icon=\"@mipmap/ic_launcher_alt\"\n            android:label=\"@string/app_name\"\n            android:targetActivity=\".ui.MainActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity-alias>\n\n        <activity-alias\n            android:name=\".ui.MainActivityAliasSu\"\n            android:enabled=\"false\"\n            android:exported=\"true\"\n            android:icon=\"@mipmap/ic_launcher\"\n            android:label=\"@string/app_name_fpatch\"\n            android:targetActivity=\".ui.MainActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity-alias>\n\n        <activity-alias\n            android:name=\".ui.MainActivityAliasAltSu\"\n            android:enabled=\"false\"\n            android:exported=\"true\"\n            android:icon=\"@mipmap/ic_launcher_alt\"\n            android:label=\"@string/app_name_fpatch\"\n            android:targetActivity=\".ui.MainActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity-alias>\n\n        <activity\n            android:name=\".ui.WebUIActivity\"\n            android:process=\":webui\"\n            android:autoRemoveFromRecents=\"true\"\n            android:documentLaunchMode=\"intoExisting\"\n            android:exported=\"false\"\n            android:theme=\"@style/Theme.APatch.WebUI\" />\n\n        <activity\n            android:name=\".ui.CrashHandleActivity\"\n            android:exported=\"false\" />\n\n        <provider\n            android:name=\".util.SafeFileProvider\"\n            android:authorities=\"${applicationId}.fileprovider\"\n            android:exported=\"false\"\n            android:grantUriPermissions=\"true\">\n            <meta-data\n                android:name=\"android.support.FILE_PROVIDER_PATHS\"\n                android:resource=\"@xml/file_paths\" />\n        </provider>\n\n        <service\n            android:name=\".services.RootServices\"\n            android:exported=\"false\"\n            android:process=\":root\" />\n\n        <service\n            android:name=\"androidx.appcompat.app.AppLocalesMetadataHolderService\"\n            android:enabled=\"false\"\n            android:exported=\"false\">\n            <meta-data\n                android:name=\"autoStoreLocales\"\n                android:value=\"true\" />\n        </service>\n\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "app/src/main/aidl/me/bmax/apatch/IAPRootService.aidl",
    "content": "// IAPRootService.aidl\npackage me.bmax.apatch;\n\nimport android.content.pm.PackageInfo;\nimport rikka.parcelablelist.ParcelableListSlice;\n\ninterface IAPRootService {\n    ParcelableListSlice<PackageInfo> getPackages(int flags);\n}"
  },
  {
    "path": "app/src/main/assets/.gitignore",
    "content": "kpimg\n*.kpm"
  },
  {
    "path": "app/src/main/assets/InstallAP.sh",
    "content": "#!/bin/sh\n# By SakuraKyuo\n\nOUTFD=/proc/self/fd/$2\n\nfunction ui_print() {\n  echo -e \"ui_print $1\\nui_print\" >> $OUTFD\n}\n\nfunction ui_printfile() {\n  while IFS='' read -r line || $BB [[ -n \"$line\" ]]; do\n    ui_print \"$line\";\n  done < $1;\n}\n\nfunction kernelFlagsErr(){\n\tui_print \"- Installation has Aborted!\"\n\tui_print \"- APatch requires CONFIG_KALLSYMS to be Enabled.\"\n\tui_print \"- But your kernel seems NOT enabled it.\"\n\texit\n}\n\nfunction apatchNote(){\n\tui_print \"- APatch Patch Done\"\n\tui_print \"- APatch Key is: Ap$skey\"\n\tui_print \"- We do have saved Origin Boot image to /data\"\n\tui_print \"- If you encounter bootloop, reboot into Recovery and flash it\"\n\texit\n}\n\nfunction failed(){\n\tui_printfile /dev/tmp/install/log\n\tui_print \"- APatch Patch Failed.\"\n\tui_print \"- Please feedback to the developer with the screenshots.\"\n\texit\n}\n\nfunction boot_execute_ab(){\n\t./lib/arm64-v8a/libkptools.so unpack boot.img\n\tif [[ ! $(./lib/arm64-v8a/libkptools.so -i ./kernel -f | grep CONFIG_KALLSYMS=y) ]]; then\n\t\tkernelFlagsErr\n\tfi\n\tmv kernel kernel-origin\n\t./lib/arm64-v8a/libkptools.so -p --image kernel-origin --skey \"Ap$skey\" --kpimg ./assets/kpimg --out ./kernel 2>&1 | tee /dev/tmp/install/log\n\tif [[ ! $(cat /dev/tmp/install/log | grep \"patch done\") ]]; then\n\t\tfailed\n\tfi\n\tui_printfile /dev/tmp/install/log\n\t./lib/arm64-v8a/libkptools.so repack boot.img\n\tdd if=/dev/tmp/install/new-boot.img of=/dev/block/by-name/boot$slot\n\tmv boot.img /data/boot.img\n\tapatchNote\n}\n\nfunction boot_execute(){\n\t./lib/arm64-v8a/libkptools.so unpack boot.img\n\tif [[ ! $(./lib/arm64-v8a/libkptools.so -i ./kernel -f | grep CONFIG_KALLSYMS=y) ]]; then\n\t\tkernelFlagsErr\n\tfi\n\tmv kernel kernel-origin\n\t./lib/arm64-v8a/libkptools.so -p --image kernel-origin --skey \"Ap$skey\" --kpimg ./assets/kpimg --out ./kernel 2>&1 | tee /dev/tmp/install/log\n\tif [[ ! $(cat /dev/tmp/install/log | grep \"patch done\") ]]; then\n\t\tfailed\n\tfi\n\tui_printfile /dev/tmp/install/log\n\t./lib/arm64-v8a/libkptools.so repack boot.img\n\tdd if=/dev/tmp/install/new-boot.img of=/dev/block/by-name/boot$slot\n\tmv boot.img /data/boot.img\n\tapatchNote\n}\n\nfunction main(){\n\ncd /dev/tmp/install\n\nchmod a+x ./assets/kpimg\nchmod a+x ./lib/arm64-v8a/libkptools.so\n\nslot=$(getprop ro.boot.slot_suffix)\n\nskey=$(cat /proc/sys/kernel/random/uuid | cut -d \\- -f1)\n\nif [[ ! \"$slot\" == \"\" ]]; then\n\n\tui_print \"\"\n\tui_print \"- You are using A/B device.\"\n\n\t# Script author\n\tui_print \"- Install Script by SakuraKyuo\"\n\n\t# Get kernel\n\tui_print \"\"\n\tdd if=/dev/block/by-name/boot$slot of=/dev/tmp/install/boot.img\n\tif [[ \"$?\" == 0 ]]; then\n\t\tui_print \"- Detected boot partition.\"\n\t\tboot_execute_ab\n\tfi\n\nelse\n\n\tui_print \"You are using A Only device.\"\n\n\t# Get kernel\n\tui_print \"\"\n\tdd if=/dev/block/by-name/boot of=/dev/tmp/install/boot.img\n\tif [[ \"$?\" == 0 ]]; then\n\t\tui_print \"- Detected boot partition.\"\n\t\tboot_execute\n\tfi\n\nfi\n\n}\n\nmain\n"
  },
  {
    "path": "app/src/main/assets/UninstallAP.sh",
    "content": "#!/bin/sh\n# By SakuraKyuo\n\nOUTFD=/proc/self/fd/$2\n\nfunction ui_print() {\n  echo -e \"ui_print $1\\nui_print\" >> $OUTFD\n}\n\nfunction ui_printfile() {\n  while IFS='' read -r line || $BB [[ -n \"$line\" ]]; do\n    ui_print \"$line\";\n  done < $1;\n}\n\nfunction apatchNote(){\n\tui_print \"- APatch Unpatch Done\"\n\texit\n}\n\nfunction failed(){\n\tui_print \"- APatch Unpatch Failed.\"\n\tui_print \"- Please feedback to the developer with the screenshots.\"\n\texit\n}\n\nfunction boot_execute_ab(){\n\t./lib/arm64-v8a/libkptools.so unpack boot.img\n\tmv kernel kernel-origin\n\t./lib/arm64-v8a/libkptools.so -u --image kernel-origin  --out ./kernel\n\tif [[ ! \"$?\" == 0 ]]; then\n\t\tfailed\n\tfi\n\t./lib/arm64-v8a/libkptools.so repack boot.img\n\tdd if=/dev/tmp/install/new-boot.img of=/dev/block/by-name/boot$slot\n\tapatchNote\n}\n\nfunction boot_execute(){\n\t./lib/arm64-v8a/libkptools.so unpack boot.img\n\tmv kernel kernel-origin\n\t./lib/arm64-v8a/libkptools.so -u --image kernel-origin  --out ./kernel\n\tif [[ ! \"$?\" == 0 ]]; then\n\t\tfailed\n\tfi\n\t./lib/arm64-v8a/libkptools.so repack boot.img\n\tdd if=/dev/tmp/install/new-boot.img of=/dev/block/by-name/boot\n\tapatchNote\n}\n\nfunction main(){\n\ncd /dev/tmp/install\n\nchmod a+x ./lib/arm64-v8a/libkptools.so\n\n\nslot=$(getprop ro.boot.slot_suffix)\n\nif [[ ! \"$slot\" == \"\" ]]; then\n\n\tui_print \"\"\n\tui_print \"- You are using A/B device.\"\n\n\t# Get kernel\n\tui_print \"\"\n\tdd if=/dev/block/by-name/boot$slot of=/dev/tmp/install/boot.img\n\tif [[ \"$?\" == 0 ]]; then\n\t\tui_print \"- Detected boot partition.\"\n\t\tboot_execute_ab\n\tfi\n\nelse\n\n\tui_print \"You are using A Only device.\"\n\n\t# Get kernel\n\tui_print \"\"\n\tdd if=/dev/block/by-name/boot of=/dev/tmp/install/boot.img\n\tif [[ \"$?\" == 0 ]]; then\n\t\tui_print \"- Detected boot partition.\"\n\t\tboot_execute\n\tfi\n\nfi\n\n}\n\nmain"
  },
  {
    "path": "app/src/main/assets/boot_extract.sh",
    "content": "#!/system/bin/sh\n\nARCH=$(getprop ro.product.cpu.abi)\n\nIS_INSTALL_NEXT_SLOT=$1\n\n# Load utility functions\n. ./util_functions.sh\n\nif [ \"$IS_INSTALL_NEXT_SLOT\" = \"true\" ]; then\n  get_next_slot\nelse\n  get_current_slot\nfi\n\nfind_boot_image\n\n[ -e \"$BOOTIMAGE\" ] || { >&2 echo \"- can't find boot.img!\"; exit 1; }\n\ntrue\n"
  },
  {
    "path": "app/src/main/assets/boot_flash.sh",
    "content": "#!/system/bin/sh\n#######################################################################################\n# APatch Boot Image Flasher\n#######################################################################################\n\nARCH=$(getprop ro.product.cpu.abi)\n\n# Load utility functions\n. ./util_functions.sh\n\necho \"****************************\"\necho \" FolkPatch Boot Image Flasher\"\necho \"****************************\"\n\nBOOTIMAGE=$1\nSOURCE_IMAGE=$2\n\n[ -e \"$SOURCE_IMAGE\" ] || { echo \"- $SOURCE_IMAGE does not exist!\"; exit 1; }\n\necho \"- Target image: $BOOTIMAGE\"\necho \"- Source image: $SOURCE_IMAGE\"\n\necho \"- Flashing boot image\"\nflash_image \"$SOURCE_IMAGE\" \"$BOOTIMAGE\"\n\nif [ $? -ne 0 ]; then\n  >&2 echo \"- Flash error: $?\"\n  exit $?\nfi\n\necho \"- Flash successful\"\n"
  },
  {
    "path": "app/src/main/assets/boot_patch.sh",
    "content": "#!/system/bin/sh\n#######################################################################################\n# APatch Boot Image Patcher\n#######################################################################################\n#\n# Usage: boot_patch.sh <superkey> <bootimage> [ARGS_PASS_TO_KPTOOLS]\n#\n# This script should be placed in a directory with the following files:\n#\n# File name          Type          Description\n#\n# boot_patch.sh      script        A script to patch boot image for APatch.\n#                  (this file)      The script will use files in its same\n#                                  directory to complete the patching process.\n# bootimg            binary        The target boot image\n# kpimg              binary        KernelPatch core Image\n# kptools            executable    The KernelPatch tools binary to inject kpimg to kernel Image\n#\n#######################################################################################\n\nARCH=$(getprop ro.product.cpu.abi)\n\n# Load utility functions\n. ./util_functions.sh\n\necho \"****************************\"\necho \" FolkPatch Boot Image Patcher\"\necho \"****************************\"\n\nSUPERKEY=\"$1\"\nBOOTIMAGE=$2\nFLASH_TO_DEVICE=$3\nshift 2\n\n[ -z \"$SUPERKEY\" ] && { >&2 echo \"- SuperKey empty!\"; exit 1; }\n[ -e \"$BOOTIMAGE\" ] || { >&2 echo \"- $BOOTIMAGE does not exist!\"; exit 1; }\n\n# Check for dependencies\n\ncommand -v ./kptools >/dev/null 2>&1 || { >&2 echo \"- Command kptools not found!\"; exit 1; }\n\nif [ ! -f kernel ]; then\necho \"- Unpacking boot image\"\n\nset -x\n./kptools unpack \"$BOOTIMAGE\" \"$@\"\npatch_rc=$?\nset +x\n  if [ $patch_rc -ne 0 ]; then\n    >&2 echo \"- Unpack error: $patch_rc\"\n    exit $?\n  fi\nfi\n\nif [ ! $(./kptools -i kernel -f | grep CONFIG_KALLSYMS=y) ]; then\n\techo \"- Patcher has Aborted!\"\n\techo \"- APatch requires CONFIG_KALLSYMS to be Enabled.\"\n\techo \"- But your kernel seems NOT enabled it.\"\n\texit 0\nfi\n\nif [  $(./kptools -i kernel -l | grep patched=false) ]; then\n\techo \"- Backing boot.img \"\n  cp \"$BOOTIMAGE\" \"ori.img\" >/dev/null 2>&1\nfi\n\nmv kernel kernel.ori\n\necho \"- Patching kernel\"\n\nset -x\n./kptools -p -i kernel.ori -S \"$SUPERKEY\" -k kpimg -o kernel \"$@\"\npatch_rc=$?\nset +x\n\nif [ $patch_rc -ne 0 ]; then\n  >&2 echo \"- Patch kernel error: $patch_rc\"\n  exit $?\nfi\n\necho \"- Repacking boot image\"\n./kptools repack \"$BOOTIMAGE\"\n\nif [ ! $(./kptools -i kernel.ori -f | grep CONFIG_KALLSYMS_ALL=y) ]; then\n\techo \"- Detected CONFIG_KALLSYMS_ALL is not set!\"\n\techo \"- APatch has patched but maybe your device won't boot.\"\n\techo \"- Make sure you have original boot image backup.\"\nfi\n\nif [ $? -ne 0 ]; then\n  >&2 echo \"- Repack error: $?\"\n  exit $?\nfi\n\nif [ \"$FLASH_TO_DEVICE\" = \"true\" ]; then\n  # flash\n  if [ -b \"$BOOTIMAGE\" ] || [ -c \"$BOOTIMAGE\" ] && [ -f \"new-boot.img\" ]; then\n    echo \"- Flashing new boot image\"\n    flash_image new-boot.img \"$BOOTIMAGE\"\n    if [ $? -ne 0 ]; then\n      >&2 echo \"- Flash error: $?\"\n      exit $?\n    fi\n  fi\n\n  echo \"- Successfully Flashed!\"\nelse\n  echo \"- Successfully Patched!\"\nfi\n\n"
  },
  {
    "path": "app/src/main/assets/boot_unpatch.sh",
    "content": "#!/system/bin/sh\n#######################################################################################\n# APatch Boot Image Unpatcher\n#######################################################################################\n\nARCH=$(getprop ro.product.cpu.abi)\n\n# Load utility functions\n. ./util_functions.sh\n\necho \"****************************\"\necho \" FolkPatch Boot Image Unpatcher\"\necho \"****************************\"\n\nBOOTIMAGE=$1\n\n[ -e \"$BOOTIMAGE\" ] || { echo \"- $BOOTIMAGE does not exist!\"; exit 1; }\n\necho \"- Target image: $BOOTIMAGE\"\n\n  # Check for dependencies\n\ncommand -v ./kptools >/dev/null 2>&1 || { echo \"- Command kptools not found!\"; exit 1; }\n\nif [ ! -f kernel ]; then\necho \"- Unpacking boot image\"\n\nset -x\n./kptools unpack \"$BOOTIMAGE\" \"$@\"\npatch_rc=$?\nif [ $patch_rc -ne 0 ]; then\n    >&2 echo \"- Unpack error: $patch_rc\"\n    exit $patch_rc\n  fi\nfi\n\nif [ ! $(./kptools -i kernel -l | grep patched=false) ]; then\n\techo \"- kernel has been patched \"\n  if [ -f \"new-boot.img\" ]; then\n    echo \"- found backup boot.img ,use it for recovery\"\n  else\n    mv kernel kernel.ori\n    echo \"- Unpatching kernel\"\n    ./kptools -u --image kernel.ori --out kernel \"$@\"\n    if [ $? -ne 0 ]; then\n      >&2 echo \"- Unpatch error: $?\"\n      exit $?\n    fi\n    echo \"- Repacking boot image\"\n    ./kptools repack \"$BOOTIMAGE\"\n    if [ $? -ne 0 ]; then\n      >&2 echo \"- Repack error: $?\"\n      exit $?\n    fi\n  fi\n\nelse\n  echo \"- no need unpatch\"\n  exit 0\nfi\n\n\n\nif [ -f \"new-boot.img\" ]; then\n  echo \"- Flashing boot image\"\n  flash_image new-boot.img \"$BOOTIMAGE\"\n\n  if [ $? -ne 0 ]; then\n    >&2 echo \"- Flash error: $?\"\n    exit $?\n  fi\nfi\n\necho \"- Flash successful\"\n\n# Reset any error code\ntrue\n"
  },
  {
    "path": "app/src/main/assets/kpimg.version",
    "content": "0.13.1"
  },
  {
    "path": "app/src/main/assets/util_functions.sh",
    "content": "#!/system/bin/sh\n#######################################################################################\n# Helper Functions (credits to topjohnwu)\n#######################################################################################\nAPATCH_VER='0.10.4'\nAPATCH_VER_CODE=12146\n\nui_print() {\n  if $BOOTMODE; then\n    echo \"$1\"\n  else\n    echo -e \"ui_print $1\\nui_print\" >> /proc/self/fd/$OUTFD\n  fi\n}\n\ntoupper() {\n  echo \"$@\" | tr '[:lower:]' '[:upper:]'\n}\n\ngrep_cmdline() {\n  local REGEX=\"s/^$1=//p\"\n  { echo $(cat /proc/cmdline)$(sed -e 's/[^\"]//g' -e 's/\"\"//g' /proc/cmdline) | xargs -n 1; \\\n    sed -e 's/ = /=/g' -e 's/, /,/g' -e 's/\"//g' /proc/bootconfig; \\\n  } 2>/dev/null | sed -n \"$REGEX\"\n}\n\ngrep_prop() {\n  local REGEX=\"s/^$1=//p\"\n  shift\n  local FILES=$@\n  [ -z \"$FILES\" ] && FILES='/system/build.prop'\n  cat $FILES 2>/dev/null | dos2unix | sed -n \"$REGEX\" | head -n 1\n}\n\ngetvar() {\n  local VARNAME=$1\n  local VALUE\n  local PROPPATH='/data/.magisk /cache/.magisk'\n  [ ! -z $MAGISKTMP ] && PROPPATH=\"$MAGISKTMP/.magisk/config $PROPPATH\"\n  VALUE=$(grep_prop $VARNAME $PROPPATH)\n  [ ! -z $VALUE ] && eval $VARNAME=\\$VALUE\n}\n\nis_mounted() {\n  grep -q \" $(readlink -f $1) \" /proc/mounts 2>/dev/null\n  return $?\n}\nabort() {\n  ui_print \"$1\"\n  $BOOTMODE || recovery_cleanup\n  [ ! -z $MODPATH ] && rm -rf $MODPATH\n  rm -rf $TMPDIR\n  exit 1\n}\nset_nvbase() {\n  NVBASE=\"$1\"\n  MAGISKBIN=\"$1/magisk\"\n}\n\nprint_title() {\n  local len line1len line2len bar\n  line1len=$(echo -n $1 | wc -c)\n  line2len=$(echo -n $2 | wc -c)\n  len=$line2len\n  [ $line1len -gt $line2len ] && len=$line1len\n  len=$((len + 2))\n  bar=$(printf \"%${len}s\" | tr ' ' '*')\n  ui_print \"$bar\"\n  ui_print \" $1 \"\n  [ \"$2\" ] && ui_print \" $2 \"\n  ui_print \"$bar\"\n}\nsetup_flashable() {\n  ensure_bb\n  $BOOTMODE && return\n  if [ -z $OUTFD ] || readlink /proc/$$/fd/$OUTFD | grep -q /tmp; then\n    # We will have to manually find out OUTFD\n    for FD in $(ls /proc/$$/fd); do\n      if readlink /proc/$$/fd/$FD | grep -q pipe; then\n        if ps | grep -v grep | grep -qE \" 3 $FD |status_fd=$FD\"; then\n          OUTFD=$FD\n          break\n        fi\n      fi\n    done\n  fi\n  recovery_actions\n}\n\nensure_bb() {\n  if set -o | grep -q standalone; then\n    # We are definitely in busybox ash\n    set -o standalone\n    return\n  fi\n\n  # Find our busybox binary\n  local bb\n  if [ -f $TMPDIR/busybox ]; then\n    bb=$TMPDIR/busybox\n  elif [ -f $MAGISKBIN/busybox ]; then\n    bb=$MAGISKBIN/busybox\n  else\n    abort \"! Cannot find BusyBox\"\n  fi\n  chmod 755 $bb\n\n  # Busybox could be a script, make sure /system/bin/sh exists\n  if [ ! -f /system/bin/sh ]; then\n    umount -l /system 2>/dev/null\n    mkdir -p /system/bin\n    ln -s $(command -v sh) /system/bin/sh\n  fi\n\n  export ASH_STANDALONE=1\n\n  # Find our current arguments\n  # Run in busybox environment to ensure consistent results\n  # /proc/<pid>/cmdline shall be <interpreter> <script> <arguments...>\n  local cmds=\"$($bb sh -c \"\n  for arg in \\$(tr '\\0' '\\n' < /proc/$$/cmdline); do\n    if [ -z \\\"\\$cmds\\\" ]; then\n      # Skip the first argument as we want to change the interpreter\n      cmds=\\\"sh\\\"\n    else\n      cmds=\\\"\\$cmds '\\$arg'\\\"\n    fi\n  done\n  echo \\$cmds\")\"\n\n  # Re-exec our script\n  echo $cmds | $bb xargs $bb\n  exit\n}\nrecovery_actions() {\n  # Make sure random won't get blocked\n  mount -o bind /dev/urandom /dev/random\n  # Unset library paths\n  OLD_LD_LIB=$LD_LIBRARY_PATH\n  OLD_LD_PRE=$LD_PRELOAD\n  OLD_LD_CFG=$LD_CONFIG_FILE\n  unset LD_LIBRARY_PATH\n  unset LD_PRELOAD\n  unset LD_CONFIG_FILE\n}\nrecovery_cleanup() {\n  local DIR\n  ui_print \"- Unmounting partitions\"\n  (\n  if [ ! -d /postinstall/tmp ]; then\n    umount -l /system\n    umount -l /system_root\n  fi\n  umount -l /vendor\n  umount -l /persist\n  umount -l /metadata\n  for DIR in /apex /system /system_root; do\n    if [ -L \"${DIR}_link\" ]; then\n      rmdir $DIR\n      mv -f ${DIR}_link $DIR\n    fi\n  done\n  umount -l /dev/random\n  ) 2>/dev/null\n  [ -z $OLD_LD_LIB ] || export LD_LIBRARY_PATH=$OLD_LD_LIB\n  [ -z $OLD_LD_PRE ] || export LD_PRELOAD=$OLD_LD_PRE\n  [ -z $OLD_LD_CFG ] || export LD_CONFIG_FILE=$OLD_LD_CFG\n}\n\nfind_block() {\n  local BLOCK DEV DEVICE DEVNAME PARTNAME UEVENT\n  for BLOCK in \"$@\"; do\n    DEVICE=$(find /dev/block \\( -type b -o -type c -o -type l \\) -iname $BLOCK | head -n 1) 2>/dev/null\n    if [ ! -z $DEVICE ]; then\n      readlink -f $DEVICE\n      return 0\n    fi\n  done\n  # Fallback by parsing sysfs uevents\n  for UEVENT in /sys/dev/block/*/uevent; do\n    DEVNAME=$(grep_prop DEVNAME $UEVENT)\n    PARTNAME=$(grep_prop PARTNAME $UEVENT)\n    for BLOCK in \"$@\"; do\n      if [ \"$(toupper $BLOCK)\" = \"$(toupper $PARTNAME)\" ]; then\n        echo /dev/block/$DEVNAME\n        return 0\n      fi\n    done\n  done\n  # Look just in /dev in case we're dealing with MTD/NAND without /dev/block devices/links\n  for DEV in \"$@\"; do\n    DEVICE=$(find /dev \\( -type b -o -type c -o -type l \\) -maxdepth 1 -iname $DEV | head -n 1) 2>/dev/null\n    if [ ! -z $DEVICE ]; then\n      readlink -f $DEVICE\n      return 0\n    fi\n  done\n  return 1\n}\n\n# After calling this method, the following variables will be set:\n# SLOT\nget_current_slot() {\n  # Check A/B slot\n  SLOT=$(grep_cmdline androidboot.slot_suffix)\n  if [ -z $SLOT ]; then\n    SLOT=$(grep_cmdline androidboot.slot)\n    [ -z $SLOT ] || SLOT=_${SLOT}\n  fi\n  if [ -z $SLOT ]; then\n    SLOT=$(getprop ro.boot.slot_suffix)\n  fi\n  [ \"$SLOT\" = \"normal\" ] && unset SLOT\n  [ -z $SLOT ] || echo \"SLOT=$SLOT\"\n}\n\n# After calling this method, the following variables will be set:\n# SLOT\n# This is used after OTA\nget_next_slot() {\n  # Check A/B slot\n  SLOT=$(grep_cmdline androidboot.slot_suffix)\n  if [ -z $SLOT ]; then\n    SLOT=$(grep_cmdline androidboot.slot)\n    [ -z $SLOT ] || SLOT=_${SLOT}\n  fi\n  if [ -z $SLOT ]; then\n    SLOT=$(getprop ro.boot.slot_suffix)\n  fi\n   [ -z $SLOT ] && { >&2 echo \"can't determined next boot slot! check your devices is A/B\"; exit 1; }\n   [ \"$SLOT\" = \"normal\" ] &&  { >&2 echo \"can't determined next boot slot! check your devices is A/B\"; exit 1; }\n  if [[ $SLOT == *_a ]]; then\n    SLOT='_b'\n  else\n    SLOT='_a'\n  fi\n  echo \"SLOT=$SLOT\"\n}\n\nfind_boot_image() {\n  if [ ! -z $SLOT ]; then\n    BOOTIMAGE=$(find_block \"boot$SLOT\")\n  fi\n  if [ -z $BOOTIMAGE ]; then\n    BOOTIMAGE=$(find_block kern-a android_boot kernel bootimg boot lnx boot_a)\n  fi\n  if [ -z $BOOTIMAGE ]; then\n    # Lets see what fstabs tells me\n    BOOTIMAGE=$(grep -v '#' /etc/*fstab* | grep -E '/boot(img)?[^a-zA-Z]' | grep -oE '/dev/[a-zA-Z0-9_./-]*' | head -n 1)\n  fi\n  [ -z $BOOTIMAGE ] || echo \"BOOTIMAGE=$BOOTIMAGE\"\n}\n\nflash_image() {\n  local CMD1\n  case \"$1\" in\n    *.gz) CMD1=\"gzip -d < '$1' 2>/dev/null\";;\n    *)    CMD1=\"cat '$1'\";;\n  esac\n  if [ -b \"$2\" ]; then {\n      local img_sz=$(stat -c '%s' \"$1\")\n      local blk_sz=$(blockdev --getsize64 \"$2\")\n      local blk_bs=$(blockdev --getbsz \"$2\")\n      [ \"$img_sz\" -gt \"$blk_sz\" ] && return 1\n      blockdev --setrw \"$2\"\n      local blk_ro=$(blockdev --getro \"$2\")\n      [ \"$blk_ro\" -eq 1 ] && return 2\n      eval \"$CMD1\" | dd of=\"$2\" bs=\"$blk_bs\" iflag=fullblock conv=notrunc,fsync 2>/dev/null\n      sync\n  } elif [ -c \"$2\" ]; then {\n      flash_eraseall \"$2\" >&2\n      eval \"$CMD1\" | nandwrite -p \"$2\" - >&2\n  } else {\n      echo \"- Not block or char device, storing image\"\n      eval \"$CMD1\" > \"$2\" 2>/dev/null\n  } fi\n  return 0\n}\n\nsetup_mntpoint() {\n  local POINT=$1\n  [ -L $POINT ] && mv -f $POINT ${POINT}_link\n  if [ ! -d $POINT ]; then\n    rm -f $POINT\n    mkdir -p $POINT\n  fi\n}\n\nmount_name() {\n  local PART=$1\n  local POINT=$2\n  local FLAG=$3\n  setup_mntpoint $POINT\n  is_mounted $POINT && return\n  # First try mounting with fstab\n  mount $FLAG $POINT 2>/dev/null\n  if ! is_mounted $POINT; then\n    local BLOCK=$(find_block $PART)\n    mount $FLAG $BLOCK $POINT || return\n  fi\n  ui_print \"- Mounting $POINT\"\n}\n\nmount_ro_ensure() {\n  # We handle ro partitions only in recovery\n  $BOOTMODE && return\n  local PART=$1\n  local POINT=$2\n  mount_name \"$PART\" $POINT '-o ro'\n  is_mounted $POINT || abort \"! Cannot mount $POINT\"\n}\n\n# After calling this method, the following variables will be set:\n# SLOT, SYSTEM_AS_ROOT, LEGACYSAR\nmount_partitions() {\n  # Check A/B slot\n  SLOT=$(grep_cmdline androidboot.slot_suffix)\n  if [ -z $SLOT ]; then\n    SLOT=$(grep_cmdline androidboot.slot)\n    [ -z $SLOT ] || SLOT=_${SLOT}\n  fi\n  [ \"$SLOT\" = \"normal\" ] && unset SLOT\n  [ -z $SLOT ] || ui_print \"- Current boot slot: $SLOT\"\n\n  # Mount ro partitions\n  if is_mounted /system_root; then\n    umount /system 2>/dev/null\n    umount /system_root 2>/dev/null\n  fi\n  mount_ro_ensure \"system$SLOT app$SLOT\" /system\n  if [ -f /system/init -o -L /system/init ]; then\n    SYSTEM_AS_ROOT=true\n    setup_mntpoint /system_root\n    if ! mount --move /system /system_root; then\n      umount /system\n      umount -l /system 2>/dev/null\n      mount_ro_ensure \"system$SLOT app$SLOT\" /system_root\n    fi\n    mount -o bind /system_root/system /system\n  else\n    if grep ' / ' /proc/mounts | grep -qv 'rootfs' || grep -q ' /system_root ' /proc/mounts; then\n      SYSTEM_AS_ROOT=true\n    else\n      SYSTEM_AS_ROOT=false\n    fi\n  fi\n  $SYSTEM_AS_ROOT && ui_print \"- Device is system-as-root\"\n\n  LEGACYSAR=false\n  if $BOOTMODE; then\n    grep ' / ' /proc/mounts | grep -q '/dev/root' && LEGACYSAR=true\n  else\n    # Recovery mode, assume devices that don't use dynamic partitions are legacy SAR\n    local IS_DYNAMIC=false\n    if grep -q 'androidboot.super_partition' /proc/cmdline; then\n      IS_DYNAMIC=true\n    elif [ -n \"$(find_block super)\" ]; then\n      IS_DYNAMIC=true\n    fi\n    if $SYSTEM_AS_ROOT && ! $IS_DYNAMIC; then\n      LEGACYSAR=true\n      ui_print \"- Legacy SAR, force kernel to load rootfs\"\n    fi\n  fi\n}\n\nget_flags() {\n  if grep ' /data ' /proc/mounts | grep -q 'dm-'; then\n    ISENCRYPTED=true\n  elif [ \"$(getprop ro.crypto.state)\" = \"encrypted\" ]; then\n    ISENCRYPTED=true\n  elif [ \"$DATA\" = \"false\" ]; then\n    # No data access means unable to decrypt in recovery\n    ISENCRYPTED=true\n  else\n    ISENCRYPTED=false\n  fi\n  if [ -n \"$(find_block vbmeta vbmeta_a)\" ]; then\n    PATCHVBMETAFLAG=false\n  else\n    PATCHVBMETAFLAG=true\n    ui_print \"- No vbmeta partition, patch vbmeta in boot image\"\n  fi\n\n  # Overridable config flags with safe defaults\n  getvar KEEPVERITY\n  getvar KEEPFORCEENCRYPT\n  getvar RECOVERYMODE\n  if [ -z $KEEPVERITY ]; then\n    if $SYSTEM_AS_ROOT; then\n      KEEPVERITY=true\n      ui_print \"- System-as-root, keep dm-verity\"\n    else\n      KEEPVERITY=false\n    fi\n  fi\n  if [ -z $KEEPFORCEENCRYPT ]; then\n    if $ISENCRYPTED; then\n      KEEPFORCEENCRYPT=true\n      ui_print \"- Encrypted data, keep forceencrypt\"\n    else\n      KEEPFORCEENCRYPT=false\n    fi\n  fi\n  [ -z $RECOVERYMODE ] && RECOVERYMODE=false\n}\n\ninstall_apatch() {\n  cd $MAGISKBIN\n\n  # Source the boot patcher\n  SOURCEDMODE=true\n  . ./boot_patch.sh \"$BOOTIMAGE\"\n  ui_print \"- Flashing new boot image\"\n  flash_image new-boot.img \"$BOOTIMAGE\"\n  case $? in\n    1)\n      abort \"! Insufficient partition size\"\n      ;;\n    2)\n      abort \"! $BOOTIMAGE is read only\"\n      ;;\n  esac\n  \n  rm -f new-boot.img\n\n  run_migrations\n}\n\ncheck_data() {\n  DATA=false\n  DATA_DE=false\n  if grep ' /data ' /proc/mounts | grep -vq 'tmpfs'; then\n    # Test if data is writable\n    touch /data/.rw && rm /data/.rw && DATA=true\n    # Test if data is decrypted\n    $DATA && [ -d /data/adb ] && touch /data/adb/.rw && rm /data/adb/.rw && DATA_DE=true\n    $DATA_DE && [ -d /data/adb/magisk ] || mkdir /data/adb/magisk || DATA_DE=false\n  fi\n  set_nvbase \"/data\"\n  $DATA || set_nvbase \"/cache/data_adb\"\n  $DATA_DE && set_nvbase \"/data/adb\"\n}\n\napi_level_arch_detect() {\n  API=$(grep_get_prop ro.build.version.sdk)\n  ABI=$(grep_get_prop ro.product.cpu.abi)\n  if [ \"$ABI\" = \"x86\" ]; then\n    ARCH=x86\n    ABI32=x86\n    IS64BIT=false\n  elif [ \"$ABI\" = \"arm64-v8a\" ]; then\n    ARCH=arm64\n    ABI32=armeabi-v7a\n    IS64BIT=true\n  elif [ \"$ABI\" = \"x86_64\" ]; then\n    ARCH=x64\n    ABI32=x86\n    IS64BIT=true\n  else\n    ARCH=arm\n    ABI=armeabi-v7a\n    ABI32=armeabi-v7a\n    IS64BIT=false\n  fi\n}\n\nremove_system_su() {\n  [ -d /postinstall/tmp ] && POSTINST=/postinstall\n  cd $POSTINST/system\n  if [ -f bin/su -o -f xbin/su ] && [ ! -f /su/bin/su ]; then\n    ui_print \"- Removing system installed root\"\n    blockdev --setrw /dev/block/mapper/system$SLOT 2>/dev/null\n    mount -o rw,remount $POSTINST/system\n    # SuperSU\n    cd bin\n    if [ -e .ext/.su ]; then\n      mv -f app_process32_original app_process32 2>/dev/null\n      mv -f app_process64_original app_process64 2>/dev/null\n      mv -f install-recovery_original.sh install-recovery.sh 2>/dev/null\n      if [ -e app_process64 ]; then\n        ln -sf app_process64 app_process\n      elif [ -e app_process32 ]; then\n        ln -sf app_process32 app_process\n      fi\n    fi\n    # More SuperSU, SuperUser & ROM su\n    cd ..\n    rm -rf .pin bin/.ext etc/.installed_su_daemon etc/.has_su_daemon \\\n    xbin/daemonsu xbin/su xbin/sugote xbin/sugote-mksh xbin/supolicy \\\n    bin/app_process_init bin/su /cache/su lib/libsupol.so lib64/libsupol.so \\\n    su.d etc/init.d/99SuperSUDaemon etc/install-recovery.sh /cache/install-recovery.sh \\\n    .supersu /cache/.supersu /data/.supersu \\\n    app/Superuser.apk app/SuperSU /cache/Superuser.apk\n  elif [ -f /cache/su.img -o -f /data/su.img -o -d /data/su -o -d /data/adb/su ]; then\n    ui_print \"- Removing systemless installed root\"\n    umount -l /su 2>/dev/null\n    rm -rf /cache/su.img /data/su.img /data/su /data/adb/su /data/adb/suhide \\\n    /cache/.supersu /data/.supersu /cache/supersu_install /data/supersu_install\n  fi\n  cd $TMPDIR\n}\n\nrun_migrations() {\n  local LOCSHA1\n  local TARGET\n  # Legacy app installation\n  local BACKUP=$MAGISKBIN/stock_boot*.gz\n  if [ -f $BACKUP ]; then\n    cp $BACKUP /data\n    rm -f $BACKUP\n  fi\n\n  # Legacy backup\n  for gz in /data/stock_boot*.gz; do\n    [ -f $gz ] || break\n    LOCSHA1=$(basename $gz | sed -e 's/stock_boot_//' -e 's/.img.gz//')\n    [ -z $LOCSHA1 ] && break\n    mkdir /data/magisk_backup_${LOCSHA1} 2>/dev/null\n    mv $gz /data/magisk_backup_${LOCSHA1}/boot.img.gz\n  done\n\n  # Stock backups\n  LOCSHA1=$SHA1\n  for name in boot dtb dtbo dtbs; do\n    BACKUP=$MAGISKBIN/stock_${name}.img\n    [ -f $BACKUP ] || continue\n    if [ $name = 'boot' ]; then\n      LOCSHA1=$($MAGISKBIN/kptools sha1 $BACKUP)\n      mkdir /data/magisk_backup_${LOCSHA1} 2>/dev/null\n    fi\n    TARGET=/data/magisk_backup_${LOCSHA1}/${name}.img\n    cp $BACKUP $TARGET\n    rm -f $BACKUP\n    gzip -9f $TARGET\n  done\n}"
  },
  {
    "path": "app/src/main/cpp/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.28.0)\nproject(\"apjni\")\n\nfind_program(CCACHE ccache)\n\nif (CCACHE)\n        set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE})\n        set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE})\nendif ()\n\nfind_package(cxx REQUIRED CONFIG)\nlink_libraries(cxx::cxx)\n\nmacro(SET_OPTION option value)\n        set(${option} ${value} CACHE INTERNAL \"\" FORCE)\nendmacro()\n\nset(CHERISH_POLLY_FLAGS \"-mllvm -polly -mllvm -polly-run-dce -mllvm -polly-run-inliner -mllvm -polly-reschedule=1 -mllvm -polly-loopfusion-greedy=1 -mllvm -polly-postopts=1 -mllvm -polly-num-threads=0 -mllvm -polly-omp-backend=LLVM -mllvm -polly-scheduling=dynamic -mllvm -polly-scheduling-chunksize=1 -mllvm -polly-isl-arg=--no-schedule-serialize-sccs -mllvm -polly-ast-use-context -mllvm -polly-detect-keep-going -mllvm -polly-position=before-vectorizer -mllvm -polly-vectorizer=stripmine -mllvm -polly-detect-profitability-min-per-loop-insts=40 -mllvm -polly-invariant-load-hoisting\")\nset(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} ${CHERISH_POLLY_FLAGS} -ffunction-sections -fdata-sections -fvisibility=hidden -fvisibility-inlines-hidden -O3 -flto -Wno-vla-cxx-extension -finput-charset=UTF-8 -fexec-charset=UTF-8\")\nset(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} ${CHERISH_POLLY_FLAGS} -ffunction-sections -fdata-sections -fvisibility=hidden -fvisibility-inlines-hidden -fno-rtti -fno-exceptions -O3 -flto -Wno-vla-cxx-extension -finput-charset=UTF-8 -fexec-charset=UTF-8\")\nset(CMAKE_CXX_STANDARD 23)\n\nadd_library(${PROJECT_NAME} SHARED apjni.cpp security.cpp)\nadd_custom_command(TARGET ${PROJECT_NAME} POST_BUILD\n        COMMAND ${CMAKE_STRIP} --strip-all --remove-section=.note.gnu.build-id --remove-section=.note.android.ident $<TARGET_FILE:${PROJECT_NAME}>)\n\ntarget_link_libraries(${PROJECT_NAME} PRIVATE log)\ntarget_compile_options(${PROJECT_NAME} PRIVATE -flto)\ntarget_link_options(${PROJECT_NAME} PRIVATE \"-Wl,--build-id=none\" \"-Wl,-icf=all,--lto-O3\" \"-Wl,-s,-x,--gc-sections\" \"-Wl,--no-undefined\")\n"
  },
  {
    "path": "app/src/main/cpp/apjni.cpp",
    "content": "/* SPDX-License-Identifier: GPL-2.0-or-later */\n/* \n * Copyright (C) 2023 bmax121. All Rights Reserved.\n * Copyright (C) 2024 GarfieldHan. All Rights Reserved.\n * Copyright (C) 2024 1f2003d5. All Rights Reserved.\n */\n\n#include <cstring>\n#include <cstdio>\n#include <string>\n#include <vector>\n\n#include \"apjni.hpp\"\n#include \"supercall.h\"\n#include \"security.hpp\"\n\njboolean nativeReady(JNIEnv *env, jobject /* this */, jstring super_key_jstr) {\n    ensureSuperKeyNonNull(super_key_jstr);\n\n    const auto super_key = JUTFString(env, super_key_jstr);\n    if (super_key.get() == nullptr || strlen(super_key.get()) == 0) {\n        return sc_ready(\"su\");\n    }\n    return sc_ready(super_key.get());\n}\n\njlong nativeKernelPatchVersion(JNIEnv *env, jobject /* this */, jstring super_key_jstr) {\n    ensureSuperKeyNonNull(super_key_jstr);\n\n    const auto super_key = JUTFString(env, super_key_jstr);\n\n    return sc_kp_ver(super_key.get());\n}\n\njstring nativeKernelPatchBuildTime(JNIEnv *env, jobject /* this */, jstring super_key_jstr) {\n    ensureSuperKeyNonNull(super_key_jstr);\n\n    const auto super_key = JUTFString(env, super_key_jstr);\n    char buf[4096] = { '\\0' };\n\n    sc_get_build_time(super_key.get(), buf, sizeof(buf));\n    return env->NewStringUTF(buf);\n}\n\njlong nativeSu(JNIEnv *env, jobject /* this */, jstring super_key_jstr, jint to_uid, jstring selinux_context_jstr) {\n    ensureSuperKeyNonNull(super_key_jstr);\n\n    const auto super_key = JUTFString(env, super_key_jstr);\n    const char *selinux_context = nullptr;\n    if (selinux_context_jstr) selinux_context = JUTFString(env, selinux_context_jstr);\n    struct su_profile profile{};\n    profile.uid = getuid();\n    profile.to_uid = (uid_t)to_uid;\n    if (selinux_context) strncpy(profile.scontext, selinux_context, sizeof(profile.scontext) - 1);\n    long rc = sc_su(super_key.get(), &profile);\n    if (rc < 0) [[unlikely]] {\n        LOGE(\"nativeSu error: %ld\", rc);\n    }\n\n    return rc;\n}\n\njint nativeSetUidExclude(JNIEnv *env, jobject /* this */, jstring super_key_jstr, jint uid, jint exclude) {\n    ensureSuperKeyNonNull(super_key_jstr);\n\n    const auto super_key = JUTFString(env, super_key_jstr);\n    return static_cast<int>(sc_set_ap_mod_exclude(super_key.get(), (uid_t) uid, exclude));\n}\n\njint nativeGetUidExclude(JNIEnv *env, jobject /* this */, jstring super_key_jstr, uid_t uid) {\n    ensureSuperKeyNonNull(super_key_jstr);\n\n    const auto super_key = JUTFString(env, super_key_jstr);\n    return static_cast<int>(sc_get_ap_mod_exclude(super_key.get(), uid));\n}\n\njlong nativeSetNewAppProfileMode(JNIEnv *env, jobject /* this */, jstring super_key_jstr, jint mode) {\n    ensureSuperKeyNonNull(super_key_jstr);\n\n    const auto super_key = JUTFString(env, super_key_jstr);\n    const int value = static_cast<int>(mode);\n    if (value != 0) {\n        return sc_kstorage_write(super_key.get(), KSTORAGE_UNUSED_GROUP_3, 0, const_cast<int *>(&value), 0, sizeof(value));\n    }\n    return sc_kstorage_remove(super_key.get(), KSTORAGE_UNUSED_GROUP_3, 0);\n}\n\njint nativeGetNewAppProfileMode(JNIEnv *env, jobject /* this */, jstring super_key_jstr) {\n    ensureSuperKeyNonNull(super_key_jstr);\n\n    const auto super_key = JUTFString(env, super_key_jstr);\n    int value = 0;\n    const long rc = sc_kstorage_read(super_key.get(), KSTORAGE_UNUSED_GROUP_3, 0, &value, 0, sizeof(value));\n    if (rc < 0) {\n        return 0;\n    }\n    return value;\n}\n\njintArray nativeSuUids(JNIEnv *env, jobject /* this */, jstring super_key_jstr) {\n    ensureSuperKeyNonNull(super_key_jstr);\n\n    const auto super_key = JUTFString(env, super_key_jstr);\n    int num = static_cast<int>(sc_su_uid_nums(super_key.get()));\n\n    if (num <= 0) [[unlikely]] {\n        LOGW(\"SuperUser Count less than 1, skip allocating vector...\");\n        return env->NewIntArray(0);\n    }\n\n    std::vector<int> uids(num);\n\n    long n = sc_su_allow_uids(super_key.get(), (uid_t *) uids.data(), num);\n    if (n > 0) [[unlikely]] {\n        auto array = env->NewIntArray(n);\n        env->SetIntArrayRegion(array, 0, n, uids.data());\n        return array;\n    }\n\n    return env->NewIntArray(0);\n}\n\njobject nativeSuProfile(JNIEnv *env, jobject /* this */, jstring super_key_jstr, jint uid) {\n    ensureSuperKeyNonNull(super_key_jstr);\n\n    const auto super_key = JUTFString(env, super_key_jstr);\n    struct su_profile profile{};\n    long rc = sc_su_uid_profile(super_key.get(), (uid_t) uid, &profile);\n    if (rc < 0) [[unlikely]] {\n        LOGE(\"nativeSuProfile error: %ld\\n\", rc);\n        return nullptr;\n    }\n    jclass cls = env->FindClass(\"me/bmax/apatch/Natives$Profile\");\n    jmethodID constructor = env->GetMethodID(cls, \"<init>\", \"()V\");\n    jfieldID uidField = env->GetFieldID(cls, \"uid\", \"I\");\n    jfieldID toUidField = env->GetFieldID(cls, \"toUid\", \"I\");\n    jfieldID scontextFild = env->GetFieldID(cls, \"scontext\", \"Ljava/lang/String;\");\n\n    jobject obj = env->NewObject(cls, constructor);\n    env->SetIntField(obj, uidField, (int) profile.uid);\n    env->SetIntField(obj, toUidField, (int) profile.to_uid);\n    env->SetObjectField(obj, scontextFild, env->NewStringUTF(profile.scontext));\n\n    return obj;\n}\n\njlong nativeLoadKernelPatchModule(JNIEnv *env, jobject /* this */, jstring super_key_jstr, jstring module_path_jstr, jstring args_jstr) {\n    ensureSuperKeyNonNull(super_key_jstr);\n\n    const auto super_key = JUTFString(env, super_key_jstr);\n    const auto module_path = JUTFString(env, module_path_jstr);\n    const auto args = JUTFString(env, args_jstr);\n    long rc = sc_kpm_load(super_key.get(), module_path.get(), args.get(), nullptr);\n    if (rc < 0) [[unlikely]] {\n        LOGE(\"nativeLoadKernelPatchModule error: %ld\", rc);\n    }\n\n    return rc;\n}\n\njobject nativeControlKernelPatchModule(JNIEnv *env, jobject /* this */, jstring super_key_jstr, jstring module_name_jstr, jstring control_args_jstr) {\n    ensureSuperKeyNonNull(super_key_jstr);\n\n    const auto super_key = JUTFString(env, super_key_jstr);\n    const auto module_name = JUTFString(env, module_name_jstr);\n    const auto control_args = JUTFString(env, control_args_jstr);\n\n    char buf[4096] = { '\\0' };\n    long rc = sc_kpm_control(super_key.get(), module_name.get(), control_args.get(), buf, sizeof(buf));\n    if (rc < 0) [[unlikely]] {\n        LOGE(\"nativeControlKernelPatchModule error: %ld\", rc);\n    }\n\n    jclass cls = env->FindClass(\"me/bmax/apatch/Natives$KPMCtlRes\");\n    jmethodID constructor = env->GetMethodID(cls, \"<init>\", \"()V\");\n    jfieldID rcField = env->GetFieldID(cls, \"rc\", \"J\");\n    jfieldID outMsg = env->GetFieldID(cls, \"outMsg\", \"Ljava/lang/String;\");\n\n    jobject obj = env->NewObject(cls, constructor);\n    env->SetLongField(obj, rcField, rc);\n    env->SetObjectField(obj, outMsg, env->NewStringUTF(buf));\n\n    return obj;\n}\n\njlong nativeUnloadKernelPatchModule(JNIEnv *env, jobject /* this */, jstring super_key_jstr, jstring module_name_jstr) {\n    ensureSuperKeyNonNull(super_key_jstr);\n\n    const auto super_key = JUTFString(env, super_key_jstr);\n    const auto module_name = JUTFString(env, module_name_jstr);\n    long rc = sc_kpm_unload(super_key.get(), module_name.get(), nullptr);\n    if (rc < 0) [[unlikely]] {\n        LOGE(\"nativeUnloadKernelPatchModule error: %ld\", rc);\n    }\n\n    return rc;\n}\n\njlong nativeKernelPatchModuleNum(JNIEnv *env, jobject /* this */, jstring super_key_jstr) {\n    ensureSuperKeyNonNull(super_key_jstr);\n\n    const auto super_key = JUTFString(env, super_key_jstr);\n    long rc = sc_kpm_nums(super_key.get());\n    if (rc < 0) [[unlikely]] {\n        LOGE(\"nativeKernelPatchModuleNum error: %ld\", rc);\n    }\n\n    return rc;\n}\n\njstring nativeKernelPatchModuleList(JNIEnv *env, jobject /* this */, jstring super_key_jstr) {\n    ensureSuperKeyNonNull(super_key_jstr);\n\n    const auto super_key = JUTFString(env, super_key_jstr);\n\n    char buf[4096] = { '\\0' };\n    long rc = sc_kpm_list(super_key.get(), buf, sizeof(buf));\n    if (rc < 0) [[unlikely]] {\n        LOGE(\"nativeKernelPatchModuleList error: %ld\", rc);\n    }\n\n    return env->NewStringUTF(buf);\n}\n\njstring nativeKernelPatchModuleInfo(JNIEnv *env, jobject /* this */, jstring super_key_jstr, jstring module_name_jstr) {\n    ensureSuperKeyNonNull(super_key_jstr);\n\n    const auto super_key = JUTFString(env, super_key_jstr);\n    const auto module_name = JUTFString(env, module_name_jstr);\n    char buf[1024] = { '\\0' };\n    long rc = sc_kpm_info(super_key.get(), module_name.get(), buf, sizeof(buf));\n    if (rc < 0) [[unlikely]] {\n        LOGE(\"nativeKernelPatchModuleInfo error: %ld\", rc);\n    }\n\n    return env->NewStringUTF(buf);\n}\n\njlong nativeGrantSu(JNIEnv *env, jobject /* this */, jstring super_key_jstr, jint uid, jint to_uid, jstring selinux_context_jstr) {\n    ensureSuperKeyNonNull(super_key_jstr);\n\n    const auto super_key = JUTFString(env, super_key_jstr);\n    const auto selinux_context = JUTFString(env, selinux_context_jstr);\n    struct su_profile profile{};\n    profile.uid = uid;\n    profile.to_uid = to_uid;\n    if (selinux_context) strncpy(profile.scontext, selinux_context, sizeof(profile.scontext) - 1);\n    return sc_su_grant_uid(super_key.get(), &profile);\n}\n\njlong nativeRevokeSu(JNIEnv *env, jobject /* this */, jstring super_key_jstr, jint uid) {\n    ensureSuperKeyNonNull(super_key_jstr);\n\n    const auto super_key = JUTFString(env, super_key_jstr);\n    return sc_su_revoke_uid(super_key.get(), (uid_t) uid);\n}\n\njstring nativeSuPath(JNIEnv *env, jobject /* this */, jstring super_key_jstr) {\n    ensureSuperKeyNonNull(super_key_jstr);\n\n    const auto super_key = JUTFString(env, super_key_jstr);\n    char buf[SU_PATH_MAX_LEN] = { '\\0' };\n    long rc = sc_su_get_path(super_key.get(), buf, sizeof(buf));\n    if (rc < 0) [[unlikely]] {\n        LOGE(\"nativeSuPath error: %ld\", rc);\n    }\n\n    return env->NewStringUTF(buf);\n}\n\njboolean nativeResetSuPath(JNIEnv *env, jobject /* this */, jstring super_key_jstr, jstring su_path_jstr) {\n    ensureSuperKeyNonNull(super_key_jstr);\n\n    const auto super_key = JUTFString(env, super_key_jstr);\n    const auto su_path = JUTFString(env, su_path_jstr);\n\n    return sc_su_reset_path(super_key.get(), su_path.get()) == 0;\n}\n\njlong nativeUtsSet(JNIEnv *env, jobject /* this */, jstring super_key_jstr,\n                   jstring release_jstr, jstring version_jstr) {\n    ensureSuperKeyNonNull(super_key_jstr);\n\n    const auto super_key = JUTFString(env, super_key_jstr);\n    const auto release_str = JUTFString(env, release_jstr);\n    const auto version_str = JUTFString(env, version_jstr);\n\n    long rc = sc_uts_set(super_key.get(), release_str.get(), version_str.get());\n    if (rc < 0) [[unlikely]] {\n        LOGE(\"nativeUtsSet error: %ld\", rc);\n    }\n    return rc;\n}\n\njlong nativeUtsReset(JNIEnv *env, jobject /* this */, jstring super_key_jstr) {\n    ensureSuperKeyNonNull(super_key_jstr);\n\n    const auto super_key = JUTFString(env, super_key_jstr);\n    long rc = sc_uts_reset(super_key.get());\n    if (rc < 0) [[unlikely]] {\n        LOGE(\"nativeUtsReset error: %ld\", rc);\n    }\n    return rc;\n}\n\njlong nativePathHideAdd(JNIEnv *env, jobject /* this */, jstring super_key_jstr, jstring path_jstr) {\n    ensureSuperKeyNonNull(super_key_jstr);\n\n    const auto super_key = JUTFString(env, super_key_jstr);\n    const auto path = JUTFString(env, path_jstr);\n    long rc = sc_pathhide_add(super_key.get(), path.get());\n    if (rc < 0) [[unlikely]] {\n        LOGE(\"nativePathHideAdd error: %ld\", rc);\n    }\n    return rc;\n}\n\njlong nativePathHideRemove(JNIEnv *env, jobject /* this */, jstring super_key_jstr, jstring path_jstr) {\n    ensureSuperKeyNonNull(super_key_jstr);\n\n    const auto super_key = JUTFString(env, super_key_jstr);\n    const auto path = JUTFString(env, path_jstr);\n    long rc = sc_pathhide_remove(super_key.get(), path.get());\n    if (rc < 0) [[unlikely]] {\n        LOGE(\"nativePathHideRemove error: %ld\", rc);\n    }\n    return rc;\n}\n\njstring nativePathHideList(JNIEnv *env, jobject /* this */, jstring super_key_jstr) {\n    ensureSuperKeyNonNull(super_key_jstr);\n\n    const auto super_key = JUTFString(env, super_key_jstr);\n    char buf[4096] = { '\\0' };\n    long rc = sc_pathhide_list(super_key.get(), buf, sizeof(buf));\n    if (rc < 0) [[unlikely]] {\n        LOGE(\"nativePathHideList error: %ld\", rc);\n    }\n    return env->NewStringUTF(buf);\n}\n\njlong nativePathHideClear(JNIEnv *env, jobject /* this */, jstring super_key_jstr) {\n    ensureSuperKeyNonNull(super_key_jstr);\n\n    const auto super_key = JUTFString(env, super_key_jstr);\n    long rc = sc_pathhide_clear(super_key.get());\n    if (rc < 0) [[unlikely]] {\n        LOGE(\"nativePathHideClear error: %ld\", rc);\n    }\n    return rc;\n}\n\njlong nativePathHideEnable(JNIEnv *env, jobject /* this */, jstring super_key_jstr, jint enable) {\n    ensureSuperKeyNonNull(super_key_jstr);\n\n    const auto super_key = JUTFString(env, super_key_jstr);\n    long rc = sc_pathhide_enable(super_key.get(), enable);\n    if (rc < 0) [[unlikely]] {\n        LOGE(\"nativePathHideEnable error: %ld\", rc);\n    }\n    return rc;\n}\n\njlong nativePathHideStatus(JNIEnv *env, jobject /* this */, jstring super_key_jstr) {\n    ensureSuperKeyNonNull(super_key_jstr);\n\n    const auto super_key = JUTFString(env, super_key_jstr);\n    long rc = sc_pathhide_status(super_key.get());\n    if (rc < 0) [[unlikely]] {\n        LOGE(\"nativePathHideStatus error: %ld\", rc);\n    }\n    return rc;\n}\n\njlong nativePathHideUidAdd(JNIEnv *env, jobject /* this */, jstring super_key_jstr, jint uid) {\n    ensureSuperKeyNonNull(super_key_jstr);\n    const auto super_key = JUTFString(env, super_key_jstr);\n    long rc = sc_pathhide_uid_add(super_key.get(), (int)uid);\n    if (rc < 0) [[unlikely]] {\n        LOGE(\"nativePathHideUidAdd error: %ld\", rc);\n    }\n    return rc;\n}\n\njlong nativePathHideUidRemove(JNIEnv *env, jobject /* this */, jstring super_key_jstr, jint uid) {\n    ensureSuperKeyNonNull(super_key_jstr);\n    const auto super_key = JUTFString(env, super_key_jstr);\n    long rc = sc_pathhide_uid_remove(super_key.get(), (int)uid);\n    if (rc < 0) [[unlikely]] {\n        LOGE(\"nativePathHideUidRemove error: %ld\", rc);\n    }\n    return rc;\n}\n\njstring nativePathHideUidList(JNIEnv *env, jobject /* this */, jstring super_key_jstr) {\n    ensureSuperKeyNonNull(super_key_jstr);\n    const auto super_key = JUTFString(env, super_key_jstr);\n    char buf[4096] = {0};\n    long rc = sc_pathhide_uid_list(super_key.get(), buf, sizeof(buf));\n    if (rc < 0) [[unlikely]] {\n        LOGE(\"nativePathHideUidList error: %ld\", rc);\n        return env->NewStringUTF(\"\");\n    }\n    return env->NewStringUTF(buf);\n}\n\njlong nativePathHideUidClear(JNIEnv *env, jobject /* this */, jstring super_key_jstr) {\n    ensureSuperKeyNonNull(super_key_jstr);\n    const auto super_key = JUTFString(env, super_key_jstr);\n    long rc = sc_pathhide_uid_clear(super_key.get());\n    if (rc < 0) [[unlikely]] {\n        LOGE(\"nativePathHideUidClear error: %ld\", rc);\n    }\n    return rc;\n}\n\njlong nativePathHideUidMode(JNIEnv *env, jobject /* this */, jstring super_key_jstr, jint enable) {\n    ensureSuperKeyNonNull(super_key_jstr);\n    const auto super_key = JUTFString(env, super_key_jstr);\n    long rc = sc_pathhide_uid_mode(super_key.get(), (int)enable);\n    if (rc < 0) [[unlikely]] {\n        LOGE(\"nativePathHideUidMode error: %ld\", rc);\n    }\n    return rc;\n}\n\njlong nativePathHideFilterSystem(JNIEnv *env, jobject /* this */, jstring super_key_jstr, jint enable) {\n    ensureSuperKeyNonNull(super_key_jstr);\n    const auto super_key = JUTFString(env, super_key_jstr);\n    long rc = sc_pathhide_filter_system(super_key.get(), (int)enable);\n    if (rc < 0) [[unlikely]] {\n        LOGE(\"nativePathHideFilterSystem error: %ld\", rc);\n    }\n    return rc;\n}\n\njlong nativeNetIsolateEnable(JNIEnv *env, jobject /* this */, jstring super_key_jstr, jint enable) {\n    ensureSuperKeyNonNull(super_key_jstr);\n    const auto super_key = JUTFString(env, super_key_jstr);\n    long rc = sc_netisolate_enable(super_key.get(), (int)enable);\n    if (rc < 0) [[unlikely]] {\n        LOGE(\"nativeNetIsolateEnable error: %ld\", rc);\n    }\n    return rc;\n}\n\njlong nativeNetIsolateStatus(JNIEnv *env, jobject /* this */, jstring super_key_jstr) {\n    ensureSuperKeyNonNull(super_key_jstr);\n    const auto super_key = JUTFString(env, super_key_jstr);\n    long rc = sc_netisolate_status(super_key.get());\n    if (rc < 0) [[unlikely]] {\n        LOGE(\"nativeNetIsolateStatus error: %ld\", rc);\n    }\n    return rc;\n}\n\njlong nativeNetIsolateUidAdd(JNIEnv *env, jobject /* this */, jstring super_key_jstr, jint uid) {\n    ensureSuperKeyNonNull(super_key_jstr);\n    const auto super_key = JUTFString(env, super_key_jstr);\n    long rc = sc_netisolate_uid_add(super_key.get(), (int)uid);\n    if (rc < 0) [[unlikely]] {\n        LOGE(\"nativeNetIsolateUidAdd error: %ld\", rc);\n    }\n    return rc;\n}\n\njlong nativeNetIsolateUidRemove(JNIEnv *env, jobject /* this */, jstring super_key_jstr, jint uid) {\n    ensureSuperKeyNonNull(super_key_jstr);\n    const auto super_key = JUTFString(env, super_key_jstr);\n    long rc = sc_netisolate_uid_remove(super_key.get(), (int)uid);\n    if (rc < 0) [[unlikely]] {\n        LOGE(\"nativeNetIsolateUidRemove error: %ld\", rc);\n    }\n    return rc;\n}\n\njstring nativeNetIsolateUidList(JNIEnv *env, jobject /* this */, jstring super_key_jstr) {\n    ensureSuperKeyNonNull(super_key_jstr);\n    const auto super_key = JUTFString(env, super_key_jstr);\n    char buf[4096] = {0};\n    long rc = sc_netisolate_uid_list(super_key.get(), buf, sizeof(buf));\n    if (rc < 0) [[unlikely]] {\n        LOGE(\"nativeNetIsolateUidList error: %ld\", rc);\n        return env->NewStringUTF(\"\");\n    }\n    return env->NewStringUTF(buf);\n}\n\njlong nativeNetIsolateUidClear(JNIEnv *env, jobject /* this */, jstring super_key_jstr) {\n    ensureSuperKeyNonNull(super_key_jstr);\n    const auto super_key = JUTFString(env, super_key_jstr);\n    long rc = sc_netisolate_uid_clear(super_key.get());\n    if (rc < 0) [[unlikely]] {\n        LOGE(\"nativeNetIsolateUidClear error: %ld\", rc);\n    }\n    return rc;\n}\n\njstring nativeSuAuditList(JNIEnv *env, jobject /* this */, jstring super_key_jstr) {\n    ensureSuperKeyNonNull(super_key_jstr);\n\n    const auto super_key = JUTFString(env, super_key_jstr);\n\n    // First get the count\n    long count = sc_su_audit_list(super_key.get(), nullptr, 0);\n    if (count <= 0) {\n        return env->NewStringUTF(\"[]\");\n    }\n    if (count > 256) count = 256;\n\n    // Allocate buffer and fetch entries\n    struct su_audit_entry *entries = (struct su_audit_entry *)malloc(count * sizeof(struct su_audit_entry));\n    if (!entries) {\n        return env->NewStringUTF(\"[]\");\n    }\n\n    long actual = sc_su_audit_list(super_key.get(), entries, (int)count);\n    if (actual <= 0) {\n        free(entries);\n        return env->NewStringUTF(\"[]\");\n    }\n\n    // Build JSON array\n    std::string json = \"[\";\n    for (int i = 0; i < actual; i++) {\n        struct su_audit_entry *e = &entries[i];\n        if (i > 0) json += \",\";\n\n        char buf[512];\n        snprintf(buf, sizeof(buf),\n                 \"{\\\"ts\\\":%llu,\\\"uid\\\":%d,\\\"pid\\\":%d,\\\"tgid\\\":%d,\\\"to_uid\\\":%d,\\\"sctx\\\":\\\"%s\\\",\\\"comm\\\":\\\"%s\\\"}\",\n                 (unsigned long long)e->timestamp, e->uid, e->pid, e->tgid, e->to_uid,\n                 e->scontext, e->comm);\n        json += buf;\n    }\n    json += \"]\";\n\n    free(entries);\n    return env->NewStringUTF(json.c_str());\n}\n\njlong nativeSuAuditClear(JNIEnv *env, jobject /* this */, jstring super_key_jstr) {\n    ensureSuperKeyNonNull(super_key_jstr);\n\n    const auto super_key = JUTFString(env, super_key_jstr);\n    long rc = sc_su_audit_clear(super_key.get());\n    if (rc < 0) [[unlikely]] {\n        LOGE(\"nativeSuAuditClear error: %ld\", rc);\n    }\n    return rc;\n}\n\nJNIEXPORT jint JNI_OnLoad(JavaVM* vm, void * /*reserved*/) {\n    LOGI(\"Enter OnLoad\");\n\n    JNIEnv* env{};\n    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) [[unlikely]] {\n        LOGE(\"Get JNIEnv error!\");\n        return -1;\n    }\n\n    auto clazz = JNI_FindClass(env, \"me/bmax/apatch/Natives\");\n    if (clazz.get() == nullptr) [[unlikely]] {\n        LOGE(\"Failed to find Natives class\");\n        return -1;\n    }\n\n    const static JNINativeMethod gMethods[] = {\n        {\"nativeReady\", \"(Ljava/lang/String;)Z\", reinterpret_cast<void *>(&nativeReady)},\n        {\"nativeKernelPatchVersion\", \"(Ljava/lang/String;)J\", reinterpret_cast<void *>(&nativeKernelPatchVersion)},\n        {\"nativeKernelPatchBuildTime\", \"(Ljava/lang/String;)Ljava/lang/String;\", reinterpret_cast<void *>(&nativeKernelPatchBuildTime)},\n        {\"nativeSu\", \"(Ljava/lang/String;ILjava/lang/String;)J\", reinterpret_cast<void *>(&nativeSu)},\n        {\"nativeSetUidExclude\", \"(Ljava/lang/String;II)I\", reinterpret_cast<void *>(&nativeSetUidExclude)},\n        {\"nativeGetUidExclude\", \"(Ljava/lang/String;I)I\", reinterpret_cast<void *>(&nativeGetUidExclude)},\n        {\"nativeSetNewAppProfileMode\", \"(Ljava/lang/String;I)J\", reinterpret_cast<void *>(&nativeSetNewAppProfileMode)},\n        {\"nativeGetNewAppProfileMode\", \"(Ljava/lang/String;)I\", reinterpret_cast<void *>(&nativeGetNewAppProfileMode)},\n        {\"nativeSuUids\", \"(Ljava/lang/String;)[I\", reinterpret_cast<void *>(&nativeSuUids)},\n        {\"nativeSuProfile\", \"(Ljava/lang/String;I)Lme/bmax/apatch/Natives$Profile;\", reinterpret_cast<void *>(&nativeSuProfile)},\n        {\"nativeLoadKernelPatchModule\", \"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)J\", reinterpret_cast<void *>(&nativeLoadKernelPatchModule)},\n        {\"nativeControlKernelPatchModule\", \"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lme/bmax/apatch/Natives$KPMCtlRes;\", reinterpret_cast<void *>(&nativeControlKernelPatchModule)},\n        {\"nativeUnloadKernelPatchModule\", \"(Ljava/lang/String;Ljava/lang/String;)J\", reinterpret_cast<void *>(&nativeUnloadKernelPatchModule)},\n        {\"nativeKernelPatchModuleNum\", \"(Ljava/lang/String;)J\", reinterpret_cast<void *>(&nativeKernelPatchModuleNum)},\n        {\"nativeKernelPatchModuleList\", \"(Ljava/lang/String;)Ljava/lang/String;\", reinterpret_cast<void *>(&nativeKernelPatchModuleList)},\n        {\"nativeKernelPatchModuleInfo\", \"(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;\", reinterpret_cast<void *>(&nativeKernelPatchModuleInfo)},\n        {\"nativeGrantSu\", \"(Ljava/lang/String;IILjava/lang/String;)J\", reinterpret_cast<void *>(&nativeGrantSu)},\n        {\"nativeRevokeSu\", \"(Ljava/lang/String;I)J\", reinterpret_cast<void *>(&nativeRevokeSu)},\n        {\"nativeSuPath\", \"(Ljava/lang/String;)Ljava/lang/String;\", reinterpret_cast<void *>(&nativeSuPath)},\n        {\"nativeResetSuPath\", \"(Ljava/lang/String;Ljava/lang/String;)Z\", reinterpret_cast<void *>(&nativeResetSuPath)},\n        {\"nativeUtsSet\", \"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)J\", reinterpret_cast<void *>(&nativeUtsSet)},\n        {\"nativeUtsReset\", \"(Ljava/lang/String;)J\", reinterpret_cast<void *>(&nativeUtsReset)},\n        {\"nativePathHideAdd\", \"(Ljava/lang/String;Ljava/lang/String;)J\", reinterpret_cast<void *>(&nativePathHideAdd)},\n        {\"nativePathHideRemove\", \"(Ljava/lang/String;Ljava/lang/String;)J\", reinterpret_cast<void *>(&nativePathHideRemove)},\n        {\"nativePathHideList\", \"(Ljava/lang/String;)Ljava/lang/String;\", reinterpret_cast<void *>(&nativePathHideList)},\n        {\"nativePathHideClear\", \"(Ljava/lang/String;)J\", reinterpret_cast<void *>(&nativePathHideClear)},\n        {\"nativePathHideEnable\", \"(Ljava/lang/String;I)J\", reinterpret_cast<void *>(&nativePathHideEnable)},\n        {\"nativePathHideStatus\", \"(Ljava/lang/String;)J\", reinterpret_cast<void *>(&nativePathHideStatus)},\n        {\"nativePathHideUidAdd\", \"(Ljava/lang/String;I)J\", reinterpret_cast<void *>(&nativePathHideUidAdd)},\n        {\"nativePathHideUidRemove\", \"(Ljava/lang/String;I)J\", reinterpret_cast<void *>(&nativePathHideUidRemove)},\n        {\"nativePathHideUidList\", \"(Ljava/lang/String;)Ljava/lang/String;\", reinterpret_cast<void *>(&nativePathHideUidList)},\n        {\"nativePathHideUidClear\", \"(Ljava/lang/String;)J\", reinterpret_cast<void *>(&nativePathHideUidClear)},\n        {\"nativePathHideUidMode\", \"(Ljava/lang/String;I)J\", reinterpret_cast<void *>(&nativePathHideUidMode)},\n        {\"nativePathHideFilterSystem\", \"(Ljava/lang/String;I)J\", reinterpret_cast<void *>(&nativePathHideFilterSystem)},\n        {\"nativeNetIsolateEnable\", \"(Ljava/lang/String;I)J\", reinterpret_cast<void *>(&nativeNetIsolateEnable)},\n        {\"nativeNetIsolateStatus\", \"(Ljava/lang/String;)J\", reinterpret_cast<void *>(&nativeNetIsolateStatus)},\n        {\"nativeNetIsolateUidAdd\", \"(Ljava/lang/String;I)J\", reinterpret_cast<void *>(&nativeNetIsolateUidAdd)},\n        {\"nativeNetIsolateUidRemove\", \"(Ljava/lang/String;I)J\", reinterpret_cast<void *>(&nativeNetIsolateUidRemove)},\n        {\"nativeNetIsolateUidList\", \"(Ljava/lang/String;)Ljava/lang/String;\", reinterpret_cast<void *>(&nativeNetIsolateUidList)},\n        {\"nativeNetIsolateUidClear\", \"(Ljava/lang/String;)J\", reinterpret_cast<void *>(&nativeNetIsolateUidClear)},\n        {\"nativeSuAuditList\", \"(Ljava/lang/String;)Ljava/lang/String;\", reinterpret_cast<void *>(&nativeSuAuditList)},\n        {\"nativeSuAuditClear\", \"(Ljava/lang/String;)J\", reinterpret_cast<void *>(&nativeSuAuditClear)},\n        {\"nativeGetApiToken\", \"(Landroid/content/Context;)Ljava/lang/String;\", reinterpret_cast<void *>(&nativeGetApiToken)},\n    };\n\n    if (JNI_RegisterNatives(env, clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0])) < 0) [[unlikely]] {\n        LOGE(\"Failed to register native methods\");\n        return -1;\n    }\n    \n    LOGI(\"JNI_OnLoad Done!\");\n    return JNI_VERSION_1_6;\n}\n"
  },
  {
    "path": "app/src/main/cpp/apjni.hpp",
    "content": "//\n// Created by GarfieldHan on 2024/6/11.\n//\n\n#ifndef APATCH_APJNI_HPP\n#define APATCH_APJNI_HPP\n\n#include <jni.h>\n#include <android/log.h>\n#include \"jni_helper.hpp\"\n\nusing namespace lsplant;\n\n#define LOG_TAG \"APatchNative\"\n#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)\n#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)\n#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)\n#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)\n#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)\n\nvoid ensureSuperKeyNonNull(jstring super_key_jstr) {\n    if (!super_key_jstr) [[unlikely]] {\n        LOGE(\"[%s] Super Key is null!\", __PRETTY_FUNCTION__);\n        abort();\n    }\n}\n\n#endif //APATCH_APJNI_HPP\n"
  },
  {
    "path": "app/src/main/cpp/jni_helper.hpp",
    "content": "#pragma once\n\n#include <android/log.h>\n#include <jni.h>\n\n#include <string>\n#include <string_view>\n\n#include \"type_traits.hpp\"\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Winvalid-partial-specialization\"\n#pragma clang diagnostic ignored \"-Wunknown-pragmas\"\n#pragma ide diagnostic ignored \"OCUnusedGlobalDeclarationInspection\"\n\n#define DISALLOW_COPY_AND_ASSIGN(TypeName)                                                         \\\n    TypeName(const TypeName &) = delete;                                                           \\\n    void operator=(const TypeName &) = delete\n\nnamespace lsplant {\ntemplate <typename T>\nconcept JObject = std::is_base_of_v<std::remove_pointer_t<_jobject>, std::remove_pointer_t<T>>;\n\ntemplate <JObject T>\nclass ScopedLocalRef {\npublic:\n    using BaseType [[maybe_unused]] = T;\n\n    ScopedLocalRef(JNIEnv *env, T local_ref) : env_(env), local_ref_(nullptr) { reset(local_ref); }\n\n    ScopedLocalRef(ScopedLocalRef &&s) noexcept : ScopedLocalRef(s.env_, s.release()) {}\n\n    template <JObject U>\n    ScopedLocalRef(ScopedLocalRef<U> &&s) noexcept : ScopedLocalRef(s.env_, (T)s.release()) {}\n\n    explicit ScopedLocalRef(JNIEnv *env) noexcept : ScopedLocalRef(env, T{nullptr}) {}\n\n    ~ScopedLocalRef() { reset(); }\n\n    void reset(T ptr = nullptr) {\n        if (ptr != local_ref_) {\n            if (local_ref_ != nullptr) {\n                env_->DeleteLocalRef(local_ref_);\n            }\n            local_ref_ = ptr;\n        }\n    }\n\n    [[nodiscard]] T release() {\n        T localRef = local_ref_;\n        local_ref_ = nullptr;\n        return localRef;\n    }\n\n    T get() const { return local_ref_; }\n\n    ScopedLocalRef<T> clone() const {\n        return ScopedLocalRef<T>(env_, (T)env_->NewLocalRef(local_ref_));\n    }\n\n    ScopedLocalRef &operator=(ScopedLocalRef &&s) noexcept {\n        reset(s.release());\n        env_ = s.env_;\n        return *this;\n    }\n\n    operator bool() const { return local_ref_; }\n\n    template <JObject U>\n    friend class ScopedLocalRef;\n\n    friend class JUTFString;\n\nprivate:\n    JNIEnv *env_;\n    T local_ref_;\n    DISALLOW_COPY_AND_ASSIGN(ScopedLocalRef);\n};\n\nclass JObjectArrayElement;\n\ntemplate <typename T>\nconcept JArray = std::is_base_of_v<std::remove_pointer_t<_jarray>, std::remove_pointer_t<T>>;\n\ntemplate <JArray T>\nclass ScopedLocalRef<T>;\n\nclass JNIScopeFrame {\n    JNIEnv *env_;\n\n    DISALLOW_COPY_AND_ASSIGN(JNIScopeFrame);\n\npublic:\n    JNIScopeFrame(JNIEnv *env, jint size) : env_(env) { env_->PushLocalFrame(size); }\n\n    ~JNIScopeFrame() { env_->PopLocalFrame(nullptr); }\n};\n\nclass JNIMonitor {\n    JNIEnv *env_;\n    jobject obj_;\n\n    DISALLOW_COPY_AND_ASSIGN(JNIMonitor);\n\npublic:\n    JNIMonitor(JNIEnv *env, jobject obj) : env_(env), obj_(obj) { env_->MonitorEnter(obj_); }\n\n    ~JNIMonitor() { env_->MonitorExit(obj_); }\n};\n\ntemplate <typename T, typename U>\nconcept ScopeOrRaw =\n    std::is_convertible_v<T, U> ||\n    (is_instance_v<std::decay_t<T>, ScopedLocalRef> &&\n     std::is_convertible_v<typename std::decay_t<T>::BaseType, U>) ||\n    (std::is_same_v<std::decay_t<T>, JObjectArrayElement> && std::is_convertible_v<jobject, U>);\n\ntemplate <typename T>\nconcept ScopeOrClass = ScopeOrRaw<T, jclass>;\n\ntemplate <typename T>\nconcept ScopeOrObject = ScopeOrRaw<T, jobject>;\n\ninline ScopedLocalRef<jstring> ClearException(JNIEnv *env) {\n    if (auto exception = env->ExceptionOccurred()) {\n        env->ExceptionClear();\n        jclass log = (jclass)env->FindClass(\"android/util/Log\");\n        static jmethodID toString = env->GetStaticMethodID(\n            log, \"getStackTraceString\", \"(Ljava/lang/Throwable;)Ljava/lang/String;\");\n        auto str = (jstring)env->CallStaticObjectMethod(log, toString, exception);\n        env->DeleteLocalRef(log);\n        env->DeleteLocalRef(exception);\n        return {env, str};\n    }\n    return {env, nullptr};\n}\n\ntemplate <typename T>\n[[maybe_unused]] inline auto UnwrapScope(T &&x) {\n    if constexpr (std::is_same_v<std::decay_t<T>, std::string_view>)\n        return x.data();\n    else if constexpr (is_instance_v<std::decay_t<T>, ScopedLocalRef>)\n        return x.get();\n    else if constexpr (std::is_same_v<std::decay_t<T>, JObjectArrayElement>)\n        return x.get();\n    else\n        return std::forward<T>(x);\n}\n\ntemplate <typename T>\n[[maybe_unused]] inline auto WrapScope(JNIEnv *env, T &&x) {\n    if constexpr (std::is_convertible_v<T, _jobject *>) {\n        return ScopedLocalRef(env, std::forward<T>(x));\n    } else\n        return x;\n}\n\ntemplate <typename... T, size_t... I>\n[[maybe_unused]] inline auto WrapScope(JNIEnv *env, std::tuple<T...> &&x,\n                                       std::index_sequence<I...>) {\n    return std::make_tuple(WrapScope(env, std::forward<T>(std::get<I>(x)))...);\n}\n\ntemplate <typename... T>\n[[maybe_unused]] inline auto WrapScope(JNIEnv *env, std::tuple<T...> &&x) {\n    return WrapScope(env, std::forward<std::tuple<T...>>(x),\n                     std::make_index_sequence<sizeof...(T)>());\n}\n\ninline auto JNI_NewStringUTF(JNIEnv *env, std::string_view sv) {\n    return ScopedLocalRef(env, env->NewStringUTF(sv.data()));\n}\n\nclass JUTFString {\npublic:\n    JUTFString(JNIEnv *env, jstring jstr) : JUTFString(env, jstr, nullptr) {}\n\n    JUTFString(const ScopedLocalRef<jstring> &jstr)\n        : JUTFString(jstr.env_, jstr.local_ref_, nullptr) {}\n\n    JUTFString(JNIEnv *env, jstring jstr, const char *default_cstr) : env_(env), jstr_(jstr) {\n        if (env_ && jstr_)\n            cstr_ = env_->GetStringUTFChars(jstr, nullptr);\n        else\n            cstr_ = default_cstr;\n    }\n\n    operator const char *() const { return cstr_; }\n\n    operator const std::string() const { return cstr_; }\n\n    operator const bool() const { return cstr_ != nullptr; }\n\n    auto get() const { return cstr_; }\n\n    ~JUTFString() {\n        if (env_ && jstr_) env_->ReleaseStringUTFChars(jstr_, cstr_);\n    }\n\n    JUTFString(JUTFString &&other)\n        : env_(std::move(other.env_)),\n          jstr_(std::move(other.jstr_)),\n          cstr_(std::move(other.cstr_)) {\n        other.cstr_ = nullptr;\n    }\n\n    JUTFString &operator=(JUTFString &&other) {\n        if (&other != this) {\n            env_ = std::move(other.env_);\n            jstr_ = std::move(other.jstr_);\n            cstr_ = std::move(other.cstr_);\n            other.cstr_ = nullptr;\n        }\n        return *this;\n    }\n\nprivate:\n    JNIEnv *env_;\n    jstring jstr_;\n    const char *cstr_;\n\n    JUTFString(const JUTFString &) = delete;\n\n    JUTFString &operator=(const JUTFString &) = delete;\n};\n\ntemplate <typename Func, typename... Args>\n    requires(std::is_function_v<Func>)\n[[maybe_unused]] inline auto JNI_SafeInvoke(JNIEnv *env, Func JNIEnv::*f, Args &&...args) {\n    struct finally {\n        finally(JNIEnv *env) : env_(env) {}\n\n        ~finally() {\n            if (auto exception = ClearException(env_)) {\n                __android_log_print(ANDROID_LOG_ERROR,\n#ifdef LOG_TAG\n                                    LOG_TAG,\n#else\n                                    \"JNIHelper\",\n#endif\n                                    \"%s\", JUTFString(env_, exception.get()).get());\n            }\n        }\n\n        JNIEnv *env_;\n    } _(env);\n\n    if constexpr (!std::is_same_v<void,\n                                  std::invoke_result_t<Func, decltype(UnwrapScope(\n                                                                 std::forward<Args>(args)))...>>)\n        return WrapScope(env, (env->*f)(UnwrapScope(std::forward<Args>(args))...));\n    else\n        (env->*f)(UnwrapScope(std::forward<Args>(args))...);\n}\n\n// functions to class\n\n[[maybe_unused]] inline auto JNI_FindClass(JNIEnv *env, std::string_view name) {\n    return JNI_SafeInvoke(env, &JNIEnv::FindClass, name);\n}\n\ntemplate <ScopeOrObject Object>\n[[maybe_unused]] inline auto JNI_GetObjectClass(JNIEnv *env, const Object &obj) {\n    return JNI_SafeInvoke(env, &JNIEnv::GetObjectClass, obj);\n}\n\n// functions to field\n\ntemplate <ScopeOrClass Class>\n[[maybe_unused]] inline auto JNI_GetFieldID(JNIEnv *env, Class &&clazz, std::string_view name,\n                                            std::string_view sig) {\n    return JNI_SafeInvoke(env, &JNIEnv::GetFieldID, std::forward<Class>(clazz), name, sig);\n}\n\n// getters\n\ntemplate <ScopeOrObject Object>\n[[maybe_unused]] inline auto JNI_GetObjectField(JNIEnv *env, Object &&obj, jfieldID fieldId) {\n    return JNI_SafeInvoke(env, &JNIEnv::GetObjectField, std::forward<Object>(obj), fieldId);\n}\n\ntemplate <ScopeOrObject Object>\n[[maybe_unused]] inline auto JNI_GetBooleanField(JNIEnv *env, Object &&obj, jfieldID fieldId) {\n    return JNI_SafeInvoke(env, &JNIEnv::GetBooleanField, std::forward<Object>(obj), fieldId);\n}\n\ntemplate <ScopeOrObject Object>\n[[maybe_unused]] inline auto JNI_GetByteField(JNIEnv *env, Object &&obj, jfieldID fieldId) {\n    return JNI_SafeInvoke(env, &JNIEnv::GetByteField, std::forward<Object>(obj), fieldId);\n}\n\ntemplate <ScopeOrObject Object>\n[[maybe_unused]] inline auto JNI_GetCharField(JNIEnv *env, Object &&obj, jfieldID fieldId) {\n    return JNI_SafeInvoke(env, &JNIEnv::GetCharField, std::forward<Object>(obj), fieldId);\n}\n\ntemplate <ScopeOrObject Object>\n[[maybe_unused]] inline auto JNI_GetShortField(JNIEnv *env, Object &&obj, jfieldID fieldId) {\n    return JNI_SafeInvoke(env, &JNIEnv::GetShortField, std::forward<Object>(obj), fieldId);\n}\n\ntemplate <ScopeOrObject Object>\n[[maybe_unused]] inline auto JNI_GetIntField(JNIEnv *env, Object &&obj, jfieldID fieldId) {\n    return JNI_SafeInvoke(env, &JNIEnv::GetIntField, std::forward<Object>(obj), fieldId);\n}\n\ntemplate <ScopeOrObject Object>\n[[maybe_unused]] inline auto JNI_GetLongField(JNIEnv *env, Object &&obj, jfieldID fieldId) {\n    return JNI_SafeInvoke(env, &JNIEnv::GetLongField, std::forward<Object>(obj), fieldId);\n}\n\ntemplate <ScopeOrObject Object>\n[[maybe_unused]] inline auto JNI_GetFloatField(JNIEnv *env, Object &&obj, jfieldID fieldId) {\n    return JNI_SafeInvoke(env, &JNIEnv::GetFloatField, std::forward<Object>(obj), fieldId);\n}\n\ntemplate <ScopeOrObject Object>\n[[maybe_unused]] inline auto JNI_GetDoubleField(JNIEnv *env, Object &&obj, jfieldID fieldId) {\n    return JNI_SafeInvoke(env, &JNIEnv::GetDoubleField, std::forward<Object>(obj), fieldId);\n}\n\n// setters\n\ntemplate <ScopeOrObject Object, ScopeOrObject Value>\n[[maybe_unused]] inline auto JNI_SetObjectField(JNIEnv *env, Object &&obj, jfieldID fieldId,\n                                                const Value &value) {\n    return JNI_SafeInvoke(env, &JNIEnv::SetObjectField, std::forward<Object>(obj), fieldId, value);\n}\n\ntemplate <ScopeOrObject Object>\n[[maybe_unused]] inline auto JNI_SetBooleanField(JNIEnv *env, Object &&obj, jfieldID fieldId,\n                                                 jboolean value) {\n    return JNI_SafeInvoke(env, &JNIEnv::SetBooleanField, std::forward<Object>(obj), fieldId, value);\n}\n\ntemplate <ScopeOrObject Object>\n[[maybe_unused]] inline auto JNI_SetByteField(JNIEnv *env, Object &&obj, jfieldID fieldId,\n                                              jbyte value) {\n    return JNI_SafeInvoke(env, &JNIEnv::SetByteField, std::forward<Object>(obj), fieldId, value);\n}\n\ntemplate <ScopeOrObject Object>\n[[maybe_unused]] inline auto JNI_SetCharField(JNIEnv *env, Object &&obj, jfieldID fieldId,\n                                              jchar value) {\n    return JNI_SafeInvoke(env, &JNIEnv::SetCharField, std::forward<Object>(obj), fieldId, value);\n}\n\ntemplate <ScopeOrObject Object>\n[[maybe_unused]] inline auto JNI_SetShortField(JNIEnv *env, Object &&obj, jfieldID fieldId,\n                                               jshort value) {\n    return JNI_SafeInvoke(env, &JNIEnv::SetShortField, std::forward<Object>(obj), fieldId, value);\n}\n\ntemplate <ScopeOrObject Object>\n[[maybe_unused]] inline auto JNI_SetIntField(JNIEnv *env, Object &&obj, jfieldID fieldId,\n                                             jint value) {\n    return JNI_SafeInvoke(env, &JNIEnv::SetIntField, std::forward<Object>(obj), fieldId, value);\n}\n\ntemplate <ScopeOrObject Object>\n[[maybe_unused]] inline auto JNI_SetLongField(JNIEnv *env, Object &&obj, jfieldID fieldId,\n                                              jlong value) {\n    return JNI_SafeInvoke(env, &JNIEnv::SetLongField, std::forward<Object>(obj), fieldId, value);\n}\n\ntemplate <ScopeOrObject Object>\n[[maybe_unused]] inline auto JNI_SetFloatField(JNIEnv *env, Object &&obj, jfieldID fieldId,\n                                               jfloat value) {\n    return JNI_SafeInvoke(env, &JNIEnv::SetFloatField, std::forward<Object>(obj), fieldId, value);\n}\n\ntemplate <ScopeOrObject Object>\n[[maybe_unused]] inline auto JNI_SetDoubleField(JNIEnv *env, Object &&obj, jfieldID fieldId,\n                                                jdouble value) {\n    return JNI_SafeInvoke(env, &JNIEnv::SetDoubleField, std::forward<Object>(obj), fieldId, value);\n}\n\n// functions to static field\n\ntemplate <ScopeOrClass Class>\n[[maybe_unused]] inline auto JNI_GetStaticFieldID(JNIEnv *env, Class &&clazz, std::string_view name,\n                                                  std::string_view sig) {\n    return JNI_SafeInvoke(env, &JNIEnv::GetStaticFieldID, std::forward<Class>(clazz), name, sig);\n}\n\n// getters\n\ntemplate <ScopeOrClass Class>\n[[maybe_unused]] inline auto JNI_GetStaticObjectField(JNIEnv *env, Class &&clazz,\n                                                      jfieldID fieldId) {\n    return JNI_SafeInvoke(env, &JNIEnv::GetStaticObjectField, std::forward<Class>(clazz), fieldId);\n}\n\ntemplate <ScopeOrClass Class>\n[[maybe_unused]] inline auto JNI_GetStaticBooleanField(JNIEnv *env, Class &&clazz,\n                                                       jfieldID fieldId) {\n    return JNI_SafeInvoke(env, &JNIEnv::GetStaticBooleanField, std::forward<Class>(clazz), fieldId);\n}\n\ntemplate <ScopeOrClass Class>\n[[maybe_unused]] inline auto JNI_GetStaticByteField(JNIEnv *env, Class &&clazz, jfieldID fieldId) {\n    return JNI_SafeInvoke(env, &JNIEnv::GetStaticByteField, std::forward<Class>(clazz), fieldId);\n}\n\ntemplate <ScopeOrClass Class>\n[[maybe_unused]] inline auto JNI_GetStaticCharField(JNIEnv *env, Class &&clazz, jfieldID fieldId) {\n    return JNI_SafeInvoke(env, &JNIEnv::GetStaticCharField, std::forward<Class>(clazz), fieldId);\n}\n\ntemplate <ScopeOrClass Class>\n[[maybe_unused]] inline auto JNI_GetStaticShortField(JNIEnv *env, Class &&clazz, jfieldID fieldId) {\n    return JNI_SafeInvoke(env, &JNIEnv::GetStaticShortField, std::forward<Class>(clazz), fieldId);\n}\n\ntemplate <ScopeOrClass Class>\n[[maybe_unused]] inline auto JNI_GetStaticIntField(JNIEnv *env, Class &&clazz, jfieldID fieldId) {\n    return JNI_SafeInvoke(env, &JNIEnv::GetStaticIntField, std::forward<Class>(clazz), fieldId);\n}\n\ntemplate <ScopeOrClass Class>\n[[maybe_unused]] inline auto JNI_GetStaticLongField(JNIEnv *env, Class &&clazz, jfieldID fieldId) {\n    return JNI_SafeInvoke(env, &JNIEnv::GetStaticLongField, std::forward<Class>(clazz), fieldId);\n}\n\ntemplate <ScopeOrClass Class>\n[[maybe_unused]] inline auto JNI_GetStaticFloatField(JNIEnv *env, Class &&clazz, jfieldID fieldId) {\n    return JNI_SafeInvoke(env, &JNIEnv::GetStaticFloatField, std::forward<Class>(clazz), fieldId);\n}\n\ntemplate <ScopeOrClass Class>\n[[maybe_unused]] inline auto JNI_GetStaticDoubleField(JNIEnv *env, Class &&clazz,\n                                                      jfieldID fieldId) {\n    return JNI_SafeInvoke(env, &JNIEnv::GetStaticDoubleField, std::forward<Class>(clazz), fieldId);\n}\n\n// setters\n\ntemplate <ScopeOrClass Class, ScopeOrObject Object>\n[[maybe_unused]] inline auto JNI_SetStaticObjectField(JNIEnv *env, Class &&clazz, jfieldID fieldId,\n                                                      const Object &value) {\n    return JNI_SafeInvoke(env, &JNIEnv::SetStaticObjectField, std::forward<Class>(clazz), fieldId,\n                          value);\n}\n\ntemplate <ScopeOrClass Class>\n[[maybe_unused]] inline auto JNI_SetStaticBooleanField(JNIEnv *env, Class &&clazz, jfieldID fieldId,\n                                                       jboolean value) {\n    return JNI_SafeInvoke(env, &JNIEnv::SetStaticBooleanField, std::forward<Class>(clazz), fieldId,\n                          value);\n}\n\ntemplate <ScopeOrClass Class>\n[[maybe_unused]] inline auto JNI_SetStaticByteField(JNIEnv *env, Class &&clazz, jfieldID fieldId,\n                                                    jbyte value) {\n    return JNI_SafeInvoke(env, &JNIEnv::SetStaticByteField, std::forward<Class>(clazz), fieldId,\n                          value);\n}\n\ntemplate <ScopeOrClass Class>\n[[maybe_unused]] inline auto JNI_SetStaticCharField(JNIEnv *env, Class &&clazz, jfieldID fieldId,\n                                                    jchar value) {\n    return JNI_SafeInvoke(env, &JNIEnv::SetStaticCharField, std::forward<Class>(clazz), fieldId,\n                          value);\n}\n\ntemplate <ScopeOrClass Class>\n[[maybe_unused]] inline auto JNI_SetStaticShortField(JNIEnv *env, Class &&clazz, jfieldID fieldId,\n                                                     jshort value) {\n    return JNI_SafeInvoke(env, &JNIEnv::SetStaticShortField, std::forward<Class>(clazz), fieldId,\n                          value);\n}\n\ntemplate <ScopeOrClass Class>\n[[maybe_unused]] inline auto JNI_SetStaticIntField(JNIEnv *env, Class &&clazz, jfieldID fieldId,\n                                                   jint value) {\n    return JNI_SafeInvoke(env, &JNIEnv::SetStaticIntField, std::forward<Class>(clazz), fieldId,\n                          value);\n}\n\ntemplate <ScopeOrClass Class>\n[[maybe_unused]] inline auto JNI_SetStaticLongField(JNIEnv *env, Class &&clazz, jfieldID fieldId,\n                                                    jlong value) {\n    return JNI_SafeInvoke(env, &JNIEnv::SetStaticLongField, std::forward<Class>(clazz), fieldId,\n                          value);\n}\n\ntemplate <ScopeOrClass Class>\n[[maybe_unused]] inline auto JNI_SetStaticFloatField(JNIEnv *env, Class &&clazz, jfieldID fieldId,\n                                                     jfloat value) {\n    return JNI_SafeInvoke(env, &JNIEnv::SetStaticFloatField, std::forward<Class>(clazz), fieldId,\n                          value);\n}\n\ntemplate <ScopeOrClass Class>\n[[maybe_unused]] inline auto JNI_SetStaticDoubleField(JNIEnv *env, Class &&clazz, jfieldID fieldId,\n                                                      jdouble value) {\n    return JNI_SafeInvoke(env, &JNIEnv::SetStaticDoubleField, std::forward<Class>(clazz), fieldId,\n                          value);\n}\n\ntemplate <ScopeOrClass Class>\n[[maybe_unused]] inline auto JNI_ToReflectedMethod(JNIEnv *env, Class &&clazz, jmethodID method,\n                                                   jboolean isStatic = JNI_FALSE) {\n    return JNI_SafeInvoke(env, &JNIEnv::ToReflectedMethod, std::forward<Class>(clazz), method,\n                          isStatic);\n}\n\ntemplate <ScopeOrClass Class>\n[[maybe_unused]] inline auto JNI_ToReflectedField(JNIEnv *env, Class &&clazz, jfieldID field,\n                                                   jboolean isStatic = JNI_FALSE) {\n    return JNI_SafeInvoke(env, &JNIEnv::ToReflectedField, std::forward<Class>(clazz), field,\n                          isStatic);\n}\n\n// functions to method\n\n// virtual methods\n\ntemplate <ScopeOrClass Class>\n[[maybe_unused]] inline auto JNI_GetMethodID(JNIEnv *env, Class &&clazz, std::string_view name,\n                                             std::string_view sig) {\n    return JNI_SafeInvoke(env, &JNIEnv::GetMethodID, std::forward<Class>(clazz), name, sig);\n}\n\ntemplate <ScopeOrObject Object, typename... Args>\n[[maybe_unused]] inline auto JNI_CallVoidMethod(JNIEnv *env, Object &&obj, jmethodID method,\n                                                Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::CallVoidMethod, std::forward<Object>(obj), method,\n                          std::forward<Args>(args)...);\n}\n\ntemplate <ScopeOrObject Object, typename... Args>\n[[maybe_unused]] inline auto JNI_CallObjectMethod(JNIEnv *env, Object &&obj, jmethodID method,\n                                                  Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::CallObjectMethod, std::forward<Object>(obj), method,\n                          std::forward<Args>(args)...);\n}\n\ntemplate <ScopeOrObject Object, typename... Args>\n[[maybe_unused]] inline auto JNI_CallBooleanMethod(JNIEnv *env, Object &&obj, jmethodID method,\n                                                   Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::CallBooleanMethod, std::forward<Object>(obj), method,\n                          std::forward<Args>(args)...);\n}\n\ntemplate <ScopeOrObject Object, typename... Args>\n[[maybe_unused]] inline auto JNI_CallByteMethod(JNIEnv *env, Object &&obj, jmethodID method,\n                                                Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::CallByteMethod, std::forward<Object>(obj), method,\n                          std::forward<Args>(args)...);\n}\n\ntemplate <ScopeOrObject Object, typename... Args>\n[[maybe_unused]] inline auto JNI_CallCharMethod(JNIEnv *env, Object &&obj, jmethodID method,\n                                                Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::CallCharMethod, std::forward<Object>(obj), method,\n                          std::forward<Args>(args)...);\n}\n\ntemplate <ScopeOrObject Object, typename... Args>\n[[maybe_unused]] inline auto JNI_CallShortMethod(JNIEnv *env, Object &&obj, jmethodID method,\n                                                 Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::CallShortMethod, std::forward<Object>(obj), method,\n                          std::forward<Args>(args)...);\n}\n\ntemplate <ScopeOrObject Object, typename... Args>\n[[maybe_unused]] inline auto JNI_CallIntMethod(JNIEnv *env, Object &&obj, jmethodID method,\n                                               Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::CallIntMethod, std::forward<Object>(obj), method,\n                          std::forward<Args>(args)...);\n}\n\ntemplate <ScopeOrObject Object, typename... Args>\n[[maybe_unused]] inline auto JNI_CallLongMethod(JNIEnv *env, Object &&obj, jmethodID method,\n                                                Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::CallLongMethod, std::forward<Object>(obj), method,\n                          std::forward<Args>(args)...);\n}\n\ntemplate <ScopeOrObject Object, typename... Args>\n[[maybe_unused]] inline auto JNI_CallFloatMethod(JNIEnv *env, Object &&obj, jmethodID method,\n                                                 Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::CallFloatMethod, std::forward<Object>(obj), method,\n                          std::forward<Args>(args)...);\n}\n\ntemplate <ScopeOrObject Object, typename... Args>\n[[maybe_unused]] inline auto JNI_CallDoubleMethod(JNIEnv *env, Object &&obj, jmethodID method,\n                                                  Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::CallDoubleMethod, std::forward<Object>(obj), method,\n                          std::forward<Args>(args)...);\n}\n\n// static methods\n\ntemplate <ScopeOrClass Class>\n[[maybe_unused]] inline auto JNI_GetStaticMethodID(JNIEnv *env, Class &&clazz,\n                                                   std::string_view name, std::string_view sig) {\n    return JNI_SafeInvoke(env, &JNIEnv::GetStaticMethodID, std::forward<Class>(clazz), name, sig);\n}\n\ntemplate <ScopeOrClass Class, typename... Args>\n[[maybe_unused]] inline auto JNI_CallStaticVoidMethod(JNIEnv *env, Class &&clazz, jmethodID method,\n                                                      Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::CallStaticVoidMethod, std::forward<Class>(clazz), method,\n                          std::forward<Args>(args)...);\n}\n\ntemplate <ScopeOrClass Class, typename... Args>\n[[maybe_unused]] inline auto JNI_CallStaticObjectMethod(JNIEnv *env, Class &&clazz,\n                                                        jmethodID method, Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::CallStaticObjectMethod, std::forward<Class>(clazz), method,\n                          std::forward<Args>(args)...);\n}\n\ntemplate <ScopeOrClass Class, typename... Args>\n[[maybe_unused]] inline auto JNI_CallStaticBooleanMethod(JNIEnv *env, Class &&clazz,\n                                                         jmethodID method, Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::CallStaticBooleanMethod, std::forward<Class>(clazz), method,\n                          std::forward<Args>(args)...);\n}\n\ntemplate <ScopeOrClass Class, typename... Args>\n[[maybe_unused]] inline auto JNI_CallStaticByteMethod(JNIEnv *env, Class &&clazz, jmethodID method,\n                                                      Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::CallStaticByteMethod, std::forward<Class>(clazz), method,\n                          std::forward<Args>(args)...);\n}\n\ntemplate <ScopeOrClass Class, typename... Args>\n[[maybe_unused]] inline auto JNI_CallStaticCharMethod(JNIEnv *env, Class &&clazz, jmethodID method,\n                                                      Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::CallStaticCharMethod, std::forward<Class>(clazz), method,\n                          std::forward<Args>(args)...);\n}\n\ntemplate <ScopeOrClass Class, typename... Args>\n[[maybe_unused]] inline auto JNI_CallStaticShortMethod(JNIEnv *env, Class &&clazz, jmethodID method,\n                                                       Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::CallStaticShortMethod, std::forward<Class>(clazz), method,\n                          std::forward<Args>(args)...);\n}\n\ntemplate <ScopeOrClass Class, typename... Args>\n[[maybe_unused]] inline auto JNI_CallStaticIntMethod(JNIEnv *env, Class &&clazz, jmethodID method,\n                                                     Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::CallStaticIntMethod, std::forward<Class>(clazz), method,\n                          std::forward<Args>(args)...);\n}\n\ntemplate <ScopeOrClass Class, typename... Args>\n[[maybe_unused]] inline auto JNI_CallStaticLongMethod(JNIEnv *env, Class &&clazz, jmethodID method,\n                                                      Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::CallStaticLongMethod, std::forward<Class>(clazz), method,\n                          std::forward<Args>(args)...);\n}\n\ntemplate <ScopeOrClass Class, typename... Args>\n[[maybe_unused]] inline auto JNI_CallStaticFloatMethod(JNIEnv *env, Class &&clazz, jmethodID method,\n                                                       Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::CallStaticFloatMethod, std::forward<Class>(clazz), method,\n                          std::forward<Args>(args)...);\n}\n\ntemplate <ScopeOrClass Class, typename... Args>\n[[maybe_unused]] inline auto JNI_CallStaticDoubleMethod(JNIEnv *env, Class &&clazz,\n                                                        jmethodID method, Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::CallStaticDoubleMethod, std::forward<Class>(clazz), method,\n                          std::forward<Args>(args)...);\n}\n\n// non-virtual methods\n\ntemplate <ScopeOrObject Object, ScopeOrClass Class, typename... Args>\n[[maybe_unused]] inline auto JNI_CallNonvirtualVoidMethod(JNIEnv *env, Object &&obj, Class &&clazz,\n                                                          jmethodID method, Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualVoidMethod, std::forward<Object>(obj),\n                          std::forward<Class>(clazz), method, std::forward<Args>(args)...);\n}\n\ntemplate <ScopeOrObject Object, ScopeOrClass Class, typename... Args>\n[[maybe_unused]] inline auto JNI_CallNonvirtualObjectMethod(JNIEnv *env, Object &&obj,\n                                                            Class &&clazz, jmethodID method,\n                                                            Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualObjectMethod, std::forward<Object>(obj),\n                          std::forward<Class>(clazz), method, std::forward<Args>(args)...);\n}\n\ntemplate <ScopeOrObject Object, ScopeOrClass Class, typename... Args>\n[[maybe_unused]] inline auto JNI_CallNonvirtualBooleanMethod(JNIEnv *env, Object &&obj,\n                                                             Class &&clazz, jmethodID method,\n                                                             Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualBooleanMethod, std::forward<Object>(obj),\n                          std::forward<Class>(clazz), method, std::forward<Args>(args)...);\n}\n\ntemplate <ScopeOrObject Object, ScopeOrClass Class, typename... Args>\n[[maybe_unused]] inline auto JNI_CallNonvirtualByteMethod(JNIEnv *env, Object &&obj, Class &&clazz,\n                                                          jmethodID method, Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualByteMethod, std::forward<Object>(obj),\n                          std::forward<Class>(clazz), method, std::forward<Args>(args)...);\n}\n\ntemplate <ScopeOrObject Object, ScopeOrClass Class, typename... Args>\n[[maybe_unused]] inline auto JNI_CallNonvirtualCharMethod(JNIEnv *env, Object &&obj, Class &&clazz,\n                                                          jmethodID method, Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualCharMethod, std::forward<Object>(obj),\n                          std::forward<Class>(clazz), method, std::forward<Args>(args)...);\n}\n\ntemplate <ScopeOrObject Object, ScopeOrClass Class, typename... Args>\n[[maybe_unused]] inline auto JNI_CallNonvirtualShortMethod(JNIEnv *env, Object &&obj, Class &&clazz,\n                                                           jmethodID method, Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualShortMethod, std::forward<Object>(obj),\n                          std::forward<Class>(clazz), method, std::forward<Args>(args)...);\n}\n\ntemplate <ScopeOrObject Object, ScopeOrClass Class, typename... Args>\n[[maybe_unused]] inline auto JNI_CallNonvirtualIntMethod(JNIEnv *env, Object &&obj, Class &&clazz,\n                                                         jmethodID method, Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualIntMethod, std::forward<Object>(obj),\n                          std::forward<Class>(clazz), method, std::forward<Args>(args)...);\n}\n\ntemplate <ScopeOrObject Object, ScopeOrClass Class, typename... Args>\n[[maybe_unused]] inline auto JNI_CallNonvirtualLongMethod(JNIEnv *env, Object &&obj, Class &&clazz,\n                                                          jmethodID method, Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualLongMethod, std::forward<Object>(obj),\n                          std::forward<Class>(clazz), method, std::forward<Args>(args)...);\n}\n\ntemplate <ScopeOrObject Object, ScopeOrClass Class, typename... Args>\n[[maybe_unused]] inline auto JNI_CallNonvirtualFloatMethod(JNIEnv *env, Object &&obj, Class &&clazz,\n                                                           jmethodID method, Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualFloatMethod, std::forward<Object>(obj),\n                          std::forward<Class>(clazz), method, std::forward<Args>(args)...);\n}\n\ntemplate <ScopeOrObject Object, ScopeOrClass Class, typename... Args>\n[[maybe_unused]] inline auto JNI_CallNonvirtualDoubleMethod(JNIEnv *env, Object &&obj,\n                                                            Class &&clazz, jmethodID method,\n                                                            Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualDoubleMethod, std::forward<Object>(obj),\n                          std::forward<Class>(clazz), method, std::forward<Args>(args)...);\n}\n\ntemplate <ScopeOrClass Class, typename... Args>\n[[maybe_unused]] inline auto JNI_NewObject(JNIEnv *env, Class &&clazz, jmethodID method,\n                                           Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::NewObject, std::forward<Class>(clazz), method,\n                          std::forward<Args>(args)...);\n}\n\ntemplate <typename... Args>\n[[maybe_unused]] inline auto JNI_NewDirectByteBuffer(JNIEnv *env, Args &&...args) {\n    return JNI_SafeInvoke(env, &JNIEnv::NewDirectByteBuffer, std::forward<Args>(args)...);\n}\n\ntemplate <ScopeOrClass Class>\n[[maybe_unused]] inline auto JNI_RegisterNatives(JNIEnv *env, Class &&clazz,\n                                                 const JNINativeMethod *methods, jint size) {\n    return JNI_SafeInvoke(env, &JNIEnv::RegisterNatives, std::forward<Class>(clazz), methods, size);\n}\n\ntemplate <ScopeOrObject Object, ScopeOrClass Class>\n[[maybe_unused]] inline auto JNI_IsInstanceOf(JNIEnv *env, Object &&obj, Class &&clazz) {\n    return JNI_SafeInvoke(env, &JNIEnv::IsInstanceOf, std::forward<Object>(obj),\n                          std::forward<Class>(clazz));\n}\n\ntemplate <ScopeOrObject Object1, ScopeOrObject Object2>\n[[maybe_unused]] inline auto JNI_IsSameObject(JNIEnv *env, Object1 &&a, Object2 &&b) {\n    return JNI_SafeInvoke(env, &JNIEnv::IsSameObject, std::forward<Object1>(a),\n                          std::forward<Object2>(b));\n}\n\ntemplate <ScopeOrObject Object>\n[[maybe_unused]] inline auto JNI_NewGlobalRef(JNIEnv *env, Object &&x) {\n    return (decltype(UnwrapScope(std::forward<Object>(x))))env->NewGlobalRef(\n        UnwrapScope(std::forward<Object>(x)));\n}\n\ntemplate <typename U, typename T>\n[[maybe_unused]] inline auto JNI_Cast(ScopedLocalRef<T> &&x)\n    requires(std::is_convertible_v<T, _jobject *>)\n{\n    return ScopedLocalRef<U>(std::move(x));\n}\n\ntemplate <typename U>\n[[maybe_unused]] inline auto JNI_Cast(JObjectArrayElement &&x) {\n    return JNI_Cast<U, jobject>(std::move(x));\n}\n\n[[maybe_unused]] inline auto JNI_NewDirectByteBuffer(JNIEnv *env, void *address, jlong capacity) {\n    return JNI_SafeInvoke(env, &JNIEnv::NewDirectByteBuffer, address, capacity);\n}\n\ntemplate <JArray T>\nstruct JArrayUnderlyingTypeHelper;\n\ntemplate <>\nstruct JArrayUnderlyingTypeHelper<jbooleanArray> {\n    using Type = jboolean;\n};\n\ntemplate <>\nstruct JArrayUnderlyingTypeHelper<jbyteArray> {\n    using Type = jbyte;\n};\n\ntemplate <>\nstruct JArrayUnderlyingTypeHelper<jcharArray> {\n    using Type = jchar;\n};\n\ntemplate <>\nstruct JArrayUnderlyingTypeHelper<jshortArray> {\n    using Type = jshort;\n};\n\ntemplate <>\nstruct JArrayUnderlyingTypeHelper<jintArray> {\n    using Type = jint;\n};\n\ntemplate <>\nstruct JArrayUnderlyingTypeHelper<jlongArray> {\n    using Type = jlong;\n};\n\ntemplate <>\nstruct JArrayUnderlyingTypeHelper<jfloatArray> {\n    using Type = jfloat;\n};\n\ntemplate <>\nstruct JArrayUnderlyingTypeHelper<jdoubleArray> {\n    using Type = jdouble;\n};\n\ntemplate <JArray T>\nusing JArrayUnderlyingType = typename JArrayUnderlyingTypeHelper<T>::Type;\n\ntemplate <JArray T>\nclass ScopedLocalRef<T> {\npublic:\n    class Iterator {\n        friend class ScopedLocalRef<T>;\n        Iterator(JArrayUnderlyingType<T> *e) : e_(e) {}\n        JArrayUnderlyingType<T> *e_;\n\n    public:\n        auto &operator*() { return *e_; }\n        auto *operator->() { return e_; }\n        Iterator &operator++() { return ++e_, *this; }\n        Iterator &operator--() { return --e_, *this; }\n        Iterator operator++(int) { return Iterator(e_++); }\n        Iterator operator--(int) { return Iterator(e_--); }\n        bool operator==(const Iterator &other) const { return other.e_ == e_; }\n        bool operator!=(const Iterator &other) const { return other.e_ != e_; }\n    };\n\n    class ConstIterator {\n        friend class ScopedLocalRef<T>;\n        ConstIterator(const JArrayUnderlyingType<T> *e) : e_(e) {}\n        const JArrayUnderlyingType<T> *e_;\n\n    public:\n        const auto &operator*() { return *e_; }\n        const auto *operator->() { return e_; }\n        ConstIterator &operator++() { return ++e_, *this; }\n        ConstIterator &operator--() { return --e_, *this; }\n        ConstIterator operator++(int) { return ConstIterator(e_++); }\n        ConstIterator operator--(int) { return ConstIterator(e_--); }\n        bool operator==(const ConstIterator &other) const { return other.e_ == e_; }\n        bool operator!=(const ConstIterator &other) const { return other.e_ != e_; }\n    };\n\n    auto begin() {\n        modified_ = true;\n        return Iterator(elements_);\n    }\n\n    auto end() {\n        modified_ = true;\n        return Iterator(elements_ + size_);\n    }\n\n    const auto begin() const { return ConstIterator(elements_); }\n\n    auto end() const { return ConstIterator(elements_ + size_); }\n\n    const auto cbegin() const { return ConstIterator(elements_); }\n\n    auto cend() const { return ConstIterator(elements_ + size_); }\n\n    using BaseType [[maybe_unused]] = T;\n\n    ScopedLocalRef(JNIEnv *env, T local_ref) noexcept : env_(env), local_ref_(nullptr) {\n        reset(local_ref);\n    }\n\n    ScopedLocalRef(ScopedLocalRef &&s) noexcept { *this = std::move(s); }\n\n    template <JObject U>\n    ScopedLocalRef(ScopedLocalRef<U> &&s) noexcept : ScopedLocalRef(s.env_, (T)s.release()) {}\n\n    explicit ScopedLocalRef(JNIEnv *env) noexcept : ScopedLocalRef(env, T{nullptr}) {}\n\n    ~ScopedLocalRef() { env_->DeleteLocalRef(release()); }\n\n    void reset(T ptr = nullptr) {\n        if (ptr != local_ref_) {\n            if (local_ref_ != nullptr) {\n                ReleaseElements(modified_ ? 0 : JNI_ABORT);\n                env_->DeleteLocalRef(local_ref_);\n                elements_ = nullptr;\n            }\n            local_ref_ = ptr;\n            size_ = local_ref_ ? env_->GetArrayLength(local_ref_) : 0;\n            if (!local_ref_) return;\n            static_assert(!std::is_same_v<T, jobjectArray>);\n            if constexpr (std::is_same_v<T, jbooleanArray>) {\n                elements_ = env_->GetBooleanArrayElements(local_ref_, nullptr);\n            } else if constexpr (std::is_same_v<T, jbyteArray>) {\n                elements_ = env_->GetByteArrayElements(local_ref_, nullptr);\n            } else if constexpr (std::is_same_v<T, jcharArray>) {\n                elements_ = env_->GetCharArrayElements(local_ref_, nullptr);\n            } else if constexpr (std::is_same_v<T, jshortArray>) {\n                elements_ = env_->GetShortArrayElements(local_ref_, nullptr);\n            } else if constexpr (std::is_same_v<T, jintArray>) {\n                elements_ = env_->GetIntArrayElements(local_ref_, nullptr);\n            } else if constexpr (std::is_same_v<T, jlongArray>) {\n                elements_ = env_->GetLongArrayElements(local_ref_, nullptr);\n            } else if constexpr (std::is_same_v<T, jfloatArray>) {\n                elements_ = env_->GetFloatArrayElements(local_ref_, nullptr);\n            } else if constexpr (std::is_same_v<T, jdoubleArray>) {\n                elements_ = env_->GetDoubleArrayElements(local_ref_, nullptr);\n            }\n        }\n    }\n\n    [[nodiscard]] T release() {\n        T localRef = local_ref_;\n        size_ = 0;\n        local_ref_ = nullptr;\n        ReleaseElements(modified_ ? 0 : JNI_ABORT);\n        elements_ = nullptr;\n        return localRef;\n    }\n\n    T get() const { return local_ref_; }\n\n    JArrayUnderlyingType<T> &operator[](size_t index) {\n        modified_ = true;\n        return elements_[index];\n    }\n\n    const JArrayUnderlyingType<T> &operator[](size_t index) const { return elements_[index]; }\n\n    void commit() {\n        ReleaseElements(JNI_COMMIT);\n        modified_ = false;\n    }\n\n    // We do not expose an empty constructor as it can easily lead to errors\n    // using common idioms, e.g.:\n    //   ScopedLocalRef<...> ref;\n    //   ref.reset(...);\n    // Move assignment operator.\n    ScopedLocalRef &operator=(ScopedLocalRef &&s) noexcept {\n        env_ = s.env_;\n        local_ref_ = s.local_ref_;\n        size_ = s.size_;\n        elements_ = s.elements_;\n        modified_ = s.modified_;\n        s.elements_ = nullptr;\n        s.size_ = 0;\n        s.modified_ = false;\n        s.local_ref_ = nullptr;\n        return *this;\n    }\n\n    size_t size() const { return size_; }\n\n    operator bool() const { return local_ref_; }\n\n    template <JObject U>\n    friend class ScopedLocalRef;\n\n    friend class JUTFString;\n\nprivate:\n    void ReleaseElements(jint mode) {\n        if (!local_ref_ || !elements_) return;\n        if constexpr (std::is_same_v<T, jbooleanArray>) {\n            env_->ReleaseBooleanArrayElements(local_ref_, elements_, mode);\n        } else if constexpr (std::is_same_v<T, jbyteArray>) {\n            env_->ReleaseByteArrayElements(local_ref_, elements_, mode);\n        } else if constexpr (std::is_same_v<T, jcharArray>) {\n            env_->ReleaseCharArrayElements(local_ref_, elements_, mode);\n        } else if constexpr (std::is_same_v<T, jshortArray>) {\n            env_->ReleaseShortArrayElements(local_ref_, elements_, mode);\n        } else if constexpr (std::is_same_v<T, jintArray>) {\n            env_->ReleaseIntArrayElements(local_ref_, elements_, mode);\n        } else if constexpr (std::is_same_v<T, jlongArray>) {\n            env_->ReleaseLongArrayElements(local_ref_, elements_, mode);\n        } else if constexpr (std::is_same_v<T, jfloatArray>) {\n            env_->ReleaseFloatArrayElements(local_ref_, elements_, mode);\n        } else if constexpr (std::is_same_v<T, jdoubleArray>) {\n            env_->ReleaseDoubleArrayElements(local_ref_, elements_, mode);\n        }\n    }\n\n    JNIEnv *env_;\n    T local_ref_;\n    size_t size_;\n    JArrayUnderlyingType<T> *elements_{nullptr};\n    bool modified_ = false;\n    DISALLOW_COPY_AND_ASSIGN(ScopedLocalRef);\n};\n\nclass JObjectArrayElement {\n    friend class ScopedLocalRef<jobjectArray>;\n\n    auto obtain() {\n        if (i_ < 0 || i_ >= size_) return ScopedLocalRef<jobject>{nullptr};\n        return JNI_SafeInvoke(env_, &JNIEnv::GetObjectArrayElement, array_, i_);\n    }\n\n    explicit JObjectArrayElement(JNIEnv *env, jobjectArray array, int i, size_t size)\n        : env_(env), array_(array), i_(i), size_(size), item_(obtain()) {}\n\n    JObjectArrayElement &operator++() {\n        ++i_;\n        item_ = obtain();\n        return *this;\n    }\n\n    JObjectArrayElement &operator--() {\n        --i_;\n        item_ = obtain();\n        return *this;\n    }\n\n    JObjectArrayElement operator++(int) { return JObjectArrayElement(env_, array_, i_ + 1, size_); }\n\n    JObjectArrayElement operator--(int) { return JObjectArrayElement(env_, array_, i_ - 1, size_); }\n\npublic:\n    JObjectArrayElement(JObjectArrayElement &&s)\n        : env_(s.env_), array_(s.array_), i_(s.i_), size_(s.size_), item_(std::move(s.item_)) {}\n\n    operator ScopedLocalRef<jobject> &() & { return item_; }\n\n    operator ScopedLocalRef<jobject> &&() && { return std::move(item_); }\n\n    JObjectArrayElement &operator=(JObjectArrayElement &&s) {\n        reset(s.item_.release());\n        return *this;\n    }\n\n    JObjectArrayElement &operator=(const JObjectArrayElement &s) {\n        reset(env_->NewLocalRef(s.item_.get()));\n        return *this;\n    }\n\n    template<JObject T>\n    JObjectArrayElement &operator=(ScopedLocalRef<T> &&s) {\n        reset(s.release());\n        return *this;\n    }\n\n    template<JObject T>\n    JObjectArrayElement &operator=(const ScopedLocalRef<T> &s) {\n        reset(s.clone());\n        return *this;\n    }\n\n    JObjectArrayElement &operator=(jobject s) {\n        reset(env_->NewLocalRef(s));\n        return *this;\n    }\n\n    void reset(jobject item) {\n        item_.reset(item);\n        JNI_SafeInvoke(env_, &JNIEnv::SetObjectArrayElement, array_, i_, item_);\n    }\n\n    ScopedLocalRef<jobject> clone() const { return item_.clone(); }\n\n    jobject get() const { return item_.get(); }\n\n    jobject release() { return item_.release(); }\n\n    jobject operator->() const { return item_.get(); }\n\n    jobject operator*() const { return item_.get(); }\n\nprivate:\n    JNIEnv *env_;\n    jobjectArray array_;\n    int i_;\n    int size_;\n    ScopedLocalRef<jobject> item_;\n    JObjectArrayElement(const JObjectArrayElement &) = delete;\n};\n\ntemplate <>\nclass ScopedLocalRef<jobjectArray> {\npublic:\n    class Iterator {\n        friend class ScopedLocalRef<jobjectArray>;\n\n        Iterator(JObjectArrayElement &&e) : e_(std::move(e)) {}\n        Iterator(JNIEnv *env, jobjectArray array, int i, size_t size) : e_(env, array, i, size) {}\n\n    public:\n        auto &operator*() { return e_; }\n\n        auto *operator->() { return e_.get(); }\n\n        Iterator &operator++() {\n            ++e_;\n            return *this;\n        }\n\n        Iterator &operator--() {\n            --e_;\n            return *this;\n        }\n\n        Iterator operator++(int) { return Iterator(e_++); }\n\n        Iterator operator--(int) { return Iterator(e_--); }\n\n        bool operator==(const Iterator &other) const { return other.e_.i_ == e_.i_; }\n\n        bool operator!=(const Iterator &other) const { return other.e_.i_ != e_.i_; }\n\n    private:\n        JObjectArrayElement e_;\n    };\n\n    class ConstIterator {\n        friend class ScopedLocalRef<jobjectArray>;\n\n        auto obtain() {\n            if (i_ < 0 || i_ >= size_) return ScopedLocalRef<jobject>{nullptr};\n            return JNI_SafeInvoke(env_, &JNIEnv::GetObjectArrayElement, array_, i_);\n        }\n\n        ConstIterator(JNIEnv *env, jobjectArray array, int i, int size)\n            : env_(env), array_(array), i_(i), size_(size), item_(obtain()) {}\n\n    public:\n        auto &operator*() { return item_; }\n\n        auto *operator->() { return &item_; }\n\n        ConstIterator &operator++() {\n            ++i_;\n            item_ = obtain();\n            return *this;\n        }\n\n        ConstIterator &operator--() {\n            --i_;\n            item_ = obtain();\n            return *this;\n        }\n\n        ConstIterator operator++(int) { return ConstIterator(env_, array_, i_ + 1, size_); }\n\n        ConstIterator operator--(int) { return ConstIterator(env_, array_, i_ - 1, size_); }\n\n        bool operator==(const ConstIterator &other) const { return other.i_ == i_; }\n\n        bool operator!=(const ConstIterator &other) const { return other.i_ != i_; }\n\n    private:\n        JNIEnv *env_;\n        jobjectArray array_;\n        int i_;\n        int size_;\n        ScopedLocalRef<jobject> item_;\n    };\n\n    auto begin() { return Iterator(env_, local_ref_, 0, size_); }\n\n    auto end() { return Iterator(env_, local_ref_, size_, size_); }\n\n    const auto begin() const { return ConstIterator(env_, local_ref_, 0, size_); }\n\n    auto end() const { return ConstIterator(env_, local_ref_, size_, size_); }\n\n    const auto cbegin() const { return ConstIterator(env_, local_ref_, 0, size_); }\n\n    auto cend() const { return ConstIterator(env_, local_ref_, size_, size_); }\n\n    ScopedLocalRef(JNIEnv *env, jobjectArray local_ref) noexcept : env_(env), local_ref_(nullptr) {\n        reset(local_ref);\n    }\n\n    ScopedLocalRef(ScopedLocalRef &&s) noexcept { *this = std::move(s); }\n\n    template <JObject U>\n    ScopedLocalRef(ScopedLocalRef<U> &&s) noexcept\n        : ScopedLocalRef(s.env_, (jobjectArray)s.release()) {}\n\n    explicit ScopedLocalRef(JNIEnv *env) noexcept : ScopedLocalRef(env, jobjectArray{nullptr}) {}\n\n    ~ScopedLocalRef() { env_->DeleteLocalRef(release()); }\n\n    void reset(jobjectArray ptr = nullptr) {\n        if (ptr != local_ref_) {\n            if (local_ref_ != nullptr) {\n                env_->DeleteLocalRef(local_ref_);\n            }\n            local_ref_ = ptr;\n            size_ = local_ref_ ? env_->GetArrayLength(local_ref_) : 0;\n            if (!local_ref_) return;\n        }\n    }\n\n    [[nodiscard]] jobjectArray release() {\n        jobjectArray localRef = local_ref_;\n        size_ = 0;\n        local_ref_ = nullptr;\n        return localRef;\n    }\n\n    jobjectArray get() const { return local_ref_; }\n\n    JObjectArrayElement operator[](size_t index) {\n        return JObjectArrayElement(env_, local_ref_, index, size_);\n    }\n\n    const ScopedLocalRef<jobject> operator[](size_t index) const {\n        return JNI_SafeInvoke(env_, &JNIEnv::GetObjectArrayElement, local_ref_, index);\n    }\n\n    // We do not expose an empty constructor as it can easily lead to errors\n    // using common idioms, e.g.:\n    //   ScopedLocalRef<...> ref;\n    //   ref.reset(...);\n    // Move assignment operator.\n    ScopedLocalRef &operator=(ScopedLocalRef &&s) noexcept {\n        env_ = s.env_;\n        local_ref_ = s.local_ref_;\n        size_ = s.size_;\n        s.size_ = 0;\n        s.local_ref_ = nullptr;\n        return *this;\n    }\n\n    size_t size() const { return size_; }\n\n    operator bool() const { return local_ref_; }\n\n    template <JObject U>\n    friend class ScopedLocalRef;\n\n    friend class JUTFString;\n\nprivate:\n    JNIEnv *env_;\n    jobjectArray local_ref_;\n    size_t size_;\n    DISALLOW_COPY_AND_ASSIGN(ScopedLocalRef);\n};\n// functions to array\n\ntemplate <ScopeOrRaw<jarray> Array>\n[[maybe_unused]] inline auto JNI_GetArrayLength(JNIEnv *env, const Array &array) {\n    return JNI_SafeInvoke(env, &JNIEnv::GetArrayLength, array);\n}\n\n// newers\n\ntemplate <ScopeOrClass Class, ScopeOrObject Object>\n[[maybe_unused]] inline auto JNI_NewObjectArray(JNIEnv *env, jsize len, Class &&clazz,\n                                                const Object &init) {\n    return JNI_SafeInvoke(env, &JNIEnv::NewObjectArray, len, std::forward<Class>(clazz), init);\n}\n\n[[maybe_unused]] inline auto JNI_NewBooleanArray(JNIEnv *env, jsize len) {\n    return JNI_SafeInvoke(env, &JNIEnv::NewBooleanArray, len);\n}\n\n[[maybe_unused]] inline auto JNI_NewByteArray(JNIEnv *env, jsize len) {\n    return JNI_SafeInvoke(env, &JNIEnv::NewByteArray, len);\n}\n\n[[maybe_unused]] inline auto JNI_NewCharArray(JNIEnv *env, jsize len) {\n    return JNI_SafeInvoke(env, &JNIEnv::NewCharArray, len);\n}\n\n[[maybe_unused]] inline auto JNI_NewShortArray(JNIEnv *env, jsize len) {\n    return JNI_SafeInvoke(env, &JNIEnv::NewShortArray, len);\n}\n\n[[maybe_unused]] inline auto JNI_NewIntArray(JNIEnv *env, jsize len) {\n    return JNI_SafeInvoke(env, &JNIEnv::NewIntArray, len);\n}\n\n[[maybe_unused]] inline auto JNI_NewLongArray(JNIEnv *env, jsize len) {\n    return JNI_SafeInvoke(env, &JNIEnv::NewLongArray, len);\n}\n\n[[maybe_unused]] inline auto JNI_NewFloatArray(JNIEnv *env, jsize len) {\n    return JNI_SafeInvoke(env, &JNIEnv::NewFloatArray, len);\n}\n\n[[maybe_unused]] inline auto JNI_NewDoubleArray(JNIEnv *env, jsize len) {\n    return JNI_SafeInvoke(env, &JNIEnv::NewDoubleArray, len);\n}\n\ntemplate <ScopeOrObject Object>\n[[maybe_unused]] inline auto JNI_GetObjectFieldOf(JNIEnv *env, Object &&object,\n                                                  std::string_view field_name,\n                                                  std::string_view field_class) {\n    auto &&o = std::forward<Object>(object);\n    return JNI_GetObjectField(\n        env, o, JNI_GetFieldID(env, JNI_GetObjectClass(env, o), field_name, field_class));\n}\n\n}  // namespace lsplant\n\n#undef DISALLOW_COPY_AND_ASSIGN\n\n#pragma clang diagnostic pop\n"
  },
  {
    "path": "app/src/main/cpp/security.cpp",
    "content": "#include \"security.hpp\"\n#include <string>\n#include <cstring>\n#include <strings.h>\n#include <android/log.h>\n#include <jni.h>\n#include <vector>\n\n#define LOG_TAG \"APSecurity\"\n#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)\n#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)\n\n#ifndef API_TOKEN\n#define API_TOKEN \"\"\n#endif\n\n#ifndef APP_SIGNATURE_HASH\n#define APP_SIGNATURE_HASH \"\"\n#endif\n\n#ifndef APP_PACKAGE_NAME\n#define APP_PACKAGE_NAME \"\"\n#endif\n\n// Helper to convert byte array to hex string\nstd::string bytesToHex(JNIEnv *env, jbyteArray bytes) {\n    jsize length = env->GetArrayLength(bytes);\n    jbyte *elements = env->GetByteArrayElements(bytes, nullptr);\n    std::string out;\n    out.reserve(static_cast<size_t>(length) * 2);\n    static const char HEX[] = \"0123456789abcdef\";\n    for (int i = 0; i < length; ++i) {\n        unsigned char b = static_cast<unsigned char>(elements[i]);\n        out.push_back(HEX[(b >> 4) & 0x0F]);\n        out.push_back(HEX[b & 0x0F]);\n    }\n    env->ReleaseByteArrayElements(bytes, elements, JNI_ABORT);\n    return out;\n}\n\nstd::string getSignatureHash(JNIEnv *env, jobject context) {\n    jclass contextClass = env->GetObjectClass(context);\n    jmethodID getPackageManager = env->GetMethodID(contextClass, \"getPackageManager\", \"()Landroid/content/pm/PackageManager;\");\n    jobject packageManager = env->CallObjectMethod(context, getPackageManager);\n    \n    jmethodID getPackageName = env->GetMethodID(contextClass, \"getPackageName\", \"()Ljava/lang/String;\");\n    jstring packageName = (jstring)env->CallObjectMethod(context, getPackageName);\n    \n    jclass packageManagerClass = env->GetObjectClass(packageManager);\n    jmethodID getPackageInfo = env->GetMethodID(packageManagerClass, \"getPackageInfo\", \"(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;\");\n    \n    // GET_SIGNATURES = 64\n    jobject packageInfo = env->CallObjectMethod(packageManager, getPackageInfo, packageName, 64);\n    \n    if (packageInfo == nullptr) {\n        LOGE(\"Failed to get PackageInfo\");\n        return \"\";\n    }\n    \n    jclass packageInfoClass = env->GetObjectClass(packageInfo);\n    jfieldID signaturesField = env->GetFieldID(packageInfoClass, \"signatures\", \"[Landroid/content/pm/Signature;\");\n    jobjectArray signatures = (jobjectArray)env->GetObjectField(packageInfo, signaturesField);\n    \n    if (signatures == nullptr) {\n        LOGE(\"Failed to get signatures\");\n        return \"\";\n    }\n    \n    jobject signature = env->GetObjectArrayElement(signatures, 0);\n    jclass signatureClass = env->GetObjectClass(signature);\n    jmethodID toByteArray = env->GetMethodID(signatureClass, \"toByteArray\", \"()[B\");\n    jbyteArray signatureBytes = (jbyteArray)env->CallObjectMethod(signature, toByteArray);\n    \n    // Calculate SHA-256\n    jclass messageDigestClass = env->FindClass(\"java/security/MessageDigest\");\n    jmethodID getInstance = env->GetStaticMethodID(messageDigestClass, \"getInstance\", \"(Ljava/lang/String;)Ljava/security/MessageDigest;\");\n    jobject messageDigest = env->CallStaticObjectMethod(messageDigestClass, getInstance, env->NewStringUTF(\"SHA-256\"));\n    \n    jmethodID digestMethod = env->GetMethodID(messageDigestClass, \"digest\", \"([B)[B\");\n    jbyteArray hashBytes = (jbyteArray)env->CallObjectMethod(messageDigest, digestMethod, signatureBytes);\n    \n    return bytesToHex(env, hashBytes);\n}\n\nstd::string getPackageName(JNIEnv *env, jobject context) {\n    jclass contextClass = env->GetObjectClass(context);\n    jmethodID getPackageNameMethod = env->GetMethodID(contextClass, \"getPackageName\", \"()Ljava/lang/String;\");\n    jstring packageName = (jstring)env->CallObjectMethod(context, getPackageNameMethod);\n    \n    const char *pkgNameChars = env->GetStringUTFChars(packageName, 0);\n    std::string pkgNameStr(pkgNameChars);\n    env->ReleaseStringUTFChars(packageName, pkgNameChars);\n    \n    return pkgNameStr;\n}\n\njstring nativeGetApiToken(JNIEnv *env, jobject thiz, jobject context) {\n    if (context == nullptr) {\n        LOGE(\"Context is null\");\n        return env->NewStringUTF(\"\");\n    }\n    \n    // 1. Verify Package Name\n    std::string currentPkg = getPackageName(env, context);\n    std::string expectedPkg = XSTR(APP_PACKAGE_NAME);\n    \n    // Simple protection: if expected is empty (not set), fail safe or warn?\n    // User said \"verify software package name\".\n    if (expectedPkg.empty()) {\n        LOGE(\"Expected package name is not set in build config\");\n         // For now allow it if not configured? No, user wants security.\n         // But during development, maybe they didn't set it.\n         // Let's assume strict.\n    }\n    \n    if (currentPkg != expectedPkg) {\n        LOGE(\"Package name mismatch! Expected: %s, Got: %s\", expectedPkg.c_str(), currentPkg.c_str());\n        return env->NewStringUTF(\"\");\n    }\n    \n    // 2. Verify Signature\n    std::string currentHash = getSignatureHash(env, context);\n    std::string expectedHash = XSTR(APP_SIGNATURE_HASH); // SHA-256 hex string\n    \n    if (expectedHash.empty()) {\n         LOGI(\"Expected signature hash is empty, skipping verification (Development Mode)\");\n    } else {\n        // Case insensitive comparison\n        if (strcasecmp(currentHash.c_str(), expectedHash.c_str()) != 0) {\n            LOGE(\"Signature mismatch! Expected: %s, Got: %s\", expectedHash.c_str(), currentHash.c_str());\n            return env->NewStringUTF(\"\");\n        }\n    }\n    \n    // 3. Return Token\n    return env->NewStringUTF(XSTR(API_TOKEN));\n}\n"
  },
  {
    "path": "app/src/main/cpp/security.hpp",
    "content": "#pragma once\n\n#include <jni.h>\n\n#define XSTR(s) STR(s)\n#define STR(s) #s\n\njstring nativeGetApiToken(JNIEnv *env, jobject thiz, jobject context);\n"
  },
  {
    "path": "app/src/main/cpp/supercall.h",
    "content": "/* SPDX-License-Identifier: GPL-2.0-or-later */\n/* \n * Copyright (C) 2023 bmax121. All Rights Reserved.\n */\n\n#ifndef _KPU_SUPERCALL_H_\n#define _KPU_SUPERCALL_H_\n\n#include <unistd.h>\n#include <sys/syscall.h>\n#include <stdbool.h>\n#include <stddef.h>\n#include <string.h>\n#include <errno.h>\n\n#include \"uapi/scdefs.h\"\n#include \"version\"\n\n/// KernelPatch version is greater than or equal to 0x0a05\nstatic inline long ver_and_cmd(const char *key, long cmd)\n{\n    uint32_t version_code = (MAJOR << 16) + (MINOR << 8) + PATCH;\n    return ((long)version_code << 32) | (0x1158 << 16) | (cmd & 0xFFFF);\n}\n\n/**\n * @brief If KernelPatch installed, @see SUPERCALL_HELLO_ECHO will echoed.\n * \n * @param key : superkey or 'su' string if caller uid is su allowed \n * @return long \n */\nstatic inline long sc_hello(const char *key)\n{\n    if (!key || !key[0]) return -EINVAL;\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_HELLO));\n    return ret;\n}\n\n/**\n * @brief Is KernelPatch installed?\n * \n * @param key : superkey or 'su' string if caller uid is su allowed \n * @return true \n * @return false \n */\nstatic inline bool sc_ready(const char *key)\n{\n    return sc_hello(key) == SUPERCALL_HELLO_MAGIC;\n}\n\n/**\n * @brief Print messages by printk in the kernel\n * \n * @param key : superkey or 'su' string if caller uid is su allowed \n * @param msg \n * @return long \n */\nstatic inline long sc_klog(const char *key, const char *msg)\n{\n    if (!key || !key[0]) return -EINVAL;\n    if (!msg || strlen(msg) <= 0) return -EINVAL;\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_KLOG), msg);\n    return ret;\n}\n\n/**\n * @brief Print build kernel time\n * \n * @param key : superkey or 'su' string if caller uid is su allowed \n * @param buildtime \n * @param timestamp\n * @return long \n */\n static inline long sc_get_build_time(const char *key, const char *buildtime, size_t len)\n {\n     if (!key || !key[0]) return -EINVAL;\n     if (!buildtime) return -EINVAL;\n     long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_BUILD_TIME), buildtime,len);\n     return ret;\n }\n\n/**\n * @brief KernelPatch version number\n * \n * @param key \n * @return uint32_t \n */\nstatic inline uint32_t sc_kp_ver(const char *key)\n{\n    if (!key || !key[0]) return -EINVAL;\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_KERNELPATCH_VER));\n    return (uint32_t)ret;\n}\n\n/**\n * @brief Kernel version number\n * \n * @param key : superkey or 'su' string if caller uid is su allowed \n * @return uint32_t \n */\nstatic inline uint32_t sc_k_ver(const char *key)\n{\n    if (!key || !key[0]) return -EINVAL;\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_KERNEL_VER));\n    return (uint32_t)ret;\n}\n\n/**\n * @brief Substitute user of current thread\n * \n * @param key : superkey or 'su' string if caller uid is su allowed \n * @param profile : if scontext is invalid or illegal, all selinux permission checks will bypass via hook\n * @see struct su_profile\n * @return long : 0 if succeed\n */\nstatic inline long sc_su(const char *key, struct su_profile *profile)\n{\n    if (!key || !key[0]) return -EINVAL;\n    if (strlen(profile->scontext) >= SUPERCALL_SCONTEXT_LEN) return -EINVAL;\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_SU), profile);\n    return ret;\n}\n\n/**\n * @brief Substitute user of tid specfied thread\n * \n * @param key : superkey or 'su' string if caller uid is su allowed \n * @param tid : target thread id\n * @param profile : if scontext is invalid or illegal, all selinux permission checks will bypass via hook\n * @see struct su_profile\n * @return long : 0 if succeed \n */\nstatic inline long sc_su_task(const char *key, pid_t tid, struct su_profile *profile)\n{\n    if (!key || !key[0]) return -EINVAL;\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_SU_TASK), tid, profile);\n    return ret;\n}\n\n/**\n * @brief \n * \n * @param key \n * @param gid group id\n * @param did data id\n * @param data \n * @param dlen \n * @return long \n */\nstatic inline long sc_kstorage_write(const char *key, int gid, long did, void *data, int offset, int dlen)\n{\n    if (!key || !key[0]) return -EINVAL;\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_KSTORAGE_WRITE), gid, did, data, (((long)offset << 32) | dlen));\n    return ret;\n}\n\n/**\n * @brief \n * \n * @param key \n * @param gid \n * @param did \n * @param out_data \n * @param dlen \n * @return long \n */\nstatic inline long sc_kstorage_read(const char *key, int gid, long did, void *out_data, int offset, int dlen)\n{\n    if (!key || !key[0]) return -EINVAL;\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_KSTORAGE_READ), gid, did, out_data, (((long)offset << 32) | dlen));\n    return ret;\n}\n\n\n/**\n * @brief \n * \n * @param key \n * @param gid \n * @param ids \n * @param ids_len \n * @return long numbers of listed ids\n */\nstatic inline long sc_kstorage_list_ids(const char *key, int gid, long *ids, int ids_len)\n{\n    if (!key || !key[0]) return -EINVAL;\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_KSTORAGE_LIST_IDS), gid, ids, ids_len);\n    return ret;\n}\n\n\n/**\n * @brief \n * \n * @param key \n * @param gid \n * @param did \n * @return long \n */\nstatic inline long sc_kstorage_remove(const char *key, int gid, long did)\n{\n    if (!key || !key[0]) return -EINVAL;\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_KSTORAGE_REMOVE), gid, did);\n    return ret;\n}\n\n#ifdef ANDROID\n/**\n * @brief \n * \n * @param key \n * @param uid \n * @param exclude \n * @return long \n */\nstatic inline long sc_set_ap_mod_exclude(const char *key, uid_t uid, int exclude)\n{\n    if(exclude) {\n        return sc_kstorage_write(key, KSTORAGE_EXCLUDE_LIST_GROUP, uid, &exclude, 0, sizeof(exclude));\n    } else {\n        return sc_kstorage_remove(key, KSTORAGE_EXCLUDE_LIST_GROUP, uid);\n    }\n}\n\n\n/**\n * @brief \n * \n * @param key \n * @param uid \n * @param exclude \n * @return long \n */\nstatic inline int sc_get_ap_mod_exclude(const char *key, uid_t uid)\n{\n    int exclude = 0;\n    int rc = sc_kstorage_read(key, KSTORAGE_EXCLUDE_LIST_GROUP, uid, &exclude, 0, sizeof(exclude));\n    if (rc < 0) return 0;\n    return exclude;\n}\n\n/**\n *\n */\nstatic inline int sc_list_ap_mod_exclude(const char *key, uid_t *uids, int uids_len)\n{\n    if(uids_len < 0 || uids_len > 512) return -E2BIG;\n    long ids[uids_len];\n    int rc = sc_kstorage_list_ids(key, KSTORAGE_EXCLUDE_LIST_GROUP, ids, uids_len);\n    if (rc < 0) return 0;\n    for(int i = 0; i < rc; i ++) {\n        uids[i] = (uid_t)ids[i];\n    }\n    return rc;\n}\n\n#endif\n\n/**\n * @brief Grant su permission\n * \n * @param key \n * @param profile : if scontext is invalid or illegal, all selinux permission checks will bypass via hook\n * @return long : 0 if succeed\n */\nstatic inline long sc_su_grant_uid(const char *key, struct su_profile *profile)\n{\n    if (!key || !key[0]) return -EINVAL;\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_SU_GRANT_UID), profile);\n    return ret;\n}\n\n/**\n * @brief Revoke su permission\n * \n * @param key \n * @param uid \n * @return long 0 if succeed\n */\nstatic inline long sc_su_revoke_uid(const char *key, uid_t uid)\n{\n    if (!key || !key[0]) return -EINVAL;\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_SU_REVOKE_UID), uid);\n    return ret;\n}\n\n/**\n * @brief Get numbers of su allowed uids\n * \n * @param key \n * @return long \n */\nstatic inline long sc_su_uid_nums(const char *key)\n{\n    if (!key || !key[0]) return -EINVAL;\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_SU_NUMS));\n    return ret;\n}\n\n/**\n * @brief \n * \n * @param key : superkey or 'su' string if caller uid is su allowed \n * @param buf \n * @param num \n * @return long : The numbers of uids if succeed, nagative value if failed\n */\nstatic inline long sc_su_allow_uids(const char *key, uid_t *buf, int num)\n{\n    if (!key || !key[0]) return -EINVAL;\n    if (!buf || num <= 0) return -EINVAL;\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_SU_LIST), buf, num);\n    return ret;\n}\n\n/**\n * @brief Get su profile of specified uid\n * \n * @param key \n * @param uid \n * @param out_profile \n * @return long : 0 if succeed\n */\nstatic inline long sc_su_uid_profile(const char *key, uid_t uid, struct su_profile *out_profile)\n{\n    if (!key || !key[0]) return -EINVAL;\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_SU_PROFILE), uid, out_profile);\n    return ret;\n}\n\n/**\n * @brief Get full path of current 'su' command \n * \n * @param key : superkey or 'su' string if caller uid is su allowed \n * @param out_path \n * @param path_len \n * @return long : The length of result string if succeed, negative if failed\n */\nstatic inline long sc_su_get_path(const char *key, char *out_path, int path_len)\n{\n    if (!key || !key[0]) return -EINVAL;\n    if (!out_path || path_len <= 0) return -EINVAL;\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_SU_GET_PATH), out_path, path_len);\n    return ret;\n}\n\n/**\n * @brief Reset full path of 'su' command \n * \n * @param key \n * @param path \n * @return long : 0 if succeed\n */\nstatic inline long sc_su_reset_path(const char *key, const char *path)\n{\n    if (!key || !key[0]) return -EINVAL;\n    if (!path || !path[0]) return -EINVAL;\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_SU_RESET_PATH), path);\n    return ret;\n}\n\n/**\n * @brief Get current all-allowed selinux context\n * \n * @param key : superkey or 'su' string if caller uid is su allowed  \n * @param out_sctx \n * @param sctx_len\n * @return long 0 if there is a all-allowed selinux context now\n */\nstatic inline long sc_su_get_all_allow_sctx(const char *key, char *out_sctx, int sctx_len)\n{\n    if (!key || !key[0]) return -EINVAL;\n    if (!out_sctx) return -EINVAL;\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_SU_GET_ALLOW_SCTX), out_sctx);\n    return ret;\n}\n\n/**\n * @brief Reset current all-allowed selinux context\n * \n * @param key : superkey or 'su' string if caller uid is su allowed  \n * @param sctx If sctx is empty string, clear all-allowed selinux, \n * otherwise, try to reset a new all-allowed selinux context\n * @return long 0 if succeed\n */\nstatic inline long sc_su_reset_all_allow_sctx(const char *key, const char *sctx)\n{\n    if (!key || !key[0]) return -EINVAL;\n    if (!sctx) return -EINVAL;\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_SU_SET_ALLOW_SCTX), sctx);\n    return ret;\n}\n\n/**\n * @brief Load module\n * \n * @param key : superkey\n * @param path \n * @param args \n * @param reserved \n * @return long : 0 if succeed\n */\nstatic inline long sc_kpm_load(const char *key, const char *path, const char *args, void *reserved)\n{\n    if (!key || !key[0]) return -EINVAL;\n    if (!path || strlen(path) <= 0) return -EINVAL;\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_KPM_LOAD), path, args, reserved);\n    return ret;\n}\n\n/**\n * @brief Control module with arguments \n * \n * @param key : superkey\n * @param name : module name\n * @param ctl_args : control argument\n * @param out_msg : output message buffer\n * @param outlen : buffer length of out_msg\n * @return long : 0 if succeed\n */\nstatic inline long sc_kpm_control(const char *key, const char *name, const char *ctl_args, char *out_msg, long outlen)\n{\n    if (!key || !key[0]) return -EINVAL;\n    if (!name || strlen(name) <= 0) return -EINVAL;\n    if (!ctl_args || strlen(ctl_args) <= 0) return -EINVAL;\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_KPM_CONTROL), name, ctl_args, out_msg, outlen);\n    return ret;\n}\n\n/**\n * @brief Unload module\n * \n * @param key : superkey\n * @param name : module name\n * @param reserved \n * @return long : 0 if succeed\n */\nstatic inline long sc_kpm_unload(const char *key, const char *name, void *reserved)\n{\n    if (!key || !key[0]) return -EINVAL;\n    if (!name || strlen(name) <= 0) return -EINVAL;\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_KPM_UNLOAD), name, reserved);\n    return ret;\n}\n\n/**\n * @brief Current loaded module numbers\n * \n * @param key : superkey\n * @return long\n */\nstatic inline long sc_kpm_nums(const char *key)\n{\n    if (!key || !key[0]) return -EINVAL;\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_KPM_NUMS));\n    return ret;\n}\n\n/**\n * @brief List names of current loaded modules, splited with '\\n'\n * \n * @param key : superkey\n * @param names_buf : output buffer\n * @param buf_len : the length of names_buf\n * @return long : the length of result string if succeed, negative if failed\n */\nstatic inline long sc_kpm_list(const char *key, char *names_buf, int buf_len)\n{\n    if (!key || !key[0]) return -EINVAL;\n    if (!names_buf || buf_len <= 0) return -EINVAL;\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_KPM_LIST), names_buf, buf_len);\n    return ret;\n}\n\n/**\n * @brief Get module information. \n * \n * @param key : superkey\n * @param name : module name\n * @param buf : \n * @param buf_len : \n * @return long : The length of result string if succeed, negative if failed\n */\nstatic inline long sc_kpm_info(const char *key, const char *name, char *buf, int buf_len)\n{\n    if (!key || !key[0]) return -EINVAL;\n    if (!buf || buf_len <= 0) return -EINVAL;\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_KPM_INFO), name, buf, buf_len);\n    return ret;\n}\n\n/**\n * @brief Get current superkey\n * \n * @param key : superkey\n * @param out_key \n * @param outlen \n * @return long : 0 if succeed\n */\nstatic inline long sc_skey_get(const char *key, char *out_key, int outlen)\n{\n    if (!key || !key[0]) return -EINVAL;\n    if (outlen < SUPERCALL_KEY_MAX_LEN) return -EINVAL;\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_SKEY_GET), out_key, outlen);\n    return ret;\n}\n\n/**\n * @brief Reset current superkey\n * \n * @param key : superkey\n * @param new_key \n * @return long : 0 if succeed\n */\nstatic inline long sc_skey_set(const char *key, const char *new_key)\n{\n    if (!key || !key[0]) return -EINVAL;\n    if (!new_key || !new_key[0]) return -EINVAL;\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_SKEY_SET), new_key);\n    return ret;\n}\n\n/**\n * @brief Whether to enable hash verification for root superkey.\n * \n * @param key : superkey\n * @param enable \n * @return long \n */\nstatic inline long sc_skey_root_enable(const char *key, bool enable)\n{\n    if (!key || !key[0]) return -EINVAL;\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_SKEY_ROOT_ENABLE), (long)enable);\n    return ret;\n}\n\n/**\n * @brief Get whether in safe mode\n *\n * @param key\n * @return long\n */\nstatic inline long sc_su_get_safemode(const char *key)\n{\n    if (!key || !key[0]) return -EINVAL;\n    return syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_SU_GET_SAFEMODE));\n}\n\nstatic inline long sc_su_audit_nums(const char *key)\n{\n    if (!key || !key[0]) return -EINVAL;\n    return syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_SU_AUDIT_LIST), (struct su_audit_entry *)0, 0);\n}\n\nstatic inline long sc_su_audit_list(const char *key, struct su_audit_entry *entries, int num)\n{\n    if (!key || !key[0]) return -EINVAL;\n    return syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_SU_AUDIT_LIST), entries, num);\n}\n\nstatic inline long sc_su_audit_clear(const char *key)\n{\n    if (!key || !key[0]) return -EINVAL;\n    return syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_SU_AUDIT_CLEAR));\n}\n\nstatic inline long sc_bootlog(const char *key)\n{\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_BOOTLOG));\n    return ret;\n}\n\nstatic inline long sc_panic(const char *key)\n{\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_PANIC));\n    return ret;\n}\n\nstatic inline long __sc_test(const char *key, long a1, long a2, long a3)\n{\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_TEST), a1, a2, a3);\n    return ret;\n}\n\n/**\n * @brief Set UTS namespace strings (kernel version + build time)\n *\n * @param key     : superkey\n * @param release : release string for uname -r (NULL to skip)\n * @param version : version string for uname -v (NULL to skip)\n * @return long   : 0 on success, negative errno on failure\n */\nstatic inline long sc_uts_set(const char *key, const char *release, const char *version)\n{\n    if (!key || !key[0]) return -EINVAL;\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_UTS_SET), release, version);\n    return ret;\n}\n\n/**\n * @brief Reset UTS namespace strings to their original values\n *\n * @param key : superkey\n * @return long : 0 on success, negative errno on failure\n */\nstatic inline long sc_uts_reset(const char *key)\n{\n    if (!key || !key[0]) return -EINVAL;\n    long ret = syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_UTS_RESET));\n    return ret;\n}\n\nstatic inline long sc_pathhide_add(const char *key, const char *path)\n{\n    if (!key || !key[0]) return -EINVAL;\n    if (!path || !path[0]) return -EINVAL;\n    return syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_PATHHIDE_ADD), path);\n}\n\nstatic inline long sc_pathhide_remove(const char *key, const char *path)\n{\n    if (!key || !key[0]) return -EINVAL;\n    if (!path || !path[0]) return -EINVAL;\n    return syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_PATHHIDE_REMOVE), path);\n}\n\nstatic inline long sc_pathhide_list(const char *key, char *out_buf, int outlen)\n{\n    if (!key || !key[0]) return -EINVAL;\n    if (!out_buf || outlen <= 0) return -EINVAL;\n    return syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_PATHHIDE_LIST), out_buf, outlen);\n}\n\nstatic inline long sc_pathhide_clear(const char *key)\n{\n    if (!key || !key[0]) return -EINVAL;\n    return syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_PATHHIDE_CLEAR));\n}\n\nstatic inline long sc_pathhide_enable(const char *key, int enable)\n{\n    if (!key || !key[0]) return -EINVAL;\n    return syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_PATHHIDE_ENABLE), (long)enable);\n}\n\nstatic inline long sc_pathhide_status(const char *key)\n{\n    if (!key || !key[0]) return -EINVAL;\n    return syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_PATHHIDE_STATUS));\n}\n\nstatic inline long sc_pathhide_uid_add(const char *key, int uid)\n{\n    if (!key || !key[0]) return -EINVAL;\n    return syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_PATHHIDE_UID_ADD), (long)uid);\n}\n\nstatic inline long sc_pathhide_uid_remove(const char *key, int uid)\n{\n    if (!key || !key[0]) return -EINVAL;\n    return syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_PATHHIDE_UID_REMOVE), (long)uid);\n}\n\nstatic inline long sc_pathhide_uid_list(const char *key, char *out_buf, int outlen)\n{\n    if (!key || !key[0]) return -EINVAL;\n    if (!out_buf || outlen <= 0) return -EINVAL;\n    return syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_PATHHIDE_UID_LIST), out_buf, outlen);\n}\n\nstatic inline long sc_pathhide_uid_clear(const char *key)\n{\n    if (!key || !key[0]) return -EINVAL;\n    return syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_PATHHIDE_UID_CLEAR));\n}\n\nstatic inline long sc_pathhide_uid_mode(const char *key, int enable)\n{\n    if (!key || !key[0]) return -EINVAL;\n    return syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_PATHHIDE_UID_MODE), (long)enable);\n}\n\nstatic inline long sc_pathhide_filter_system(const char *key, int enable)\n{\n    if (!key || !key[0]) return -EINVAL;\n    return syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_PATHHIDE_FILTER_SYSTEM), (long)enable);\n}\n\nstatic inline long sc_netisolate_enable(const char *key, int enable)\n{\n    if (!key || !key[0]) return -EINVAL;\n    return syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_NETISOLATE_ENABLE), (long)enable);\n}\n\nstatic inline long sc_netisolate_status(const char *key)\n{\n    if (!key || !key[0]) return -EINVAL;\n    return syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_NETISOLATE_STATUS));\n}\n\nstatic inline long sc_netisolate_uid_add(const char *key, int uid)\n{\n    if (!key || !key[0]) return -EINVAL;\n    return syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_NETISOLATE_UID_ADD), (long)uid);\n}\n\nstatic inline long sc_netisolate_uid_remove(const char *key, int uid)\n{\n    if (!key || !key[0]) return -EINVAL;\n    return syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_NETISOLATE_UID_REMOVE), (long)uid);\n}\n\nstatic inline long sc_netisolate_uid_list(const char *key, char *out_buf, int outlen)\n{\n    if (!key || !key[0]) return -EINVAL;\n    if (!out_buf || outlen <= 0) return -EINVAL;\n    return syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_NETISOLATE_UID_LIST), out_buf, outlen);\n}\n\nstatic inline long sc_netisolate_uid_clear(const char *key)\n{\n    if (!key || !key[0]) return -EINVAL;\n    return syscall(__NR_supercall, key, ver_and_cmd(key, SUPERCALL_NETISOLATE_UID_CLEAR));\n}\n\n#endif"
  },
  {
    "path": "app/src/main/cpp/type_traits.hpp",
    "content": "#pragma once\n\n#include <type_traits>\n\nnamespace lsplant {\ntemplate <class, template <class, class...> class>\nstruct is_instance : public std::false_type {};\n\ntemplate <class... Ts, template <class, class...> class U>\nstruct is_instance<U<Ts...>, U> : public std::true_type {};\n\ntemplate <class T, template <class, class...> class U>\ninline constexpr bool is_instance_v = is_instance<T, U>::value;\n}  // namespace lsplant\n"
  },
  {
    "path": "app/src/main/cpp/uapi/scdefs.h",
    "content": "/* SPDX-License-Identifier: GPL-2.0-or-later */\n/* \n * Copyright (C) 2023 bmax121. All Rights Reserved.\n */\n\n#ifndef _KP_UAPI_SCDEF_H_\n#define _KP_UAPI_SCDEF_H_\n\nstatic inline long hash_key(const char *key)\n{\n    long hash = 1000000007;\n    for (int i = 0; key[i]; i++) {\n        hash = hash * 31 + key[i];\n    }\n    return hash;\n}\n\n#define SUPERCALL_HELLO_ECHO \"hello1158\"\n\n// #define __NR_supercall __NR3264_truncate // 45\n#define __NR_supercall 45\n\n#define SUPERCALL_HELLO 0x1000\n#define SUPERCALL_KLOG 0x1004\n\n#define SUPERCALL_BUILD_TIME 0x1007\n#define SUPERCALL_KERNELPATCH_VER 0x1008\n#define SUPERCALL_KERNEL_VER 0x1009\n\n#define SUPERCALL_SKEY_GET 0x100a\n#define SUPERCALL_SKEY_SET 0x100b\n#define SUPERCALL_SKEY_ROOT_ENABLE 0x100c\n\n#define SUPERCALL_SU 0x1010\n#define SUPERCALL_SU_TASK 0x1011 // syscall(__NR_gettid)\n\n#define SUPERCALL_KPM_LOAD 0x1020\n#define SUPERCALL_KPM_UNLOAD 0x1021\n#define SUPERCALL_KPM_CONTROL 0x1022\n\n#define SUPERCALL_KPM_NUMS 0x1030\n#define SUPERCALL_KPM_LIST 0x1031\n#define SUPERCALL_KPM_INFO 0x1032\n\nstruct kernel_storage\n{\n    void *data;\n    int len;\n};\n\n#define SUPERCALL_KSTORAGE_ALLOC_GROUP 0x1040\n#define SUPERCALL_KSTORAGE_WRITE 0x1041\n#define SUPERCALL_KSTORAGE_READ 0x1042\n#define SUPERCALL_KSTORAGE_LIST_IDS 0x1043\n#define SUPERCALL_KSTORAGE_REMOVE 0x1044\n#define SUPERCALL_KSTORAGE_REMOVE_GROUP 0x1045\n\n#define SUPERCALL_UTS_SET   0x1050\n#define SUPERCALL_UTS_RESET 0x1051\n\n#define SUPERCALL_PATHHIDE_ADD    0x1060\n#define SUPERCALL_PATHHIDE_REMOVE 0x1061\n#define SUPERCALL_PATHHIDE_LIST   0x1062\n#define SUPERCALL_PATHHIDE_CLEAR  0x1063\n#define SUPERCALL_PATHHIDE_ENABLE 0x1064\n#define SUPERCALL_PATHHIDE_STATUS 0x1065\n\n#define SUPERCALL_PATHHIDE_UID_ADD    0x1066\n#define SUPERCALL_PATHHIDE_UID_REMOVE 0x1067\n#define SUPERCALL_PATHHIDE_UID_LIST   0x1068\n#define SUPERCALL_PATHHIDE_UID_CLEAR  0x1069\n#define SUPERCALL_PATHHIDE_UID_MODE   0x106A\n#define SUPERCALL_PATHHIDE_FILTER_SYSTEM 0x106B\n\n#define SUPERCALL_NETISOLATE_ENABLE     0x1070\n#define SUPERCALL_NETISOLATE_STATUS     0x1071\n#define SUPERCALL_NETISOLATE_UID_ADD    0x1072\n#define SUPERCALL_NETISOLATE_UID_REMOVE 0x1073\n#define SUPERCALL_NETISOLATE_UID_LIST   0x1074\n#define SUPERCALL_NETISOLATE_UID_CLEAR  0x1075\n\n#define KSTORAGE_SU_LIST_GROUP 0\n#define KSTORAGE_EXCLUDE_LIST_GROUP 1\n#define KSTORAGE_SU_AUDIT_GROUP 2\n#define KSTORAGE_UNUSED_GROUP_3 3\n\n#define SUPERCALL_BOOTLOG 0x10fd\n#define SUPERCALL_PANIC 0x10fe\n#define SUPERCALL_TEST 0x10ff\n\n#define SUPERCALL_KEY_MAX_LEN 0x40\n#define SUPERCALL_SCONTEXT_LEN 0x60\n\nstruct su_profile\n{\n    uid_t uid;\n    uid_t to_uid;\n    char scontext[SUPERCALL_SCONTEXT_LEN];\n};\n\n#define TASK_COMM_LEN 16\n\nstruct su_audit_entry\n{\n    uint64_t timestamp;\n    uid_t uid;\n    pid_t pid;\n    pid_t tgid;\n    uid_t to_uid;\n    char scontext[SUPERCALL_SCONTEXT_LEN];\n    char comm[TASK_COMM_LEN];\n};\n\n#ifdef ANDROID\n#define SH_PATH \"/system/bin/sh\"\n#define SU_PATH \"/system/bin/kp\"\n#define LEGACY_SU_PATH \"/system/bin/su\"\n#define ECHO_PATH \"/system/bin/echo\"\n#define KERNELPATCH_DATA_DIR \"/data/adb/kp\"\n#define KERNELPATCH_MODULE_DATA_DIR KERNELPATCH_DATA_DIR \"/modules\"\n#define APD_PATH \"/data/adb/apd\"\n#define ALL_ALLOW_SCONTEXT \"u:r:kp:s0\"\n#define ALL_ALLOW_SCONTEXT_MAGISK \"u:r:magisk:s0\"\n#define ALL_ALLOW_SCONTEXT_KERNEL \"u:r:kernel:s0\"\n#else\n#define SH_PATH \"/usr/bin/sh\"\n#define ECHO_PATH \"/usr/bin/echo\"\n#define SU_PATH \"/usr/bin/kp\"\n#define ALL_ALLOW_SCONTEXT \"u:r:kernel:s0\"\n#endif\n\n#define SU_PATH_MAX_LEN 128\n\n#define SUPERCMD \"/system/bin/truncate\"\n\n#define SAFE_MODE_FLAG_FILE \"/dev/.safemode\"\n\n#define SUPERCALL_SU_GRANT_UID 0x1100\n#define SUPERCALL_SU_REVOKE_UID 0x1101\n#define SUPERCALL_SU_NUMS 0x1102\n#define SUPERCALL_SU_LIST 0x1103\n#define SUPERCALL_SU_PROFILE 0x1104\n#define SUPERCALL_SU_GET_ALLOW_SCTX 0x1105\n#define SUPERCALL_SU_SET_ALLOW_SCTX 0x1106\n#define SUPERCALL_SU_GET_PATH 0x1110\n#define SUPERCALL_SU_RESET_PATH 0x1111\n#define SUPERCALL_SU_GET_SAFEMODE 0x1112\n\n#define SUPERCALL_SU_AUDIT_LIST 0x1120\n#define SUPERCALL_SU_AUDIT_CLEAR 0x1121\n\n#define SUPERCALL_MAX 0x1200\n\n#define SUPERCALL_RES_SUCCEED 0\n\n#define SUPERCALL_HELLO_MAGIC 0x11581158\n\n#endif\n"
  },
  {
    "path": "app/src/main/cpp/version",
    "content": "#define MAJOR 0\n#define MINOR 13\n#define PATCH 1\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/APatchApp.kt",
    "content": "package me.bmax.apatch\n\nimport android.app.Application\nimport android.content.Context\nimport android.content.Intent\nimport android.content.SharedPreferences\nimport android.os.Build\nimport android.util.Log\nimport me.bmax.apatch.util.ui.showToast\nimport androidx.core.content.edit\nimport androidx.core.net.toUri\nimport androidx.lifecycle.LiveData\nimport androidx.lifecycle.MutableLiveData\nimport com.topjohnwu.superuser.CallbackList\nimport me.bmax.apatch.ui.CrashHandleActivity\nimport me.bmax.apatch.util.APatchCli\nimport me.bmax.apatch.util.APatchKeyHelper\nimport me.bmax.apatch.ui.theme.MusicConfig\nimport me.bmax.apatch.util.MusicManager\nimport me.bmax.apatch.util.Version\nimport me.bmax.apatch.util.getRootShell\nimport me.bmax.apatch.util.rootShellForResult\nimport okhttp3.Cache\nimport okhttp3.OkHttpClient\nimport java.io.File\nimport java.util.Locale\nimport kotlin.concurrent.thread\nimport kotlin.system.exitProcess\n\nimport coil.ImageLoader\nimport coil.ImageLoaderFactory\nimport coil.disk.DiskCache\nimport coil.memory.MemoryCache\nimport coil.decode.GifDecoder\nimport coil.decode.ImageDecoderDecoder\nimport coil.util.DebugLogger\n\nlateinit var apApp: APApplication\n\nconst val TAG = \"APatch\"\n\nclass APApplication : Application(), Thread.UncaughtExceptionHandler, ImageLoaderFactory {\n    lateinit var okhttpClient: OkHttpClient\n\n    init {\n        Thread.setDefaultUncaughtExceptionHandler(this)\n    }\n\n    override fun newImageLoader(): ImageLoader {\n        return ImageLoader.Builder(this)\n            .components {\n                if (Build.VERSION.SDK_INT >= 28) {\n                    add(ImageDecoderDecoder.Factory())\n                } else {\n                    add(GifDecoder.Factory())\n                }\n            }\n            .diskCache(\n                DiskCache.Builder()\n                    .directory(cacheDir.resolve(\"image_cache\"))\n                    .maxSizeBytes(100L * 1024 * 1024)\n                    .build()\n            )\n            .memoryCache(\n                MemoryCache.Builder(this)\n                    .maxSizePercent(0.20)\n                    .build()\n            )\n            .crossfade(true)\n            .logger(DebugLogger())\n            .build()\n    }\n\n    enum class State {\n        UNKNOWN_STATE,\n\n        KERNELPATCH_INSTALLED, KERNELPATCH_NEED_UPDATE, KERNELPATCH_NEED_REBOOT, KERNELPATCH_UNINSTALLING,\n\n        ANDROIDPATCH_NOT_INSTALLED, ANDROIDPATCH_INSTALLED, ANDROIDPATCH_INSTALLING, ANDROIDPATCH_NEED_UPDATE, ANDROIDPATCH_UNINSTALLING,\n    }\n\n\n    companion object {\n        const val APD_PATH = \"/data/adb/apd\"\n\n        @Deprecated(\"No more KPatch ELF from 0.11.0-dev\")\n        const val KPATCH_PATH = \"/data/adb/kpatch\"\n        const val SUPERCMD = \"/system/bin/truncate\"\n        const val APATCH_FOLDER = \"/data/adb/ap/\"\n        private const val APATCH_BIN_FOLDER = APATCH_FOLDER + \"bin/\"\n        private const val APATCH_LOG_FOLDER = APATCH_FOLDER + \"log/\"\n        private const val APD_LINK_PATH = APATCH_BIN_FOLDER + \"apd\"\n        const val PACKAGE_CONFIG_FILE = APATCH_FOLDER + \"package_config\"\n        const val SU_PATH_FILE = APATCH_FOLDER + \"su_path\"\n        const val SAFEMODE_FILE = \"/dev/.safemode\"\n        private const val NEED_REBOOT_FILE = \"/dev/.need_reboot\"\n        const val GLOBAL_NAMESPACE_FILE = \"/data/adb/.global_namespace_enable\"\n        const val MAGIC_MOUNT_FILE = \"/data/adb/.magic_mount_enable\"\n        const val HIDE_SERVICE_FILE = \"/data/adb/.hide_service_enable\"\n        const val HIDE_BINARY_PATH = \"/data/adb/fp/bin/fpd\"\n        const val UMOUNT_SERVICE_FILE = \"/data/adb/.umount_service_enable\"\n        const val UMOUNT_BINARY_PATH = \"/data/adb/fp/bin/fpd\"\n        const val UTS_SPOOF_ENABLE_FILE = \"/data/adb/.uts_spoof_enable\"\n        const val UTS_SPOOF_CONFIG_FILE = \"/data/adb/.uts_spoof_config\"\n        const val PATHHIDE_DIR = \"/data/adb/fp/pathhide/\"\n        const val PATHHIDE_PATHS_FILE = \"/data/adb/fp/pathhide/paths\"\n        const val PATHHIDE_ENABLE_FILE = \"/data/adb/fp/pathhide/enabled\"\n        const val PATHHIDE_UIDS_FILE = \"/data/adb/fp/pathhide/uids\"\n        const val PATHHIDE_UID_MODE_FILE = \"/data/adb/fp/pathhide/uid_mode\"\n        const val PATHHIDE_FILTER_SYSTEM_FILE = \"/data/adb/fp/pathhide/filter_system\"\n        const val NETISOLATE_DIR = \"/data/adb/fp/netisolate/\"\n        const val NETISOLATE_ENABLE_FILE = \"/data/adb/fp/netisolate/enabled\"\n        const val NETISOLATE_UIDS_FILE = \"/data/adb/fp/netisolate/uids\"\n        const val KPMS_DIR = APATCH_FOLDER + \"kpms/\"\n\n        @Deprecated(\"Use SHA256 comparison instead\")\n        const val APATCH_VERSION_PATH = APATCH_FOLDER + \"version\"\n        private const val MAGISKPOLICY_BIN_PATH = APATCH_BIN_FOLDER + \"magiskpolicy\"\n        private const val BUSYBOX_BIN_PATH = APATCH_BIN_FOLDER + \"busybox\"\n        private const val RESETPROP_BIN_PATH = APATCH_BIN_FOLDER + \"resetprop\"\n        private const val KPTOOLS_BIN_PATH = APATCH_BIN_FOLDER + \"kptools\"\n        const val DEFAULT_SCONTEXT = \"u:r:untrusted_app:s0\"\n        const val MAGISK_SCONTEXT = \"u:r:magisk:s0\"\n\n        private const val DEFAULT_SU_PATH = \"/system/bin/kp\"\n        private const val LEGACY_SU_PATH = \"/system/bin/su\"\n\n        const val SP_NAME = \"config\"\n        const val PREF_BLOCK_KERNELPATCH_UPDATE = \"block_kernelpatch_update\"\n        const val PREF_BLOCK_ANDROIDPATCH_UPDATE = \"block_androidpatch_update\"\n        const val PREF_AUTO_EXCLUDE_NEW_APPS = \"auto_exclude_new_apps\"\n        const val PREF_UTS_SPOOF_ENABLED = \"uts_spoof_enabled\"\n        const val PREF_UTS_SPOOF_RELEASE = \"uts_spoof_release\"\n        const val PREF_UTS_SPOOF_VERSION = \"uts_spoof_version\"\n        private const val SHOW_BACKUP_WARN = \"show_backup_warning\"\n        private const val CRASH_COUNT_KEY = \"fp_crash_count\"\n        private const val CRASH_TIMESTAMP_KEY = \"fp_crash_timestamp\"\n        private const val CRASH_LOOP_THRESHOLD = 2\n        private const val CRASH_WINDOW_MS = 30_000L\n        lateinit var sharedPreferences: SharedPreferences\n        var isSignatureValid = true // removed signature check, always valid\n\n        private val logCallback: CallbackList<String?> = object : CallbackList<String?>() {\n            override fun onAddElement(s: String?) {\n                Log.d(TAG, s.toString())\n            }\n        }\n\n        private val _kpStateLiveData = MutableLiveData(State.UNKNOWN_STATE)\n        val kpStateLiveData: LiveData<State> = _kpStateLiveData\n\n        private val _apStateLiveData = MutableLiveData(State.UNKNOWN_STATE)\n        val apStateLiveData: LiveData<State> = _apStateLiveData\n\n        @Suppress(\"DEPRECATION\")\n        fun uninstallApatch() {\n            if (_apStateLiveData.value != State.ANDROIDPATCH_INSTALLED) return\n            _apStateLiveData.value = State.ANDROIDPATCH_UNINSTALLING\n\n            Natives.resetSuPath(DEFAULT_SU_PATH)\n\n            val cmds = arrayOf(\n                \"rm -f $APD_PATH\",\n                \"rm -f $KPATCH_PATH\",\n                \"rm -rf $APATCH_BIN_FOLDER\",\n                \"rm -rf $APATCH_LOG_FOLDER\",\n                \"rm -rf $APATCH_VERSION_PATH\",\n            )\n\n            val shell = getRootShell()\n            shell.newJob().add(*cmds).to(logCallback, logCallback).exec()\n\n            Log.d(TAG, \"APatch uninstalled...\")\n            if (_kpStateLiveData.value == State.UNKNOWN_STATE) {\n                _apStateLiveData.postValue(State.UNKNOWN_STATE)\n            } else {\n                _apStateLiveData.postValue(State.ANDROIDPATCH_NOT_INSTALLED)\n            }\n        }\n\n        @Suppress(\"DEPRECATION\")\n        fun installApatch() {\n            val state = _apStateLiveData.value\n            if (state == State.ANDROIDPATCH_INSTALLING) {\n                return\n            }\n            _apStateLiveData.value = State.ANDROIDPATCH_INSTALLING\n            val nativeDir = apApp.applicationInfo.nativeLibraryDir\n\n            val cmds = arrayOf(\n                \"mkdir -p $APATCH_BIN_FOLDER\",\n                \"mkdir -p $APATCH_LOG_FOLDER\",\n\n                \"rm -f $APD_PATH\",\n                \"cp -f ${nativeDir}/libapd.so $APD_PATH\",\n                \"chmod +x $APD_PATH\",\n                \"ln -sf $APD_PATH $APD_LINK_PATH\",\n                \"restorecon $APD_PATH\",\n\n                \"rm -f $MAGISKPOLICY_BIN_PATH\",\n                \"cp -f ${nativeDir}/libmagiskpolicy.so $MAGISKPOLICY_BIN_PATH\",\n                \"chmod +x $MAGISKPOLICY_BIN_PATH\",\n                \"rm -f $RESETPROP_BIN_PATH\",\n                \"cp -f ${nativeDir}/libresetprop.so $RESETPROP_BIN_PATH\",\n                \"chmod +x $RESETPROP_BIN_PATH\",\n                \"rm -f $BUSYBOX_BIN_PATH\",\n                \"cp -f ${nativeDir}/libbusybox.so $BUSYBOX_BIN_PATH\",\n                \"chmod +x $BUSYBOX_BIN_PATH\",\n                \"cp -f ${nativeDir}/libkptools.so $KPTOOLS_BIN_PATH\",\n                \"chmod +x $KPTOOLS_BIN_PATH\",\n\n                \"touch $PACKAGE_CONFIG_FILE\",\n                \"touch $SU_PATH_FILE\",\n                \"[ -s $SU_PATH_FILE ] || echo $LEGACY_SU_PATH > $SU_PATH_FILE\",\n                \"echo ${Version.getManagerVersion().second} > $APATCH_VERSION_PATH\",\n                \"restorecon -R $APATCH_FOLDER\",\n\n                \"${nativeDir}/libmagiskpolicy.so --magisk --live\",\n            )\n\n            val shell = getRootShell()\n            shell.newJob().add(*cmds).to(logCallback, logCallback).exec()\n\n            Natives.resetSuPath(DEFAULT_SU_PATH)\n            Natives.resetSuPath(LEGACY_SU_PATH)\n\n            // clear shell cache\n            APatchCli.refresh()\n\n            Log.d(TAG, \"APatch installed...\")\n            _apStateLiveData.postValue(State.ANDROIDPATCH_INSTALLED)\n        }\n\n        fun markNeedReboot() {\n            val result = rootShellForResult(\"touch $NEED_REBOOT_FILE\")\n            _kpStateLiveData.postValue(State.KERNELPATCH_NEED_REBOOT)\n            Log.d(TAG, \"mark reboot ${result.code}\")\n        }\n\n\n        private var _superKey: String = \"\"\n\n        var superKey: String\n            get() = _superKey\n            private set(value) {\n                _superKey = value\n            }\n\n        /**\n         * Update superKey without triggering the full init chain.\n         * Use for PATCH_ONLY mode to keep the home status card unchanged.\n         */\n        fun updateSuperKeyQuietly(key: String) {\n            _superKey = key\n            APatchKeyHelper.writeSPSuperKey(key)\n        }\n\n        /**\n         * Set superKey and refresh the entire state detection chain.\n         * Use when KernelPatch is actually installed or the app starts up.\n         */\n        fun setSuperKeyAndRefresh(value: String) {\n            _superKey = value\n            // Run entire init chain on a background thread to avoid blocking main thread\n            thread(name = \"superkey-init\") {\n                val ready = BuildConfig.DEBUG_FAKE_ROOT || Natives.nativeReady(value)\n                _kpStateLiveData.postValue(\n                    if (ready) State.KERNELPATCH_INSTALLED else State.UNKNOWN_STATE\n                )\n                Log.d(TAG, \"state: \" + _kpStateLiveData.value)\n                if (!ready) return@thread\n\n                APatchKeyHelper.writeSPSuperKey(value)\n\n                val rc = BuildConfig.DEBUG_FAKE_ROOT || Natives.su(0, null)\n                if (!rc) {\n                    Log.e(TAG, \"Native.su failed\")\n                    return@thread\n                }\n\n                // Refresh shell after becoming root\n                APatchCli.refresh()\n\n                // KernelPatch version\n                //use build time to check update\n                val buildV = Version.getKpImg()\n                val installedV = Version.installedKPTime()\n\n\n                Log.d(TAG, \"kp installed version: ${installedV}, build version: $buildV\")\n\n                // use != instead of > to enable downgrade,\n                // Check if update notification is blocked\n                val isBlocked = apApp.isKernelPatchUpdateBlocked()\n\n                if (buildV != installedV) {\n                    if (isBlocked) {\n                        _kpStateLiveData.postValue(State.KERNELPATCH_INSTALLED)\n                    } else {\n                        _kpStateLiveData.postValue(State.KERNELPATCH_NEED_UPDATE)\n                    }\n                }\n                Log.d(TAG, \"kp state: \" + _kpStateLiveData.value)\n\n                if (File(NEED_REBOOT_FILE).exists()) {\n                    _kpStateLiveData.postValue(State.KERNELPATCH_NEED_REBOOT)\n                }\n                Log.d(TAG, \"kp state: \" + _kpStateLiveData.value)\n\n                // AndroidPatch version\n                val bundledHash = Version.getBundledApdSha256()\n                val installedHash = Version.getInstalledApdSha256()\n                Log.d(TAG, \"bundled apd sha256: $bundledHash, installed apd sha256: $installedHash\")\n\n                val isApBlocked = apApp.isAndroidPatchUpdateBlocked()\n\n                if (BuildConfig.DEBUG_FAKE_ROOT || installedHash.isNotEmpty()) {\n                    if (bundledHash == installedHash) {\n                        _apStateLiveData.postValue(State.ANDROIDPATCH_INSTALLED)\n                    } else {\n                        if (isApBlocked) {\n                            _apStateLiveData.postValue(State.ANDROIDPATCH_INSTALLED)\n                        } else {\n                            _apStateLiveData.postValue(State.ANDROIDPATCH_NEED_UPDATE)\n                        }\n                    }\n                } else {\n                    _apStateLiveData.postValue(State.ANDROIDPATCH_NOT_INSTALLED)\n                }\n                Log.d(TAG, \"ap state: \" + _apStateLiveData.value)\n            }\n        }\n\n        private fun bypassHiddenApiRestrictions() {\n            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) return\n            try {\n                val forName = Class::class.java.getDeclaredMethod(\"forName\", String::class.java)\n                val getDeclaredMethod = Class::class.java.getDeclaredMethod(\n                    \"getDeclaredMethod\", String::class.java, Array<Any>::class.java\n                )\n                val vmRuntimeClass = forName.invoke(null, \"dalvik.system.VMRuntime\") as Class<*>\n                val getRuntime = getDeclaredMethod.invoke(vmRuntimeClass, \"getRuntime\", null) as java.lang.reflect.Method\n                val setHiddenApiExemptions = getDeclaredMethod.invoke(\n                    vmRuntimeClass, \"setHiddenApiExemptions\", arrayOf(Array<String>::class.java)\n                ) as java.lang.reflect.Method\n                val vmRuntime = getRuntime.invoke(null)\n                setHiddenApiExemptions.invoke(vmRuntime, arrayOf(\"L\"))\n                Log.d(TAG, \"Hidden API bypass applied successfully\")\n            } catch (e: Exception) {\n                Log.e(TAG, \"Failed to bypass hidden API restrictions\", e)\n            }\n        }\n    }\n\n    override fun onCreate() {\n        super.onCreate()\n        apApp = this\n        sharedPreferences = getSharedPreferences(SP_NAME, Context.MODE_PRIVATE)\n        APatchKeyHelper.setSharedPreferences(sharedPreferences)\n        val earlySavedKey = try {\n            APatchKeyHelper.readSPSuperKey()\n        } catch (_: Exception) {\n            null\n        }\n        _superKey = earlySavedKey.takeUnless { it.isNullOrEmpty() } ?: \"su\"\n        if (Application.getProcessName().endsWith(\":root\") || Application.getProcessName().endsWith(\":webui\")) {\n            return\n        }\n        bypassHiddenApiRestrictions()\n        Log.d(TAG, \"APApplication onCreate started\")\n\n        val isArm64 = Build.SUPPORTED_ABIS.any { it == \"arm64-v8a\" }\n        Log.d(TAG, \"Device architecture check: isArm64=$isArm64, supported ABIs=${Build.SUPPORTED_ABIS.joinToString(\", \")}\")\n        if (!isArm64) {\n            Log.e(TAG, \"Unsupported architecture!\")\n            showToast(applicationContext, \"Unsupported architecture!\")\n            Thread.sleep(5000)\n            exitProcess(0)\n        }\n\n        Log.d(TAG, \"Initializing SharedPreferences...\")\n        sharedPreferences = getSharedPreferences(SP_NAME, Context.MODE_PRIVATE)\n        \n        // 初始化默认主题设置 - 只在第一次安装时设置\n        if (!sharedPreferences.contains(\"app_initialized\")) {\n            sharedPreferences.edit()\n                .putBoolean(\"app_initialized\", true)\n                .putBoolean(\"night_mode_enabled\", true)\n                .putBoolean(\"night_mode_follow_sys\", true)\n                .putBoolean(\"use_system_color_theme\", true)\n                .putString(\"custom_color\", \"indigo\")\n                .putString(\"home_layout_style\", \"circle\")\n                .apply()\n        }\n        \n        APatchKeyHelper.setSharedPreferences(sharedPreferences)\n        me.bmax.apatch.util.LauncherIconUtils.applySaved(this)\n        Log.d(TAG, \"Reading superKey...\")\n        val savedKey = try {\n            APatchKeyHelper.readSPSuperKey()\n        } catch (e: Exception) {\n            Log.e(TAG, \"Failed to read superKey from SharedPreferences\", e)\n            null\n        }\n        val key = savedKey.takeUnless { it.isNullOrEmpty() } ?: \"su\"\n        setSuperKeyAndRefresh(key)\n        Log.d(TAG, \"superKey read completed, length=${superKey.length}\")\n\n        Log.d(TAG, \"Initializing OkHttpClient...\")\n        okhttpClient =\n            OkHttpClient.Builder().cache(Cache(File(cacheDir, \"okhttp\"), 10 * 1024 * 1024))\n                .addInterceptor { block ->\n                    block.proceed(\n                        block.request().newBuilder()\n                            .header(\"User-Agent\", \"APatch/${BuildConfig.VERSION_CODE}\")\n                            .header(\"Accept-Language\", Locale.getDefault().toLanguageTag()).build()\n                    )\n                }.build()\n\n        me.bmax.apatch.util.FolkApiClient.prefetch(\n            \"https://folk.mysqil.com/api/version\"\n        )\n\n        // Initialize Music\n        MusicConfig.load(this)\n        \n        // Initialize Sound Effect\n        me.bmax.apatch.ui.theme.SoundEffectConfig.load(this)\n\n        // Initialize Vibration\n        me.bmax.apatch.ui.theme.VibrationConfig.load(this)\n\n        MusicManager.init(this)\n        \n        Log.d(TAG, \"APApplication onCreate completed\")\n\n        // Reset crash counter on successful initialization\n        sharedPreferences.edit()\n            .remove(CRASH_COUNT_KEY)\n            .remove(CRASH_TIMESTAMP_KEY)\n            .apply()\n    }\n\n    fun getBackupWarningState(): Boolean {\n        return sharedPreferences.getBoolean(SHOW_BACKUP_WARN, true)\n    }\n\n    fun isKernelPatchUpdateBlocked(): Boolean {\n        return sharedPreferences.getBoolean(PREF_BLOCK_KERNELPATCH_UPDATE, false)\n    }\n\n    fun isAndroidPatchUpdateBlocked(): Boolean {\n        return sharedPreferences.getBoolean(PREF_BLOCK_ANDROIDPATCH_UPDATE, false)\n    }\n\n    fun updateBackupWarningState(state: Boolean) {\n        sharedPreferences.edit { putBoolean(SHOW_BACKUP_WARN, state) }\n    }\n\n    override fun uncaughtException(t: Thread, e: Throwable) {\n        val exceptionMessage = Log.getStackTraceString(e)\n        val threadName = t.name\n        Log.e(TAG, \"Error on thread $threadName:\\n $exceptionMessage\")\n\n        val now = System.currentTimeMillis()\n        val prefs = getSharedPreferences(SP_NAME, Context.MODE_PRIVATE)\n        val lastCrashTime = prefs.getLong(CRASH_TIMESTAMP_KEY, 0L)\n        val crashCount = if (now - lastCrashTime < CRASH_WINDOW_MS) {\n            prefs.getInt(CRASH_COUNT_KEY, 0) + 1\n        } else {\n            1\n        }\n        prefs.edit()\n            .putInt(CRASH_COUNT_KEY, crashCount)\n            .putLong(CRASH_TIMESTAMP_KEY, now)\n            .commit()\n\n        if (crashCount <= CRASH_LOOP_THRESHOLD) {\n            val intent = Intent(this, CrashHandleActivity::class.java).apply {\n                putExtra(\"exception_message\", exceptionMessage)\n                putExtra(\"thread\", threadName)\n                flags = Intent.FLAG_ACTIVITY_NEW_TASK\n            }\n            startActivity(intent)\n        } else {\n            Log.e(TAG, \"Crash loop detected ($crashCount crashes in ${CRASH_WINDOW_MS}ms window). \" +\n                    \"Skipping CrashHandleActivity to prevent infinite loop.\")\n        }\n        exitProcess(10)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/Natives.kt",
    "content": "package me.bmax.apatch\n\nimport android.os.Parcelable\nimport android.content.Context\nimport androidx.annotation.Keep\nimport androidx.compose.runtime.Immutable\nimport dalvik.annotation.optimization.FastNative\nimport kotlinx.parcelize.Parcelize\n\nobject Natives {\n    init {\n        System.loadLibrary(\"apjni\")\n    }\n\n    @Immutable\n    @Parcelize\n    @Keep\n    data class Profile(\n        var uid: Int = 0,\n        var toUid: Int = 0,\n        var scontext: String = APApplication.DEFAULT_SCONTEXT,\n    ) : Parcelable\n\n    @Keep\n    class KPMCtlRes {\n        var rc: Long = 0\n        var outMsg: String? = null\n\n        constructor()\n\n        constructor(rc: Long, outMsg: String?) {\n            this.rc = rc\n            this.outMsg = outMsg\n        }\n    }\n\n\n    @FastNative\n    private external fun nativeSu(superKey: String, toUid: Int, scontext: String?): Long\n\n    fun su(toUid: Int, scontext: String?): Boolean {\n        return nativeSu(APApplication.superKey, toUid, scontext) == 0L\n    }\n\n    fun su(): Boolean {\n        return su(0, \"\")\n    }\n\n    @FastNative\n    external fun nativeReady(superKey: String): Boolean\n\n    @FastNative\n    private external fun nativeSuPath(superKey: String): String\n\n    fun suPath(): String {\n        return nativeSuPath(APApplication.superKey)\n    }\n\n    @FastNative\n    private external fun nativeSuUids(superKey: String): IntArray\n\n    fun suUids(): IntArray {\n        return nativeSuUids(APApplication.superKey)\n    }\n\n    @FastNative\n    private external fun nativeKernelPatchVersion(superKey: String): Long\n    fun kernelPatchVersion(): Long {\n        return nativeKernelPatchVersion(APApplication.superKey)\n    }\n\n    @FastNative\n    private external fun nativeKernelPatchBuildTime(superKey: String): String\n    fun kernelPatchBuildTime(): String {\n        return nativeKernelPatchBuildTime(APApplication.superKey)\n    }\n\n    private external fun nativeLoadKernelPatchModule(\n        superKey: String, modulePath: String, args: String\n    ): Long\n\n    fun loadKernelPatchModule(modulePath: String, args: String): Long {\n        return nativeLoadKernelPatchModule(APApplication.superKey, modulePath, args)\n    }\n\n    private external fun nativeUnloadKernelPatchModule(superKey: String, moduleName: String): Long\n    fun unloadKernelPatchModule(moduleName: String): Long {\n        return nativeUnloadKernelPatchModule(APApplication.superKey, moduleName)\n    }\n\n    @FastNative\n    private external fun nativeKernelPatchModuleNum(superKey: String): Long\n\n    fun kernelPatchModuleNum(): Long {\n        return nativeKernelPatchModuleNum(APApplication.superKey)\n    }\n\n    @FastNative\n    private external fun nativeKernelPatchModuleList(superKey: String): String\n    fun kernelPatchModuleList(): String {\n        return nativeKernelPatchModuleList(APApplication.superKey)\n    }\n\n    @FastNative\n    private external fun nativeKernelPatchModuleInfo(superKey: String, moduleName: String): String\n    fun kernelPatchModuleInfo(moduleName: String): String {\n        return nativeKernelPatchModuleInfo(APApplication.superKey, moduleName)\n    }\n\n    private external fun nativeControlKernelPatchModule(\n        superKey: String, modName: String, jctlargs: String\n    ): KPMCtlRes\n\n    fun kernelPatchModuleControl(moduleName: String, controlArg: String): KPMCtlRes {\n        return nativeControlKernelPatchModule(APApplication.superKey, moduleName, controlArg)\n    }\n\n    @FastNative\n    private external fun nativeGrantSu(\n        superKey: String, uid: Int, toUid: Int, scontext: String?\n    ): Long\n\n    fun grantSu(uid: Int, toUid: Int, scontext: String?): Long {\n        return nativeGrantSu(APApplication.superKey, uid, toUid, scontext)\n    }\n\n    @FastNative\n    private external fun nativeRevokeSu(superKey: String, uid: Int): Long\n    fun revokeSu(uid: Int): Long {\n        return nativeRevokeSu(APApplication.superKey, uid)\n    }\n\n    @FastNative\n    private external fun nativeSetUidExclude(superKey: String, uid: Int, exclude: Int): Int\n    fun setUidExclude(uid: Int, exclude: Int): Int {\n        return nativeSetUidExclude(APApplication.superKey, uid, exclude)\n    }\n\n    @FastNative\n    private external fun nativeGetUidExclude(superKey: String, uid: Int): Int\n    fun isUidExcluded(uid: Int): Int {\n        return nativeGetUidExclude(APApplication.superKey, uid)\n    }\n\n    @FastNative\n    private external fun nativeSetNewAppProfileMode(superKey: String, mode: Int): Long\n    fun setNewAppProfileMode(mode: Int): Long {\n        return nativeSetNewAppProfileMode(APApplication.superKey, mode)\n    }\n\n    @FastNative\n    private external fun nativeGetNewAppProfileMode(superKey: String): Int\n    fun getNewAppProfileMode(): Int {\n        return nativeGetNewAppProfileMode(APApplication.superKey)\n    }\n\n    @FastNative\n    private external fun nativeSuProfile(superKey: String, uid: Int): Profile\n    fun suProfile(uid: Int): Profile {\n        return nativeSuProfile(APApplication.superKey, uid)\n    }\n\n    @FastNative\n    private external fun nativeResetSuPath(superKey: String, path: String): Boolean\n    fun resetSuPath(path: String): Boolean {\n        return nativeResetSuPath(APApplication.superKey, path)\n    }\n\n    @FastNative\n    private external fun nativeUtsSet(superKey: String, release: String?, version: String?): Long\n    fun utsSet(release: String?, version: String?): Long {\n        return nativeUtsSet(APApplication.superKey, release, version)\n    }\n\n    @FastNative\n    private external fun nativeUtsReset(superKey: String): Long\n    fun utsReset(): Long {\n        return nativeUtsReset(APApplication.superKey)\n    }\n\n    @FastNative\n    private external fun nativePathHideAdd(superKey: String, path: String): Long\n    fun pathHideAdd(path: String): Long {\n        return nativePathHideAdd(APApplication.superKey, path)\n    }\n\n    @FastNative\n    private external fun nativePathHideRemove(superKey: String, path: String): Long\n    fun pathHideRemove(path: String): Long {\n        return nativePathHideRemove(APApplication.superKey, path)\n    }\n\n    @FastNative\n    private external fun nativePathHideList(superKey: String): String\n    fun pathHideList(): String {\n        return nativePathHideList(APApplication.superKey)\n    }\n\n    @FastNative\n    private external fun nativePathHideClear(superKey: String): Long\n    fun pathHideClear(): Long {\n        return nativePathHideClear(APApplication.superKey)\n    }\n\n    @FastNative\n    private external fun nativePathHideEnable(superKey: String, enable: Int): Long\n    fun pathHideEnable(enable: Boolean): Long {\n        return nativePathHideEnable(APApplication.superKey, if (enable) 1 else 0)\n    }\n\n    @FastNative\n    private external fun nativePathHideStatus(superKey: String): Long\n    fun pathHideStatus(): Long {\n        return nativePathHideStatus(APApplication.superKey)\n    }\n\n    @FastNative\n    private external fun nativePathHideUidAdd(superKey: String, uid: Int): Long\n    fun pathHideUidAdd(uid: Int): Long {\n        return nativePathHideUidAdd(APApplication.superKey, uid)\n    }\n\n    @FastNative\n    private external fun nativePathHideUidRemove(superKey: String, uid: Int): Long\n    fun pathHideUidRemove(uid: Int): Long {\n        return nativePathHideUidRemove(APApplication.superKey, uid)\n    }\n\n    @FastNative\n    private external fun nativePathHideUidList(superKey: String): String\n    fun pathHideUidList(): String {\n        return nativePathHideUidList(APApplication.superKey)\n    }\n\n    @FastNative\n    private external fun nativePathHideUidClear(superKey: String): Long\n    fun pathHideUidClear(): Long {\n        return nativePathHideUidClear(APApplication.superKey)\n    }\n\n    @FastNative\n    private external fun nativePathHideUidMode(superKey: String, enable: Int): Long\n    fun pathHideUidMode(enable: Boolean): Long {\n        return nativePathHideUidMode(APApplication.superKey, if (enable) 1 else 0)\n    }\n\n    @FastNative\n    private external fun nativePathHideFilterSystem(superKey: String, enable: Int): Long\n    fun pathHideFilterSystem(enable: Boolean): Long {\n        return nativePathHideFilterSystem(APApplication.superKey, if (enable) 1 else 0)\n    }\n\n    @FastNative\n    private external fun nativeNetIsolateEnable(superKey: String, enable: Int): Long\n    fun netIsolateEnable(enable: Boolean): Long {\n        return nativeNetIsolateEnable(APApplication.superKey, if (enable) 1 else 0)\n    }\n\n    @FastNative\n    private external fun nativeNetIsolateStatus(superKey: String): Long\n    fun netIsolateStatus(): Long {\n        return nativeNetIsolateStatus(APApplication.superKey)\n    }\n\n    @FastNative\n    private external fun nativeNetIsolateUidAdd(superKey: String, uid: Int): Long\n    fun netIsolateUidAdd(uid: Int): Long {\n        return nativeNetIsolateUidAdd(APApplication.superKey, uid)\n    }\n\n    @FastNative\n    private external fun nativeNetIsolateUidRemove(superKey: String, uid: Int): Long\n    fun netIsolateUidRemove(uid: Int): Long {\n        return nativeNetIsolateUidRemove(APApplication.superKey, uid)\n    }\n\n    @FastNative\n    private external fun nativeNetIsolateUidList(superKey: String): String\n    fun netIsolateUidList(): String {\n        return nativeNetIsolateUidList(APApplication.superKey)\n    }\n\n    @FastNative\n    private external fun nativeNetIsolateUidClear(superKey: String): Long\n    fun netIsolateUidClear(): Long {\n        return nativeNetIsolateUidClear(APApplication.superKey)\n    }\n\n    @FastNative\n    private external fun nativeSuAuditList(superKey: String): String\n    fun suAuditList(): String {\n        return nativeSuAuditList(APApplication.superKey)\n    }\n\n    @FastNative\n    private external fun nativeSuAuditClear(superKey: String): Long\n    fun suAuditClear(): Long {\n        return nativeSuAuditClear(APApplication.superKey)\n    }\n\n    external fun nativeGetApiToken(context: Context): String\n    fun getApiToken(context: Context): String {\n        return nativeGetApiToken(context)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/data/ScriptInfo.kt",
    "content": "package me.bmax.apatch.data\n\nimport android.os.Parcelable\nimport kotlinx.parcelize.Parcelize\nimport java.util.UUID\n\n@Parcelize\ndata class ScriptInfo(\n    val id: String = UUID.randomUUID().toString(),\n    val path: String,\n    val alias: String,\n    val createdAt: Long = System.currentTimeMillis()\n) : Parcelable\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/services/RootServices.java",
    "content": "package me.bmax.apatch.services;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageManager;\nimport android.os.IBinder;\nimport android.os.UserHandle;\nimport android.os.UserManager;\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\n\nimport com.topjohnwu.superuser.ipc.RootService;\n\nimport java.lang.reflect.Method;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport me.bmax.apatch.IAPRootService;\nimport rikka.parcelablelist.ParcelableListSlice;\n\npublic class RootServices extends RootService {\n    private static final String TAG = \"RootServices\";\n\n    @Override\n    public IBinder onBind(@NonNull Intent intent) {\n        return new Stub();\n    }\n\n    List<Integer> getUserIds() {\n        List<Integer> result = new ArrayList<>();\n        try {\n            UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);\n            if (um != null) {\n                List<UserHandle> userProfiles = um.getUserProfiles();\n                if (userProfiles != null) {\n                    for (UserHandle userProfile : userProfiles) {\n                        result.add(userProfile.hashCode());\n                    }\n                }\n            }\n        } catch (Throwable e) {\n            Log.e(TAG, \"getUserIds failed\", e);\n            // Fallback to current user if UserManager fails\n            result.add(0); \n        }\n        return result;\n    }\n\n    ArrayList<PackageInfo> getInstalledPackagesAll(int flags) {\n        ArrayList<PackageInfo> packages = new ArrayList<>();\n        for (Integer userId : getUserIds()) {\n            Log.i(TAG, \"getInstalledPackagesAll: \" + userId);\n            packages.addAll(getInstalledPackagesAsUser(flags, userId));\n        }\n        return packages;\n    }\n\n    List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) {\n        try {\n            PackageManager pm = getPackageManager();\n            Method getInstalledPackagesAsUser = pm.getClass().getDeclaredMethod(\"getInstalledPackagesAsUser\", int.class, int.class);\n            return (List<PackageInfo>) getInstalledPackagesAsUser.invoke(pm, flags, userId);\n        } catch (Throwable e) {\n            Log.e(TAG, \"err\", e);\n        }\n\n        return new ArrayList<>();\n    }\n\n    class Stub extends IAPRootService.Stub {\n        @Override\n        public ParcelableListSlice<PackageInfo> getPackages(int flags) {\n            try {\n                List<PackageInfo> list = getInstalledPackagesAll(flags);\n                Log.i(TAG, \"getPackages: \" + list.size());\n                return new ParcelableListSlice<>(list);\n            } catch (Throwable e) {\n                Log.e(TAG, \"getPackages failed\", e);\n                return new ParcelableListSlice<>(new ArrayList<>());\n            }\n        }\n\n    }\n}"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/CrashHandleActivity.kt",
    "content": "package me.bmax.apatch.ui\n\nimport android.content.ClipData\nimport android.os.Build\nimport android.os.Bundle\nimport androidx.activity.ComponentActivity\nimport androidx.activity.compose.setContent\nimport androidx.activity.enableEdgeToEdge\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.WindowInsetsSides\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.only\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.safeDrawing\nimport androidx.compose.foundation.layout.windowInsetsPadding\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.text.selection.SelectionContainer\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.outlined.ContentCopy\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.ExtendedFloatingActionButton\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.LargeTopAppBar\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.material3.rememberTopAppBarState\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.input.nestedscroll.nestedScroll\nimport androidx.compose.ui.platform.ClipEntry\nimport androidx.compose.ui.platform.LocalClipboard\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.TextStyle\nimport androidx.compose.ui.text.font.FontFamily\nimport androidx.compose.ui.tooling.preview.Preview\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport kotlinx.coroutines.launch\nimport me.bmax.apatch.BuildConfig\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.theme.APatchTheme\nimport java.text.SimpleDateFormat\nimport java.util.Calendar\nimport java.util.Locale\n\nclass CrashHandleActivity : ComponentActivity() {\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n\n        enableEdgeToEdge()\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n            window.isNavigationBarContrastEnforced = false\n        }\n\n        super.onCreate(savedInstanceState)\n\n        val appName = getString(R.string.app_name)\n        val versionName = BuildConfig.VERSION_NAME\n        val versionCode = BuildConfig.VERSION_CODE\n\n        val deviceBrand = Build.BRAND\n        val deviceModel = Build.MODEL\n        val sdkLevel = Build.VERSION.SDK_INT\n        val currentDateTime = Calendar.getInstance().time\n        val formatter = SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\", Locale.getDefault())\n        val formattedDateTime = formatter.format(currentDateTime)\n\n        val exceptionMessage = intent.getStringExtra(\"exception_message\")\n        val threadName = intent.getStringExtra(\"thread\")\n\n        val message = buildString {\n            append(appName).append(\" version: \").append(versionName).append(\" ($versionCode)\")\n                .append(\"\\n\\n\")\n            append(\"Brand: \").append(deviceBrand).append(\"\\n\")\n            append(\"Model: \").append(deviceModel).append(\"\\n\")\n            append(\"SDK Level: \").append(sdkLevel).append(\"\\n\")\n            append(\"Time: \").append(formattedDateTime).append(\"\\n\\n\")\n            append(\"Thread: \").append(threadName).append(\"\\n\")\n            append(\"Crash Info: \\n\").append(exceptionMessage)\n        }\n\n        setContent {\n            APatchTheme {\n                CrashHandleScreen(message)\n            }\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun CrashHandleScreen(\n    message: String\n) {\n    val scrollBehavior =\n        TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState())\n    val clipboard = LocalClipboard.current\n    val scope = rememberCoroutineScope()\n\n    Scaffold(contentWindowInsets = WindowInsets.safeDrawing, topBar = {\n        LargeTopAppBar(\n            title = { Text(text = stringResource(R.string.crash_handle_title)) },\n            scrollBehavior = scrollBehavior,\n            windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)\n        )\n    }, floatingActionButton = {\n        ExtendedFloatingActionButton(\n            onClick = {\n            scope.launch {\n                clipboard.setClipEntry(\n                    ClipEntry(ClipData.newPlainText(\"CrashLog\", message)),\n                )\n            }\n        }, text = { Text(text = stringResource(R.string.crash_handle_copy)) }, icon = {\n            Icon(\n                imageVector = Icons.Outlined.ContentCopy, contentDescription = null\n            )\n        }, containerColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n        contentColor = MaterialTheme.colorScheme.onPrimary.copy(alpha = 1f),\n        modifier = Modifier.windowInsetsPadding(\n            WindowInsets.safeDrawing.only(WindowInsetsSides.End)\n        )\n        )\n    }) {\n        SelectionContainer(\n            modifier = Modifier\n                .fillMaxSize()\n                .nestedScroll(scrollBehavior.nestedScrollConnection)\n                .verticalScroll(rememberScrollState())\n                .padding(it)\n                .padding(\n                    start = 16.dp, top = 16.dp, end = 16.dp, bottom = 16.dp + 56.dp + 16.dp\n                )\n        ) {\n            Text(\n                text = message, style = TextStyle(\n                    fontFamily = FontFamily.Monospace, fontSize = 11.sp\n                )\n            )\n        }\n    }\n}\n\n@Preview\n@Composable\nfun CrashHandleScreenPreview() {\n    APatchTheme {\n        CrashHandleScreen(\"Crash log here\")\n    }\n}"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/MainActivity.kt",
    "content": "package me.bmax.apatch.ui\n\nimport android.annotation.SuppressLint\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Build\nimport android.os.Bundle\nimport android.os.Handler\nimport android.os.Looper\nimport androidx.activity.compose.setContent\nimport androidx.activity.enableEdgeToEdge\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.compose.animation.AnimatedContentTransitionScope\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.animation.Crossfade\nimport androidx.compose.animation.EnterTransition\nimport androidx.compose.animation.ExitTransition\nimport androidx.compose.animation.core.Spring\nimport androidx.compose.animation.core.Animatable\nimport androidx.compose.animation.core.animateDpAsState\nimport androidx.compose.animation.core.animateFloatAsState\nimport androidx.compose.animation.core.spring\nimport androidx.compose.animation.core.tween\nimport androidx.compose.animation.fadeIn\nimport androidx.compose.animation.fadeOut\nimport androidx.compose.animation.scaleIn\nimport androidx.compose.animation.scaleOut\nimport androidx.compose.animation.slideInHorizontally\nimport androidx.compose.animation.slideInVertically\nimport androidx.compose.animation.slideOutHorizontally\nimport androidx.compose.animation.slideOutVertically\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.asPaddingValues\nimport androidx.compose.foundation.layout.navigationBars\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.wrapContentWidth\nimport androidx.compose.foundation.layout.offset\nimport androidx.compose.material3.Badge\nimport androidx.compose.material3.BadgedBox\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.NavigationBar\nimport androidx.compose.material3.NavigationBarItem\nimport androidx.compose.material3.NavigationRail\nimport androidx.compose.material3.NavigationRailItem\nimport androidx.compose.material3.NavigationRailDefaults\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.SnackbarHostState\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.CompositionLocalProvider\nimport androidx.compose.runtime.MutableState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.setValue\nimport androidx.compose.runtime.DisposableEffect\nimport androidx.compose.runtime.key\nimport androidx.compose.runtime.livedata.observeAsState\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.compositionLocalOf\nimport android.content.SharedPreferences\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.geometry.Offset\nimport androidx.compose.ui.input.nestedscroll.NestedScrollConnection\nimport androidx.compose.ui.input.nestedscroll.NestedScrollSource\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.input.nestedscroll.nestedScroll\nimport androidx.compose.ui.platform.LocalDensity\nimport androidx.compose.ui.unit.IntOffset\nimport androidx.compose.ui.unit.Velocity\nimport kotlin.math.abs\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen\nimport androidx.navigation.NavBackStackEntry\nimport androidx.navigation.NavHostController\nimport androidx.navigation.compose.currentBackStackEntryAsState\nimport androidx.navigation.compose.rememberNavController\nimport com.ramcosta.composedestinations.generated.destinations.InstallScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.ApmBulkInstallScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.AppearanceSettingsScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.BackupSettingsScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.BehaviorSettingsScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.FunctionSettingsScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.GeneralSettingsScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.LanguagePickerScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.ModuleSettingsScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.MultimediaSettingsScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.SecuritySettingsScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.SettingScreenDestination\nimport coil.Coil\nimport coil.ImageLoader\nimport coil.disk.DiskCache\nimport coil.memory.MemoryCache\nimport com.ramcosta.composedestinations.DestinationsNavHost\nimport com.ramcosta.composedestinations.animations.NavHostAnimatedDestinationStyle\nimport com.ramcosta.composedestinations.generated.NavGraphs\nimport com.ramcosta.composedestinations.rememberNavHostEngine\nimport com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState\nimport com.ramcosta.composedestinations.utils.rememberDestinationsNavigator\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.ui.screen.BottomBarDestination\nimport me.bmax.apatch.ui.screen.MODULE_TYPE\nimport me.bmax.apatch.ui.theme.APatchTheme\nimport me.bmax.apatch.ui.viewmodel.SuperUserViewModel\nimport me.bmax.apatch.ui.theme.APatchThemeWithBackground\nimport me.bmax.apatch.ui.theme.BackgroundConfig\nimport androidx.compose.material3.NavigationBarDefaults\nimport androidx.compose.material3.MaterialTheme\nimport me.bmax.apatch.util.PermissionRequestHandler\nimport me.bmax.apatch.util.PermissionUtils\nimport me.bmax.apatch.util.ui.LocalSnackbarHost\nimport me.zhanghai.android.appiconloader.coil.AppIconFetcher\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.ui.platform.LocalUriHandler\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.material3.BasicAlertDialog\nimport androidx.compose.material3.TextButton\nimport androidx.compose.material3.Button\nimport androidx.compose.material3.Surface\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.AlertDialogDefaults\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.BoxWithConstraints\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxHeight\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.draw.drawWithContent\nimport androidx.compose.ui.geometry.CornerRadius\nimport androidx.compose.ui.geometry.Size\nimport androidx.compose.ui.graphics.Brush\nimport androidx.compose.ui.graphics.graphicsLayer\nimport androidx.compose.ui.window.DialogProperties\nimport me.bmax.apatch.R\nimport androidx.compose.runtime.LaunchedEffect\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.coroutineScope\nimport kotlin.system.exitProcess\nimport me.zhanghai.android.appiconloader.coil.AppIconKeyer\nimport me.bmax.apatch.util.UpdateChecker\nimport me.bmax.apatch.ui.component.UpdateDialog\n\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.ui.platform.LocalContext\nimport android.provider.OpenableColumns\nimport me.bmax.apatch.ui.theme.ThemeManager\nimport me.bmax.apatch.ui.component.rememberConfirmDialog\nimport me.bmax.apatch.ui.component.rememberLoadingDialog\n\n\nimport me.bmax.apatch.ui.screen.settings.ThemeImportDialog\nimport me.bmax.apatch.util.BiometricUtils\nimport me.bmax.apatch.util.ui.navBarGlassEffect\nimport me.bmax.apatch.util.ui.navBarLiquefiable\nimport me.bmax.apatch.util.ui.rememberNavBarGlassLiquidState\nimport me.bmax.apatch.util.ui.isRealTimeBlurAvailable\nimport me.bmax.apatch.util.ui.showToast\n\ndata class ScrollState(\n    val isScrollingDown: MutableState<Boolean>,\n    val scrollOffset: MutableState<Float>,\n    val previousScrollOffset: MutableState<Float>\n)\n\nval LocalScrollState = compositionLocalOf<ScrollState?> { null }\n\nval LocalBottomBarVisible = compositionLocalOf { mutableStateOf(true) }\nval LocalIsFloatingNavMode = compositionLocalOf { false }\n\n@Composable\nfun rememberScrollConnection(\n    isScrollingDown: MutableState<Boolean>,\n    scrollOffset: MutableState<Float>,\n    previousScrollOffset: MutableState<Float>,\n    threshold: Float = 50f,\n    onUserScroll: (() -> Unit)? = null\n): NestedScrollConnection {\n    return remember(onUserScroll) {\n        object : NestedScrollConnection {\n            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {\n                val delta = available.y\n\n                if (delta != 0f) {\n                    // Any scroll counts as user interaction, reset auto-hide timer\n                    onUserScroll?.invoke()\n                }\n\n                // Update scroll offset\n                val newOffset = scrollOffset.value + delta\n                scrollOffset.value = newOffset\n\n                // Calculate the scroll delta from previous offset\n                val scrollDelta = previousScrollOffset.value - newOffset\n\n                // Only update direction if scroll delta exceeds threshold\n                if (abs(scrollDelta) > threshold) {\n                    isScrollingDown.value = scrollDelta > 0\n                    previousScrollOffset.value = newOffset\n                }\n\n                return Offset.Zero\n            }\n\n            override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {\n                // Reset offset tracking after fling\n                previousScrollOffset.value = scrollOffset.value\n                return super.onPostFling(consumed, available)\n            }\n        }\n    }\n}\n\nclass MainActivity : AppCompatActivity() {\n\n    private var isLoading = true\n    private var installUri: Uri? = null\n    private var installUris: ArrayList<Uri>? = null\n    private lateinit var permissionHandler: PermissionRequestHandler\n    private val isLocked = mutableStateOf(false)\n    private var isAuthenticated = false\n    private var biometricPromptShowing = false\n    private var startupSoundPlayed = false\n    private var pendingActionModuleId by mutableStateOf<String?>(null)\n    private var pendingScriptId by mutableStateOf<String?>(null)\n\n    private fun getFileName(context: android.content.Context, uri: Uri): String {\n        var result: String? = null\n        if (uri.scheme == \"content\") {\n            val cursor = context.contentResolver.query(uri, null, null, null, null)\n            try {\n                if (cursor != null && cursor.moveToFirst()) {\n                    val index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)\n                    if (index >= 0) {\n                        result = cursor.getString(index)\n                    }\n                }\n            } catch (e: Exception) {\n                e.printStackTrace()\n            } finally {\n                cursor?.close()\n            }\n        }\n        if (result == null) {\n            result = uri.path\n            val cut = result?.lastIndexOf('/')\n            if (cut != null && cut != -1) {\n                result = result?.substring(cut + 1)\n            }\n        }\n        return result ?: \"unknown\"\n    }\n\n    override fun dispatchTouchEvent(ev: android.view.MotionEvent?): Boolean {\n        if (ev?.action == android.view.MotionEvent.ACTION_UP) {\n            if (me.bmax.apatch.ui.theme.SoundEffectConfig.scope == me.bmax.apatch.ui.theme.SoundEffectConfig.SCOPE_GLOBAL) {\n                me.bmax.apatch.util.SoundEffectManager.play(this)\n            }\n            if (me.bmax.apatch.ui.theme.VibrationConfig.scope == me.bmax.apatch.ui.theme.VibrationConfig.SCOPE_GLOBAL) {\n                me.bmax.apatch.util.VibrationManager.vibrate(this)\n            }\n        }\n        return super.dispatchTouchEvent(ev)\n    }\n\n    override fun attachBaseContext(newBase: android.content.Context) {\n        super.attachBaseContext(me.bmax.apatch.util.DPIUtils.updateContext(newBase))\n    }\n\n    override fun onNewIntent(intent: Intent) {\n        super.onNewIntent(intent)\n        setIntent(intent)\n        updatePendingActionFromIntent(intent)\n    }\n\n    private fun updatePendingActionFromIntent(intent: Intent?) {\n        if (intent?.getBooleanExtra(\"from_action_shortcut\", false) == true) {\n            val id = intent.getStringExtra(\"apm_action_module_id\")\n            if (!id.isNullOrEmpty()) {\n                pendingActionModuleId = id\n            }\n        }\n        if (intent?.getBooleanExtra(\"from_script_shortcut\", false) == true) {\n            val id = intent.getStringExtra(\"script_id\")\n            if (!id.isNullOrEmpty()) {\n                pendingScriptId = id\n            }\n        }\n    }\n\n    @SuppressLint(\"UnusedMaterial3ScaffoldPaddingParameter\")\n    override fun onCreate(savedInstanceState: Bundle?) {\n\n        installSplashScreen().setKeepOnScreenCondition { isLoading }\n\n        // Safety net: force dismiss splash after 15 seconds to prevent permanent hang\n        Handler(Looper.getMainLooper()).postDelayed({\n            if (isLoading) {\n                android.util.Log.w(\"MainActivity\", \"Splash safety net triggered - force dismissing\")\n                isLoading = false\n            }\n        }, 15_000)\n\n        enableEdgeToEdge()\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n            window.isNavigationBarContrastEnforced = false\n        }\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE\n            && !APApplication.sharedPreferences.getBoolean(\"predictive_back_enabled\", true)\n        ) {\n            try {\n                window.javaClass\n                    .getMethod(\"setEnableOnBackInvokedCallback\", Boolean::class.javaPrimitiveType)\n                    .invoke(window, false)\n                android.util.Log.d(\"MainActivity\", \"Predictive back disabled via reflection\")\n            } catch (e: Exception) {\n                android.util.Log.e(\"MainActivity\", \"Failed to disable predictive back via reflection\", e)\n            }\n        }\n\n        super.onCreate(savedInstanceState)\n        updatePendingActionFromIntent(intent)\n        \n        installUri = if (intent.action == Intent.ACTION_SEND) {\n             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n                intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)\n            } else {\n                @Suppress(\"DEPRECATION\")\n                intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)\n            }\n        } else {\n            intent.data ?: run {\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n                    intent.getParcelableArrayListExtra(\"uris\", Uri::class.java)?.firstOrNull()\n                } else {\n                    @Suppress(\"DEPRECATION\")\n                    intent.getParcelableArrayListExtra<Uri>(\"uris\")?.firstOrNull()\n                }\n            }\n        }\n\n        if (intent.action == Intent.ACTION_SEND_MULTIPLE) {\n            installUris = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n                intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM, Uri::class.java)\n            } else {\n                @Suppress(\"DEPRECATION\")\n                intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)\n            }\n        }\n\n        // 初始化权限处理器\n        permissionHandler = PermissionRequestHandler(this)\n\n        setupUI()\n    }\n\n    override fun onResume() {\n        super.onResume()\n        showBiometricPromptIfNeeded()\n    }\n\n    private fun showBiometricPromptIfNeeded() {\n        if (isAuthenticated || biometricPromptShowing) return\n\n        val prefs = APApplication.sharedPreferences\n        val biometricLogin = prefs.getBoolean(\"biometric_login\", false)\n        val biometricManager = androidx.biometric.BiometricManager.from(this)\n        val canAuthenticate = biometricManager.canAuthenticate(\n            androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG or\n                    androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL\n        ) == androidx.biometric.BiometricManager.BIOMETRIC_SUCCESS\n\n        val isShareIntent = intent.action == Intent.ACTION_SEND || intent.action == Intent.ACTION_SEND_MULTIPLE\n        if (biometricLogin && canAuthenticate && !isShareIntent) {\n            isLocked.value = true\n            biometricPromptShowing = true\n            val biometricPrompt = androidx.biometric.BiometricPrompt(\n                this,\n                androidx.core.content.ContextCompat.getMainExecutor(this),\n                object : androidx.biometric.BiometricPrompt.AuthenticationCallback() {\n                    override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {\n                        super.onAuthenticationError(errorCode, errString)\n                        biometricPromptShowing = false\n                        if (errorCode == androidx.biometric.BiometricPrompt.ERROR_USER_CANCELED) {\n                            finishAndRemoveTask()\n                        } else {\n                            Handler(Looper.getMainLooper()).postDelayed({\n                                if (!isAuthenticated && !biometricPromptShowing) {\n                                    showBiometricPromptIfNeeded()\n                                }\n                            }, 300)\n                        }\n                    }\n\n                    override fun onAuthenticationSucceeded(result: androidx.biometric.BiometricPrompt.AuthenticationResult) {\n                        super.onAuthenticationSucceeded(result)\n                        isLocked.value = false\n                        isAuthenticated = true\n                        biometricPromptShowing = false\n                        if (!startupSoundPlayed) {\n                            startupSoundPlayed = true\n                            me.bmax.apatch.util.SoundEffectManager.playStartup(this@MainActivity)\n                        }\n                    }\n                })\n            val promptInfo = androidx.biometric.BiometricPrompt.PromptInfo.Builder()\n                .setTitle(getString(R.string.action_biometric))\n                .setSubtitle(getString(R.string.msg_biometric))\n                .setAllowedAuthenticators(androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG or androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL)\n                .build()\n            biometricPrompt.authenticate(promptInfo)\n        } else if (!biometricLogin || !canAuthenticate || isShareIntent) {\n            isAuthenticated = true\n            isLocked.value = false\n            if (!startupSoundPlayed) {\n                startupSoundPlayed = true\n                me.bmax.apatch.util.SoundEffectManager.playStartup(this)\n            }\n        }\n    }\n\n    private fun setupUI() {\n        \n        // Load DPI settings\n        me.bmax.apatch.util.DPIUtils.load(this)\n        me.bmax.apatch.util.DPIUtils.applyDpi(this)\n        \n        // 检查并请求权限\n        if (!PermissionUtils.hasExternalStoragePermission(this) || \n            !PermissionUtils.hasWriteExternalStoragePermission(this)) {\n            permissionHandler.requestPermissions(\n                onGranted = {\n                    // 权限已授予\n                },\n                onDenied = {\n                    // 权限被拒绝，可以显示一个提示\n                }\n            )\n        }\n\n        setContent {\n            val locked by remember { isLocked }\n            if (locked) {\n                Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background))\n            } else {\n            val prefs = APApplication.sharedPreferences\n            var folkXEngineEnabled by remember {\n                mutableStateOf(prefs.getBoolean(\"folkx_engine_enabled\", true))\n            }\n            var folkXAnimationType by remember {\n                mutableStateOf(prefs.getString(\"folkx_animation_type\", \"linear\"))\n            }\n            var folkXAnimationSpeed by remember {\n                mutableStateOf(prefs.getFloat(\"folkx_animation_speed\", 1.0f))\n            }\n\n            DisposableEffect(Unit) {\n                val listener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->\n                    if (key == \"folkx_engine_enabled\") {\n                        folkXEngineEnabled = sharedPreferences.getBoolean(\"folkx_engine_enabled\", true)\n                    }\n                    if (key == \"folkx_animation_type\") {\n                        folkXAnimationType = sharedPreferences.getString(\"folkx_animation_type\", \"linear\")\n                    }\n                    if (key == \"folkx_animation_speed\") {\n                        folkXAnimationSpeed = sharedPreferences.getFloat(\"folkx_animation_speed\", 1.0f)\n                    }\n                }\n                prefs.registerOnSharedPreferenceChangeListener(listener)\n                onDispose {\n                    prefs.unregisterOnSharedPreferenceChangeListener(listener)\n                }\n            }\n\n            val navController = rememberNavController()\n            val navigator = navController.rememberDestinationsNavigator()\n            val snackBarHostState = remember { SnackbarHostState() }\n            val bottomBarRoutes = remember {\n                BottomBarDestination.entries.map { it.direction.route }.toSet()\n            }\n            val settingsRoutes = remember {\n                setOf(\n                    SettingScreenDestination.route,\n                    GeneralSettingsScreenDestination.route,\n                    LanguagePickerScreenDestination.route,\n                    AppearanceSettingsScreenDestination.route,\n                    BehaviorSettingsScreenDestination.route,\n                    SecuritySettingsScreenDestination.route,\n                    BackupSettingsScreenDestination.route,\n                    ModuleSettingsScreenDestination.route,\n                    FunctionSettingsScreenDestination.route,\n                    MultimediaSettingsScreenDestination.route,\n                )\n            }\n\n            LaunchedEffect(pendingActionModuleId) {\n                val id = pendingActionModuleId\n                if (!id.isNullOrEmpty()) {\n                    navigator.navigate(com.ramcosta.composedestinations.generated.destinations.ExecuteAPMActionScreenDestination(id))\n                    pendingActionModuleId = null\n                }\n            }\n\n            LaunchedEffect(pendingScriptId) {\n                val id = pendingScriptId\n                if (!id.isNullOrEmpty()) {\n                    kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) {\n                        me.bmax.apatch.util.ScriptLibraryManager.loadScripts().find { it.id == id }\n                    }?.let { scriptInfo ->\n                        navigator.navigate(com.ramcosta.composedestinations.generated.destinations.ScriptExecutionLogScreenDestination(scriptInfo))\n                    }\n                    pendingScriptId = null\n                }\n            }\n\n            LaunchedEffect(Unit) {\n                if (SuperUserViewModel.apps.isEmpty()) {\n                    SuperUserViewModel().fetchAppList()\n                }\n            }\n\n\n            LaunchedEffect(Unit) {\n                me.bmax.apatch.util.AppData.DataRefreshManager.ensureCountsLoaded()\n                \n                val badgePrefs = APApplication.sharedPreferences\n                var lastEnableSuperUser = badgePrefs.getBoolean(\"badge_superuser\", true)\n                var lastEnableApm = badgePrefs.getBoolean(\"badge_apm\", true)\n                var lastEnableKernel = badgePrefs.getBoolean(\"badge_kernel\", true)\n\n                while (isActive) {\n                    val enableSuperUser = badgePrefs.getBoolean(\"badge_superuser\", true)\n                    val enableApm = badgePrefs.getBoolean(\"badge_apm\", true)\n                    val enableKernel = badgePrefs.getBoolean(\"badge_kernel\", true)\n                    val forceRefresh =\n                        (!lastEnableSuperUser && enableSuperUser) ||\n                        (!lastEnableApm && enableApm) ||\n                        (!lastEnableKernel && enableKernel)\n\n                    lastEnableSuperUser = enableSuperUser\n                    lastEnableApm = enableApm\n                    lastEnableKernel = enableKernel\n\n                    // Always refresh counts for UI components, badge settings only control display\n                    try {\n                        me.bmax.apatch.util.AppData.DataRefreshManager.ensureCountsLoaded(force = forceRefresh)\n                    } catch (e: Exception) {\n                        android.util.Log.e(\"BadgeCount\", \"Failed to refresh badge data\", e)\n                    }\n\n                    delay(3000L)\n                }\n            }\n\n            APatchThemeWithBackground(\n                navController = navController,\n                folkXEngineEnabled = folkXEngineEnabled,\n                folkXAnimationType = folkXAnimationType,\n                folkXAnimationSpeed = folkXAnimationSpeed\n            ) {\n                \n                val showUpdateDialog = remember { mutableStateOf(false) }\n                val context = LocalContext.current\n\n                val loadingDialog = rememberLoadingDialog()\n                val showThemeImportDialog = remember { mutableStateOf(false) }\n                val themeImportUri = remember { mutableStateOf<Uri?>(null) }\n                val themeImportMetadata = remember { mutableStateOf<ThemeManager.ThemeMetadata?>(null) }\n                val scope = androidx.compose.runtime.rememberCoroutineScope()\n\n                var pendingExternalInstallUri by remember { mutableStateOf<Uri?>(null) }\n                val externalInstallConfirmDialog = rememberConfirmDialog(\n                    onConfirm = {\n                        pendingExternalInstallUri?.let { u ->\n                            navigator.navigate(InstallScreenDestination(u, MODULE_TYPE.APM))\n                        }\n                        pendingExternalInstallUri = null\n                    },\n                    onDismiss = {\n                        pendingExternalInstallUri = null\n                    }\n                )\n\n                val uri = installUri\n                val uris = installUris\n                val lastHandledExternalKey = rememberSaveable { mutableStateOf<String?>(null) }\n                LaunchedEffect(uri, uris) {\n                    val key = when {\n                        uris != null && uris.isNotEmpty() -> uris.joinToString(\"|\") { it.toString() }\n                        uri != null -> uri.toString()\n                        else -> null\n                    }\n                    if (key == null || key == lastHandledExternalKey.value) {\n                        return@LaunchedEffect\n                    }\n                    lastHandledExternalKey.value = key\n\n                    if (uris != null && uris.isNotEmpty()) {\n                        navigator.navigate(ApmBulkInstallScreenDestination(initialUris = uris))\n                        installUris = null\n                        installUri = null\n                    } else if (uri != null) {\n                        val fileName = withContext(Dispatchers.IO) {\n                            getFileName(context, uri)\n                        }\n                        if (fileName.endsWith(\".fpt\", ignoreCase = true)) {\n                            themeImportUri.value = uri\n                            scope.launch {\n                                loadingDialog.show()\n                                val metadata = ThemeManager.readThemeMetadata(context, uri)\n                                loadingDialog.hide()\n                                if (metadata != null) {\n                                    themeImportMetadata.value = metadata\n                                    showThemeImportDialog.value = true\n                                } else {\n                                    showToast(context, context.getString(R.string.settings_theme_import_failed))\n                                }\n                            }\n                        } else {\n                            if (prefs.getBoolean(\"strong_biometric\", false) && prefs.getBoolean(\"biometric_login\", false)) {\n                                if (!BiometricUtils.authenticate(this@MainActivity)) return@LaunchedEffect\n                            }\n                            if (prefs.getBoolean(\"apm_install_confirm_enabled\", true)) {\n                                pendingExternalInstallUri = uri\n                                externalInstallConfirmDialog.showConfirm(\n                                    title = context.getString(R.string.apm_install_confirm_title),\n                                    content = context.getString(R.string.apm_install_confirm_content, fileName),\n                                    markdown = false\n                                )\n                            } else {\n                                navigator.navigate(InstallScreenDestination(uri, MODULE_TYPE.APM))\n                            }\n                        }\n                        installUri = null\n                        installUris = null\n                    }\n                }\n\n                if (showThemeImportDialog.value && themeImportMetadata.value != null) {\n                    ThemeImportDialog(\n                        showDialog = showThemeImportDialog,\n                        metadata = themeImportMetadata.value!!,\n                        onConfirm = {\n                            scope.launch {\n                                val success = loadingDialog.withLoading {\n                                    ThemeManager.importTheme(context, themeImportUri.value!!)\n                                }\n                                if (success) {\n                                    showToast(context, context.getString(R.string.settings_theme_imported))\n                                } else {\n                                    showToast(context, context.getString(R.string.settings_theme_import_failed))\n                                }\n                            }\n                        }\n                    )\n                }\n                \n                LaunchedEffect(Unit) {\n                    if (prefs.getBoolean(\"auto_update_check\", true)) {\n                        withContext(Dispatchers.IO) {\n                             // Delay a bit to wait for network connection\n                             kotlinx.coroutines.delay(2000)\n                             val hasUpdate = me.bmax.apatch.util.UpdateChecker.checkUpdate()\n                             if (hasUpdate) {\n                                 showUpdateDialog.value = true\n                             }\n                        }\n                    }\n                }\n\n                if (showUpdateDialog.value) {\n                    UpdateDialog(\n                        onDismiss = { showUpdateDialog.value = false },\n                        onUpdate = {\n                            showUpdateDialog.value = false\n                            UpdateChecker.openUpdateUrl(context)\n                        }\n                    )\n                }\n\n                // 读取导航栏模式设置\n                var navMode by remember { mutableStateOf(prefs.getString(\"nav_mode\", \"floating\") ?: \"floating\") }\n                var floatingAutoHide by remember { mutableStateOf(prefs.getBoolean(\"floating_auto_hide\", true)) }\n                var floatingSwipeHide by remember { mutableStateOf(prefs.getBoolean(\"floating_swipe_hide\", true)) }\n                \n                DisposableEffect(Unit) {\n                    val navModeListener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPrefs, key ->\n                        when (key) {\n                            \"nav_mode\" -> navMode = sharedPrefs.getString(\"nav_mode\", \"floating\") ?: \"floating\"\n                            \"floating_auto_hide\" -> floatingAutoHide = sharedPrefs.getBoolean(\"floating_auto_hide\", true)\n                            \"floating_swipe_hide\" -> floatingSwipeHide = sharedPrefs.getBoolean(\"floating_swipe_hide\", true)\n                        }\n                    }\n                    prefs.registerOnSharedPreferenceChangeListener(navModeListener)\n                    onDispose {\n                        prefs.unregisterOnSharedPreferenceChangeListener(navModeListener)\n                    }\n                }\n\n                // Scroll state for bottom bar visibility\n                val isScrollingDown = remember { mutableStateOf(false) }\n                val scrollOffset = remember { mutableStateOf(0f) }\n                val previousScrollOffset = remember { mutableStateOf(0f) }\n\n                // Floating bottom bar visibility & 3s auto-hide timer\n                var isBottomBarVisible by rememberSaveable { mutableStateOf(true) }\n                var autoHideKey by remember { mutableStateOf(0) }\n\n                fun resetBottomBarAutoHide() {\n                    isBottomBarVisible = true\n                    autoHideKey++\n                }\n\n                // Remember the last valid navbar selection (persists across navbar hide/show)\n                val lastValidNavbarSelection = remember { mutableStateOf(0) }\n\n                val currentBackStackEntry by navController.currentBackStackEntryAsState()\n                val currentRoute = currentBackStackEntry?.destination?.route\n\n\n                // Show bottom bar logic: hide when scrolling down in floating mode,\n                // plus 3s auto-hide after last interaction.\n                val isFloatingMode = navMode == \"floating\"\n\n                LaunchedEffect(isFloatingMode, autoHideKey, floatingAutoHide) {\n                    if (isFloatingMode && floatingAutoHide && isBottomBarVisible) {\n                        delay(3000L)\n                        isBottomBarVisible = false\n                    }\n                }\n\n                // Auto-hide floating bar on secondary/detail pages (non-main-tab routes)\n                val isOnMainTabPage = currentRoute in bottomBarRoutes\n\n                val showBottomBar = if (isFloatingMode) {\n                    if (!isOnMainTabPage) false\n                    else if (!floatingAutoHide && !floatingSwipeHide) true\n                    else if (!floatingAutoHide) !isScrollingDown.value\n                    else if (!floatingSwipeHide) isBottomBarVisible\n                    else isBottomBarVisible && !isScrollingDown.value\n                } else {\n                    true\n                }\n\n                // 使用 BoxWithConstraints 检测屏幕宽度\n                BoxWithConstraints(modifier = Modifier.fillMaxSize()) {\n                    val useNavigationRail = when (navMode) {\n                        \"rail\" -> true\n                        \"bottom\" -> false\n                        \"floating\" -> false\n                        else -> maxWidth >= 600.dp && maxWidth > maxHeight // auto\n                    }\n\n                    val bottomBarVisibleState = remember { mutableStateOf(showBottomBar) }\n                    bottomBarVisibleState.value = showBottomBar\n                    val shouldExposeContentToLiquid = currentRoute !in settingsRoutes\n                    val floatingLiquidState = if (\n                        isFloatingMode &&\n                        showBottomBar &&\n                        isOnMainTabPage &&\n                        BackgroundConfig.isNavBarGlassEnabled &&\n                        isRealTimeBlurAvailable()\n                    ) {\n                        rememberNavBarGlassLiquidState()\n                    } else null\n\n                    val navTransitions = remember(\n                        folkXEngineEnabled, folkXAnimationType, folkXAnimationSpeed, bottomBarRoutes, useNavigationRail\n                    ) {\n                        createNavTransitions(folkXEngineEnabled, folkXAnimationType, folkXAnimationSpeed, bottomBarRoutes, useNavigationRail)\n                    }\n\n                    val scrollConnection = rememberScrollConnection(\n                        isScrollingDown, scrollOffset, previousScrollOffset,\n                        onUserScroll = { resetBottomBarAutoHide() }\n                    )\n\n                    Box(modifier = Modifier.fillMaxSize()) {\n                        val baseContentModifier = Modifier\n                            .navBarLiquefiable(\n                                if (shouldExposeContentToLiquid) floatingLiquidState else null\n                            )\n                            .then(\n                                when {\n                                    isFloatingMode -> Modifier.nestedScroll(scrollConnection)\n                                    !useNavigationRail -> Modifier.padding(bottom = 80.dp)\n                                    else -> Modifier\n                                }\n                            )\n\n                        if (useNavigationRail) {\n                            Row(modifier = Modifier.fillMaxSize()) {\n                                NavigationRailBar(navController)\n                                CompositionLocalProvider(\n                                    LocalSnackbarHost provides snackBarHostState,\n                                    LocalScrollState provides if (isFloatingMode) ScrollState(\n                                        isScrollingDown = isScrollingDown,\n                                        scrollOffset = scrollOffset,\n                                        previousScrollOffset = previousScrollOffset\n                                    ) else null,\n                                    LocalBottomBarVisible provides bottomBarVisibleState,\n                                    LocalIsFloatingNavMode provides isFloatingMode\n                                ) {\n                                    DestinationsNavHost(\n                                        modifier = Modifier.weight(1f).then(baseContentModifier),\n                                        navGraph = NavGraphs.root,\n                                        navController = navController,\n                                        engine = rememberNavHostEngine(navHostContentAlignment = Alignment.TopCenter),\n                                        defaultTransitions = navTransitions\n                                    )\n                                }\n                            }\n                        } else {\n                            CompositionLocalProvider(\n                                LocalSnackbarHost provides snackBarHostState,\n                                LocalScrollState provides if (isFloatingMode) ScrollState(\n                                    isScrollingDown = isScrollingDown,\n                                    scrollOffset = scrollOffset,\n                                    previousScrollOffset = previousScrollOffset\n                                ) else null,\n                                LocalBottomBarVisible provides bottomBarVisibleState,\n                                LocalIsFloatingNavMode provides isFloatingMode\n                            ) {\n                                DestinationsNavHost(\n                                    modifier = Modifier.fillMaxSize().then(baseContentModifier),\n                                    navGraph = NavGraphs.root,\n                                    navController = navController,\n                                    engine = rememberNavHostEngine(navHostContentAlignment = Alignment.TopCenter),\n                                    defaultTransitions = navTransitions\n                                )\n                            }\n                        }\n\n                        if (!useNavigationRail) {\n                            if (isFloatingMode) {\n                                AnimatedVisibility(\n                                    visible = showBottomBar,\n                                    modifier = Modifier.align(Alignment.BottomCenter),\n                                    enter = slideInVertically(initialOffsetY = { it }) + fadeIn(),\n                                    exit = slideOutVertically(targetOffsetY = { it }) + fadeOut()\n                                ) {\n                                    BottomBar(\n                                        navController = navController,\n                                        isFloating = true,\n                                        lastValidSelection = lastValidNavbarSelection,\n                                        onUserInteraction = { resetBottomBarAutoHide() },\n                                        liquidState = floatingLiquidState\n                                    )\n                                }\n                            } else {\n                                BottomBar(\n                                    modifier = Modifier.align(Alignment.BottomCenter),\n                                    navController = navController,\n                                    isFloating = false,\n                                    lastValidSelection = lastValidNavbarSelection\n                                )\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        }\n\n        // Initialize Coil\n        val iconSize = resources.getDimensionPixelSize(android.R.dimen.app_icon_size)\n        Coil.setImageLoader(\n            ImageLoader.Builder(this)\n                .components {\n                    add(AppIconKeyer())\n                    add(AppIconFetcher.Factory(iconSize, false, this@MainActivity))\n                    if (Build.VERSION.SDK_INT >= 28) {\n                        add(coil.decode.ImageDecoderDecoder.Factory())\n                    } else {\n                        add(coil.decode.GifDecoder.Factory())\n                    }\n                }\n                .diskCache(\n                    DiskCache.Builder()\n                        .directory(cacheDir.resolve(\"image_cache\"))\n                        .maxSizeBytes(100L * 1024 * 1024)\n                        .build()\n                )\n                .memoryCache(\n                    MemoryCache.Builder(this@MainActivity)\n                        .maxSizePercent(0.20)\n                        .build()\n                )\n                .crossfade(true)\n                .build()\n        )\n\n        Handler(Looper.getMainLooper()).postDelayed({\n            isLoading = false\n        }, 500)\n    }\n}\n\n@Composable\nprivate fun BottomBar(\n    modifier: Modifier = Modifier,\n    navController: NavHostController,\n    isFloating: Boolean = false,\n    lastValidSelection: MutableState<Int> = mutableStateOf(0),\n    onUserInteraction: (() -> Unit)? = null,\n    liquidState: io.github.fletchmckee.liquid.LiquidState? = null\n) {\n    val context = LocalContext.current\n    val state by APApplication.apStateLiveData.observeAsState(APApplication.State.UNKNOWN_STATE)\n    val navigator = navController.rememberDestinationsNavigator()\n\n    val prefs = APApplication.sharedPreferences\n    var showNavApm by remember { mutableStateOf(prefs.getBoolean(\"show_nav_apm\", true)) }\n    var showNavKpm by remember { mutableStateOf(prefs.getBoolean(\"show_nav_kpm\", true)) }\n    var showNavSuperUser by remember { mutableStateOf(prefs.getBoolean(\"show_nav_superuser\", true)) }\n\n    // Individual badge count settings - default enabled\n    var enableSuperUserBadge by remember { mutableStateOf(prefs.getBoolean(\"badge_superuser\", true)) }\n    var enableApmBadge by remember { mutableStateOf(prefs.getBoolean(\"badge_apm\", true)) }\n    var enableKernelBadge by remember { mutableStateOf(prefs.getBoolean(\"badge_kernel\", true)) }\n\n    // Collect badge counts from AppData\n    val superuserCount by me.bmax.apatch.util.AppData.DataRefreshManager.superuserCount.collectAsState()\n    val apmModuleCount by me.bmax.apatch.util.AppData.DataRefreshManager.apmModuleCount.collectAsState()\n    val kernelModuleCount by me.bmax.apatch.util.AppData.DataRefreshManager.kernelModuleCount.collectAsState()\n\n    DisposableEffect(Unit) {\n        val listener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPrefs, key ->\n            when (key) {\n                \"show_nav_apm\" -> showNavApm = sharedPrefs.getBoolean(key, true)\n                \"show_nav_kpm\" -> showNavKpm = sharedPrefs.getBoolean(key, true)\n                \"show_nav_superuser\" -> showNavSuperUser = sharedPrefs.getBoolean(key, true)\n                \"badge_superuser\" -> enableSuperUserBadge = sharedPrefs.getBoolean(key, true)\n                \"badge_apm\" -> enableApmBadge = sharedPrefs.getBoolean(key, true)\n                \"badge_kernel\" -> enableKernelBadge = sharedPrefs.getBoolean(key, true)\n            }\n        }\n        prefs.registerOnSharedPreferenceChangeListener(listener)\n        onDispose {\n            prefs.unregisterOnSharedPreferenceChangeListener(listener)\n        }\n    }\n\n    Crossfade(\n        modifier = modifier,\n        targetState = state,\n        label = \"BottomBarStateCrossfade\"\n    ) { state ->\n        val kPatchReady = state != APApplication.State.UNKNOWN_STATE\n        val aPatchReady = state == APApplication.State.ANDROIDPATCH_INSTALLED\n\n        // Determine visible destinations\n        val visibleDestinations = BottomBarDestination.entries.filter { destination ->\n            when {\n                destination == BottomBarDestination.AModule && !showNavApm -> false\n                destination == BottomBarDestination.KModule && !showNavKpm -> false\n                destination == BottomBarDestination.SuperUser && !showNavSuperUser -> false\n                (destination.kPatchRequired && !kPatchReady) || (destination.aPatchRequired && !aPatchReady) -> false\n                else -> true\n            }\n        }\n\n        val currentBackStackEntry by navController.currentBackStackEntryAsState()\n        val currentRoute = currentBackStackEntry?.destination?.route\n\n        val isOnBackStack = visibleDestinations.map { destination ->\n            navController.isRouteOnBackStackAsState(destination.direction).value\n        }\n\n        // Prefer an exact current-route match; fall back to whichever tab is on the back stack.\n        val selectedIndex = run {\n            val exactMatch = visibleDestinations.indexOfFirst { it.direction.route == currentRoute }\n            if (exactMatch != -1) exactMatch\n            else isOnBackStack.indexOfLast { it }\n        }\n\n        // Persist the selection so the indicator doesn't jump while the navbar is animating out/in.\n        if (selectedIndex != -1) {\n            lastValidSelection.value = selectedIndex\n        }\n\n        // Use current selection if on navbar, otherwise use last valid selection\n        val effectiveSelectedIndex = if (selectedIndex != -1) selectedIndex else lastValidSelection.value\n        val isGlassEnabled = isFloating && BackgroundConfig.isNavBarGlassEnabled\n\n        val animatedSelectedIndex = remember { Animatable(effectiveSelectedIndex.toFloat()) }\n        val previousEffectiveSelectedIndex = remember { mutableStateOf(effectiveSelectedIndex) }\n        val moveDirection = remember { mutableStateOf(0f) }\n        val liquidMotion = remember { Animatable(0f) }\n\n        LaunchedEffect(effectiveSelectedIndex, isGlassEnabled) {\n            if (isGlassEnabled) {\n                val previous = previousEffectiveSelectedIndex.value\n                moveDirection.value = (effectiveSelectedIndex - previous).toFloat().coerceIn(-1f, 1f)\n                previousEffectiveSelectedIndex.value = effectiveSelectedIndex\n                liquidMotion.snapTo(1f)\n                coroutineScope {\n                    launch {\n                        animatedSelectedIndex.animateTo(\n                            targetValue = effectiveSelectedIndex.toFloat(),\n                            animationSpec = spring(\n                                dampingRatio = Spring.DampingRatioLowBouncy,\n                                stiffness = Spring.StiffnessVeryLow,\n                            )\n                        )\n                    }\n                    launch {\n                        liquidMotion.animateTo(\n                            targetValue = 0f,\n                            animationSpec = tween(durationMillis = 520)\n                        )\n                    }\n                }\n                moveDirection.value = 0f\n            } else {\n                previousEffectiveSelectedIndex.value = effectiveSelectedIndex\n                moveDirection.value = 0f\n                liquidMotion.snapTo(0f)\n                animatedSelectedIndex.animateTo(\n                    targetValue = effectiveSelectedIndex.toFloat(),\n                    animationSpec = spring(\n                        dampingRatio = Spring.DampingRatioMediumBouncy,\n                        stiffness = Spring.StiffnessLow,\n                    )\n                )\n            }\n        }\n\n        val containerColor = if (BackgroundConfig.isCustomBackgroundEnabled) {\n            MaterialTheme.colorScheme.surface.copy(alpha = BackgroundConfig.customBackgroundOpacity)\n        } else {\n            NavigationBarDefaults.containerColor\n        }\n\n        if (isFloating) {\n            BoxWithConstraints(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .padding(\n                        bottom = WindowInsets.navigationBars\n                            .asPaddingValues()\n                            .calculateBottomPadding()\n                    )\n            ) {\n                val screenWidth = maxWidth\n                val horizontalScreenPadding = when {\n                    screenWidth > 600.dp -> 32.dp\n                    screenWidth > 400.dp -> 24.dp\n                    else -> 16.dp\n                }\n\n                Box(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(horizontal = horizontalScreenPadding, vertical = 14.dp),\n                    contentAlignment = Alignment.Center\n                ) {\n                    val isCustomBg = BackgroundConfig.isCustomBackgroundEnabled\n                    if (isGlassEnabled) {\n                        val glassShape = CircleShape\n                        Surface(\n                            modifier = Modifier\n                                .wrapContentWidth()\n                                .clip(glassShape)\n                                .navBarGlassEffect(\n                                    shape = glassShape,\n                                    liquidState = liquidState,\n                                ),\n                            shape = glassShape,\n                            color = Color.Transparent,\n                            tonalElevation = 0.dp,\n                            shadowElevation = 0.dp\n                        ) {\n                            BottomBarContent(\n                                visibleDestinations = visibleDestinations,\n                                effectiveSelectedIndex = effectiveSelectedIndex,\n                                animatedSelectedIndex = animatedSelectedIndex.value,\n                                moveDirection = moveDirection.value,\n                                liquidMotion = liquidMotion.value,\n                                superuserCount = superuserCount,\n                                apmModuleCount = apmModuleCount,\n                                kernelModuleCount = kernelModuleCount,\n                                enableSuperUserBadge = enableSuperUserBadge,\n                                enableApmBadge = enableApmBadge,\n                                enableKernelBadge = enableKernelBadge,\n                                currentRoute = currentRoute,\n                                navController = navController,\n                                context = context,\n                                onUserInteraction = onUserInteraction\n                            )\n                        }\n                    } else {\n                        Surface(\n                            modifier = Modifier.wrapContentWidth(),\n                            shape = MaterialTheme.shapes.large,\n                            color = containerColor,\n                            tonalElevation = if (isCustomBg) 0.dp else 3.dp,\n                            shadowElevation = if (isCustomBg) 0.dp else 8.dp\n                        ) {\n                            BottomBarContent(\n                                visibleDestinations = visibleDestinations,\n                                effectiveSelectedIndex = effectiveSelectedIndex,\n                                animatedSelectedIndex = animatedSelectedIndex.value,\n                                moveDirection = moveDirection.value,\n                                liquidMotion = liquidMotion.value,\n                                superuserCount = superuserCount,\n                                apmModuleCount = apmModuleCount,\n                                kernelModuleCount = kernelModuleCount,\n                                enableSuperUserBadge = enableSuperUserBadge,\n                                enableApmBadge = enableApmBadge,\n                                enableKernelBadge = enableKernelBadge,\n                                currentRoute = currentRoute,\n                                navController = navController,\n                                context = context,\n                                onUserInteraction = onUserInteraction\n                            )\n                        }\n                    }\n                }\n            }\n        } else {\n            // Non-floating mode: use standard NavigationBar.\n            // Use the same single-selection logic as floating mode (effectiveSelectedIndex)\n            // so that only one item is visually highlighted even if multiple routes are\n            // present anywhere on the back stack.\n            NavigationBar(\n                tonalElevation = if (BackgroundConfig.isCustomBackgroundEnabled) 0.dp else 8.dp,\n                containerColor = containerColor\n            ) {\n                visibleDestinations.forEachIndexed { index, destination ->\n                    key(destination) {\n                        val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction)\n                        val isSelected = index == effectiveSelectedIndex\n\n                        NavigationBarItem(\n                            selected = isSelected,\n                            onClick = {\n                                onUserInteraction?.invoke()\n                                if (me.bmax.apatch.ui.theme.SoundEffectConfig.scope == me.bmax.apatch.ui.theme.SoundEffectConfig.SCOPE_BOTTOM_BAR) {\n                                    me.bmax.apatch.util.SoundEffectManager.play(context)\n                                }\n                                if (me.bmax.apatch.ui.theme.VibrationConfig.scope == me.bmax.apatch.ui.theme.VibrationConfig.SCOPE_BOTTOM_BAR) {\n                                    me.bmax.apatch.util.VibrationManager.vibrate(context)\n                                }\n                                if (isCurrentDestOnBackStack) {\n                                    navigator.popBackStack(destination.direction, false)\n                                }\n                                navigator.navigate(destination.direction) {\n                                    popUpTo(NavGraphs.root) {\n                                        saveState = true\n                                    }\n                                    launchSingleTop = true\n                                    restoreState = true\n                                }\n                            },\n                            icon = {\n                                val badgeContent = when {\n                                    destination == BottomBarDestination.SuperUser && enableSuperUserBadge -> superuserCount\n                                    destination == BottomBarDestination.AModule && enableApmBadge -> apmModuleCount\n                                    destination == BottomBarDestination.KModule && enableKernelBadge -> kernelModuleCount\n                                    else -> 0\n                                }\n\n                                BadgedBox(\n                                    badge = {\n                                        if (badgeContent > 0) {\n                                            Badge(containerColor = MaterialTheme.colorScheme.secondary) {\n                                                Text(text = badgeContent.toString())\n                                            }\n                                        }\n                                    }\n                                ) {\n                                    if (isSelected) {\n                                        Icon(destination.iconSelected, stringResource(destination.label))\n                                    } else {\n                                        Icon(destination.iconNotSelected, stringResource(destination.label))\n                                    }\n                                }\n                            },\n                            label = {\n                                Text(\n                                    text = stringResource(destination.label),\n                                    overflow = TextOverflow.Visible,\n                                    maxLines = 1,\n                                    softWrap = false\n                                )\n                            },\n                            alwaysShowLabel = false\n                        )\n                    }\n                }\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun BottomBarContent(\n    visibleDestinations: List<BottomBarDestination>,\n    effectiveSelectedIndex: Int,\n    animatedSelectedIndex: Float,\n    moveDirection: Float = 0f,\n    liquidMotion: Float = 0f,\n    superuserCount: Int,\n    apmModuleCount: Int,\n    kernelModuleCount: Int,\n    enableSuperUserBadge: Boolean,\n    enableApmBadge: Boolean,\n    enableKernelBadge: Boolean,\n    currentRoute: String?,\n    navController: NavHostController,\n    context: android.content.Context,\n    onUserInteraction: (() -> Unit)? = null\n) {\n    val navigator = navController.rememberDestinationsNavigator()\n    val itemSize = 56.dp\n    val itemSpacing = 4.dp\n    val containerPadding = 7.dp\n    val itemShape = if (BackgroundConfig.isNavBarGlassEnabled) CircleShape else MaterialTheme.shapes.large\n    val isGlassEnabled = BackgroundConfig.isNavBarGlassEnabled\n    val indicatorHorizontalPadding by animateDpAsState(\n        targetValue = if (isGlassEnabled) 3.dp else 0.dp,\n        animationSpec = spring(\n            dampingRatio = Spring.DampingRatioLowBouncy,\n            stiffness = Spring.StiffnessLow,\n        ),\n        label = \"indicatorHorizontalPadding\"\n    )\n    val indicatorScale by animateFloatAsState(\n        targetValue = if (isGlassEnabled) 1.06f else 1f,\n        animationSpec = spring(\n            dampingRatio = Spring.DampingRatioMediumBouncy,\n            stiffness = Spring.StiffnessLow,\n        ),\n        label = \"indicatorScale\"\n    )\n    val waterStretch by animateFloatAsState(\n        targetValue = if (isGlassEnabled) liquidMotion else 0f,\n        animationSpec = tween(durationMillis = 120),\n        label = \"waterStretch\"\n    )\n    val leadingDropAlpha by animateFloatAsState(\n        targetValue = if (isGlassEnabled) liquidMotion else 0f,\n        animationSpec = tween(durationMillis = 100),\n        label = \"leadingDropAlpha\"\n    )\n\n    // Calculate exact width based on items\n    val navBarWidth = (itemSize * visibleDestinations.size) +\n            (itemSpacing * (visibleDestinations.size - 1)) +\n            (containerPadding * 2)\n\n    Box(\n        modifier = Modifier\n            .width(navBarWidth)\n            .height(72.dp)\n    ) {\n        Box(\n            modifier = Modifier\n                .fillMaxSize()\n                .padding(horizontal = containerPadding)\n        ) {\n            // Animated sliding indicator\n            if (visibleDestinations.isNotEmpty()) {\n                val density = LocalDensity.current\n                val itemSizePx = with(density) { itemSize.toPx() }\n                val itemSpacingPx = with(density) { itemSpacing.toPx() }\n                val stretchPx = with(density) { (22.dp * waterStretch).toPx() }\n\n                // Calculate offset: each item position = (itemSize + spacing) * index\n                val indicatorOffset = (itemSizePx + itemSpacingPx) * animatedSelectedIndex\n                val stretchOffset = if (moveDirection < 0f) -stretchPx else 0f\n\n                Box(\n                    modifier = Modifier\n                        .fillMaxHeight()\n                        .padding(vertical = 8.dp)\n                        .offset {\n                            IntOffset(\n                                x = (indicatorOffset - with(density) { indicatorHorizontalPadding.toPx() }).toInt(),\n                                y = 0\n                            )\n                        }\n                        .width(itemSize + indicatorHorizontalPadding * 2 + with(density) { stretchPx.toDp() })\n                        .graphicsLayer {\n                            translationX = stretchOffset\n                            scaleX = indicatorScale\n                            scaleY = if (isGlassEnabled) 1.02f else 1f\n                        },\n                    contentAlignment = Alignment.Center\n                ) {\n                    Box(\n                        modifier = Modifier\n                            .fillMaxHeight()\n                            .padding(horizontal = indicatorHorizontalPadding)\n                            .width(itemSize + with(density) { stretchPx.toDp() })\n                            .background(\n                                color = if (isGlassEnabled) {\n                                    MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.8f)\n                                } else {\n                                    MaterialTheme.colorScheme.secondaryContainer\n                                },\n                                shape = itemShape\n                            )\n                            .then(\n                                if (isGlassEnabled) {\n                                    Modifier.drawWithContent {\n                                        drawContent()\n                                        val dropRadius = size.height * 0.28f\n                                        val dropX = if (moveDirection >= 0f) {\n                                            size.width - dropRadius * 0.7f\n                                        } else {\n                                            dropRadius * 0.7f\n                                        }\n                                        drawRoundRect(\n                                            brush = Brush.radialGradient(\n                                                colors = listOf(\n                                                    Color.White.copy(alpha = 0.22f * leadingDropAlpha),\n                                                    Color.Transparent,\n                                                ),\n                                                center = Offset(dropX, size.height * 0.42f),\n                                                radius = dropRadius * 1.35f,\n                                            ),\n                                            size = Size(size.width, size.height),\n                                            cornerRadius = CornerRadius(size.height / 2f, size.height / 2f),\n                                        )\n                                    }\n                                } else {\n                                    Modifier\n                                }\n                            )\n                    )\n                }\n            }\n\n            // Navigation items\n            Row(\n                modifier = Modifier.fillMaxSize(),\n                horizontalArrangement = Arrangement.spacedBy(itemSpacing),\n                verticalAlignment = Alignment.CenterVertically\n            ) {\n                visibleDestinations.forEachIndexed { index, destination ->\n                    val isSelected = index == effectiveSelectedIndex\n\n                    Box(\n                        modifier = Modifier\n                            .size(itemSize)\n                            .clip(itemShape)\n                            .clickable {\n                                onUserInteraction?.invoke()\n                                // If already on this destination, do nothing\n                                if (destination.direction.route == currentRoute) return@clickable\n\n                                if (me.bmax.apatch.ui.theme.SoundEffectConfig.scope == me.bmax.apatch.ui.theme.SoundEffectConfig.SCOPE_BOTTOM_BAR) {\n                                    me.bmax.apatch.util.SoundEffectManager.play(context)\n                                }\n                                if (me.bmax.apatch.ui.theme.VibrationConfig.scope == me.bmax.apatch.ui.theme.VibrationConfig.SCOPE_BOTTOM_BAR) {\n                                    me.bmax.apatch.util.VibrationManager.vibrate(context)\n                                }\n\n                                navigator.navigate(destination.direction) {\n                                    popUpTo(NavGraphs.root) {\n                                        saveState = true\n                                    }\n                                    launchSingleTop = true\n                                    restoreState = true\n                                }\n                            },\n                        contentAlignment = Alignment.Center\n                    ) {\n                        val badgeContent = when {\n                            destination == BottomBarDestination.SuperUser && enableSuperUserBadge -> superuserCount\n                            destination == BottomBarDestination.AModule && enableApmBadge -> apmModuleCount\n                            destination == BottomBarDestination.KModule && enableKernelBadge -> kernelModuleCount\n                            else -> 0\n                        }\n\n                        BadgedBox(\n                            badge = {\n                                if (badgeContent > 0) {\n                                    Badge(containerColor = MaterialTheme.colorScheme.secondary) {\n                                        Text(text = badgeContent.toString())\n                                    }\n                                }\n                            }\n                        ) {\n                            Icon(\n                                if (isSelected) destination.iconSelected else destination.iconNotSelected,\n                                stringResource(destination.label),\n                                tint = if (isSelected) {\n                                    MaterialTheme.colorScheme.primary\n                                } else {\n                                    MaterialTheme.colorScheme.onSurfaceVariant\n                                }\n                            )\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun NavigationRailBar(navController: NavHostController) {\n    val context = LocalContext.current\n    val state by APApplication.apStateLiveData.observeAsState(APApplication.State.UNKNOWN_STATE)\n    val navigator = navController.rememberDestinationsNavigator()\n\n    val prefs = APApplication.sharedPreferences\n    var showNavApm by remember { mutableStateOf(prefs.getBoolean(\"show_nav_apm\", true)) }\n    var showNavKpm by remember { mutableStateOf(prefs.getBoolean(\"show_nav_kpm\", true)) }\n    var showNavSuperUser by remember { mutableStateOf(prefs.getBoolean(\"show_nav_superuser\", true)) }\n\n    var enableSuperUserBadge by remember { mutableStateOf(prefs.getBoolean(\"badge_superuser\", true)) }\n    var enableApmBadge by remember { mutableStateOf(prefs.getBoolean(\"badge_apm\", true)) }\n    var enableKernelBadge by remember { mutableStateOf(prefs.getBoolean(\"badge_kernel\", true)) }\n\n    val superuserCount by me.bmax.apatch.util.AppData.DataRefreshManager.superuserCount.collectAsState()\n    val apmModuleCount by me.bmax.apatch.util.AppData.DataRefreshManager.apmModuleCount.collectAsState()\n    val kernelModuleCount by me.bmax.apatch.util.AppData.DataRefreshManager.kernelModuleCount.collectAsState()\n\n    DisposableEffect(Unit) {\n        val listener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPrefs, key ->\n            when (key) {\n                \"show_nav_apm\" -> showNavApm = sharedPrefs.getBoolean(key, true)\n                \"show_nav_kpm\" -> showNavKpm = sharedPrefs.getBoolean(key, true)\n                \"show_nav_superuser\" -> showNavSuperUser = sharedPrefs.getBoolean(key, true)\n                \"badge_superuser\" -> enableSuperUserBadge = sharedPrefs.getBoolean(key, true)\n                \"badge_apm\" -> enableApmBadge = sharedPrefs.getBoolean(key, true)\n                \"badge_kernel\" -> enableKernelBadge = sharedPrefs.getBoolean(key, true)\n            }\n        }\n        prefs.registerOnSharedPreferenceChangeListener(listener)\n        onDispose {\n            prefs.unregisterOnSharedPreferenceChangeListener(listener)\n        }\n    }\n\n    Crossfade(\n        targetState = state,\n        label = \"NavigationRailStateCrossfade\"\n    ) { state ->\n        val kPatchReady = state != APApplication.State.UNKNOWN_STATE\n        val aPatchReady = state == APApplication.State.ANDROIDPATCH_INSTALLED\n\n        val visibleDestinations = BottomBarDestination.entries.filter { destination ->\n            when {\n                destination == BottomBarDestination.AModule && !showNavApm -> false\n                destination == BottomBarDestination.KModule && !showNavKpm -> false\n                destination == BottomBarDestination.SuperUser && !showNavSuperUser -> false\n                (destination.kPatchRequired && !kPatchReady) || (destination.aPatchRequired && !aPatchReady) -> false\n                else -> true\n            }\n        }\n\n        val currentBackStackEntry by navController.currentBackStackEntryAsState()\n        val currentRoute = currentBackStackEntry?.destination?.route\n\n        val isOnBackStack = visibleDestinations.map { destination ->\n            navController.isRouteOnBackStackAsState(destination.direction).value\n        }\n\n        val selectedIndex = run {\n            val exactMatch = visibleDestinations.indexOfFirst { it.direction.route == currentRoute }\n            if (exactMatch != -1) exactMatch\n            else isOnBackStack.indexOfLast { it }\n        }\n\n        NavigationRail(\n            modifier = Modifier.fillMaxHeight(),\n            containerColor = if (BackgroundConfig.isCustomBackgroundEnabled) {\n                MaterialTheme.colorScheme.surface.copy(alpha = BackgroundConfig.customBackgroundOpacity)\n            } else {\n                NavigationRailDefaults.ContainerColor\n            }\n        ) {\n            Spacer(Modifier.weight(1f))\n\n            visibleDestinations.forEachIndexed { index, destination ->\n                key(destination) {\n                    val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction)\n                    val isSelected = index == selectedIndex\n\n                    NavigationRailItem(\n                        selected = isSelected,\n                        onClick = {\n                            if (me.bmax.apatch.ui.theme.SoundEffectConfig.scope == me.bmax.apatch.ui.theme.SoundEffectConfig.SCOPE_BOTTOM_BAR) {\n                                me.bmax.apatch.util.SoundEffectManager.play(context)\n                            }\n                            if (me.bmax.apatch.ui.theme.VibrationConfig.scope == me.bmax.apatch.ui.theme.VibrationConfig.SCOPE_BOTTOM_BAR) {\n                                me.bmax.apatch.util.VibrationManager.vibrate(context)\n                            }\n                            if (isCurrentDestOnBackStack) {\n                                navigator.popBackStack(destination.direction, false)\n                            }\n                            navigator.navigate(destination.direction) {\n                                popUpTo(NavGraphs.root) {\n                                    saveState = true\n                                }\n                                launchSingleTop = true\n                                restoreState = true\n                            }\n                        },\n                        icon = {\n                            val badgeContent = when {\n                                destination == BottomBarDestination.SuperUser && enableSuperUserBadge -> superuserCount\n                                destination == BottomBarDestination.AModule && enableApmBadge -> apmModuleCount\n                                destination == BottomBarDestination.KModule && enableKernelBadge -> kernelModuleCount\n                                else -> 0\n                            }\n\n                            BadgedBox(\n                                badge = {\n                                    if (badgeContent > 0) {\n                                        Badge(containerColor = MaterialTheme.colorScheme.secondary) {\n                                            Text(text = badgeContent.toString())\n                                        }\n                                    }\n                                }\n                            ) {\n                                if (isSelected) {\n                                    Icon(destination.iconSelected, stringResource(destination.label))\n                                } else {\n                                    Icon(destination.iconNotSelected, stringResource(destination.label))\n                                }\n                            }\n                        },\n                        label = {\n                            Text(\n                                text = stringResource(destination.label),\n                                overflow = TextOverflow.Ellipsis,\n                                maxLines = 1,\n                                softWrap = false\n                            )\n                        },\n                        alwaysShowLabel = false\n                    )\n                }\n            }\n\n            Spacer(Modifier.weight(1f))\n        }\n    }\n}\n\nprivate fun createNavTransitions(\n    folkXEngineEnabled: Boolean,\n    folkXAnimationType: String?,\n    folkXAnimationSpeed: Float,\n    bottomBarRoutes: Set<String>,\n    useNavigationRail: Boolean = false\n): NavHostAnimatedDestinationStyle {\n    return object : NavHostAnimatedDestinationStyle() {\n        override val enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition = {\n            if (targetState.destination.route !in bottomBarRoutes) {\n                slideInHorizontally(initialOffsetX = { it })\n            } else {\n                if (folkXEngineEnabled) {\n                    val initialRoute = initialState.destination.route\n                    val targetRoute = targetState.destination.route\n                    val initialIndex = BottomBarDestination.entries.indexOfFirst { it.direction.route == initialRoute }\n                    val targetIndex = BottomBarDestination.entries.indexOfFirst { it.direction.route == targetRoute }\n\n                    val stiffness = 300f * folkXAnimationSpeed * folkXAnimationSpeed\n                    val duration300 = (300 / folkXAnimationSpeed).toInt()\n\n                    if (initialIndex != -1 && targetIndex != -1) {\n                        when (folkXAnimationType) {\n                            \"spatial\" -> {\n                                if (targetIndex > initialIndex) {\n                                    scaleIn(initialScale = 0.9f, animationSpec = spring(dampingRatio = 0.8f, stiffness = stiffness)) + fadeIn(animationSpec = tween(duration300))\n                                } else {\n                                    scaleIn(initialScale = 1.1f, animationSpec = spring(dampingRatio = 0.8f, stiffness = stiffness)) + fadeIn(animationSpec = tween(duration300))\n                                }\n                            }\n                            \"fade\" -> fadeIn(animationSpec = tween(duration300))\n                            \"vertical\" -> {\n                                if (targetIndex > initialIndex) {\n                                    slideInVertically(animationSpec = spring(dampingRatio = 0.8f, stiffness = stiffness), initialOffsetY = { height -> height }) + fadeIn()\n                                } else {\n                                    slideInVertically(animationSpec = spring(dampingRatio = 0.8f, stiffness = stiffness), initialOffsetY = { height -> -height }) + fadeIn()\n                                }\n                            }\n                            \"diagonal\" -> {\n                                if (targetIndex > initialIndex) {\n                                    slideInHorizontally(animationSpec = spring(dampingRatio = 0.8f, stiffness = stiffness), initialOffsetX = { width -> width }) +\n                                    slideInVertically(animationSpec = spring(dampingRatio = 0.8f, stiffness = stiffness), initialOffsetY = { height -> height }) + fadeIn()\n                                } else {\n                                    slideInHorizontally(animationSpec = spring(dampingRatio = 0.8f, stiffness = stiffness), initialOffsetX = { width -> -width }) +\n                                    slideInVertically(animationSpec = spring(dampingRatio = 0.8f, stiffness = stiffness), initialOffsetY = { height -> -height }) + fadeIn()\n                                }\n                            }\n                            else -> {\n                                // linear: 侧边导航栏使用上下滑动，底部导航栏使用左右滑动\n                                if (useNavigationRail) {\n                                    if (targetIndex > initialIndex) {\n                                        slideInVertically(animationSpec = spring(dampingRatio = 0.8f, stiffness = stiffness), initialOffsetY = { height -> height }) + fadeIn()\n                                    } else {\n                                        slideInVertically(animationSpec = spring(dampingRatio = 0.8f, stiffness = stiffness), initialOffsetY = { height -> -height }) + fadeIn()\n                                    }\n                                } else {\n                                    if (targetIndex > initialIndex) {\n                                        slideInHorizontally(animationSpec = spring(dampingRatio = 0.8f, stiffness = stiffness), initialOffsetX = { width -> width })\n                                    } else {\n                                        slideInHorizontally(animationSpec = spring(dampingRatio = 0.8f, stiffness = stiffness), initialOffsetX = { width -> -width })\n                                    }\n                                }\n                            }\n                        }\n                    } else {\n                        fadeIn(animationSpec = tween(340))\n                    }\n                } else {\n                    fadeIn(animationSpec = tween(340))\n                }\n            }\n        }\n\n        override val exitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition = {\n            if (initialState.destination.route in bottomBarRoutes && targetState.destination.route !in bottomBarRoutes) {\n                slideOutHorizontally(targetOffsetX = { -it / 4 }) + fadeOut()\n            } else {\n                if (folkXEngineEnabled && initialState.destination.route in bottomBarRoutes && targetState.destination.route in bottomBarRoutes) {\n                    val initialRoute = initialState.destination.route\n                    val targetRoute = targetState.destination.route\n                    val initialIndex = BottomBarDestination.entries.indexOfFirst { it.direction.route == initialRoute }\n                    val targetIndex = BottomBarDestination.entries.indexOfFirst { it.direction.route == targetRoute }\n\n                    val stiffness = 300f * folkXAnimationSpeed * folkXAnimationSpeed\n                    val duration300 = (300 / folkXAnimationSpeed).toInt()\n                    val duration600 = (600 / folkXAnimationSpeed).toInt()\n\n                    if (initialIndex != -1 && targetIndex != -1) {\n                        when (folkXAnimationType) {\n                            \"spatial\" -> {\n                                if (targetIndex > initialIndex) {\n                                    scaleOut(targetScale = 1.1f, animationSpec = spring(dampingRatio = 0.8f, stiffness = stiffness)) + fadeOut(animationSpec = tween(duration300))\n                                } else {\n                                    scaleOut(targetScale = 0.9f, animationSpec = spring(dampingRatio = 0.8f, stiffness = stiffness)) + fadeOut(animationSpec = tween(duration300))\n                                }\n                            }\n                            \"fade\" -> fadeOut(animationSpec = tween(duration600))\n                            \"vertical\" -> {\n                                if (targetIndex > initialIndex) {\n                                    slideOutVertically(animationSpec = spring(dampingRatio = 0.8f, stiffness = stiffness), targetOffsetY = { height -> -height }) + fadeOut()\n                                } else {\n                                    slideOutVertically(animationSpec = spring(dampingRatio = 0.8f, stiffness = stiffness), targetOffsetY = { height -> height }) + fadeOut()\n                                }\n                            }\n                            \"diagonal\" -> {\n                                if (targetIndex > initialIndex) {\n                                    slideOutHorizontally(animationSpec = tween(duration600), targetOffsetX = { width -> -width }) +\n                                    slideOutVertically(animationSpec = tween(duration600), targetOffsetY = { height -> -height }) + fadeOut(animationSpec = tween(duration600))\n                                } else {\n                                    slideOutHorizontally(animationSpec = tween(duration600), targetOffsetX = { width -> width }) +\n                                    slideOutVertically(animationSpec = tween(duration600), targetOffsetY = { height -> height }) + fadeOut(animationSpec = tween(duration600))\n                                }\n                            }\n                            else -> {\n                                // linear: 侧边导航栏使用上下滑动，底部导航栏使用左右滑动\n                                if (useNavigationRail) {\n                                    if (targetIndex > initialIndex) {\n                                        slideOutVertically(animationSpec = spring(dampingRatio = 0.8f, stiffness = stiffness), targetOffsetY = { height -> -height }) + fadeOut()\n                                    } else {\n                                        slideOutVertically(animationSpec = spring(dampingRatio = 0.8f, stiffness = stiffness), targetOffsetY = { height -> height }) + fadeOut()\n                                    }\n                                } else {\n                                    if (targetIndex > initialIndex) {\n                                        slideOutHorizontally(animationSpec = spring(dampingRatio = 0.8f, stiffness = stiffness), targetOffsetX = { width -> -width })\n                                    } else {\n                                        slideOutHorizontally(animationSpec = spring(dampingRatio = 0.8f, stiffness = stiffness), targetOffsetX = { width -> width })\n                                    }\n                                }\n                            }\n                        }\n                    } else {\n                        fadeOut(animationSpec = tween(340))\n                    }\n                } else {\n                    fadeOut(animationSpec = tween(340))\n                }\n            }\n        }\n\n        override val popEnterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition = {\n            if (targetState.destination.route in bottomBarRoutes) {\n                if (initialState.destination.route !in bottomBarRoutes || !useNavigationRail) {\n                    slideInHorizontally(initialOffsetX = { -it / 4 }) + fadeIn()\n                } else {\n                    slideInVertically(initialOffsetY = { -it / 4 }) + fadeIn()\n                }\n            } else {\n                fadeIn(animationSpec = tween(340))\n            }\n        }\n\n        override val popExitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition = {\n            if (initialState.destination.route !in bottomBarRoutes) {\n                scaleOut(targetScale = 0.9f) + fadeOut()\n            } else {\n                fadeOut(animationSpec = tween(340))\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/WebUIActivity.kt",
    "content": "package me.bmax.apatch.ui\n\nimport android.annotation.SuppressLint\nimport android.app.ActivityManager\nimport android.content.ActivityNotFoundException\nimport android.content.Intent\nimport android.os.Build\nimport android.os.Bundle\nimport android.net.Uri\nimport android.view.ViewGroup.MarginLayoutParams\nimport android.webkit.ValueCallback\nimport android.webkit.WebChromeClient\nimport android.webkit.WebResourceRequest\nimport android.webkit.WebResourceResponse\nimport android.webkit.WebView\nimport android.webkit.WebViewClient\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.appcompat.app.AppCompatDelegate\nimport androidx.activity.compose.setContent\nimport androidx.activity.result.ActivityResultLauncher\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.activity.enableEdgeToEdge\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.systemBarsPadding\nimport androidx.compose.material3.CircularProgressIndicator\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.toArgb\nimport androidx.compose.ui.viewinterop.AndroidView\nimport androidx.core.view.ViewCompat\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updateLayoutParams\nimport androidx.lifecycle.lifecycleScope\nimport androidx.webkit.WebViewAssetLoader\nimport kotlinx.coroutines.launch\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.ui.theme.APatchTheme\nimport me.bmax.apatch.ui.viewmodel.SuperUserViewModel\nimport me.bmax.apatch.ui.webui.AppIconUtil\nimport me.bmax.apatch.ui.webui.SuFilePathHandler\nimport me.bmax.apatch.ui.webui.WebViewInterface\nimport java.io.File\n\n@SuppressLint(\"SetJavaScriptEnabled\")\nclass WebUIActivity : AppCompatActivity() {\n    private lateinit var webViewInterface: WebViewInterface\n    private lateinit var fileChooserLauncher: ActivityResultLauncher<Intent>\n    private var filePathCallback: ValueCallback<Array<Uri>>? = null\n    private var isWebViewReady by mutableStateOf(false)\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n\n        enableEdgeToEdge()\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n            window.isNavigationBarContrastEnforced = false\n        }\n\n        val prefs = APApplication.sharedPreferences\n        val nightModeFollowSys = prefs.getBoolean(\"night_mode_follow_sys\", false)\n        val nightModeEnabled = prefs.getBoolean(\"night_mode_enabled\", false)\n        val mode = if (nightModeFollowSys) {\n            AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM\n        } else if (nightModeEnabled) {\n            AppCompatDelegate.MODE_NIGHT_YES\n        } else {\n            AppCompatDelegate.MODE_NIGHT_NO\n        }\n        AppCompatDelegate.setDefaultNightMode(mode)\n\n        super.onCreate(savedInstanceState)\n        \n        setupActivityInfo()\n\n        setContent {\n            APatchTheme(allowCustomBackground = false) {\n                val backgroundColor = MaterialTheme.colorScheme.background\n                Box(\n                    modifier = Modifier.fillMaxSize().background(backgroundColor),\n                    contentAlignment = Alignment.Center\n                ) {\n                    if (isWebViewReady) {\n                        AndroidView(\n                            modifier = Modifier.fillMaxSize().systemBarsPadding(),\n                            factory = { context ->\n                                WebView(context).apply {\n                                    layoutParams = android.view.ViewGroup.LayoutParams(\n                                        android.view.ViewGroup.LayoutParams.MATCH_PARENT,\n                                        android.view.ViewGroup.LayoutParams.MATCH_PARENT\n                                    )\n                                    configureWebView(this)\n                                }\n                            },\n                            update = { view ->\n                                view.setBackgroundColor(backgroundColor.toArgb())\n                            }\n                        )\n                    } else {\n                        CircularProgressIndicator()\n                    }\n                }\n            }\n        }\n\n        lifecycleScope.launch {\n            if (SuperUserViewModel.apps.isEmpty()) {\n                SuperUserViewModel().fetchAppList()\n            }\n            isWebViewReady = true\n        }\n\n        fileChooserLauncher = registerForActivityResult(\n            ActivityResultContracts.StartActivityForResult()\n        ) { result ->\n            val uris: Array<Uri>? = when (result.resultCode) {\n                RESULT_OK -> result.data?.let { data ->\n                    when {\n                        data.clipData != null -> {\n                            Array(data.clipData!!.itemCount) { i ->\n                                data.clipData!!.getItemAt(i).uri\n                            }\n                        }\n                        data.data != null -> arrayOf(data.data!!)\n                        else -> null\n                    }\n                }\n                else -> null\n            }\n            filePathCallback?.onReceiveValue(uris)\n            filePathCallback = null\n        }\n    }\n\n    private fun setupActivityInfo() {\n        val name = intent.getStringExtra(\"name\")!!\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {\n            @Suppress(\"DEPRECATION\")\n            setTaskDescription(ActivityManager.TaskDescription(\"FolkPatch - $name\"))\n        } else {\n            val taskDescription = ActivityManager.TaskDescription.Builder().setLabel(\"FolkPatch - $name\").build()\n            setTaskDescription(taskDescription)\n        }\n    }\n\n    private fun configureWebView(webView: WebView) {\n        val moduleId = intent.getStringExtra(\"id\")!!\n\n        val prefs = APApplication.sharedPreferences\n        WebView.setWebContentsDebuggingEnabled(prefs.getBoolean(\"enable_web_debugging\", false))\n\n        val webRoot = File(\"/data/adb/modules/${moduleId}/webroot\")\n        val webViewAssetLoader = WebViewAssetLoader.Builder()\n            .setDomain(\"mui.kernelsu.org\")\n            .addPathHandler(\n                \"/\",\n                SuFilePathHandler(this, webRoot)\n            )\n            .build()\n\n        val webViewClient = object : WebViewClient() {\n            override fun shouldInterceptRequest(\n                view: WebView,\n                request: WebResourceRequest\n            ): WebResourceResponse? {\n                val url = request.url\n\n                // Handle ksu://icon/[packageName] to serve app icon via WebView\n                if (url.scheme.equals(\"ksu\", ignoreCase = true) && url.host.equals(\"icon\", ignoreCase = true)) {\n                    val packageName = url.path?.substring(1)\n                    if (!packageName.isNullOrEmpty()) {\n                        val icon = AppIconUtil.loadAppIconSync(this@WebUIActivity, packageName, 512)\n                        if (icon != null) {\n                            val stream = java.io.ByteArrayOutputStream()\n                            icon.compress(android.graphics.Bitmap.CompressFormat.PNG, 100, stream)\n                            val inputStream = java.io.ByteArrayInputStream(stream.toByteArray())\n                            return WebResourceResponse(\"image/png\", null, inputStream)\n                        }\n                    }\n                }\n\n                return webViewAssetLoader.shouldInterceptRequest(request.url)\n            }\n        }\n\n        webView.apply {\n            settings.javaScriptEnabled = true\n            settings.domStorageEnabled = true\n            settings.allowFileAccess = false\n            webViewInterface = WebViewInterface(this@WebUIActivity, this)\n            addJavascriptInterface(webViewInterface, \"ksu\")\n            setWebViewClient(webViewClient)\n            webChromeClient = object : WebChromeClient() {\n                override fun onShowFileChooser(\n                    webView: WebView?,\n                    filePathCallback: ValueCallback<Array<Uri>>?,\n                    fileChooserParams: FileChooserParams?\n                ): Boolean {\n                    this@WebUIActivity.filePathCallback?.onReceiveValue(null)\n                    this@WebUIActivity.filePathCallback = filePathCallback\n                    val intent = fileChooserParams?.createIntent() ?: Intent(Intent.ACTION_GET_CONTENT).apply { type = \"*/*\" }\n                    if (fileChooserParams?.mode == FileChooserParams.MODE_OPEN_MULTIPLE) {\n                        intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)\n                    }\n                    try {\n                        fileChooserLauncher.launch(intent)\n                    } catch (_: ActivityNotFoundException) {\n                        filePathCallback?.onReceiveValue(null)\n                        this@WebUIActivity.filePathCallback = null\n                        return false\n                    }\n                    return true\n                }\n            }\n            loadUrl(\"https://mui.kernelsu.org/index.html\")\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/Dialog.kt",
    "content": "package me.bmax.apatch.ui.component\n\nimport android.graphics.text.LineBreaker\nimport android.os.Build\nimport android.os.Parcelable\nimport android.text.Layout\nimport android.text.method.LinkMovementMethod\nimport android.util.Log\nimport android.view.ViewGroup\nimport android.widget.TextView\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.layout.wrapContentHeight\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.AlertDialogDefaults\nimport androidx.compose.material3.BasicAlertDialog\nimport androidx.compose.material3.CircularProgressIndicator\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.LocalContentColor\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Surface\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.MutableState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.rememberUpdatedState\nimport androidx.compose.runtime.saveable.Saver\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.toArgb\nimport androidx.compose.ui.platform.LocalView\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.viewinterop.AndroidView\nimport androidx.compose.ui.window.Dialog\nimport androidx.compose.ui.window.DialogProperties\nimport androidx.compose.ui.window.DialogWindowProvider\nimport androidx.compose.ui.window.SecureFlagPolicy\nimport io.noties.markwon.Markwon\nimport io.noties.markwon.utils.NoCopySpannableFactory\nimport kotlinx.coroutines.CancellableContinuation\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.channels.Channel\nimport kotlinx.coroutines.channels.ReceiveChannel\nimport kotlinx.coroutines.flow.FlowCollector\nimport kotlinx.coroutines.flow.consumeAsFlow\nimport kotlinx.coroutines.flow.onEach\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.suspendCancellableCoroutine\nimport kotlinx.parcelize.Parcelize\nimport me.bmax.apatch.util.ui.APDialogBlurBehindUtils.Companion.setupWindowBlurListener\nimport kotlin.coroutines.resume\n\nprivate const val TAG = \"DialogComponent\"\n\ninterface ConfirmDialogVisuals : Parcelable {\n    val title: String\n    val content: String\n    val isMarkdown: Boolean\n    val confirm: String?\n    val dismiss: String?\n}\n\n@Parcelize\nprivate data class ConfirmDialogVisualsImpl(\n    override val title: String,\n    override val content: String,\n    override val isMarkdown: Boolean,\n    override val confirm: String?,\n    override val dismiss: String?,\n) : ConfirmDialogVisuals {\n    companion object {\n        val Empty: ConfirmDialogVisuals = ConfirmDialogVisualsImpl(\"\", \"\", false, null, null)\n    }\n}\n\ninterface DialogHandle {\n    val isShown: Boolean\n    val dialogType: String\n    fun show()\n    fun hide()\n}\n\ninterface LoadingDialogHandle : DialogHandle {\n    suspend fun <R> withLoading(block: suspend () -> R): R\n    fun showLoading()\n}\n\nsealed interface ConfirmResult {\n    data object Confirmed : ConfirmResult\n    data object Canceled : ConfirmResult\n}\n\ninterface ConfirmDialogHandle : DialogHandle {\n    val visuals: ConfirmDialogVisuals\n\n    fun showConfirm(\n        title: String,\n        content: String,\n        markdown: Boolean = false,\n        confirm: String? = null,\n        dismiss: String? = null\n    )\n\n    suspend fun awaitConfirm(\n        title: String,\n        content: String,\n        markdown: Boolean = false,\n        confirm: String? = null,\n        dismiss: String? = null\n    ): ConfirmResult\n}\n\nprivate abstract class DialogHandleBase(\n    protected val visible: MutableState<Boolean>,\n    protected val coroutineScope: CoroutineScope\n) : DialogHandle {\n    override val isShown: Boolean\n        get() = visible.value\n\n    override fun show() {\n        coroutineScope.launch {\n            visible.value = true\n        }\n    }\n\n    final override fun hide() {\n        coroutineScope.launch {\n            visible.value = false\n        }\n    }\n\n    override fun toString(): String {\n        return dialogType\n    }\n}\n\nprivate class LoadingDialogHandleImpl(\n    visible: MutableState<Boolean>,\n    coroutineScope: CoroutineScope\n) : LoadingDialogHandle, DialogHandleBase(visible, coroutineScope) {\n    override suspend fun <R> withLoading(block: suspend () -> R): R {\n        return coroutineScope.async {\n            try {\n                visible.value = true\n                block()\n            } finally {\n                visible.value = false\n            }\n        }.await()\n    }\n\n    override fun showLoading() {\n        show()\n    }\n\n    override val dialogType: String get() = \"LoadingDialog\"\n}\n\ntypealias NullableCallback = (() -> Unit)?\n\ninterface ConfirmCallback {\n\n    val onConfirm: NullableCallback\n\n    val onDismiss: NullableCallback\n\n    val isEmpty: Boolean get() = onConfirm == null && onDismiss == null\n\n    companion object {\n        operator fun invoke(\n            onConfirmProvider: () -> NullableCallback,\n            onDismissProvider: () -> NullableCallback\n        ): ConfirmCallback {\n            return object : ConfirmCallback {\n                override val onConfirm: NullableCallback\n                    get() = onConfirmProvider()\n                override val onDismiss: NullableCallback\n                    get() = onDismissProvider()\n            }\n        }\n    }\n}\n\nprivate class ConfirmDialogHandleImpl(\n    visible: MutableState<Boolean>,\n    coroutineScope: CoroutineScope,\n    callback: ConfirmCallback,\n    override var visuals: ConfirmDialogVisuals = ConfirmDialogVisualsImpl.Empty,\n    private val resultFlow: ReceiveChannel<ConfirmResult>\n) : ConfirmDialogHandle, DialogHandleBase(visible, coroutineScope) {\n    private class ResultCollector(\n        private val callback: ConfirmCallback\n    ) : FlowCollector<ConfirmResult> {\n        fun handleResult(result: ConfirmResult) {\n            Log.d(TAG, \"handleResult: ${result.javaClass.simpleName}\")\n            when (result) {\n                ConfirmResult.Confirmed -> onConfirm()\n                ConfirmResult.Canceled -> onDismiss()\n            }\n        }\n\n        fun onConfirm() {\n            callback.onConfirm?.invoke()\n        }\n\n        fun onDismiss() {\n            callback.onDismiss?.invoke()\n        }\n\n        override suspend fun emit(value: ConfirmResult) {\n            handleResult(value)\n        }\n    }\n\n    private val resultCollector = ResultCollector(callback)\n\n    private var awaitContinuation: CancellableContinuation<ConfirmResult>? = null\n\n    private val isCallbackEmpty = callback.isEmpty\n\n    init {\n        coroutineScope.launch {\n            resultFlow\n                .consumeAsFlow()\n                .onEach { result ->\n                    awaitContinuation?.let {\n                        awaitContinuation = null\n                        if (it.isActive) {\n                            it.resume(result)\n                        }\n                    }\n                }\n                .onEach { hide() }\n                .collect(resultCollector)\n        }\n    }\n\n    private suspend fun awaitResult(): ConfirmResult {\n        return suspendCancellableCoroutine {\n            awaitContinuation = it.apply {\n                if (isCallbackEmpty) {\n                    invokeOnCancellation {\n                        visible.value = false\n                    }\n                }\n            }\n        }\n    }\n\n    fun updateVisuals(visuals: ConfirmDialogVisuals) {\n        this.visuals = visuals\n    }\n\n    override fun show() {\n        if (visuals !== ConfirmDialogVisualsImpl.Empty) {\n            super.show()\n        } else {\n            throw UnsupportedOperationException(\"can't show confirm dialog with the Empty visuals\")\n        }\n    }\n\n    override fun showConfirm(\n        title: String,\n        content: String,\n        markdown: Boolean,\n        confirm: String?,\n        dismiss: String?\n    ) {\n        coroutineScope.launch {\n            updateVisuals(ConfirmDialogVisualsImpl(title, content, markdown, confirm, dismiss))\n            show()\n        }\n    }\n\n    override suspend fun awaitConfirm(\n        title: String,\n        content: String,\n        markdown: Boolean,\n        confirm: String?,\n        dismiss: String?\n    ): ConfirmResult {\n        coroutineScope.launch {\n            updateVisuals(ConfirmDialogVisualsImpl(title, content, markdown, confirm, dismiss))\n            show()\n        }\n        return awaitResult()\n    }\n\n    override val dialogType: String get() = \"ConfirmDialog\"\n\n    override fun toString(): String {\n        return \"${super.toString()}(visuals: $visuals)\"\n    }\n\n    companion object {\n        fun Saver(\n            visible: MutableState<Boolean>,\n            coroutineScope: CoroutineScope,\n            callback: ConfirmCallback,\n            resultChannel: ReceiveChannel<ConfirmResult>\n        ) = Saver<ConfirmDialogHandle, ConfirmDialogVisuals>(\n            save = {\n                it.visuals\n            },\n            restore = {\n                Log.d(TAG, \"ConfirmDialog restore, visuals: $it\")\n                ConfirmDialogHandleImpl(visible, coroutineScope, callback, it, resultChannel)\n            }\n        )\n    }\n}\n\nprivate class CustomDialogHandleImpl(\n    visible: MutableState<Boolean>,\n    coroutineScope: CoroutineScope\n) : DialogHandleBase(visible, coroutineScope) {\n    override val dialogType: String get() = \"CustomDialog\"\n}\n\n@Composable\nfun rememberLoadingDialog(): LoadingDialogHandle {\n    val visible = remember {\n        mutableStateOf(false)\n    }\n    val coroutineScope = rememberCoroutineScope()\n\n    if (visible.value) {\n        LoadingDialog()\n    }\n\n    return remember {\n        LoadingDialogHandleImpl(visible, coroutineScope)\n    }\n}\n\n@Composable\nprivate fun rememberConfirmDialog(\n    visuals: ConfirmDialogVisuals,\n    callback: ConfirmCallback\n): ConfirmDialogHandle {\n    val visible = rememberSaveable {\n        mutableStateOf(false)\n    }\n    val coroutineScope = rememberCoroutineScope()\n    val resultChannel = remember {\n        Channel<ConfirmResult>()\n    }\n\n    val handle = rememberSaveable(\n        saver = ConfirmDialogHandleImpl.Saver(visible, coroutineScope, callback, resultChannel),\n        init = {\n            ConfirmDialogHandleImpl(visible, coroutineScope, callback, visuals, resultChannel)\n        }\n    )\n\n    if (visible.value) {\n        ConfirmDialog(\n            handle.visuals,\n            confirm = { coroutineScope.launch { resultChannel.send(ConfirmResult.Confirmed) } },\n            dismiss = { coroutineScope.launch { resultChannel.send(ConfirmResult.Canceled) } }\n        )\n    }\n\n    return handle\n}\n\n@Composable\nfun rememberConfirmCallback(\n    onConfirm: NullableCallback,\n    onDismiss: NullableCallback\n): ConfirmCallback {\n    val currentOnConfirm by rememberUpdatedState(newValue = onConfirm)\n    val currentOnDismiss by rememberUpdatedState(newValue = onDismiss)\n    return remember {\n        ConfirmCallback({ currentOnConfirm }, { currentOnDismiss })\n    }\n}\n\n@Composable\nfun rememberConfirmDialog(\n    onConfirm: NullableCallback = null,\n    onDismiss: NullableCallback = null\n): ConfirmDialogHandle {\n    return rememberConfirmDialog(rememberConfirmCallback(onConfirm, onDismiss))\n}\n\n@Composable\nfun rememberConfirmDialog(callback: ConfirmCallback): ConfirmDialogHandle {\n    return rememberConfirmDialog(ConfirmDialogVisualsImpl.Empty, callback)\n}\n\n@Composable\nfun rememberCustomDialog(composable: @Composable (dismiss: () -> Unit) -> Unit): DialogHandle {\n    val visible = rememberSaveable {\n        mutableStateOf(false)\n    }\n    val coroutineScope = rememberCoroutineScope()\n    if (visible.value) {\n        composable { visible.value = false }\n    }\n    return remember {\n        CustomDialogHandleImpl(visible, coroutineScope)\n    }\n}\n\n@Composable\nprivate fun LoadingDialog() {\n    Dialog(\n        onDismissRequest = {},\n        properties = DialogProperties(\n            dismissOnClickOutside = false,\n            dismissOnBackPress = false,\n            usePlatformDefaultWidth = false\n        )\n    ) {\n        Surface(\n            modifier = Modifier.size(100.dp), shape = RoundedCornerShape(8.dp)\n        ) {\n            Box(\n                contentAlignment = Alignment.Center,\n            ) {\n                CircularProgressIndicator()\n            }\n        }\n        val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n        setupWindowBlurListener(dialogWindowProvider.window)\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun ConfirmDialog(visuals: ConfirmDialogVisuals, confirm: () -> Unit, dismiss: () -> Unit) {\n    BasicAlertDialog(\n        onDismissRequest = {\n            dismiss()\n        },\n        properties = DialogProperties(\n            decorFitsSystemWindows = true,\n            usePlatformDefaultWidth = false,\n            securePolicy = SecureFlagPolicy.SecureOff\n        )\n    ) {\n        Surface(\n            modifier = Modifier\n                .width(320.dp)\n                .wrapContentHeight(),\n            shape = RoundedCornerShape(20.dp),\n            tonalElevation = AlertDialogDefaults.TonalElevation,\n            color = AlertDialogDefaults.containerColor,\n        ) {\n            Column(modifier = Modifier.padding(PaddingValues(all = 24.dp))) {\n                Box(\n                    Modifier\n                        .padding(PaddingValues(bottom = 16.dp))\n                        .align(Alignment.Start)\n                ) {\n                    Text(text = visuals.title, style = MaterialTheme.typography.headlineSmall)\n                }\n                Box(\n                    Modifier\n                        .weight(weight = 1f, fill = false)\n                        .padding(PaddingValues(bottom = 24.dp))\n                        .align(Alignment.Start)\n                ) {\n\n                    if (visuals.isMarkdown) {\n                        MarkdownContent(content = visuals.content)\n                    } else {\n                        Text(text = visuals.content, style = MaterialTheme.typography.bodyMedium)\n                    }\n                }\n                Row(\n                    modifier = Modifier.fillMaxWidth(),\n                    horizontalArrangement = Arrangement.End\n                ) {\n                    TextButton(onClick = dismiss) {\n                        Text(text = visuals.dismiss ?: stringResource(id = android.R.string.cancel))\n                    }\n\n                    TextButton(onClick = confirm) {\n                        Text(text = visuals.confirm ?: stringResource(id = android.R.string.ok))\n                    }\n                }\n            }\n            val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n            setupWindowBlurListener(dialogWindowProvider.window)\n        }\n    }\n\n}\n\n@Composable\nprivate fun MarkdownContent(content: String) {\n    val contentColor = LocalContentColor.current\n\n    AndroidView(\n        factory = { context ->\n            TextView(context).apply {\n                movementMethod = LinkMovementMethod.getInstance()\n                setSpannableFactory(NoCopySpannableFactory.getInstance())\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n                    breakStrategy = LineBreaker.BREAK_STRATEGY_SIMPLE\n                    hyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE\n                }\n\n                layoutParams = ViewGroup.LayoutParams(\n                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT\n                )\n            }\n        },\n        modifier = Modifier\n            .fillMaxWidth()\n            .wrapContentHeight(),\n        update = {\n            Markwon.create(it.context).setMarkdown(it, content)\n            it.setTextColor(contentColor.toArgb())\n        }\n    )\n}"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/DropdownMenu.kt",
    "content": "package me.bmax.apatch.ui.component\n\nimport androidx.compose.foundation.layout.ColumnScope\nimport androidx.compose.foundation.shape.CornerBasedShape\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.DropdownMenu\nimport androidx.compose.material3.DropdownMenuItem\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.shadow\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.unit.dp\nimport me.bmax.apatch.ui.theme.BackgroundConfig\n\n@Composable\nfun ProvideMenuShape(\n    value: CornerBasedShape = RoundedCornerShape(8.dp), content: @Composable () -> Unit\n) = MaterialTheme(\n    shapes = MaterialTheme.shapes.copy(extraSmall = value), content = content\n)\n\n@Composable\nfun WallpaperAwareDropdownMenu(\n    expanded: Boolean,\n    onDismissRequest: () -> Unit,\n    modifier: Modifier = Modifier,\n    shape: CornerBasedShape = RoundedCornerShape(10.dp),\n    containerColor: androidx.compose.ui.graphics.Color = androidx.compose.material3.MaterialTheme.colorScheme.surfaceContainer,\n    content: @Composable ColumnScope.() -> Unit\n) {\n    // 在壁纸模式下移除阴影效果\n    if (BackgroundConfig.isCustomBackgroundEnabled) {\n        DropdownMenu(\n            expanded = expanded,\n            onDismissRequest = onDismissRequest,\n            modifier = modifier.shadow(\n                elevation = 0.dp,\n                shape = shape,\n                ambientColor = Color.Transparent,\n                spotColor = Color.Transparent\n            ),\n            shape = shape,\n            tonalElevation = 0.dp, // 在壁纸模式下不使用阴影\n            shadowElevation = 0.dp, // 确保没有阴影\n            containerColor = containerColor.copy(alpha = 0.95f), // 略微透明，与壁纸更好地融合\n            content = content\n        )\n    } else {\n        DropdownMenu(\n            expanded = expanded,\n            onDismissRequest = onDismissRequest,\n            modifier = modifier,\n            shape = shape,\n            tonalElevation = 3.dp, // 默认阴影\n            shadowElevation = 3.dp, // 默认阴影\n            containerColor = containerColor,\n            content = content\n        )\n    }\n}\n\n@Composable\nfun WallpaperAwareDropdownMenuItem(\n    text: @Composable () -> Unit,\n    onClick: () -> Unit,\n    modifier: Modifier = Modifier,\n    leadingIcon: @Composable (() -> Unit)? = null,\n    trailingIcon: @Composable (() -> Unit)? = null,\n    enabled: Boolean = true\n) {\n    if (BackgroundConfig.isCustomBackgroundEnabled) {\n        DropdownMenuItem(\n            text = text,\n            onClick = onClick,\n            modifier = modifier.shadow(\n                elevation = 0.dp,\n                ambientColor = Color.Transparent,\n                spotColor = Color.Transparent\n            ),\n            leadingIcon = leadingIcon,\n            trailingIcon = trailingIcon,\n            enabled = enabled\n        )\n    } else {\n        DropdownMenuItem(\n            text = text,\n            onClick = onClick,\n            modifier = modifier,\n            leadingIcon = leadingIcon,\n            trailingIcon = trailingIcon,\n            enabled = enabled\n        )\n    }\n}"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/ExpressiveCard.kt",
    "content": "package me.bmax.apatch.ui.component\n\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.interaction.MutableInteractionSource\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.Card\nimport androidx.compose.material3.CardDefaults\nimport androidx.compose.material3.ElevatedCard\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.ripple\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.unit.dp\n\n@Composable\nfun ExpressiveCard(\n    modifier: Modifier = Modifier,\n    onClick: (() -> Unit)? = null,\n    flat: Boolean = false,\n    content: @Composable () -> Unit,\n) {\n    if (LocalInsideSplicedGroup.current) {\n        // Inside SplicedColumnGroup: skip card wrapper, parent provides container\n        if (onClick != null) {\n            Box(\n                modifier = modifier\n                    .fillMaxWidth()\n                    .clickable(\n                        interactionSource = remember { MutableInteractionSource() },\n                        indication = ripple(),\n                        onClick = onClick,\n                    ),\n            ) {\n                content()\n            }\n        } else {\n            Box(modifier = modifier.fillMaxWidth()) {\n                content()\n            }\n        }\n        return\n    }\n\n    // Standalone: use Card/ElevatedCard with rounded corners\n    val shape = RoundedCornerShape(32.dp)\n    val colors = if (flat) {\n        CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainer)\n    } else {\n        CardDefaults.elevatedCardColors(containerColor = MaterialTheme.colorScheme.surfaceContainer)\n    }\n\n    if (flat) {\n        if (onClick != null) {\n            Card(\n                modifier = modifier.fillMaxWidth(),\n                colors = colors,\n                onClick = onClick,\n                shape = shape,\n                content = { content() },\n            )\n        } else {\n            Card(\n                modifier = modifier.fillMaxWidth(),\n                colors = colors,\n                shape = shape,\n                content = { content() },\n            )\n        }\n    } else {\n        if (onClick != null) {\n            ElevatedCard(\n                modifier = modifier.fillMaxWidth(),\n                colors = colors,\n                onClick = onClick,\n                shape = shape,\n                content = { content() },\n            )\n        } else {\n            ElevatedCard(\n                modifier = modifier.fillMaxWidth(),\n                colors = colors,\n                shape = shape,\n                content = { content() },\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/ExpressiveSwitch.kt",
    "content": "package me.bmax.apatch.ui.component\n\nimport androidx.compose.foundation.interaction.MutableInteractionSource\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.Check\nimport androidx.compose.material.icons.filled.Close\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Switch\nimport androidx.compose.material3.SwitchColors\nimport androidx.compose.material3.SwitchDefaults\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport me.bmax.apatch.APApplication\n\nobject SwitchIconState {\n    var showIcon by mutableStateOf(\n        APApplication.sharedPreferences.getBoolean(\"show_switch_icon\", true)\n    )\n}\n\n@Composable\nfun ExpressiveSwitch(\n    checked: Boolean,\n    onCheckedChange: ((Boolean) -> Unit)?,\n    modifier: Modifier = Modifier,\n    enabled: Boolean = true,\n    colors: SwitchColors = SwitchDefaults.colors(),\n    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },\n) {\n    Switch(\n        checked = checked,\n        onCheckedChange = onCheckedChange,\n        modifier = modifier,\n        enabled = enabled,\n        colors = colors,\n        interactionSource = interactionSource,\n        thumbContent = if (SwitchIconState.showIcon) {\n            {\n                if (checked) {\n                    ThumbIcon(Icons.Filled.Check, MaterialTheme.colorScheme.primary)\n                } else {\n                    ThumbIcon(Icons.Filled.Close, MaterialTheme.colorScheme.surfaceContainerHighest)\n                }\n            }\n        } else null,\n    )\n}\n\n@Composable\nprivate fun ThumbIcon(icon: ImageVector, tint: androidx.compose.ui.graphics.Color) {\n    Icon(\n        imageVector = icon,\n        contentDescription = null,\n        tint = tint,\n        modifier = Modifier.size(SwitchDefaults.IconSize),\n    )\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/FilePicker.kt",
    "content": "package me.bmax.apatch.ui.component\n\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Build\nimport android.os.Environment\nimport android.provider.Settings\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.filled.Folder\nimport androidx.compose.material.icons.automirrored.filled.InsertDriveFile\nimport androidx.compose.material.icons.filled.Warning\nimport androidx.compose.material3.*\nimport androidx.compose.runtime.*\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.lifecycle.compose.LocalLifecycleOwner\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.window.DialogProperties\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.LifecycleEventObserver\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.R\nimport java.io.File\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun FilePickerDialog(\n    initialPath: String? = null,\n    allowedExtensions: List<String> = listOf(\"fpt\"),\n    onDismissRequest: () -> Unit,\n    onFileSelected: (File) -> Unit\n) {\n    val context = LocalContext.current\n    val prefs = APApplication.sharedPreferences\n    val rootPath = \"/storage/emulated/0\"\n\n    // Permission Check\n    var hasPermission by remember {\n        mutableStateOf(\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {\n                Environment.isExternalStorageManager()\n            } else {\n                true\n            }\n        )\n    }\n\n    // Refresh permission status on resume\n    val lifecycleOwner = LocalLifecycleOwner.current\n    DisposableEffect(lifecycleOwner) {\n        val observer = LifecycleEventObserver { _, event ->\n            if (event == Lifecycle.Event.ON_RESUME) {\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {\n                    hasPermission = Environment.isExternalStorageManager()\n                }\n            }\n        }\n        lifecycleOwner.lifecycle.addObserver(observer)\n        onDispose {\n            lifecycleOwner.lifecycle.removeObserver(observer)\n        }\n    }\n\n    BasicAlertDialog(\n        onDismissRequest = onDismissRequest,\n        properties = DialogProperties(\n            usePlatformDefaultWidth = false\n        )\n    ) {\n        Surface(\n            modifier = Modifier\n                .fillMaxWidth(0.95f)\n                .fillMaxHeight(0.85f),\n            shape = MaterialTheme.shapes.extraLarge,\n            // Force opaque color to avoid transparency issues\n            color = MaterialTheme.colorScheme.surface.copy(alpha = 1f),\n            tonalElevation = 6.dp\n        ) {\n            if (!hasPermission && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {\n                Column(\n                    modifier = Modifier\n                        .fillMaxSize()\n                        .padding(24.dp),\n                    horizontalAlignment = Alignment.CenterHorizontally,\n                    verticalArrangement = Arrangement.Center\n                ) {\n                    Icon(\n                        Icons.Default.Warning,\n                        contentDescription = null,\n                        modifier = Modifier.size(48.dp),\n                        tint = MaterialTheme.colorScheme.primary\n                    )\n                    Spacer(modifier = Modifier.height(16.dp))\n                    Text(\n                        stringResource(R.string.file_picker_permission_required),\n                        style = MaterialTheme.typography.titleLarge\n                    )\n                    Spacer(modifier = Modifier.height(8.dp))\n                    Text(\n                        stringResource(R.string.file_picker_permission_desc),\n                        style = MaterialTheme.typography.bodyMedium\n                    )\n                    Spacer(modifier = Modifier.height(24.dp))\n                    Button(onClick = {\n                        try {\n                            val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)\n                            intent.data = Uri.parse(\"package:${context.packageName}\")\n                            context.startActivity(intent)\n                        } catch (e: Exception) {\n                            // Fallback if specific intent fails\n                            val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)\n                            context.startActivity(intent)\n                        }\n                    }) {\n                        Text(stringResource(R.string.file_picker_grant_permission))\n                    }\n                    Spacer(modifier = Modifier.height(8.dp))\n                    TextButton(onClick = onDismissRequest) {\n                        Text(stringResource(R.string.file_picker_cancel))\n                    }\n                }\n            } else {\n                // Determine initial path\n                val savedPath = remember { prefs.getString(\"last_file_picker_path\", rootPath) ?: rootPath }\n                // Use initialPath if provided, otherwise savedPath. Ensure it starts with rootPath.\n                val startPath = initialPath ?: savedPath\n                val validStartPath = if (startPath.startsWith(rootPath)) startPath else rootPath\n                \n                var currentPath by remember { mutableStateOf(validStartPath) }\n                \n                // Save path whenever it changes\n                LaunchedEffect(currentPath) {\n                    prefs.edit().putString(\"last_file_picker_path\", currentPath).apply()\n                }\n\n                val currentFile = File(currentPath)\n                // If current path doesn't exist or not directory, fallback to root\n                if (!currentFile.exists() || !currentFile.isDirectory) {\n                    currentPath = rootPath\n                }\n\n                val files = remember(currentPath) {\n                    try {\n                        File(currentPath).listFiles()?.sortedWith(compareBy({ !it.isDirectory }, { it.name.lowercase() }))\n                            ?: emptyList()\n                    } catch (e: Exception) {\n                        emptyList()\n                    }\n                }\n\n                val isAtRoot = currentPath == rootPath\n\n                Column(modifier = Modifier.fillMaxSize()) {\n                    TopAppBar(\n                        title = {\n                            Text(\n                                text = if (isAtRoot) stringResource(R.string.file_picker_internal_storage) else currentFile.name,\n                                style = MaterialTheme.typography.titleMedium,\n                                maxLines = 1\n                            )\n                        },\n                        navigationIcon = {\n                            if (!isAtRoot) {\n                                IconButton(onClick = {\n                                    val parent = currentFile.parentFile\n                                    if (parent != null && parent.absolutePath.startsWith(rootPath)) {\n                                        currentPath = parent.absolutePath\n                                    } else {\n                                        currentPath = rootPath\n                                    }\n                                }) {\n                                    Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = \"Back\")\n                                }\n                            }\n                        }\n                    )\n\n                    LazyColumn(\n                        modifier = Modifier.weight(1f)\n                    ) {\n                        if (files.isEmpty()) {\n                            item {\n                                Box(\n                                    modifier = Modifier\n                                        .fillParentMaxSize()\n                                        .padding(16.dp),\n                                    contentAlignment = Alignment.Center\n                                ) {\n                                    Text(stringResource(R.string.file_picker_no_files), color = MaterialTheme.colorScheme.onSurfaceVariant)\n                                }\n                            }\n                        } else {\n                            items(files) { file ->\n                                if (file.isDirectory) {\n                                    ListItem(\n                                        headlineContent = { Text(file.name) },\n                                        leadingContent = {\n                                            Icon(\n                                                Icons.Default.Folder,\n                                                contentDescription = null,\n                                                tint = MaterialTheme.colorScheme.primary\n                                            )\n                                        },\n                                        modifier = Modifier.clickable {\n                                            currentPath = file.absolutePath\n                                        }\n                                    )\n                                } else {\n                                    val ext = file.extension.lowercase()\n                                    if (allowedExtensions.isEmpty() || allowedExtensions.contains(ext)) {\n                                        ListItem(\n                                            headlineContent = { Text(file.name) },\n                                            leadingContent = {\n                                                Icon(\n                                                    Icons.AutoMirrored.Default.InsertDriveFile,\n                                                    contentDescription = null\n                                                )\n                                            },\n                                            modifier = Modifier.clickable {\n                                                onFileSelected(file)\n                                            }\n                                        )\n                                    }\n                                }\n                            }\n                        }\n                    }\n\n                    Row(\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .padding(16.dp),\n                        horizontalArrangement = Arrangement.End\n                    ) {\n                        TextButton(onClick = onDismissRequest) {\n                            Text(stringResource(R.string.file_picker_cancel))\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/KeyEventBlocker.kt",
    "content": "package me.bmax.apatch.ui.component\n\nimport androidx.compose.foundation.focusable\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.focus.FocusRequester\nimport androidx.compose.ui.focus.focusRequester\nimport androidx.compose.ui.input.key.KeyEvent\nimport androidx.compose.ui.input.key.onKeyEvent\n\n@Composable\nfun KeyEventBlocker(predicate: (KeyEvent) -> Boolean) {\n    val requester = remember { FocusRequester() }\n    Box(\n        Modifier\n            .onKeyEvent {\n                predicate(it)\n            }\n            .focusRequester(requester)\n            .focusable()\n    )\n    LaunchedEffect(Unit) {\n        requester.requestFocus()\n    }\n}"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/KpmAutoLoadConfig.kt",
    "content": "package me.bmax.apatch.ui.component\n\nimport android.content.Context\nimport android.util.Log\nimport androidx.compose.runtime.mutableStateOf\nimport me.bmax.apatch.util.SafeUriResolver\nimport me.bmax.apatch.util.getRootShell\nimport org.json.JSONArray\nimport org.json.JSONObject\nimport java.io.File\nimport java.io.FileOutputStream\nimport android.util.Base64\n\ndata class KpmAutoLoadEntry(\n    val path: String,\n    val event: String = \"service\",\n    val args: String = \"\"\n)\n\ndata class KpmAutoLoadConfig(\n    val enabled: Boolean = false,\n    val entries: List<KpmAutoLoadEntry> = emptyList()\n)\n\nobject KpmAutoLoadManager {\n    private const val TAG = \"KpmAutoLoadManager\"\n    private const val PREFS_NAME = \"kpm_autoload_prefs\"\n    private const val KEY_FIRST_TIME_SHOWN = \"first_time_shown\"\n    private const val KEY_FIRST_TIME_KPM_PAGE_SHOWN = \"first_time_kpm_page_shown\"\n    private const val KPMS_BASE_DIR = \"/data/adb/fp/kpms\"\n    private const val KPMS_AUTOLOAD_DIR = \"/data/adb/fp/kpms/autoload\"\n    private const val CONFIG_PATH = \"/data/adb/fp/kpms/kpm_autoload_config.json\"\n\n    var isEnabled = mutableStateOf(false)\n        private set\n    var entries = mutableStateOf<List<KpmAutoLoadEntry>>(emptyList())\n        private set\n\n    fun isFirstTime(context: Context): Boolean {\n        val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)\n        return !prefs.getBoolean(KEY_FIRST_TIME_SHOWN, false)\n    }\n\n    fun setFirstTimeShown(context: Context) {\n        context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)\n            .edit().putBoolean(KEY_FIRST_TIME_SHOWN, true).apply()\n    }\n\n    fun isFirstTimeKpmPage(context: Context): Boolean {\n        return !context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)\n            .getBoolean(KEY_FIRST_TIME_KPM_PAGE_SHOWN, false)\n    }\n\n    fun setFirstTimeKpmPageShown(context: Context) {\n        context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)\n            .edit().putBoolean(KEY_FIRST_TIME_KPM_PAGE_SHOWN, true).apply()\n    }\n\n    fun importKpm(context: Context, uri: android.net.Uri): String? {\n        val rawFileName = getFileName(context, uri) ?: \"unknown_${System.currentTimeMillis()}.kpm\"\n        val safeFileName = rawFileName.replace(Regex(\"[^a-zA-Z0-9._\\\\-]\"), \"_\")\n            .ifEmpty { \"unknown_${System.currentTimeMillis()}.kpm\" }\n        val finalFileName = if (!safeFileName.endsWith(\".kpm\", ignoreCase = true))\n            \"${safeFileName}.kpm\" else safeFileName\n\n        var destPath = \"$KPMS_AUTOLOAD_DIR/$finalFileName\"\n        val tempFile = File(context.cacheDir, finalFileName)\n\n        try {\n            SafeUriResolver.openInputStream(context, uri)?.use { input ->\n                FileOutputStream(tempFile).use { output ->\n                    input.copyTo(output)\n                }\n            } ?: return null\n\n            val shell = getRootShell()\n\n            val existsOutList = java.util.ArrayList<String>()\n            val existsResult = shell.newJob().add(\"test -f '$destPath' && echo 1 || echo 0\").to(existsOutList, null).exec()\n            if (existsResult.isSuccess && existsOutList.any { it.trim() == \"1\" }) {\n                val baseName = finalFileName.substringBeforeLast(\".\", finalFileName)\n                destPath = \"$KPMS_AUTOLOAD_DIR/${baseName}_${System.currentTimeMillis()}.kpm\"\n            }\n\n            val result = shell.newJob().add(\n                \"mkdir -p '$KPMS_AUTOLOAD_DIR'\",\n                \"cp -f '${tempFile.absolutePath}' '$destPath'\",\n                \"chmod 644 '$destPath'\"\n            ).to(null, null).exec()\n\n            return if (result.isSuccess) {\n                Log.d(TAG, \"KPM导入成功: $destPath\")\n                destPath\n            } else {\n                Log.e(TAG, \"KPM导入失败\")\n                null\n            }\n        } catch (e: Exception) {\n            Log.e(TAG, \"KPM导入失败: ${e.message}\", e)\n            return null\n        } finally {\n            tempFile.delete()\n        }\n    }\n\n    private fun getFileName(context: Context, uri: android.net.Uri): String? {\n        var result: String? = null\n        if (uri.scheme == \"content\") {\n            val cursor = context.contentResolver.query(uri, null, null, null, null)\n            try {\n                if (cursor != null && cursor.moveToFirst()) {\n                    val index = cursor.getColumnIndex(android.provider.OpenableColumns.DISPLAY_NAME)\n                    if (index >= 0) result = cursor.getString(index)\n                }\n            } finally {\n                cursor?.close()\n            }\n        }\n        if (result == null) {\n            result = uri.path\n            val cut = result?.lastIndexOf('/')\n            if (cut != null && cut != -1) result = result?.substring(cut + 1)\n        }\n        return result\n    }\n\n    fun cleanupUnusedKpms(currentEntries: List<KpmAutoLoadEntry>) {\n        try {\n            val shell = getRootShell()\n            val keepSet = currentEntries.map { it.path }.toSet()\n            val outList = java.util.ArrayList<String>()\n            val listResult = shell.newJob().add(\"ls -1 '$KPMS_AUTOLOAD_DIR'/*.kpm 2>/dev/null\").to(outList, null).exec()\n            if (!listResult.isSuccess) return\n            outList.filter { it.isNotEmpty() && !keepSet.contains(it) }.forEach { path ->\n                shell.newJob().add(\"rm -f '${path.replace(\"'\", \"'\\\\''\")}' 2>/dev/null\").exec()\n            }\n        } catch (e: Exception) {\n            Log.e(TAG, \"清理KPM文件失败: ${e.message}\", e)\n        }\n    }\n\n    fun loadConfig(context: Context): KpmAutoLoadConfig {\n        return try {\n            val shell = getRootShell()\n            val outList = java.util.ArrayList<String>()\n            val result = shell.newJob().add(\"cat '$CONFIG_PATH' 2>/dev/null\").to(outList, null).exec()\n            if (!result.isSuccess || outList.isEmpty()) {\n                Log.d(TAG, \"配置文件不存在或为空，使用默认配置\")\n                return KpmAutoLoadConfig()\n            }\n\n            val jsonContent = outList.joinToString(\"\\n\")\n            val config = parseConfigFromJson(jsonContent) ?: KpmAutoLoadConfig()\n            isEnabled.value = config.enabled\n            entries.value = config.entries\n            config\n        } catch (e: Exception) {\n            Log.e(TAG, \"加载配置失败: ${e.message}\", e)\n            KpmAutoLoadConfig()\n        }\n    }\n\n    fun saveConfig(context: Context, config: KpmAutoLoadConfig): Boolean {\n        return try {\n            val jsonContent = getConfigJson(config)\n            val encoded = Base64.encodeToString(jsonContent.toByteArray(Charsets.UTF_8), Base64.NO_WRAP)\n\n            val shell = getRootShell()\n            val result = shell.newJob().add(\n                \"mkdir -p '$KPMS_BASE_DIR'\",\n                \"echo '$encoded' | base64 -d > '$CONFIG_PATH'\",\n                \"chmod 644 '$CONFIG_PATH'\"\n            ).to(null, null).exec()\n\n            if (result.isSuccess) {\n                isEnabled.value = config.enabled\n                entries.value = config.entries\n                cleanupUnusedKpms(config.entries)\n                true\n            } else {\n                Log.e(TAG, \"配置保存失败\")\n                false\n            }\n        } catch (e: Exception) {\n            Log.e(TAG, \"保存配置失败: ${e.message}\", e)\n            false\n        }\n    }\n\n    fun getConfigJson(): String {\n        return getConfigJson(KpmAutoLoadConfig(isEnabled.value, entries.value))\n    }\n\n    fun getConfigJson(config: KpmAutoLoadConfig): String {\n        val jsonObject = JSONObject()\n        jsonObject.put(\"enabled\", config.enabled)\n\n        val entriesArray = JSONArray()\n        config.entries.forEach { entry ->\n            val entryObj = JSONObject()\n            entryObj.put(\"path\", entry.path)\n            entryObj.put(\"event\", entry.event)\n            entryObj.put(\"args\", entry.args)\n            entriesArray.put(entryObj)\n        }\n        jsonObject.put(\"kpmEntries\", entriesArray)\n\n        return jsonObject.toString(2)\n    }\n\n    fun parseConfigFromJson(jsonString: String): KpmAutoLoadConfig? {\n        return try {\n            val jsonObject = JSONObject(jsonString)\n            val enabled = jsonObject.optBoolean(\"enabled\", false)\n\n            val kpmEntries = mutableListOf<KpmAutoLoadEntry>()\n\n            val entriesArray = jsonObject.optJSONArray(\"kpmEntries\")\n            if (entriesArray != null) {\n                for (i in 0 until entriesArray.length()) {\n                    val item = entriesArray.optJSONObject(i) ?: continue\n                    val path = item.optString(\"path\", \"\")\n                    if (path.isNotEmpty()) {\n                        kpmEntries.add(KpmAutoLoadEntry(\n                            path = path,\n                            event = item.optString(\"event\", \"service\"),\n                            args = item.optString(\"args\", \"\")\n                        ))\n                    }\n                }\n            }\n\n            KpmAutoLoadConfig(enabled, kpmEntries)\n        } catch (e: Exception) {\n            Log.e(TAG, \"解析JSON失败: ${e.message}\", e)\n            null\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/LoadingIndicator.kt",
    "content": "package me.bmax.apatch.ui.component\n\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.material3.CircularWavyProgressIndicator\nimport androidx.compose.material3.ExperimentalMaterial3ExpressiveApi\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.unit.dp\n\n@OptIn(ExperimentalMaterial3ExpressiveApi::class)\n@Composable\nfun AppLoadingIndicator(\n    text: String? = null,\n    modifier: Modifier = Modifier,\n) {\n    Column(\n        modifier = modifier,\n        horizontalAlignment = Alignment.CenterHorizontally,\n        verticalArrangement = Arrangement.Center,\n    ) {\n        CircularWavyProgressIndicator()\n\n        if (text != null) {\n            Spacer(modifier = Modifier.height(8.dp))\n\n            Text(\n                text = text,\n                style = MaterialTheme.typography.titleMedium,\n                color = MaterialTheme.colorScheme.onSurfaceVariant,\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/ModuleCardComponents.kt",
    "content": "package me.bmax.apatch.ui.component\n\nimport androidx.annotation.DrawableRes\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.RowScope\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.requiredSize\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.material3.ButtonColors\nimport androidx.compose.material3.ButtonDefaults\nimport androidx.compose.material3.FilledTonalButton\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.ColorFilter\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport androidx.compose.ui.layout.SubcomposeLayout\nimport androidx.compose.ui.res.painterResource\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.Constraints\nimport androidx.compose.ui.unit.dp\nimport me.bmax.apatch.R\n\n@Composable\nfun ModuleUpdateButton(\n    onClick: () -> Unit\n) = FilledTonalButton(\n    onClick = onClick, enabled = true, contentPadding = PaddingValues(horizontal = 12.dp)\n) {\n    Icon(\n        modifier = Modifier.size(20.dp),\n        painter = painterResource(id = R.drawable.device_mobile_down),\n        contentDescription = null\n    )\n\n    Spacer(modifier = Modifier.width(6.dp))\n    Text(\n        text = stringResource(id = R.string.apm_update),\n        maxLines = 1,\n        overflow = TextOverflow.Visible,\n        softWrap = false\n    )\n}\n\n@Composable\nfun ModuleRemoveButton(\n    enabled: Boolean, onClick: () -> Unit\n) = FilledTonalButton(\n    onClick = onClick, enabled = enabled, contentPadding = PaddingValues(horizontal = 12.dp)\n) {\n    Icon(\n        modifier = Modifier.size(20.dp),\n        painter = painterResource(id = R.drawable.trash),\n        contentDescription = null\n    )\n\n    Spacer(modifier = Modifier.width(6.dp))\n    Text(\n        text = stringResource(id = R.string.apm_remove),\n        maxLines = 1,\n        overflow = TextOverflow.Visible,\n        softWrap = false\n    )\n}\n\n@Composable\nfun KPModuleRemoveButton(\n    enabled: Boolean, onClick: () -> Unit\n) = FilledTonalButton(\n    onClick = onClick, enabled = enabled, contentPadding = PaddingValues(horizontal = 12.dp)\n) {\n    Icon(\n        modifier = Modifier.size(20.dp),\n        painter = painterResource(id = R.drawable.trash),\n        contentDescription = null\n    )\n\n    Spacer(modifier = Modifier.width(6.dp))\n    Text(\n        text = stringResource(id = R.string.kpm_unload),\n        maxLines = 1,\n        overflow = TextOverflow.Visible,\n        softWrap = false\n    )\n}\n\n@Composable\nfun ModuleInstallButton(\n    onClick: () -> Unit\n) = FilledTonalButton(\n    onClick = onClick, enabled = true, contentPadding = PaddingValues(horizontal = 12.dp)\n) {\n    Icon(\n        modifier = Modifier.size(20.dp),\n        painter = painterResource(id = R.drawable.device_mobile_down),\n        contentDescription = null\n    )\n\n    Spacer(modifier = Modifier.width(6.dp))\n    Text(\n        text = stringResource(id = R.string.apm_install),\n        maxLines = 1,\n        overflow = TextOverflow.Visible,\n        softWrap = false\n    )\n}\n\n@Composable\nfun ModuleStateIndicator(\n    @DrawableRes icon: Int, color: Color = MaterialTheme.colorScheme.outline\n) {\n    Image(\n        modifier = Modifier.requiredSize(150.dp),\n        painter = painterResource(id = icon),\n        contentDescription = null,\n        alpha = 0.1f,\n        colorFilter = ColorFilter.tint(color)\n    )\n}\n\n/**\n * 模块按钮配置数据类\n */\ndata class ModuleButtonConfig(\n    val icon: ImageVector,\n    val text: String,\n    val contentDescription: String,\n    val onClick: () -> Unit,\n    val enabled: Boolean = true,\n    val colors: ButtonColors? = null\n)\n\n/**\n * 自适应模块按钮行\n * 当空间不足时，从左边开始将按钮转为纯图标模式\n * \n * @param buttons 左侧按钮列表\n * @param trailingButton 右侧固定按钮（如删除按钮）\n * @param simpleListBottomBar 是否为简约模式\n * @param spacing 按钮间距\n * @param opacity 背景透明度\n */\n@Composable\nfun AdaptiveModuleButtonRow(\n    buttons: List<ModuleButtonConfig>,\n    trailingButton: ModuleButtonConfig? = null,\n    simpleListBottomBar: Boolean,\n    spacing: Int = 8,\n    opacity: Float = 1f\n) {\n    if (simpleListBottomBar) {\n        // 简约模式：所有按钮都显示为纯图标\n        Row(\n            modifier = Modifier.fillMaxWidth(),\n            horizontalArrangement = Arrangement.spacedBy(12.dp)\n        ) {\n            buttons.forEach { config ->\n                FilledTonalButton(\n                    onClick = config.onClick,\n                    enabled = config.enabled,\n                    contentPadding = PaddingValues(12.dp),\n                    colors = config.colors ?: ButtonDefaults.filledTonalButtonColors(\n                        containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = (opacity + 0.3f).coerceAtMost(1f))\n                    )\n                ) {\n                    Icon(\n                        imageVector = config.icon,\n                        contentDescription = config.contentDescription,\n                        modifier = Modifier.size(20.dp)\n                    )\n                }\n            }\n            \n            Spacer(modifier = Modifier.weight(1f))\n            \n            trailingButton?.let { config ->\n                FilledTonalButton(\n                    onClick = config.onClick,\n                    enabled = config.enabled,\n                    contentPadding = PaddingValues(12.dp),\n                    colors = config.colors ?: ButtonDefaults.filledTonalButtonColors(\n                        containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = (opacity + 0.3f).coerceAtMost(1f))\n                    )\n                ) {\n                    Icon(\n                        imageVector = config.icon,\n                        contentDescription = config.contentDescription,\n                        modifier = Modifier.size(20.dp)\n                    )\n                }\n            }\n        }\n    } else {\n        // 非简约模式：自适应显示文字\n        AdaptiveButtonRowLayout(\n            buttons = buttons,\n            trailingButton = trailingButton,\n            spacing = spacing,\n            opacity = opacity\n        )\n    }\n}\n\n/**\n * 使用 SubcomposeLayout 实现的自适应按钮行布局\n * 自动测量并决定哪些按钮需要切换到纯图标模式\n */\n@Composable\nprivate fun AdaptiveButtonRowLayout(\n    buttons: List<ModuleButtonConfig>,\n    trailingButton: ModuleButtonConfig?,\n    spacing: Int,\n    opacity: Float\n) {\n    var iconOnlyCount by remember(buttons.size) { mutableIntStateOf(0) }\n    \n    SubcomposeLayout(\n        modifier = Modifier.fillMaxWidth()\n    ) { constraints ->\n        val spacingPx = spacing.dp.roundToPx()\n        val availableWidth = constraints.maxWidth\n        \n        // 1. 测量所有按钮在带文字状态下的宽度\n        val buttonWidthsWithText = buttons.mapIndexed { index, config ->\n            val placeable = subcompose(\"measure_text_$index\") {\n                ModuleActionButton(\n                    config = config,\n                    iconOnly = false,\n                    opacity = opacity\n                )\n            }.first().measure(Constraints())\n            placeable.width\n        }\n        \n        // 2. 测量所有按钮在纯图标状态下的宽度\n        val buttonWidthsIconOnly = buttons.mapIndexed { index, config ->\n            val placeable = subcompose(\"measure_icon_$index\") {\n                ModuleActionButton(\n                    config = config,\n                    iconOnly = true,\n                    opacity = opacity\n                )\n            }.first().measure(Constraints())\n            placeable.width\n        }\n        \n        // 3. 测量尾部按钮宽度（如删除按钮，优先保留文字）\n        val trailingWidthWithText = trailingButton?.let { config ->\n            subcompose(\"measure_trailing_text\") {\n                ModuleActionButton(\n                    config = config,\n                    iconOnly = false,\n                    opacity = opacity\n                )\n            }.first().measure(Constraints()).width\n        } ?: 0\n        \n        val trailingWidthIconOnly = trailingButton?.let { config ->\n            subcompose(\"measure_trailing_icon\") {\n                ModuleActionButton(\n                    config = config,\n                    iconOnly = true,\n                    opacity = opacity\n                )\n            }.first().measure(Constraints()).width\n        } ?: 0\n        \n        // 4. 计算需要多少个按钮切换到纯图标模式\n        val totalSpacing = if (buttons.isNotEmpty() || trailingButton != null) {\n            (buttons.size + (if (trailingButton != null) 1 else 0) - 1).coerceAtLeast(0) * spacingPx\n        } else 0\n        \n        // 计算最小所需宽度（所有按钮都是纯图标 + Spacer最小宽度）\n        val minSpacerWidth = 8.dp.roundToPx()\n        \n        // 从左边开始尝试将按钮转为纯图标，直到总宽度合适\n        var currentIconOnlyCount = 0\n        for (i in 0..buttons.size) {\n            val leftButtonsWidth = buttons.indices.sumOf { index ->\n                if (index < i) buttonWidthsIconOnly[index] else buttonWidthsWithText[index]\n            }\n            \n            // 检查尾部按钮是否需要切换为纯图标\n            val trailingWidth = if (trailingButton != null) {\n                // 先尝试保留尾部按钮的文字\n                val totalWithTrailingText = leftButtonsWidth + trailingWidthWithText + totalSpacing + minSpacerWidth\n                if (totalWithTrailingText <= availableWidth) {\n                    trailingWidthWithText\n                } else {\n                    trailingWidthIconOnly\n                }\n            } else 0\n            \n            val totalWidth = leftButtonsWidth + trailingWidth + totalSpacing + minSpacerWidth\n            \n            if (totalWidth <= availableWidth) {\n                currentIconOnlyCount = i\n                break\n            }\n            currentIconOnlyCount = i + 1\n        }\n        \n        // 限制在按钮数量范围内\n        iconOnlyCount = currentIconOnlyCount.coerceAtMost(buttons.size)\n        \n        // 5. 确定尾部按钮是否需要纯图标\n        val leftButtonsWidth = buttons.indices.sumOf { index ->\n            if (index < iconOnlyCount) buttonWidthsIconOnly[index] else buttonWidthsWithText[index]\n        }\n        val trailingIconOnly = if (trailingButton != null) {\n            val totalWithText = leftButtonsWidth + trailingWidthWithText + totalSpacing + minSpacerWidth\n            totalWithText > availableWidth\n        } else false\n        \n        // 6. 渲染最终的按钮\n        val finalPlaceables = buttons.mapIndexed { index, config ->\n            subcompose(\"button_$index\") {\n                ModuleActionButton(\n                    config = config,\n                    iconOnly = index < iconOnlyCount,\n                    opacity = opacity\n                )\n            }.first().measure(Constraints())\n        }\n        \n        val trailingPlaceable = trailingButton?.let { config ->\n            subcompose(\"trailing_button\") {\n                ModuleActionButton(\n                    config = config,\n                    iconOnly = trailingIconOnly,\n                    opacity = opacity\n                )\n            }.first().measure(Constraints())\n        }\n        \n        // 7. 布局\n        val layoutHeight = maxOf(\n            finalPlaceables.maxOfOrNull { it.height } ?: 0,\n            trailingPlaceable?.height ?: 0\n        )\n\n        layout(availableWidth, layoutHeight) {\n            var xPosition = 0\n            \n            finalPlaceables.forEach { placeable ->\n                placeable.placeRelative(xPosition, 0)\n                xPosition += placeable.width + spacingPx\n            }\n            \n            // 放置尾部按钮（右对齐）\n            trailingPlaceable?.let { placeable ->\n                placeable.placeRelative(availableWidth - placeable.width, 0)\n            }\n        }\n    }\n}\n\n/**\n * 模块操作按钮\n */\n@Composable\nfun ModuleActionButton(\n    config: ModuleButtonConfig,\n    iconOnly: Boolean,\n    opacity: Float,\n    modifier: Modifier = Modifier\n) {\n    FilledTonalButton(\n        onClick = config.onClick,\n        enabled = config.enabled,\n        contentPadding = if (iconOnly) PaddingValues(12.dp) else ButtonDefaults.TextButtonContentPadding,\n        modifier = if (iconOnly) modifier else modifier,\n        colors = config.colors ?: ButtonDefaults.filledTonalButtonColors(\n            containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = (opacity + 0.3f).coerceAtMost(1f))\n        )\n    ) {\n        Icon(\n            imageVector = config.icon,\n            contentDescription = config.contentDescription,\n            modifier = Modifier.size(20.dp)\n        )\n        if (!iconOnly) {\n            Spacer(Modifier.width(8.dp))\n            Text(config.text)\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/SearchBar.kt",
    "content": "package me.bmax.apatch.ui.component\n\nimport android.util.Log\nimport androidx.activity.compose.BackHandler\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.animation.fadeIn\nimport androidx.compose.animation.fadeOut\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.foundation.text.KeyboardActions\nimport androidx.compose.foundation.text.KeyboardOptions\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.outlined.ArrowBack\nimport androidx.compose.material.icons.filled.Close\nimport androidx.compose.material.icons.filled.Search\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.OutlinedTextField\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.DisposableEffect\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.focus.FocusRequester\nimport androidx.compose.ui.focus.focusRequester\nimport androidx.compose.ui.focus.onFocusChanged\nimport androidx.compose.ui.platform.LocalSoftwareKeyboardController\nimport androidx.compose.ui.text.input.ImeAction\nimport androidx.compose.ui.text.input.KeyboardType\nimport androidx.compose.ui.tooling.preview.Preview\nimport androidx.compose.ui.unit.dp\n\nprivate const val TAG = \"SearchBar\"\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun SearchAppBar(\n    title: @Composable () -> Unit,\n    searchText: String,\n    onSearchTextChange: (String) -> Unit,\n    onClearClick: () -> Unit,\n    onBackClick: (() -> Unit)? = null,\n    onConfirm: (() -> Unit)? = null,\n    dropdownContent: @Composable (() -> Unit)? = null,\n    leadingActions: @Composable (() -> Unit)? = null,\n    trailingActions: @Composable (() -> Unit)? = null,\n) {\n    val keyboardController = LocalSoftwareKeyboardController.current\n    val focusRequester = remember { FocusRequester() }\n    var onSearch by remember { mutableStateOf(false) }\n\n    if (onSearch) {\n        LaunchedEffect(Unit) { focusRequester.requestFocus() }\n    }\n\n    BackHandler(\n        enabled = onSearch,\n        onBack = {\n            keyboardController?.hide()\n            onClearClick()\n            onSearch = !onSearch\n        }\n    )\n\n    DisposableEffect(Unit) {\n        onDispose {\n            keyboardController?.hide()\n        }\n    }\n\n    TopAppBar(\n        title = {\n            Box {\n                AnimatedVisibility(\n                    modifier = Modifier.align(Alignment.CenterStart),\n                    visible = !onSearch,\n                    enter = fadeIn(),\n                    exit = fadeOut(),\n                    content = { title() }\n                )\n\n                AnimatedVisibility(\n                    visible = onSearch,\n                    enter = fadeIn(),\n                    exit = fadeOut()\n                ) {\n                    OutlinedTextField(\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .padding(\n                                top = 2.dp,\n                                bottom = 2.dp,\n                                end = if (onBackClick != null) 0.dp else 14.dp\n                            )\n                            .focusRequester(focusRequester)\n                            .onFocusChanged { focusState ->\n                                if (focusState.isFocused) onSearch = true\n                                Log.d(TAG, \"onFocusChanged: $focusState\")\n                            },\n                        value = searchText,\n                        onValueChange = onSearchTextChange,\n                        shape = RoundedCornerShape(15.dp),\n                        trailingIcon = {\n                            IconButton(\n                                onClick = {\n                                    onSearch = false\n                                    keyboardController?.hide()\n                                    onClearClick()\n                                },\n                                content = { Icon(Icons.Filled.Close, null) }\n                            )\n                        },\n                        maxLines = 1,\n                        singleLine = true,\n                        keyboardOptions = KeyboardOptions(\n                            keyboardType = KeyboardType.Text,\n                            imeAction = ImeAction.Search\n                        ),\n                        keyboardActions = KeyboardActions {\n                            defaultKeyboardAction(ImeAction.Search)\n                            keyboardController?.hide()\n                            onConfirm?.invoke()\n                        },\n                    )\n                }\n            }\n        },\n        navigationIcon = {\n            if (onBackClick != null) {\n                IconButton(\n                    onClick = onBackClick,\n                    content = { Icon(Icons.AutoMirrored.Outlined.ArrowBack, null) }\n                )\n            }\n        },\n        actions = {\n            AnimatedVisibility(\n                visible = !onSearch\n            ) {\n                androidx.compose.foundation.layout.Row(\n                    verticalAlignment = Alignment.CenterVertically\n                ) {\n                    leadingActions?.invoke()\n                    IconButton(\n                        onClick = { onSearch = true },\n                        content = { Icon(Icons.Filled.Search, null) }\n                    )\n                }\n            }\n\n            dropdownContent?.invoke()\n            trailingActions?.invoke()\n\n        }\n    )\n}\n\n@Preview\n@Composable\nprivate fun SearchAppBarPreview() {\n    var searchText by remember { mutableStateOf(\"\") }\n    SearchAppBar(\n        title = { Text(\"Search text\") },\n        searchText = searchText,\n        onSearchTextChange = { searchText = it },\n        onClearClick = { searchText = \"\" }\n    )\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/SectionHeader.kt",
    "content": "package me.bmax.apatch.ui.component\n\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\n\n@Composable\nfun SectionHeader(\n    text: String,\n    modifier: Modifier = Modifier,\n) {\n    Text(\n        text = text,\n        style = MaterialTheme.typography.titleSmall,\n        color = MaterialTheme.colorScheme.secondary,\n        fontWeight = FontWeight.Bold,\n        modifier = modifier.padding(start = 8.dp),\n    )\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/SegmentedControl.kt",
    "content": "package me.bmax.apatch.ui.component\n\nimport androidx.compose.animation.core.animateDpAsState\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.border\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Surface\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.*\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.unit.Dp\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.zIndex\n\n@Composable\nfun SegmentedControl(\n    items: List<String>,\n    selectedIndex: Int,\n    useFixedWidth: Boolean = false,\n    itemWidth: Dp = 120.dp,\n    cornerRadius: Int = 50,\n    color: Color = MaterialTheme.colorScheme.secondaryContainer,\n    contentColor: Color = MaterialTheme.colorScheme.onSecondaryContainer,\n    onItemSelection: (selectedItemIndex: Int) -> Unit\n) {\n    Row(\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(horizontal = 16.dp, vertical = 8.dp)\n            .background(color, shape = RoundedCornerShape(cornerRadius))\n            .border(1.dp, color.copy(alpha = 0.1f), RoundedCornerShape(cornerRadius))\n            .clip(RoundedCornerShape(cornerRadius)),\n        horizontalArrangement = Arrangement.Center\n    ) {\n        items.forEachIndexed { index, item ->\n            val isSelected = selectedIndex == index\n            \n            Box(\n                modifier = Modifier\n                    .weight(1f)\n                    .height(40.dp)\n                    .clip(RoundedCornerShape(cornerRadius))\n                    .background(\n                        if (isSelected) MaterialTheme.colorScheme.primary else Color.Transparent\n                    )\n                    .clickable {\n                        onItemSelection(index)\n                    },\n                contentAlignment = Alignment.Center\n            ) {\n                Text(\n                    text = item,\n                    color = if (isSelected) MaterialTheme.colorScheme.onPrimary else contentColor,\n                    style = MaterialTheme.typography.bodyMedium,\n                    fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal,\n                    textAlign = TextAlign.Center\n                )\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/SettingsItem.kt",
    "content": "package me.bmax.apatch.ui.component\n\nimport androidx.compose.foundation.LocalIndication\nimport androidx.compose.foundation.interaction.MutableInteractionSource\nimport androidx.compose.foundation.selection.toggleable\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.ListItem\nimport androidx.compose.material3.LocalContentColor\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.RadioButton\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport androidx.compose.ui.semantics.Role\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.animation.core.animateFloatAsState\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.KeyboardArrowDown\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.draw.rotate\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\n\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.material3.ListItemDefaults\nimport androidx.compose.material3.Checkbox\n\n@Composable\nfun CheckboxItem(\n    icon: ImageVector?,\n    title: String,\n    summary: String?,\n    checked: Boolean,\n    enabled: Boolean = true,\n    onCheckedChange: (Boolean) -> Unit\n) {\n    ListItem(\n        headlineContent = { Text(title) },\n        supportingContent = if (summary != null) {\n            {\n                Text(\n                    text = summary,\n                    style = MaterialTheme.typography.bodyMedium,\n                    color = MaterialTheme.colorScheme.outline\n                )\n            }\n        } else null,\n        leadingContent = if (icon != null) {\n            {\n                Icon(\n                    imageVector = icon,\n                    contentDescription = null\n                )\n            }\n        } else null,\n        trailingContent = {\n            Checkbox(\n                checked = checked,\n                onCheckedChange = null,\n                enabled = enabled\n            )\n        },\n        colors = ListItemDefaults.colors(containerColor = Color.Transparent),\n        modifier = Modifier.toggleable(\n            value = checked,\n            onValueChange = onCheckedChange,\n            role = Role.Checkbox,\n            enabled = enabled\n        )\n    )\n}\n\n@Composable\nfun SwitchItem(\n    icon: ImageVector?,\n    title: String,\n    summary: String?,\n    checked: Boolean,\n    enabled: Boolean = true,\n    onCheckedChange: (Boolean) -> Unit\n) {\n    ListItem(\n        headlineContent = { Text(title) },\n        supportingContent = if (summary != null) {\n            {\n                Text(\n                    text = summary,\n                    style = MaterialTheme.typography.bodyMedium,\n                    color = MaterialTheme.colorScheme.outline\n                )\n            }\n        } else null,\n        leadingContent = if (icon != null) {\n            {\n                Icon(\n                    imageVector = icon,\n                    contentDescription = null\n                )\n            }\n        } else null,\n        trailingContent = {\n            ExpressiveSwitch(\n                checked = checked,\n                onCheckedChange = null,\n                enabled = enabled\n            )\n        },\n        colors = ListItemDefaults.colors(containerColor = Color.Transparent),\n        modifier = Modifier.toggleable(\n            value = checked,\n            onValueChange = onCheckedChange,\n            role = Role.Switch,\n            enabled = enabled\n        )\n    )\n}\n\n@Composable\nfun SettingsCategory(\n    icon: ImageVector? = null,\n    title: String,\n    summary: String? = null,\n    initialExpanded: Boolean = false,\n    isSearching: Boolean = false,\n    content: @Composable () -> Unit\n) {\n    var expanded by rememberSaveable { mutableStateOf(initialExpanded) }\n    val rotationState by animateFloatAsState(\n        targetValue = if (expanded || isSearching) 180f else 0f,\n        label = \"ArrowRotation\"\n    )\n\n    Column(\n        modifier = Modifier.fillMaxWidth()\n    ) {\n        ListItem(\n            headlineContent = {\n                Text(\n                    text = title,\n                    style = MaterialTheme.typography.titleSmall,\n                    fontWeight = FontWeight.Bold\n                )\n            },\n            supportingContent = if (summary != null) {\n                {\n                    Text(\n                        text = summary,\n                        style = MaterialTheme.typography.bodyMedium,\n                        color = MaterialTheme.colorScheme.outline\n                    )\n                }\n            } else null,\n            leadingContent = if (icon != null) {\n                {\n                    Icon(\n                        imageVector = icon,\n                        contentDescription = null\n                    )\n                }\n            } else null,\n            trailingContent = {\n                if (!isSearching) {\n                    Icon(\n                        imageVector = Icons.Filled.KeyboardArrowDown,\n                        contentDescription = null,\n                        tint = MaterialTheme.colorScheme.onSurfaceVariant,\n                        modifier = Modifier.rotate(rotationState)\n                    )\n                }\n            },\n            colors = ListItemDefaults.colors(containerColor = Color.Transparent),\n            modifier = Modifier\n                .clickable(enabled = !isSearching) { expanded = !expanded }\n        )\n        \n        AnimatedVisibility(visible = expanded || isSearching) {\n            Column {\n                content()\n            }\n        }\n    }\n}\n\n@Composable\nfun RadioItem(\n    title: String,\n    selected: Boolean,\n    onClick: () -> Unit,\n) {\n    ListItem(\n        headlineContent = {\n            Text(title)\n        },\n        leadingContent = {\n            RadioButton(selected = selected, onClick = onClick)\n        },\n    )\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/SplicedColumnGroup.kt",
    "content": "package me.bmax.apatch.ui.component\n\nimport android.os.Build\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.animation.core.Spring\nimport androidx.compose.animation.core.animateDpAsState\nimport androidx.compose.animation.core.spring\nimport androidx.compose.animation.expandVertically\nimport androidx.compose.animation.fadeIn\nimport androidx.compose.animation.fadeOut\nimport androidx.compose.animation.shrinkVertically\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.CompositionLocalProvider\nimport androidx.compose.runtime.compositionLocalOf\nimport androidx.compose.runtime.key\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.zIndex\n\n/** Whether the current composable is inside a [SplicedColumnGroup]. */\nval LocalInsideSplicedGroup = compositionLocalOf { false }\n\nprivate val CornerRadius = 16.dp\nprivate val ConnectionRadius = 5.dp\n\ndata class SplicedItemData(\n    val key: Any?,\n    val visible: Boolean,\n    val content: @Composable () -> Unit,\n)\n\nclass SplicedGroupScope {\n    val items = mutableListOf<SplicedItemData>()\n\n    fun item(key: Any? = null, visible: Boolean = true, content: @Composable () -> Unit) {\n        items.add(SplicedItemData(key ?: items.size, visible, content))\n    }\n}\n\n@Composable\nfun SplicedColumnGroup(\n    modifier: Modifier = Modifier,\n    title: String = \"\",\n    flat: Boolean = false,\n    content: SplicedGroupScope.() -> Unit,\n) {\n    val scope = SplicedGroupScope().apply(content)\n    val allItems = scope.items\n\n    if (allItems.isEmpty()) return\n\n    CompositionLocalProvider(LocalInsideSplicedGroup provides true) {\n        Column(modifier = modifier.padding(horizontal = 16.dp, vertical = 8.dp)) {\n            if (title.isNotEmpty()) {\n                Text(\n                    text = title,\n                    style = MaterialTheme.typography.titleSmall,\n                    color = MaterialTheme.colorScheme.secondary,\n                    fontWeight = FontWeight.Bold,\n                    modifier = Modifier.padding(start = 8.dp, bottom = 8.dp),\n                )\n            }\n\n            Column(verticalArrangement = Arrangement.Top) {\n                val firstVisibleIndex = allItems.indexOfFirst { it.visible }\n                val lastVisibleIndex = allItems.indexOfLast { it.visible }\n                val sharedStiffness = Spring.StiffnessMediumLow\n                val isAtLeastTiramisu = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU\n\n                allItems.forEachIndexed { index, itemData ->\n                    key(itemData.key) {\n                        val zIndex = if (itemData.visible) 0f else 1f\n\n                        AnimatedVisibility(\n                            visible = itemData.visible,\n                            modifier = Modifier.zIndex(zIndex),\n                            enter = expandVertically(\n                                animationSpec = spring(stiffness = sharedStiffness),\n                                expandFrom = Alignment.Top,\n                            ) + fadeIn(animationSpec = spring(stiffness = sharedStiffness)),\n                            exit = shrinkVertically(\n                                animationSpec = spring(stiffness = sharedStiffness),\n                                shrinkTowards = Alignment.Top,\n                            ) + fadeOut(animationSpec = spring(stiffness = sharedStiffness)),\n                        ) {\n                            val isFirst = index == firstVisibleIndex\n                            val isLast = index == lastVisibleIndex\n\n                            val targetTopRadius = if (isFirst) CornerRadius else ConnectionRadius\n                            val targetBottomRadius = if (isLast) CornerRadius else ConnectionRadius\n\n                            val currentTopRadius = if (isAtLeastTiramisu) {\n                                animateDpAsState(\n                                    targetValue = targetTopRadius,\n                                    animationSpec = spring(stiffness = sharedStiffness),\n                                    label = \"TopCornerRadius\",\n                                ).value\n                            } else {\n                                targetTopRadius\n                            }\n\n                            val currentBottomRadius = if (isAtLeastTiramisu) {\n                                animateDpAsState(\n                                    targetValue = targetBottomRadius,\n                                    animationSpec = spring(stiffness = sharedStiffness),\n                                    label = \"BottomCornerRadius\",\n                                ).value\n                            } else {\n                                targetBottomRadius\n                            }\n\n                            val shape = RoundedCornerShape(\n                                topStart = currentTopRadius,\n                                topEnd = currentTopRadius,\n                                bottomStart = currentBottomRadius,\n                                bottomEnd = currentBottomRadius,\n                            )\n\n                            val targetTopPadding = if (isFirst) 0.dp else 2.dp\n                            val currentTopPadding = if (isAtLeastTiramisu) {\n                                animateDpAsState(\n                                    targetValue = targetTopPadding,\n                                    animationSpec = spring(stiffness = sharedStiffness),\n                                    label = \"TopPadding\",\n                                ).value\n                            } else {\n                                targetTopPadding\n                            }\n\n                            val containerColor = if (flat) {\n                                MaterialTheme.colorScheme.surfaceContainer\n                            } else {\n                                MaterialTheme.colorScheme.surfaceContainer\n                            }\n\n                            Column(\n                                modifier = Modifier\n                                    .padding(top = currentTopPadding)\n                                    .clip(shape)\n                                    .background(containerColor, shape),\n                            ) {\n                                itemData.content()\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/SplicedLazyColumn.kt",
    "content": "package me.bmax.apatch.ui.component\n\nimport android.os.Build\nimport androidx.compose.animation.core.Spring\nimport androidx.compose.animation.core.animateDpAsState\nimport androidx.compose.animation.core.spring\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.lazy.LazyItemScope\nimport androidx.compose.foundation.lazy.LazyListScope\nimport androidx.compose.foundation.lazy.itemsIndexed\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Surface\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.CompositionLocalProvider\nimport androidx.compose.runtime.getValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.unit.dp\n\nprivate val CornerRadius = 16.dp\nprivate val ConnectionRadius = 5.dp\n\n/**\n * LazyColumn extension that renders items as a continuous card group with animated corner radii.\n * First and last visible items get 16dp outer corners; middle items get 5dp connection corners.\n * Uses [LocalInsideSplicedGroup] so inner [ExpressiveCard] wrappers are automatically skipped.\n */\nfun <T> LazyListScope.splicedLazyColumnGroup(\n    items: List<T>,\n    key: ((index: Int, item: T) -> Any)? = null,\n    contentType: (index: Int, item: T) -> Any? = { _, _ -> null },\n    itemContent: @Composable LazyItemScope.(index: Int, item: T) -> Unit,\n) {\n    val sharedStiffness = Spring.StiffnessMediumLow\n    val isAtLeastTiramisu = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU\n\n    itemsIndexed(\n        items = items,\n        key = key,\n        contentType = contentType,\n    ) { index, item ->\n        val isFirst = index == 0\n        val isLast = index == items.size - 1\n\n        val targetTopRadius = if (isFirst) CornerRadius else ConnectionRadius\n        val targetBottomRadius = if (isLast) CornerRadius else ConnectionRadius\n\n        val currentTopRadius = if (isAtLeastTiramisu) {\n            animateDpAsState(\n                targetValue = targetTopRadius,\n                animationSpec = spring(stiffness = sharedStiffness),\n                label = \"TopCornerRadius\",\n            ).value\n        } else {\n            targetTopRadius\n        }\n\n        val currentBottomRadius = if (isAtLeastTiramisu) {\n            animateDpAsState(\n                targetValue = targetBottomRadius,\n                animationSpec = spring(stiffness = sharedStiffness),\n                label = \"BottomCornerRadius\",\n            ).value\n        } else {\n            targetBottomRadius\n        }\n\n        val shape = RoundedCornerShape(\n            topStart = currentTopRadius,\n            topEnd = currentTopRadius,\n            bottomStart = currentBottomRadius,\n            bottomEnd = currentBottomRadius,\n        )\n\n        Surface(\n            modifier = Modifier\n                .padding(horizontal = 16.dp)\n                .padding(top = if (isFirst) 0.dp else 2.dp),\n            shape = shape,\n            color = MaterialTheme.colorScheme.surfaceContainer,\n        ) {\n            CompositionLocalProvider(LocalInsideSplicedGroup provides true) {\n                itemContent(index, item)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/ThemeColorPicker.kt",
    "content": "package me.bmax.apatch.ui.component\n\nimport androidx.compose.animation.core.Spring\nimport androidx.compose.animation.core.animateFloatAsState\nimport androidx.compose.animation.core.spring\nimport androidx.compose.animation.fadeIn\nimport androidx.compose.animation.fadeOut\nimport androidx.compose.animation.scaleIn\nimport androidx.compose.animation.scaleOut\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.border\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.lazy.LazyRow\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.Done\nimport androidx.compose.material3.ExperimentalMaterial3ExpressiveApi\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.MaterialShapes\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.toShape\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.draw.scale\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport androidx.annotation.StringRes\nimport me.bmax.apatch.R\n\ndata class ThemeColorOption(\n    val key: String,\n    @StringRes val labelId: Int,\n    val lightPrimary: Color,\n    val darkPrimary: Color,\n)\n\nval themeColorOptions = listOf(\n    ThemeColorOption(\"indigo\", R.string.indigo_theme, Color(0xFF4355B9), Color(0xFFBAC3FF)),\n    ThemeColorOption(\"blue\", R.string.blue_theme, Color(0xFF0061A4), Color(0xFF9ECAFF)),\n    ThemeColorOption(\"light_blue\", R.string.light_blue_theme, Color(0xFF006493), Color(0xFF95CCFF)),\n    ThemeColorOption(\"cyan\", R.string.cyan_theme, Color(0xFF006876), Color(0xFF4FD8EB)),\n    ThemeColorOption(\"teal\", R.string.teal_theme, Color(0xFF006A60), Color(0xFF52DEC9)),\n    ThemeColorOption(\"green\", R.string.green_theme, Color(0xFF006E1A), Color(0xFF74DD69)),\n    ThemeColorOption(\"light_green\", R.string.light_green_theme, Color(0xFF006C48), Color(0xFF6CFAAF)),\n    ThemeColorOption(\"lime\", R.string.lime_theme, Color(0xFF5B6300), Color(0xFFC4CA2E)),\n    ThemeColorOption(\"yellow\", R.string.yellow_theme, Color(0xFF695F00), Color(0xFFE4C34D)),\n    ThemeColorOption(\"amber\", R.string.amber_theme, Color(0xFF785900), Color(0xFFF0C14A)),\n    ThemeColorOption(\"orange\", R.string.orange_theme, Color(0xFF8B5000), Color(0xFFFFB870)),\n    ThemeColorOption(\"deep_orange\", R.string.deep_orange_theme, Color(0xFFB02F00), Color(0xFFFFB59E)),\n    ThemeColorOption(\"red\", R.string.red_theme, Color(0xFFBB1614), Color(0xFFFFB4AA)),\n    ThemeColorOption(\"pink\", R.string.pink_theme, Color(0xFFBC004B), Color(0xFFFFB1C3)),\n    ThemeColorOption(\"purple\", R.string.purple_theme, Color(0xFF9A25AE), Color(0xFFEFB0FF)),\n    ThemeColorOption(\"deep_purple\", R.string.deep_purple_theme, Color(0xFF6F43C0), Color(0xFFD4BBFF)),\n    ThemeColorOption(\"brown\", R.string.brown_theme, Color(0xFF9A4522), Color(0xFFFFB594)),\n    ThemeColorOption(\"blue_grey\", R.string.blue_grey_theme, Color(0xFF00668A), Color(0xFF5EF0FF)),\n    ThemeColorOption(\"sakura\", R.string.sakura_theme, Color(0xFF9B404F), Color(0xFFFFB1BD)),\n    ThemeColorOption(\"ink_wash\", R.string.ink_wash_theme, Color(0xFF424242), Color(0xFFC6C6C6)),\n)\n\n@OptIn(ExperimentalMaterial3ExpressiveApi::class)\n@Composable\nfun ThemeColorPicker(\n    selectedColorKey: String,\n    onColorSelected: (String) -> Unit,\n    isDarkTheme: Boolean,\n    modifier: Modifier = Modifier,\n    flat: Boolean = false,\n    isDynamicColorSupported: Boolean = false,\n    isDynamicColorEnabled: Boolean = false,\n    onDynamicColorSelected: () -> Unit = {},\n    bare: Boolean = false,\n) {\n    val content: @Composable () -> Unit = {\n        Column(\n            modifier = Modifier.padding(16.dp),\n        ) {\n            Text(\n                text = stringResource(R.string.theme_color),\n                style = MaterialTheme.typography.titleMedium,\n                color = MaterialTheme.colorScheme.onSurface,\n                fontWeight = FontWeight.SemiBold,\n            )\n\n            Spacer(Modifier.height(12.dp))\n\n            LazyRow(\n                modifier = Modifier.fillMaxWidth(),\n                horizontalArrangement = Arrangement.spacedBy(20.dp),\n                verticalAlignment = Alignment.CenterVertically,\n                contentPadding = PaddingValues(start = 8.dp),\n            ) {\n                if (isDynamicColorSupported) {\n                    item(key = \"system_dynamic\") {\n                        ThemeColorCircle(\n                            label = stringResource(R.string.theme_system),\n                            displayColor = MaterialTheme.colorScheme.primary,\n                            isSelected = isDynamicColorEnabled,\n                            isDynamic = true,\n                            onClick = onDynamicColorSelected,\n                        )\n                    }\n                }\n\n                items(themeColorOptions, key = { it.key }) { theme ->\n                    ThemeColorCircle(\n                        label = stringResource(theme.labelId),\n                        displayColor = if (isDarkTheme) theme.darkPrimary else theme.lightPrimary,\n                        isSelected = !isDynamicColorEnabled && selectedColorKey == theme.key,\n                        isDynamic = false,\n                        onClick = { onColorSelected(theme.key) },\n                    )\n                }\n            }\n        }\n    }\n\n    if (bare) {\n        content()\n    } else {\n        ExpressiveCard(modifier = modifier, flat = flat) {\n            content()\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3ExpressiveApi::class)\n@Composable\nprivate fun ThemeColorCircle(\n    label: String,\n    displayColor: Color,\n    isSelected: Boolean,\n    isDynamic: Boolean,\n    onClick: () -> Unit,\n) {\n    val scale by animateFloatAsState(\n        targetValue = if (isSelected) 1.1f else 1f,\n        animationSpec = spring(\n            dampingRatio = Spring.DampingRatioMediumBouncy,\n            stiffness = Spring.StiffnessLow,\n        ),\n        label = \"colorScale\",\n    )\n\n    val shape = if (isSelected) MaterialShapes.Cookie9Sided.toShape() else CircleShape\n\n    Column(\n        modifier = Modifier.clickable(onClick = onClick),\n        horizontalAlignment = Alignment.CenterHorizontally,\n        verticalArrangement = Arrangement.spacedBy(8.dp),\n    ) {\n        Box(\n            modifier = Modifier\n                .size(56.dp)\n                .scale(scale)\n                .clip(shape)\n                .background(color = displayColor)\n                .then(\n                    if (isDynamic) {\n                        Modifier.border(\n                            2.dp,\n                            MaterialTheme.colorScheme.outline,\n                            shape,\n                        )\n                    } else {\n                        Modifier\n                    },\n                ),\n            contentAlignment = Alignment.Center,\n        ) {\n            androidx.compose.animation.AnimatedVisibility(\n                visible = isSelected,\n                enter = scaleIn(spring(dampingRatio = Spring.DampingRatioMediumBouncy)) + fadeIn(),\n                exit = scaleOut() + fadeOut(),\n            ) {\n                Icon(\n                    imageVector = Icons.Default.Done,\n                    contentDescription = null,\n                    modifier = Modifier.size(28.dp),\n                    tint = MaterialTheme.colorScheme.onPrimary,\n                )\n            }\n        }\n\n        Text(\n            text = label,\n            style = MaterialTheme.typography.labelLarge,\n            color = if (isSelected) MaterialTheme.colorScheme.primary\n            else MaterialTheme.colorScheme.onSurfaceVariant,\n            fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal,\n        )\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/ThemeModeSelector.kt",
    "content": "package me.bmax.apatch.ui.component\n\nimport androidx.compose.animation.core.Spring\nimport androidx.compose.animation.core.animateFloatAsState\nimport androidx.compose.animation.core.spring\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.AutoAwesome\nimport androidx.compose.material.icons.filled.DarkMode\nimport androidx.compose.material.icons.filled.LightMode\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.draw.scale\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.unit.dp\nimport me.bmax.apatch.R\n\nenum class ThemeMode {\n    LIGHT, DARK, SYSTEM\n}\n\n@Composable\nfun ThemeModeSelector(\n    selectedMode: ThemeMode,\n    onModeSelected: (ThemeMode) -> Unit,\n    modifier: Modifier = Modifier,\n    flat: Boolean = false,\n    bare: Boolean = false,\n) {\n    val content: @Composable () -> Unit = {\n        Row(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(16.dp),\n            horizontalArrangement = Arrangement.spacedBy(12.dp),\n            verticalAlignment = Alignment.CenterVertically,\n        ) {\n            ThemeModeOption(\n                icon = Icons.Default.LightMode,\n                label = stringResource(R.string.theme_light),\n                isSelected = selectedMode == ThemeMode.LIGHT,\n                onClick = { onModeSelected(ThemeMode.LIGHT) },\n                flat = flat,\n                modifier = Modifier.weight(1f),\n            )\n\n            ThemeModeOption(\n                icon = Icons.Default.DarkMode,\n                label = stringResource(R.string.theme_dark),\n                isSelected = selectedMode == ThemeMode.DARK,\n                onClick = { onModeSelected(ThemeMode.DARK) },\n                flat = flat,\n                modifier = Modifier.weight(1f),\n            )\n\n            ThemeModeOption(\n                icon = Icons.Default.AutoAwesome,\n                label = stringResource(R.string.theme_system),\n                isSelected = selectedMode == ThemeMode.SYSTEM,\n                onClick = { onModeSelected(ThemeMode.SYSTEM) },\n                flat = flat,\n                modifier = Modifier.weight(1f),\n            )\n        }\n    }\n\n    if (bare) {\n        content()\n    } else {\n        ExpressiveCard(modifier = modifier, flat = flat) {\n            content()\n        }\n    }\n}\n\n@Composable\nprivate fun ThemeModeOption(\n    icon: ImageVector,\n    label: String,\n    isSelected: Boolean,\n    onClick: () -> Unit,\n    modifier: Modifier = Modifier,\n    flat: Boolean = false,\n) {\n    val scale by animateFloatAsState(\n        targetValue = if (isSelected) 1.05f else 1f,\n        animationSpec = spring(\n            dampingRatio = Spring.DampingRatioMediumBouncy,\n            stiffness = Spring.StiffnessLow,\n        ),\n        label = \"themeModeScale\",\n    )\n\n    val bgColor = when {\n        isSelected && flat -> MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.7f)\n        isSelected -> MaterialTheme.colorScheme.primaryContainer\n        flat -> MaterialTheme.colorScheme.surfaceContainerLow.copy(alpha = 0.4f)\n        else -> MaterialTheme.colorScheme.surface\n    }\n\n    val contentColor = if (isSelected) MaterialTheme.colorScheme.onPrimaryContainer\n    else MaterialTheme.colorScheme.onSurfaceVariant\n\n    Column(\n        modifier = modifier\n            .scale(scale)\n            .clip(RoundedCornerShape(24.dp))\n            .background(bgColor)\n            .clickable(onClick = onClick)\n            .padding(vertical = 12.dp),\n        verticalArrangement = Arrangement.spacedBy(6.dp, Alignment.CenterVertically),\n        horizontalAlignment = Alignment.CenterHorizontally,\n    ) {\n        Icon(\n            imageVector = icon,\n            contentDescription = null,\n            modifier = Modifier.size(24.dp),\n            tint = contentColor,\n        )\n\n        Text(\n            text = label,\n            style = MaterialTheme.typography.titleMedium,\n            color = if (isSelected) MaterialTheme.colorScheme.onPrimaryContainer\n            else MaterialTheme.colorScheme.onSurface,\n        )\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/ToggleSettingCard.kt",
    "content": "package me.bmax.apatch.ui.component\n\nimport androidx.compose.foundation.interaction.MutableInteractionSource\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.selection.toggleable\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.ripple\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport androidx.compose.ui.semantics.Role\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\n\n@Composable\nfun ToggleSettingCard(\n    title: String,\n    description: String,\n    checked: Boolean,\n    enabled: Boolean = true,\n    flat: Boolean = false,\n    icon: ImageVector? = null,\n    onCheckedChange: (Boolean) -> Unit,\n) {\n    ExpressiveCard(flat = flat) {\n        Row(\n            modifier = Modifier\n                .fillMaxWidth()\n                .toggleable(\n                    value = checked,\n                    onValueChange = { if (enabled) onCheckedChange(it) },\n                    role = Role.Switch,\n                    enabled = enabled,\n                    interactionSource = remember { MutableInteractionSource() },\n                    indication = ripple(),\n                )\n                .padding(16.dp),\n            horizontalArrangement = Arrangement.SpaceBetween,\n            verticalAlignment = Alignment.CenterVertically,\n        ) {\n            if (icon != null) {\n                Icon(\n                    imageVector = icon,\n                    contentDescription = null,\n                    tint = if (enabled) MaterialTheme.colorScheme.onSurfaceVariant\n                    else MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.38f),\n                    modifier = Modifier.size(24.dp),\n                )\n                Spacer(Modifier.width(16.dp))\n            }\n\n            Column(\n                modifier = Modifier\n                    .weight(1f)\n                    .padding(end = 16.dp),\n            ) {\n                Text(\n                    text = title,\n                    style = MaterialTheme.typography.titleMedium,\n                    color = if (enabled) MaterialTheme.colorScheme.onSurface\n                    else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f),\n                    fontWeight = FontWeight.SemiBold,\n                )\n\n                Spacer(Modifier.height(4.dp))\n\n                Text(\n                    text = description,\n                    style = MaterialTheme.typography.bodyMedium,\n                    color = if (enabled) MaterialTheme.colorScheme.onSurfaceVariant\n                    else MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.38f),\n                )\n            }\n\n            ExpressiveSwitch(\n                checked = checked,\n                onCheckedChange = null,\n                enabled = enabled,\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/TwoColumnGrid.kt",
    "content": "package me.bmax.apatch.ui.component\n\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.lazy.LazyListState\nimport androidx.compose.foundation.lazy.rememberLazyListState\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.key\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalLayoutDirection\nimport androidx.compose.ui.unit.Dp\nimport androidx.compose.ui.unit.dp\n\n/**\n * A true masonry two-column layout using two independent [Column]s side by side.\n *\n * Items are permanently assigned to columns by index (even → left, odd → right).\n * Because the two columns are completely independent, expanding/collapsing a card\n * in one column never affects the other column — no jumping, no layout jitter.\n *\n * @param items the data list to display\n * @param key stable key provider for each item\n * @param beforeItems composable slot rendered full-width above the two columns\n * @param itemContent composable for each item\n */\n@Composable\nfun <T> TwoColumnGrid(\n    items: List<T>,\n    key: (item: T) -> Any,\n    modifier: Modifier = Modifier,\n    state: LazyListState = rememberLazyListState(),\n    verticalSpacing: Dp = 16.dp,\n    horizontalSpacing: Dp = 16.dp,\n    contentPadding: PaddingValues = PaddingValues(),\n    beforeItems: (@Composable () -> Unit)? = null,\n    itemContent: @Composable (item: T) -> Unit,\n) {\n    val leftItems = items.filterIndexed { index, _ -> index % 2 == 0 }\n    val rightItems = items.filterIndexed { index, _ -> index % 2 == 1 }\n    val layoutDirection = LocalLayoutDirection.current\n\n    Column(\n        modifier = modifier\n            .verticalScroll(rememberScrollState())\n            .padding(\n                start = contentPadding.calculateLeftPadding(layoutDirection),\n                top = contentPadding.calculateTopPadding(),\n                end = contentPadding.calculateRightPadding(layoutDirection),\n            ),\n    ) {\n        // Full-width content above the grid (banners, empty states, etc.)\n        beforeItems?.invoke()\n\n        if (items.isEmpty()) return@Column\n\n        Row(\n            modifier = Modifier.fillMaxWidth(),\n            horizontalArrangement = Arrangement.spacedBy(horizontalSpacing),\n        ) {\n            // Left column (even indices: 0, 2, 4, ...)\n            Column(\n                modifier = Modifier.weight(1f),\n                verticalArrangement = Arrangement.spacedBy(verticalSpacing),\n            ) {\n                leftItems.forEach { item ->\n                    key(key(item)) {\n                        itemContent(item)\n                    }\n                }\n            }\n\n            // Right column (odd indices: 1, 3, 5, ...)\n            Column(\n                modifier = Modifier.weight(1f),\n                verticalArrangement = Arrangement.spacedBy(verticalSpacing),\n            ) {\n                rightItems.forEach { item ->\n                    key(key(item)) {\n                        itemContent(item)\n                    }\n                }\n            }\n        }\n\n        // Bottom spacing\n        Spacer(modifier = Modifier.height(contentPadding.calculateBottomPadding()))\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/UmountConfig.kt",
    "content": "package me.bmax.apatch.ui.component\n\nimport android.content.Context\nimport android.util.Log\nimport androidx.compose.runtime.mutableStateOf\nimport com.topjohnwu.superuser.Shell\nimport me.bmax.apatch.util.getRootShell\nimport org.json.JSONObject\nimport java.io.File\nimport java.io.FileReader\nimport java.io.FileWriter\nimport java.io.IOException\n\n/**\n * Umount 配置数据类\n */\ndata class UmountConfig(\n    val enabled: Boolean = false,\n    val paths: String = \"\"  // 挂载点路径，每行一个\n)\n\n\nobject UmountConfigManager {\n    private const val TAG = \"UmountConfigManager\"\n    private const val CONFIG_FILE_NAME = \"umount_config.json\"\n    private const val UMOUNT_PATH_FILE = \"/data/adb/fp/UmountPATH\"\n\n    // 当前配置状态\n    var isEnabled = mutableStateOf(false)\n        private set\n    var paths = mutableStateOf(\"\")\n        private set\n\n    /**\n     * 加载配置文件\n     */\n    fun loadConfig(context: Context): UmountConfig {\n        return try {\n            val shell = me.bmax.apatch.util.getRootShell()\n\n            val enabledFlag = try {\n                val result = shell.newJob().add(\"test -f ${me.bmax.apatch.APApplication.UMOUNT_SERVICE_FILE}\").exec()\n                result.isSuccess\n            } catch (_: Exception) { false }\n\n            val pathsContent = try {\n                val job = shell.newJob().add(\"cat $UMOUNT_PATH_FILE 2>/dev/null\")\n                val list = mutableListOf<String>()\n                job.to(list, null).exec()\n                list.joinToString(\"\\n\")\n            } catch (_: Exception) { \"\" }\n\n            if (enabledFlag || pathsContent.isNotBlank()) {\n                isEnabled.value = enabledFlag\n                paths.value = pathsContent\n                Log.d(TAG, \"配置从 /data/adb/ 加载: enabled=$enabledFlag, paths length=${pathsContent.length}\")\n                UmountConfig(enabledFlag, pathsContent)\n            } else {\n                val configFile = File(context.filesDir, CONFIG_FILE_NAME)\n                if (configFile.exists()) {\n                    val reader = FileReader(configFile)\n                    val jsonContent = reader.readText()\n                    reader.close()\n                    val config = parseConfigFromJson(jsonContent) ?: UmountConfig()\n                    isEnabled.value = config.enabled\n                    paths.value = config.paths\n                    Log.d(TAG, \"配置从应用私有目录加载: enabled=${config.enabled}\")\n                    config\n                } else {\n                    UmountConfig()\n                }\n            }\n        } catch (e: Exception) {\n            Log.e(TAG, \"加载配置失败: ${e.message}\", e)\n            UmountConfig()\n        }\n    }\n\n    /**\n     * 保存配置文件\n     *\n     * 流程：\n     * 1. 保存配置到应用私有目录（JSON）\n     * 2. 如果 paths 不为空，创建/更新 UmountPATH 文件\n     * 3. 如果 paths 为空，删除 UmountPATH 文件\n     * 4. 根据 enabled 状态设置服务开关（控制开机是否执行）\n     */\n    fun saveConfig(context: Context, config: UmountConfig): Boolean {\n        Log.d(TAG, \"=== saveConfig 开始 ===\")\n        Log.d(TAG, \"enabled: ${config.enabled}, paths: '${config.paths}'\")\n        return try {\n            // 1. 保存 JSON 配置到应用私有目录\n            val configFile = File(context.filesDir, CONFIG_FILE_NAME)\n            val jsonContent = getConfigJson(config)\n            val writer = FileWriter(configFile)\n            writer.write(jsonContent)\n            writer.close()\n\n            // 更新内存状态\n            isEnabled.value = config.enabled\n            paths.value = config.paths\n            Log.d(TAG, \"配置保存成功: enabled=${config.enabled}\")\n\n            // 2. 处理 UmountPATH 文件（只要 paths 不为空就创建）\n            if (config.paths.isNotBlank()) {\n                Log.d(TAG, \"paths 不为空，调用 createUmountPathFile\")\n                val result = createUmountPathFile(context, config.paths)\n                Log.d(TAG, \"createUmountPathFile 返回: $result\")\n            } else {\n                Log.d(TAG, \"paths 为空，调用 deleteUmountPathFile\")\n                deleteUmountPathFile(context)\n            }\n\n            // 3. 设置服务开关（控制开机是否执行）\n            Log.d(TAG, \"调用 setUmountServiceEnabled(${config.enabled})\")\n            me.bmax.apatch.util.setUmountServiceEnabled(config.enabled)\n\n            Log.d(TAG, \"=== saveConfig 完成 ===\")\n            true\n        } catch (e: Exception) {\n            Log.e(TAG, \"保存配置失败: ${e.message}\", e)\n            false\n        }\n    }\n\n    /**\n     * 获取配置的JSON字符串\n     */\n    fun getConfigJson(): String {\n        return getConfigJson(UmountConfig(isEnabled.value, paths.value))\n    }\n\n    /**\n     * 获取指定配置的JSON字符串\n     */\n    fun getConfigJson(config: UmountConfig): String {\n        val jsonObject = JSONObject()\n        jsonObject.put(\"enabled\", config.enabled)\n        jsonObject.put(\"paths\", config.paths)\n        return jsonObject.toString(2)\n    }\n\n    /**\n     * 从JSON字符串解析配置\n     */\n    fun parseConfigFromJson(jsonString: String): UmountConfig? {\n        return try {\n            val jsonObject = JSONObject(jsonString)\n            val enabled = jsonObject.optBoolean(\"enabled\", false)\n            val paths = jsonObject.optString(\"paths\", \"\")\n            UmountConfig(enabled, paths)\n        } catch (e: Exception) {\n            Log.e(TAG, \"解析JSON失败: ${e.message}\", e)\n            null\n        }\n    }\n\n    /**\n     * 创建或更新 UmountPATH 文件\n     * 需要 Root 权限\n     */\n    private fun createUmountPathFile(context: Context, paths: String): Boolean {\n        return try {\n            val shell = getRootShell()\n\n            // 确保目录存在\n            shell.newJob().add(\"mkdir -p /data/adb/fp/bin\").exec()\n\n            // 写入临时文件\n            val tempFile = File(context.cacheDir, \"UmountPATH_temp\")\n            tempFile.writeText(paths)\n\n            // 使用 Root 权限复制文件并设置权限\n            val result = shell.newJob().add(\n                \"cp ${tempFile.absolutePath} $UMOUNT_PATH_FILE\",\n                \"chmod 644 $UMOUNT_PATH_FILE\",\n                \"restorecon $UMOUNT_PATH_FILE\"\n            ).exec()\n\n            tempFile.delete()\n\n            if (result.isSuccess) {\n                Log.d(TAG, \"创建/更新 UmountPATH 文件成功\")\n                true\n            } else {\n                Log.e(TAG, \"创建 UmountPATH 文件失败: ${result.err.joinToString()}\")\n                false\n            }\n        } catch (e: Exception) {\n            Log.e(TAG, \"创建 UmountPATH 文件失败: ${e.message}\", e)\n            false\n        }\n    }\n\n    /**\n     * 删除 UmountPATH 文件\n     */\n    private fun deleteUmountPathFile(context: Context): Boolean {\n        return try {\n            val shell = getRootShell()\n            val result = shell.newJob().add(\"rm -f $UMOUNT_PATH_FILE\").exec()\n\n            if (result.isSuccess) {\n                Log.d(TAG, \"删除 UmountPATH 文件成功\")\n                true\n            } else {\n                Log.e(TAG, \"删除 UmountPATH 文件失败: ${result.err.joinToString()}\")\n                false\n            }\n        } catch (e: Exception) {\n            Log.e(TAG, \"删除 UmountPATH 文件失败: ${e.message}\", e)\n            false\n        }\n    }\n\n    /**\n     * 调试方法：检查当前配置状态\n     */\n    fun debugConfigState() {\n        Log.d(TAG, \"=== Umount 配置状态 ===\")\n        Log.d(TAG, \"enabled=${isEnabled.value}\")\n        Log.d(TAG, \"paths length=${paths.value.length}\")\n        Log.d(TAG, \"========================\")\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/UpdateDialog.kt",
    "content": "package me.bmax.apatch.ui.component\n\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.*\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.window.DialogProperties\nimport me.bmax.apatch.R\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun UpdateDialog(\n    onDismiss: () -> Unit,\n    onUpdate: () -> Unit\n) {\n    BasicAlertDialog(\n        onDismissRequest = { /* Cannot dismiss by default means */ },\n        properties = DialogProperties(\n            dismissOnBackPress = false,\n            dismissOnClickOutside = false\n        )\n    ) {\n        Surface(\n            modifier = Modifier\n                .width(320.dp)\n                .wrapContentHeight(),\n            shape = RoundedCornerShape(20.dp),\n            tonalElevation = AlertDialogDefaults.TonalElevation,\n            color = AlertDialogDefaults.containerColor,\n        ) {\n            Column(modifier = Modifier.padding(24.dp)) {\n                Text(\n                    text = stringResource(R.string.update_available_title),\n                    style = MaterialTheme.typography.headlineSmall,\n                    modifier = Modifier.padding(bottom = 16.dp)\n                )\n                Text(\n                    text = stringResource(R.string.update_available_message),\n                    style = MaterialTheme.typography.bodyMedium,\n                    modifier = Modifier.padding(bottom = 24.dp)\n                )\n                Row(\n                    modifier = Modifier.fillMaxWidth(),\n                    horizontalArrangement = Arrangement.End\n                ) {\n                    TextButton(onClick = onDismiss) {\n                        Text(text = stringResource(R.string.update_close))\n                    }\n                    TextButton(onClick = onUpdate) {\n                        Text(text = stringResource(R.string.update_action))\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/WarningCard.kt",
    "content": "package me.bmax.apatch.ui.component\n\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.layout.wrapContentHeight\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.Close\nimport androidx.compose.material.icons.filled.Error\nimport androidx.compose.material3.Card\nimport androidx.compose.material3.CardDefaults\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.unit.dp\n\n@Composable\nfun WarningCard(\n    message: String,\n    modifier: Modifier = Modifier,\n    color: Color? = null,\n    onClick: (() -> Unit)? = null,\n    onClose: (() -> Unit)? = null,\n    icon: (@Composable () -> Unit)? = null\n) {\n    val cardColors = CardDefaults.cardColors(\n        containerColor = color ?: MaterialTheme.colorScheme.errorContainer,\n        contentColor = MaterialTheme.colorScheme.onErrorContainer,\n        disabledContainerColor = MaterialTheme.colorScheme.errorContainer,\n        disabledContentColor = MaterialTheme.colorScheme.onErrorContainer\n    )\n\n    Card(\n        colors = cardColors,\n        modifier = modifier,\n    ) {\n        Box(\n            modifier = Modifier\n                .fillMaxWidth()\n                .then(onClick?.let { Modifier.clickable { it() } } ?: Modifier)\n                .padding(20.dp)\n        ) {\n            Row(\n                verticalAlignment = Alignment.CenterVertically,\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .align(Alignment.CenterStart)\n                    .padding(end = 40.dp)\n            ) {\n                if (icon != null) {\n                    icon()\n                } else {\n                    Icon(\n                        imageVector = Icons.Default.Error,\n                        contentDescription = null,\n                        tint = MaterialTheme.colorScheme.onErrorContainer,\n                        modifier = Modifier.size(18.dp)\n                    )\n                }\n\n                Spacer(modifier = Modifier.width(12.dp))\n\n                Text(\n                    text = message,\n                    style = MaterialTheme.typography.bodyMedium,\n                    modifier = Modifier\n                        .wrapContentHeight(Alignment.CenterVertically)\n                )\n            }\n\n            if (onClose != null) {\n                Icon(\n                    imageVector = Icons.Default.Close,\n                    contentDescription = stringResource(android.R.string.cancel),\n                    tint = MaterialTheme.colorScheme.onErrorContainer,\n                    modifier = Modifier\n                        .clickable {\n                            onClose()\n                        }\n                        .size(18.dp)\n                        .align(Alignment.TopEnd)\n                )\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/chart/ChartUtils.kt",
    "content": "package me.bmax.apatch.ui.component.chart\n\nimport androidx.compose.ui.geometry.Size\nimport androidx.compose.ui.graphics.Path\n\ninternal data class ChartPoint(val x: Float, val y: Float)\n\ninternal fun normalizePoints(dataPoints: List<Float>): List<ChartPoint> {\n    if (dataPoints.isEmpty()) return emptyList()\n    val minY = dataPoints.minOrNull() ?: 0f\n    val maxY = dataPoints.maxOrNull() ?: 0f\n    val yRange = (maxY - minY).coerceAtLeast(1f)\n    return dataPoints.mapIndexed { index, value ->\n        val x = if (dataPoints.size <= 1) 0f else index.toFloat() / (dataPoints.size - 1)\n        val y = (value - minY) / yRange\n        ChartPoint(x, y)\n    }\n}\n\ninternal fun interpolatePoints(\n    prev: List<ChartPoint>,\n    current: List<ChartPoint>,\n    progress: Float\n): List<ChartPoint> {\n    if (current.isEmpty()) return emptyList()\n    if (prev.isEmpty()) return current\n    return current.mapIndexed { index, cur ->\n        if (index < prev.size) {\n            ChartPoint(\n                x = prev[index].x + (cur.x - prev[index].x) * progress,\n                y = prev[index].y + (cur.y - prev[index].y) * progress\n            )\n        } else {\n            cur\n        }\n    }\n}\n\ninternal fun buildBezierPath(\n    points: List<ChartPoint>,\n    size: Size,\n    heightFraction: Float = 1f\n): Path {\n    if (points.isEmpty()) return Path()\n    val chartHeight = size.height * heightFraction\n    val path = Path()\n    path.moveTo(\n        points[0].x * size.width,\n        chartHeight - points[0].y * chartHeight\n    )\n    for (i in 1 until points.size - 1) {\n        val current = points[i]\n        val next = points[i + 1]\n        val midX = (current.x + next.x) / 2f\n        val midY = (current.y + next.y) / 2f\n        path.quadraticTo(\n            current.x * size.width,\n            chartHeight - current.y * chartHeight,\n            midX * size.width,\n            chartHeight - midY * chartHeight\n        )\n    }\n    path.lineTo(\n        points.last().x * size.width,\n        chartHeight - points.last().y * chartHeight\n    )\n    return path\n}\n\ninternal fun buildFillPath(linePath: Path, size: Size): Path {\n    val fillPath = Path()\n    fillPath.addPath(linePath)\n    fillPath.lineTo(size.width, size.height)\n    fillPath.lineTo(0f, size.height)\n    fillPath.close()\n    return fillPath\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/chart/ModulePieChart.kt",
    "content": "package me.bmax.apatch.ui.component.chart\n\nimport androidx.compose.foundation.Canvas\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.geometry.Offset\nimport androidx.compose.ui.geometry.Size\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.StrokeCap\nimport androidx.compose.ui.graphics.drawscope.Fill\nimport androidx.compose.ui.graphics.drawscope.Stroke\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.unit.dp\n\nprivate object ChartPalette {\n    val KPM = Color(0xFF6366F1)\n    val APM = Color(0xFF3DDC84)\n    val SU  = Color(0xFFFF9F43)\n}\n\ndata class PieSliceData(\n    val label: String,\n    val value: Float,\n    val color: Color\n)\n\n@Composable\nfun ModulePieChart(\n    data: List<PieSliceData>,\n    modifier: Modifier = Modifier,\n    centerLabel: String? = null,\n    donutMode: Boolean = true\n) {\n    val colors = MaterialTheme.colorScheme\n\n    Box(modifier = modifier.size(140.dp)) {\n        val total = data.filter { it.value > 0f }.sumOf { it.value.toDouble() }.toFloat()\n        val activeSlices = data.filter { it.value > 0f }\n        val sliceGap = if (activeSlices.size > 1) 2f else 0f\n        val totalGap = sliceGap * activeSlices.size\n        val availableAngle = 360f - totalGap\n\n        Canvas(modifier = Modifier.size(140.dp).align(Alignment.Center)) {\n            val canvasSize = size.minDimension\n            val strokeWidth = if (donutMode) 20.dp.toPx() else 30.dp.toPx()\n            val radius = (canvasSize - strokeWidth) / 2\n            val center = Offset(canvasSize / 2, canvasSize / 2)\n\n            var startAngle = -90f\n\n            data.forEach { slice ->\n                if (slice.value <= 0f) return@forEach\n\n                val sweepAngle = if (total > 0f) {\n                    (slice.value / total) * availableAngle\n                } else {\n                    360f\n                }\n\n                drawArc(\n                    color = slice.color,\n                    startAngle = startAngle,\n                    sweepAngle = sweepAngle,\n                    useCenter = !donutMode,\n                    topLeft = Offset(center.x - radius, center.y - radius),\n                    size = Size(radius * 2, radius * 2),\n                    style = if (donutMode) Stroke(width = strokeWidth, cap = StrokeCap.Butt) else Fill\n                )\n\n                startAngle += sweepAngle + sliceGap\n            }\n        }\n\n        if (donutMode && centerLabel != null) {\n            Text(\n                text = centerLabel,\n                style = MaterialTheme.typography.titleMedium.copy(\n                    fontWeight = FontWeight.Bold,\n                    color = colors.onSurface\n                ),\n                modifier = Modifier.align(Alignment.Center),\n                textAlign = TextAlign.Center\n            )\n        }\n    }\n}\n\n@Composable\nfun rememberPieSliceDataFromCounts(\n    kernelModules: Int = 0,\n    apmModules: Int = 0,\n    superusers: Int = 0,\n    kpmLabel: String = \"KPM\",\n    apmLabel: String = \"APM\",\n    suLabel: String = \"SU\"\n): List<PieSliceData> {\n    val emptyColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.15f)\n\n    return remember(kernelModules, apmModules, superusers, kpmLabel, apmLabel, suLabel) {\n        listOf(\n            PieSliceData(\n                label = kpmLabel,\n                value = kernelModules.toFloat(),\n                color = if (kernelModules > 0) ChartPalette.KPM else emptyColor\n            ),\n            PieSliceData(\n                label = apmLabel,\n                value = apmModules.toFloat(),\n                color = if (apmModules > 0) ChartPalette.APM else emptyColor\n            ),\n            PieSliceData(\n                label = suLabel,\n                value = superusers.toFloat(),\n                color = if (superusers > 0) ChartPalette.SU else emptyColor\n            )\n        )\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/chart/StorageColumnChart.kt",
    "content": "package me.bmax.apatch.ui.component.chart\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxHeight\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport kotlin.math.roundToInt\n\ndata class StoragePartition(\n    val label: String,\n    val usedPercent: Float,\n    val color: Color\n)\n\n@Composable\nfun StorageColumnChart(\n    partitions: List<StoragePartition>,\n    modifier: Modifier = Modifier\n) {\n    val colors = MaterialTheme.colorScheme\n\n    Box(\n        modifier = modifier\n            .fillMaxWidth()\n            .background(\n                color = colors.surface,\n                shape = RoundedCornerShape(16.dp)\n            )\n    ) {\n        Column(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(16.dp),\n            verticalArrangement = Arrangement.spacedBy(12.dp)\n        ) {\n            Text(\n                text = \"Storage Partitions\",\n                style = MaterialTheme.typography.titleMedium,\n                fontWeight = FontWeight.SemiBold,\n                color = colors.onSurface\n            )\n\n            partitions.forEach { partition ->\n                PartitionBar(partition)\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun PartitionBar(\n    partition: StoragePartition,\n    modifier: Modifier = Modifier\n) {\n    val colors = MaterialTheme.colorScheme\n\n    Row(\n        modifier = modifier.fillMaxWidth(),\n        verticalAlignment = Alignment.CenterVertically\n    ) {\n        Text(\n            text = partition.label,\n            style = MaterialTheme.typography.bodySmall,\n            color = colors.onSurfaceVariant,\n            modifier = Modifier.width(64.dp)\n        )\n\n        Spacer(Modifier.width(8.dp))\n\n        Box(\n            modifier = Modifier\n                .weight(1f)\n                .height(20.dp)\n                .clip(RoundedCornerShape(4.dp))\n                .background(colors.surfaceContainerHighest)\n        ) {\n            Box(\n                modifier = Modifier\n                    .fillMaxWidth(partition.usedPercent.coerceIn(0f, 100f) / 100f)\n                    .fillMaxHeight()\n                    .clip(RoundedCornerShape(4.dp))\n                    .background(partition.color)\n            )\n        }\n\n        Spacer(Modifier.width(8.dp))\n\n        Text(\n            text = \"${partition.usedPercent.roundToInt()}%\",\n            style = MaterialTheme.typography.labelSmall,\n            fontWeight = FontWeight.SemiBold,\n            color = colors.onSurface,\n            modifier = Modifier.width(40.dp)\n        )\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/chart/SystemAreaChart.kt",
    "content": "package me.bmax.apatch.ui.component.chart\n\nimport androidx.compose.animation.core.Animatable\nimport androidx.compose.animation.core.FastOutSlowInEasing\nimport androidx.compose.animation.core.tween\nimport androidx.compose.foundation.Canvas\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Brush\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.StrokeCap\nimport androidx.compose.ui.graphics.StrokeJoin\nimport androidx.compose.ui.graphics.drawscope.Stroke\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\n\n@Composable\nfun SystemAreaChart(\n    title: String,\n    dataPoints: List<Float>,\n    unit: String = \"%\",\n    modifier: Modifier = Modifier,\n    color: Color = MaterialTheme.colorScheme.primary\n) {\n    val colors = MaterialTheme.colorScheme\n    val currentValue = dataPoints.lastOrNull() ?: 0f\n\n    val animatable = remember { Animatable(0f) }\n    var prevPoints by remember { mutableStateOf<List<ChartPoint>>(emptyList()) }\n    var currentPoints by remember { mutableStateOf<List<ChartPoint>>(emptyList()) }\n\n    LaunchedEffect(dataPoints) {\n        val newPoints = normalizePoints(dataPoints)\n        prevPoints = currentPoints\n        currentPoints = newPoints\n        animatable.snapTo(0f)\n        animatable.animateTo(\n            1f,\n            animationSpec = tween(durationMillis = 500, easing = FastOutSlowInEasing)\n        )\n    }\n\n    val progress = animatable.value\n\n    Box(\n        modifier = modifier\n            .fillMaxWidth()\n            .height(140.dp)\n            .background(\n                color = colors.surface,\n                shape = RoundedCornerShape(16.dp)\n            )\n    ) {\n        Text(\n            text = \"${currentValue.toInt()}$unit\",\n            style = MaterialTheme.typography.headlineSmall.copy(\n                fontWeight = FontWeight.Bold,\n                color = color\n            ),\n            modifier = Modifier\n                .align(Alignment.TopStart)\n                .padding(start = 16.dp, top = 12.dp)\n        )\n\n        Text(\n            text = title,\n            style = MaterialTheme.typography.labelMedium.copy(\n                color = colors.onSurfaceVariant,\n                fontSize = 12.sp\n            ),\n            modifier = Modifier\n                .align(Alignment.BottomStart)\n                .padding(start = 16.dp, bottom = 12.dp)\n        )\n\n        if (dataPoints.isNotEmpty()) {\n            Canvas(\n                modifier = Modifier\n                    .matchParentSize()\n                    .padding(start = 16.dp, end = 16.dp, top = 48.dp, bottom = 36.dp)\n            ) {\n                val interpolated = interpolatePoints(prevPoints, currentPoints, progress)\n                val linePath = buildBezierPath(interpolated, size, heightFraction = 0.75f)\n                val fillPath = buildFillPath(linePath, size)\n\n                drawPath(\n                    path = fillPath,\n                    brush = Brush.verticalGradient(\n                        colors = listOf(\n                            color.copy(alpha = 0.3f),\n                            color.copy(alpha = 0.0f)\n                        ),\n                        startY = 0f,\n                        endY = size.height\n                    )\n                )\n\n                drawPath(\n                    path = linePath,\n                    color = color,\n                    style = Stroke(\n                        width = 2.5f,\n                        cap = StrokeCap.Round,\n                        join = StrokeJoin.Round\n                    )\n                )\n            }\n        } else {\n            Text(\n                text = \"--\",\n                style = MaterialTheme.typography.displaySmall.copy(\n                    color = colors.outline.copy(alpha = 0.3f)\n                ),\n                modifier = Modifier.align(Alignment.Center),\n                textAlign = TextAlign.Center\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/chart/SystemLineChart.kt",
    "content": "package me.bmax.apatch.ui.component.chart\n\nimport androidx.compose.animation.core.Animatable\nimport androidx.compose.animation.core.FastOutSlowInEasing\nimport androidx.compose.animation.core.tween\nimport androidx.compose.foundation.Canvas\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Brush\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.StrokeCap\nimport androidx.compose.ui.graphics.StrokeJoin\nimport androidx.compose.ui.graphics.drawscope.Stroke\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\n\n@Composable\nfun SystemLineChart(\n    title: String,\n    dataPoints: List<Float>,\n    unit: String = \"%\",\n    modifier: Modifier = Modifier,\n    color: Color = MaterialTheme.colorScheme.primary\n) {\n    val colors = MaterialTheme.colorScheme\n\n    val lineColor = if (dataPoints.isNotEmpty() && dataPoints.last() > 80f) {\n        colors.error\n    } else {\n        color\n    }\n\n    val currentValue = dataPoints.lastOrNull() ?: 0f\n\n    val animatable = remember { Animatable(0f) }\n    var prevPoints by remember { mutableStateOf<List<ChartPoint>>(emptyList()) }\n    var currentPoints by remember { mutableStateOf<List<ChartPoint>>(emptyList()) }\n\n    LaunchedEffect(dataPoints) {\n        val newPoints = normalizePoints(dataPoints)\n        prevPoints = currentPoints\n        currentPoints = newPoints\n        animatable.snapTo(0f)\n        animatable.animateTo(\n            1f,\n            animationSpec = tween(durationMillis = 500, easing = FastOutSlowInEasing)\n        )\n    }\n\n    val progress = animatable.value\n\n    Box(\n        modifier = modifier\n            .fillMaxWidth()\n            .height(160.dp)\n            .background(\n                color = colors.surface,\n                shape = RoundedCornerShape(16.dp)\n            )\n    ) {\n        Text(\n            text = if (currentValue > 0f && currentValue.isFinite() && currentValue < Int.MAX_VALUE) \"${currentValue.toInt()}$unit\" else \"--$unit\",\n            style = MaterialTheme.typography.headlineSmall.copy(\n                fontWeight = FontWeight.Bold,\n                color = lineColor\n            ),\n            modifier = Modifier\n                .align(Alignment.TopStart)\n                .padding(start = 16.dp, top = 12.dp)\n        )\n\n        Text(\n            text = title,\n            style = MaterialTheme.typography.labelMedium.copy(\n                color = colors.onSurfaceVariant,\n                fontSize = 12.sp\n            ),\n            modifier = Modifier\n                .align(Alignment.BottomStart)\n                .padding(start = 16.dp, bottom = 12.dp)\n        )\n\n        if (dataPoints.isNotEmpty()) {\n            Canvas(\n                modifier = Modifier\n                    .matchParentSize()\n                    .padding(start = 16.dp, end = 16.dp, top = 48.dp, bottom = 36.dp)\n            ) {\n                val interpolated = interpolatePoints(prevPoints, currentPoints, progress)\n                val linePath = buildBezierPath(interpolated, size, heightFraction = 0.75f)\n                val fillPath = buildFillPath(linePath, size)\n\n                drawPath(\n                    path = fillPath,\n                    brush = Brush.verticalGradient(\n                        colors = listOf(\n                            lineColor.copy(alpha = 0.3f),\n                            lineColor.copy(alpha = 0.0f)\n                        ),\n                        startY = 0f,\n                        endY = size.height\n                    )\n                )\n\n                drawPath(\n                    path = linePath,\n                    color = lineColor,\n                    style = Stroke(\n                        width = 2.5f,\n                        cap = StrokeCap.Round,\n                        join = StrokeJoin.Round\n                    )\n                )\n            }\n        } else {\n            Text(\n                text = \"--\",\n                style = MaterialTheme.typography.displaySmall.copy(\n                    color = colors.outline.copy(alpha = 0.3f)\n                ),\n                modifier = Modifier.align(Alignment.Center),\n                textAlign = TextAlign.Center\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/model/ApiMarketplaceItem.kt",
    "content": "package me.bmax.apatch.ui.model\n\nimport java.util.Locale\n\n/**\n * API Marketplace item data model\n * Represents a banner API source from the marketplace\n */\ndata class ApiMarketplaceItem(\n    val name: String,\n    val url: String,\n    val description: String,\n    val descriptionEn: String\n) {\n    /**\n     * Get localized description based on current locale\n     */\n    fun getLocalizedDescription(): String {\n        return if (Locale.getDefault().language == \"zh\") {\n            description\n        } else {\n            descriptionEn\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/APM.kt",
    "content": "package me.bmax.apatch.ui.screen\n\nimport android.app.Activity.RESULT_OK\nimport android.content.ClipData\nimport android.content.ClipboardManager\nimport android.content.Context\nimport android.content.Intent\nimport android.content.SharedPreferences\nimport android.graphics.Bitmap\nimport android.net.Uri\nimport android.util.Log\nimport android.util.Patterns\nimport me.bmax.apatch.util.ui.showToast\nimport androidx.activity.compose.rememberLauncherForActivityResult\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.animation.animateContentSize\nimport androidx.compose.animation.core.animateDpAsState\nimport androidx.compose.animation.core.tween\nimport androidx.compose.animation.Crossfade\nimport androidx.compose.animation.expandVertically\nimport androidx.compose.animation.fadeIn\nimport androidx.compose.animation.fadeOut\nimport androidx.compose.animation.shrinkVertically\nimport androidx.compose.foundation.combinedClickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.LazyListState\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.lazy.itemsIndexed\nimport androidx.compose.foundation.lazy.rememberLazyListState\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.material.ExperimentalMaterialApi\nimport androidx.compose.material3.AlertDialog\nimport androidx.compose.material3.AlertDialogDefaults\nimport androidx.compose.material3.BasicAlertDialog\nimport androidx.compose.material3.Button\nimport androidx.compose.material3.Checkbox\nimport androidx.compose.material3.ElevatedCard\nimport androidx.compose.material3.CardDefaults\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.Brush\nimport androidx.compose.ui.graphics.asImageBitmap\nimport coil.compose.AsyncImage\nimport coil.request.ImageRequest\nimport com.topjohnwu.superuser.Shell\nimport com.topjohnwu.superuser.io.SuFile\nimport androidx.compose.ui.layout.ContentScale\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.layout.fillMaxHeight\nimport androidx.compose.foundation.isSystemInDarkTheme\nimport androidx.compose.foundation.layout.defaultMinSize\nimport androidx.compose.foundation.layout.offset\nimport androidx.compose.material3.ButtonDefaults\nimport androidx.compose.material.icons.outlined.Add\nimport androidx.compose.material.icons.outlined.Delete\nimport androidx.compose.material.icons.outlined.Download\nimport androidx.compose.material.icons.outlined.Restore\nimport androidx.compose.material.icons.outlined.Terminal\nimport androidx.compose.material.icons.automirrored.outlined.Wysiwyg\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.ExperimentalMaterial3ExpressiveApi\nimport androidx.compose.material3.FloatingActionButton\nimport androidx.compose.material3.FloatingActionButtonMenu\nimport androidx.compose.material3.FloatingActionButtonMenuItem\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.SnackbarDuration\nimport androidx.compose.material3.SnackbarHost\nimport androidx.compose.material3.SnackbarHostState\nimport androidx.compose.material3.SnackbarResult\nimport androidx.compose.material3.Surface\nimport me.bmax.apatch.ui.component.ExpressiveSwitch\nimport me.bmax.apatch.ui.component.ExpressiveCard\nimport me.bmax.apatch.ui.component.LocalInsideSplicedGroup\nimport me.bmax.apatch.ui.component.TwoColumnGrid\nimport me.bmax.apatch.ui.component.splicedLazyColumnGroup\nimport me.bmax.apatch.ui.component.WarningCard\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.material3.OutlinedTextField\nimport androidx.compose.material3.RadioButton\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.pulltorefresh.PullToRefreshBox\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.DisposableEffect\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.livedata.observeAsState\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.produceState\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.window.DialogProperties\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.alpha\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.painterResource\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.text.style.TextDecoration\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.core.net.toUri\nimport androidx.lifecycle.viewmodel.compose.viewModel\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport com.ramcosta.composedestinations.generated.destinations.ExecuteAPMActionScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.InstallScreenDestination\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.sync.withPermit\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.R\nimport me.bmax.apatch.apApp\nimport me.bmax.apatch.ui.WebUIActivity\nimport androidx.compose.material3.pulltorefresh.PullToRefreshDefaults\nimport androidx.compose.material3.pulltorefresh.rememberPullToRefreshState\nimport me.bmax.apatch.ui.component.AdaptiveModuleButtonRow\nimport me.bmax.apatch.ui.component.ConfirmResult\nimport me.bmax.apatch.ui.component.ModuleButtonConfig\nimport me.bmax.apatch.ui.component.ModuleRemoveButton\nimport me.bmax.apatch.ui.component.ModuleStateIndicator\nimport me.bmax.apatch.ui.component.ModuleUpdateButton\nimport me.bmax.apatch.ui.component.SearchAppBar\nimport me.bmax.apatch.ui.component.rememberConfirmDialog\nimport me.bmax.apatch.ui.component.rememberLoadingDialog\nimport me.bmax.apatch.ui.viewmodel.APModuleViewModel\nimport me.bmax.apatch.util.DownloadListener\nimport me.bmax.apatch.util.download\nimport me.bmax.apatch.util.hasMagisk\nimport me.bmax.apatch.util.ModuleShortcut\nimport me.bmax.apatch.util.getRootShell\nimport me.bmax.apatch.util.reboot\nimport me.bmax.apatch.util.toggleModule\nimport me.bmax.apatch.util.ui.LocalSnackbarHost\nimport me.bmax.apatch.util.uninstallModule\nimport me.bmax.apatch.util.undoUninstallModule\n\nimport com.ramcosta.composedestinations.generated.destinations.ApmBulkInstallScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.OnlineModuleScreenDestination\nimport androidx.compose.material.icons.automirrored.filled.PlaylistAdd\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.Close\nimport androidx.compose.material.icons.filled.DeleteSweep\nimport androidx.compose.material.icons.filled.Download\nimport androidx.compose.material.icons.filled.MoreVert\nimport me.bmax.apatch.ui.component.WallpaperAwareDropdownMenu\nimport me.bmax.apatch.ui.component.WallpaperAwareDropdownMenuItem\nimport me.bmax.apatch.util.ModuleBackupUtils\nimport me.bmax.apatch.util.SafeUriResolver\nimport me.bmax.apatch.ui.theme.BackgroundConfig\nimport me.bmax.apatch.ui.LocalBottomBarVisible\nimport me.bmax.apatch.ui.LocalIsFloatingNavMode\nimport androidx.compose.ui.platform.LocalConfiguration\nimport java.text.SimpleDateFormat\nimport java.util.Date\nimport java.util.Locale\nimport java.util.Properties\nimport java.io.File\n\nimport me.bmax.apatch.util.BiometricUtils\n\n@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)\n@Destination<RootGraph>\n@Composable\nfun APModuleScreen(navigator: DestinationsNavigator) {\n    val snackBarHost = LocalSnackbarHost.current\n    val context = LocalContext.current\n\n    // First use dialog state\n    val prefs = remember { APApplication.sharedPreferences }\n    var showFirstTimeDialog by remember { \n        mutableStateOf(!prefs.getBoolean(\"apm_first_use_shown\", false)) \n    }\n    var dontShowAgain by remember { mutableStateOf(false) }\n\n    var showMoreModuleInfo by remember { mutableStateOf(prefs.getBoolean(\"show_more_module_info\", true)) }\n    var foldSystemModule by remember { mutableStateOf(prefs.getBoolean(\"fold_system_module\", true)) }\n    var simpleListBottomBar by remember { mutableStateOf(prefs.getBoolean(\"simple_list_bottom_bar\", false)) }\n    var splicedCardGroup by remember { mutableStateOf(prefs.getBoolean(\"spliced_card_group\", true)) }\n\n    val viewModel = viewModel<APModuleViewModel>()\n\n    DisposableEffect(Unit) {\n        val listener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPrefs, key ->\n            if (key == \"show_more_module_info\") {\n                showMoreModuleInfo = sharedPrefs.getBoolean(\"show_more_module_info\", true)\n            } else if (key == \"fold_system_module\") {\n                foldSystemModule = sharedPrefs.getBoolean(\"fold_system_module\", false)\n            } else if (key == \"simple_list_bottom_bar\") {\n                simpleListBottomBar = sharedPrefs.getBoolean(\"simple_list_bottom_bar\", false)\n            } else if (key == \"spliced_card_group\") {\n                splicedCardGroup = sharedPrefs.getBoolean(\"spliced_card_group\", true)\n            }\n        }\n        prefs.registerOnSharedPreferenceChangeListener(listener)\n        onDispose {\n            prefs.unregisterOnSharedPreferenceChangeListener(listener)\n        }\n    }\n\n    val state by APApplication.apStateLiveData.observeAsState(APApplication.State.UNKNOWN_STATE)\n    val scope = rememberCoroutineScope()\n\n    suspend fun checkStrongBiometric(): Boolean {\n        val prefs = APApplication.sharedPreferences\n        if (prefs.getBoolean(\"strong_biometric\", false) && prefs.getBoolean(\"biometric_login\", false)) {\n            val activity = context as? androidx.fragment.app.FragmentActivity\n            return if (activity != null) {\n                BiometricUtils.authenticate(activity)\n            } else {\n                true\n            }\n        }\n        return true\n    }\n\n    if (state != APApplication.State.ANDROIDPATCH_INSTALLED && state != APApplication.State.ANDROIDPATCH_NEED_UPDATE) {\n        Column(\n            modifier = Modifier\n                .fillMaxSize()\n                .padding(12.dp),\n            verticalArrangement = Arrangement.Center,\n            horizontalAlignment = Alignment.CenterHorizontally\n        ) {\n            Row {\n                Text(\n                    text = stringResource(id = R.string.apm_not_installed),\n                    style = MaterialTheme.typography.titleMedium\n                )\n            }\n        }\n        return\n    }\n\n    LaunchedEffect(Unit) {\n        if (viewModel.moduleList.isEmpty() || viewModel.isNeedRefresh) {\n            viewModel.fetchModuleList()\n        }\n    }\n\n    var pendingInstallUri by remember { mutableStateOf<Uri?>(null) }\n    val installConfirmDialog = rememberConfirmDialog(\n        onConfirm = {\n            pendingInstallUri?.let { uri ->\n                navigator.navigate(InstallScreenDestination(uri, MODULE_TYPE.APM))\n                viewModel.markNeedRefresh()\n            }\n            pendingInstallUri = null\n        },\n        onDismiss = {\n            pendingInstallUri = null\n        }\n    )\n\n    val webUILauncher = rememberLauncherForActivityResult(\n        contract = ActivityResultContracts.StartActivityForResult()\n    ) { viewModel.fetchModuleList() }\n    //TODO: FIXME -> val isSafeMode = Natives.getSafeMode()\n    val isSafeMode = false\n    val hasMagisk by produceState(initialValue = false) {\n        value = withContext(Dispatchers.IO) { hasMagisk() }\n    }\n    val hideInstallButton = isSafeMode || hasMagisk\n\n    val moduleListState = rememberLazyListState()\n\n    var searchQuery by remember { mutableStateOf(\"\") }\n    val filteredModuleList = remember(viewModel.moduleList, searchQuery) {\n        if (searchQuery.isEmpty()) {\n            viewModel.moduleList\n        } else {\n            viewModel.moduleList.filter {\n                it.name.contains(searchQuery, ignoreCase = true) ||\n                        it.description.contains(searchQuery, ignoreCase = true) ||\n                        it.author.contains(searchQuery, ignoreCase = true)\n            }\n        }\n    }\n\n    Scaffold(\n        topBar = {\n        TopBar(\n            navigator,\n            viewModel,\n            snackBarHost,\n            searchQuery,\n            ::checkStrongBiometric,\n            onSearchQueryChange = { searchQuery = it },\n            onToggleModuleBanner = {\n                val newValue = !BackgroundConfig.isBannerEnabled\n                BackgroundConfig.setBannerEnabledState(newValue)\n                BackgroundConfig.save(context)\n            }\n        )\n    }, floatingActionButton = if (hideInstallButton) {\n        { /* Empty */ }\n    } else {\n        {\n            val selectZipLauncher = rememberLauncherForActivityResult(\n                contract = ActivityResultContracts.StartActivityForResult()\n            ) {\n                if (it.resultCode != RESULT_OK) {\n                    return@rememberLauncherForActivityResult\n                }\n                val data = it.data ?: return@rememberLauncherForActivityResult\n                val uri = data.data ?: return@rememberLauncherForActivityResult\n\n                Log.i(\"ModuleScreen\", \"select zip result: $uri\")\n\n                val prefs = APApplication.sharedPreferences\n                if (prefs.getBoolean(\"apm_install_confirm_enabled\", true)) {\n                    pendingInstallUri = uri\n                    val fileName = try {\n                        var name = uri.path ?: \"Module\"\n                        context.contentResolver.query(uri, null, null, null, null)?.use { cursor ->\n                            val nameIndex = cursor.getColumnIndex(android.provider.OpenableColumns.DISPLAY_NAME)\n                            if (cursor.moveToFirst() && nameIndex >= 0) {\n                                name = cursor.getString(nameIndex)\n                            }\n                        }\n                        name\n                    } catch (e: Exception) {\n                        \"Module\"\n                    }\n                    installConfirmDialog.showConfirm(\n                        title = context.getString(R.string.apm_install_confirm_title),\n                        content = context.getString(R.string.apm_install_confirm_content, fileName),\n                        markdown = false\n                    )\n                } else {\n                    navigator.navigate(InstallScreenDestination(uri, MODULE_TYPE.APM))\n                    viewModel.markNeedRefresh()\n                }\n            }\n\n            val isFloatingMode = LocalIsFloatingNavMode.current\n            val bottomBarVisible = LocalBottomBarVisible.current.value\n            val configuration = LocalConfiguration.current\n            val isLandscape = configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE\n            val animatedOffset by animateDpAsState(\n                targetValue = if (isFloatingMode && bottomBarVisible && !isLandscape) (-88).dp else 0.dp,\n                animationSpec = tween(durationMillis = 300),\n                label = \"fabOffset\"\n            )\n\n            var fabExpanded by remember { mutableStateOf(false) }\n\n            val fabContent: @Composable () -> Unit = {\n                FloatingActionButtonMenu(\n                    expanded = fabExpanded,\n                    button = {\n                        FloatingActionButton(\n                            onClick = { fabExpanded = !fabExpanded },\n                            shape = CircleShape,\n                            contentColor = MaterialTheme.colorScheme.onPrimary.copy(alpha = 1f),\n                            containerColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                        ) {\n                            Crossfade(\n                                targetState = fabExpanded,\n                                animationSpec = tween(durationMillis = 200),\n                                label = \"fabIconCrossfade\"\n                            ) { isExpanded ->\n                                if (isExpanded) {\n                                    Icon(\n                                        Icons.Filled.Close,\n                                        contentDescription = null,\n                                    )\n                                } else {\n                                    Icon(\n                                        painter = painterResource(id = R.drawable.package_import),\n                                        contentDescription = null,\n                                    )\n                                }\n                            }\n                        }\n                    },\n                ) {\n                    // 批量刷入 (Bulk Install)\n                    FloatingActionButtonMenuItem(\n                        onClick = {\n                            fabExpanded = false\n                            navigator.navigate(ApmBulkInstallScreenDestination())\n                        },\n                        icon = { Icon(Icons.AutoMirrored.Filled.PlaylistAdd, contentDescription = null, modifier = Modifier.size(18.dp)) },\n                        text = { Text(text = stringResource(R.string.apm_bulk_install_action), style = MaterialTheme.typography.bodyMedium) },\n                    )\n                    // 安装 (Install)\n                    FloatingActionButtonMenuItem(\n                        onClick = {\n                            fabExpanded = false\n                            scope.launch {\n                                if (checkStrongBiometric()) {\n                                    val intent = Intent(Intent.ACTION_GET_CONTENT)\n                                    intent.type = \"application/zip\"\n                                    intent.addCategory(Intent.CATEGORY_OPENABLE)\n                                    selectZipLauncher.launch(intent)\n                                }\n                            }\n                        },\n                        icon = { Icon(Icons.Outlined.Download, contentDescription = null, modifier = Modifier.size(18.dp)) },\n                        text = { Text(text = stringResource(R.string.apm_install), style = MaterialTheme.typography.bodyMedium) },\n                    )\n                }\n            }\n            if (isFloatingMode) {\n                Box(modifier = Modifier.offset(y = animatedOffset)) {\n                    fabContent()\n                }\n            } else {\n                fabContent()\n            }\n        }\n    }, snackbarHost = { SnackbarHost(snackBarHost) }) { innerPadding ->\n        when {\n            hasMagisk -> {\n                Box(\n                    modifier = Modifier\n                        .fillMaxSize()\n                        .padding(24.dp),\n                    contentAlignment = Alignment.Center\n                ) {\n                    Text(\n                        stringResource(R.string.apm_magisk_conflict),\n                        textAlign = TextAlign.Center,\n                    )\n                }\n            }\n\n            else -> {\n                ModuleList(\n                    navigator,\n                    viewModel = viewModel,\n                    modules = filteredModuleList,\n                    showMoreModuleInfo = showMoreModuleInfo,\n                    foldSystemModule = foldSystemModule,\n                    simpleListBottomBar = simpleListBottomBar,\n                    splicedCardGroup = splicedCardGroup,\n                    checkStrongBiometric = ::checkStrongBiometric,\n                    modifier = Modifier\n                        .padding(innerPadding)\n                        .fillMaxSize(),\n                    state = moduleListState,\n                    onInstallModule = {\n                        navigator.navigate(InstallScreenDestination(it, MODULE_TYPE.APM))\n                    },\n                    onClickModule = { id, name, hasWebUi ->\n                        if (hasWebUi) {\n                            webUILauncher.launch(\n                                Intent(\n                                    context, WebUIActivity::class.java\n                                ).setData(\"apatch://webui/$id\".toUri()).putExtra(\"id\", id)\n                                    .putExtra(\"name\", name)\n                            )\n                        }\n                    },\n                    snackBarHost = snackBarHost,\n                    context = context\n                )\n            }\n        }\n    }\n\n    // First Use Dialog\n    if (showFirstTimeDialog) {\n        BasicAlertDialog(\n            onDismissRequest = {\n                if (dontShowAgain) {\n                    prefs.edit().putBoolean(\"apm_first_use_shown\", true).apply()\n                }\n                showFirstTimeDialog = false\n            },\n            properties = DialogProperties(\n                dismissOnClickOutside = false,\n                dismissOnBackPress = false\n            )\n        ) {\n            Surface(\n                modifier = Modifier\n                    .width(350.dp)\n                    .padding(16.dp),\n                shape = RoundedCornerShape(20.dp),\n                tonalElevation = AlertDialogDefaults.TonalElevation,\n                color = AlertDialogDefaults.containerColor,\n            ) {\n                Column(modifier = Modifier.padding(16.dp)) {\n                    Text(\n                        text = stringResource(R.string.apm_first_use_title),\n                        style = MaterialTheme.typography.titleMedium,\n                        modifier = Modifier.padding(bottom = 16.dp)\n                    )\n                    \n                    Text(\n                        text = stringResource(R.string.apm_first_use_text),\n                        style = MaterialTheme.typography.bodyMedium,\n                        color = MaterialTheme.colorScheme.onSurfaceVariant\n                    )\n                    \n                    Row(\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .padding(top = 16.dp),\n                        verticalAlignment = Alignment.CenterVertically\n                    ) {\n                        Checkbox(\n                            checked = dontShowAgain,\n                            onCheckedChange = { dontShowAgain = it }\n                        )\n                        Text(\n                            text = stringResource(R.string.kpm_autoload_do_not_show_again),\n                            style = MaterialTheme.typography.bodySmall,\n                            modifier = Modifier.padding(start = 8.dp)\n                        )\n                    }\n                    \n                    Row(\n                        modifier = Modifier.fillMaxWidth(),\n                        horizontalArrangement = Arrangement.End\n                    ) {\n                        Button(onClick = {\n                            if (dontShowAgain) {\n                                prefs.edit().putBoolean(\"apm_first_use_shown\", true).apply()\n                            }\n                            showFirstTimeDialog = false\n                        }) {\n                            Text(stringResource(R.string.kpm_autoload_first_time_confirm))\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun ModuleList(\n    navigator: DestinationsNavigator,\n    viewModel: APModuleViewModel,\n    modules: List<APModuleViewModel.ModuleInfo>,\n    showMoreModuleInfo: Boolean,\n    foldSystemModule: Boolean,\n    simpleListBottomBar: Boolean,\n    splicedCardGroup: Boolean,\n    checkStrongBiometric: suspend () -> Boolean,\n    modifier: Modifier = Modifier,\n    state: LazyListState,\n    onInstallModule: (Uri) -> Unit,\n    onClickModule: (id: String, name: String, hasWebUi: Boolean) -> Unit,\n    snackBarHost: SnackbarHostState,\n    context: Context\n) {\n    var expandedModuleId by rememberSaveable { mutableStateOf<String?>(null) }\n\n    // Warning Banner State\n    val prefs = remember { APApplication.sharedPreferences }\n    var showMountWarning by remember {\n        mutableStateOf(!prefs.getBoolean(\"apm_mount_warning_shown\", false))\n    }\n    val failedEnable = stringResource(R.string.apm_failed_to_enable)\n    val failedDisable = stringResource(R.string.apm_failed_to_disable)\n    val failedUninstall = stringResource(R.string.apm_uninstall_failed)\n    val successUninstall = stringResource(R.string.apm_uninstall_success)\n    val reboot = stringResource(id = R.string.reboot)\n    val rebootToApply = stringResource(id = R.string.apm_reboot_to_apply)\n    val moduleStr = stringResource(id = R.string.apm)\n    val uninstall = stringResource(id = R.string.apm_remove)\n    val cancel = stringResource(id = android.R.string.cancel)\n    val moduleUninstallConfirm = stringResource(id = R.string.apm_uninstall_confirm)\n    val updateText = stringResource(R.string.apm_update)\n    val changelogText = stringResource(R.string.apm_changelog)\n    val downloadingText = stringResource(R.string.apm_downloading)\n    val startDownloadingText = stringResource(R.string.apm_start_downloading)\n\n    // Enable Module Shortcut Add\n    var enableModuleShortcutAdd by remember {\n        mutableStateOf(prefs.getBoolean(\"enable_module_shortcut_add\", true))\n    }\n\n    DisposableEffect(Unit) {\n        val listener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->\n            if (key == \"enable_module_shortcut_add\") {\n                enableModuleShortcutAdd = sharedPreferences.getBoolean(\"enable_module_shortcut_add\", true)\n            }\n        }\n        prefs.registerOnSharedPreferenceChangeListener(listener)\n        onDispose {\n            prefs.unregisterOnSharedPreferenceChangeListener(listener)\n        }\n    }\n\n    val loadingDialog = rememberLoadingDialog()\n    val confirmDialog = rememberConfirmDialog()\n\n    suspend fun onModuleUpdate(\n        module: APModuleViewModel.ModuleInfo,\n        changelogUrl: String,\n        downloadUrl: String,\n        fileName: String\n    ) {\n        val changelog = loadingDialog.withLoading {\n            withContext(Dispatchers.IO) {\n                if (Patterns.WEB_URL.matcher(changelogUrl).matches()) {\n                    apApp.okhttpClient.newCall(\n                        okhttp3.Request.Builder().url(changelogUrl).build()\n                    ).execute().body!!.string()\n                } else {\n                    changelogUrl\n                }\n            }\n        }\n\n\n        if (changelog.isNotEmpty()) {\n            // changelog is not empty, show it and wait for confirm\n            val confirmResult = confirmDialog.awaitConfirm(\n                changelogText,\n                content = changelog,\n                markdown = true,\n                confirm = updateText,\n            )\n\n            if (confirmResult != ConfirmResult.Confirmed) {\n                return\n            }\n        }\n\n        withContext(Dispatchers.Main) {\n            showToast(context, startDownloadingText.format(module.name))\n        }\n\n        val downloading = downloadingText.format(module.name)\n        withContext(Dispatchers.IO) {\n            download(\n                context,\n                downloadUrl,\n                fileName,\n                downloading,\n                onDownloaded = onInstallModule,\n                onDownloading = {\n                    launch(Dispatchers.Main) {\n                        showToast(context, downloading)\n                    }\n                })\n        }\n    }\n\n    suspend fun onModuleUninstall(module: APModuleViewModel.ModuleInfo) {\n        if (!checkStrongBiometric()) return\n        val confirmResult = confirmDialog.awaitConfirm(\n            moduleStr,\n            content = moduleUninstallConfirm.format(module.name),\n            confirm = uninstall,\n            dismiss = cancel\n        )\n        if (confirmResult != ConfirmResult.Confirmed) {\n            return\n        }\n\n        val success = loadingDialog.withLoading {\n            withContext(Dispatchers.IO) {\n                uninstallModule(module.id)\n            }\n        }\n\n        if (success) {\n            viewModel.fetchModuleList()\n        }\n        val message = if (success) {\n            successUninstall.format(module.name)\n        } else {\n            failedUninstall.format(module.name)\n        }\n        snackBarHost.showSnackbar(\n            message = message, duration = SnackbarDuration.Short\n        )\n    }\n\n    suspend fun onModuleUndoUninstall(module: APModuleViewModel.ModuleInfo) {\n        if (!checkStrongBiometric()) return\n\n        val success = loadingDialog.withLoading {\n            withContext(Dispatchers.IO) {\n                undoUninstallModule(module.id)\n            }\n        }\n\n        if (success) {\n            viewModel.fetchModuleList()\n        }\n        val message = if (success) {\n            context.getString(R.string.apm_undo_uninstall_success).format(module.name)\n        } else {\n            context.getString(R.string.apm_undo_uninstall_failed).format(module.name)\n        }\n        snackBarHost.showSnackbar(\n            message = message, duration = SnackbarDuration.Short\n        )\n    }\n\n    val pullToRefreshState = rememberPullToRefreshState()\n    PullToRefreshBox(\n        modifier = modifier,\n        onRefresh = { viewModel.fetchModuleList() },\n        isRefreshing = viewModel.isRefreshing,\n        state = pullToRefreshState,\n        indicator = { PullToRefreshDefaults.LoadingIndicator(state = pullToRefreshState, isRefreshing = viewModel.isRefreshing, modifier = Modifier.align(Alignment.TopCenter)) }\n    ) {\n        val configuration = LocalConfiguration.current\n        val isWideScreen = configuration.screenWidthDp >= 600\n\n        if (isWideScreen) {\n            TwoColumnGrid(\n                modifier = Modifier.fillMaxSize(),\n                items = if (modules.isEmpty()) emptyList() else modules,\n                key = { module -> module.id },\n                verticalSpacing = 16.dp,\n                horizontalSpacing = 16.dp,\n                contentPadding = run {\n                    val isFloating = LocalIsFloatingNavMode.current\n                    remember(isFloating) {\n                        PaddingValues(\n                            start = 16.dp,\n                            top = 16.dp,\n                            end = 16.dp,\n                            bottom = if (isFloating) 16.dp + 16.dp + 56.dp else 16.dp\n                        )\n                    }\n                },\n                beforeItems = {\n                    if (showMountWarning) {\n                        AnimatedVisibility(\n                            visible = true,\n                            enter = fadeIn() + expandVertically(),\n                            exit = fadeOut() + shrinkVertically()\n                        ) {\n                            WarningCard(\n                                modifier = Modifier\n                                    .fillMaxWidth()\n                                    .padding(bottom = 16.dp),\n                                message = stringResource(R.string.apm_mount_warning_message),\n                                onClose = {\n                                    prefs.edit()\n                                        .putBoolean(\"apm_mount_warning_shown\", true)\n                                        .apply()\n                                    showMountWarning = false\n                                }\n                            )\n                        }\n                    }\n                    if (modules.isEmpty()) {\n                        Box(\n                            modifier = Modifier\n                                .fillMaxWidth()\n                                .defaultMinSize(minHeight = 300.dp),\n                            contentAlignment = Alignment.Center\n                        ) {\n                            Text(\n                                stringResource(R.string.apm_empty), textAlign = TextAlign.Center\n                            )\n                        }\n                    }\n                },\n                itemContent = { module ->\n                    var isChecked by rememberSaveable(module) { mutableStateOf(module.enabled) }\n                    val scope = rememberCoroutineScope()\n                    val updatedModule = viewModel.getCachedUpdate(module.id)\n\n                    ModuleItem(\n                        navigator,\n                        module,\n                        isChecked,\n                        updatedModule.first,\n                        showMoreModuleInfo = showMoreModuleInfo,\n                        foldSystemModule = foldSystemModule,\n                        simpleListBottomBar = simpleListBottomBar,\n                        enableModuleShortcutAdd = enableModuleShortcutAdd,\n                        expanded = expandedModuleId == module.id,\n                        onExpandToggle = {\n                            expandedModuleId = if (expandedModuleId == module.id) null else module.id\n                        },\n                        onUninstall = {\n                            scope.launch { onModuleUninstall(module) }\n                        },\n                        onUndoUninstall = {\n                            scope.launch { onModuleUndoUninstall(module) }\n                        },\n                        onCheckChanged = { checked ->\n                            scope.launch {\n                                if (!checkStrongBiometric()) return@launch\n                                val success = loadingDialog.withLoading {\n                                    withContext(Dispatchers.IO) {\n                                        toggleModule(module.id, !isChecked)\n                                    }\n                                }\n                                if (success) {\n                                    isChecked = checked\n                                    viewModel.fetchModuleList()\n\n                                    val result = snackBarHost.showSnackbar(\n                                        message = rebootToApply,\n                                        actionLabel = reboot,\n                                        duration = SnackbarDuration.Long\n                                    )\n                                    if (result == SnackbarResult.ActionPerformed) {\n                                        reboot()\n                                    }\n                                } else {\n                                    val message = if (isChecked) failedDisable else failedEnable\n                                    snackBarHost.showSnackbar(message.format(module.name))\n                                }\n                            }\n                        },\n                        onUpdate = {\n                            scope.launch {\n                                onModuleUpdate(\n                                    module,\n                                    updatedModule.third,\n                                    updatedModule.first,\n                                    \"${module.name}-${updatedModule.second}.zip\"\n                                )\n                            }\n                        },\n                        onClick = { clickedModule ->\n                            onClickModule(clickedModule.id, clickedModule.name, clickedModule.hasWebUi)\n                        })\n                }\n            )\n        } else {\n            LazyColumn(\n                modifier = Modifier.fillMaxSize(),\n                state = state,\n                contentPadding = run {\n                    val isFloating = LocalIsFloatingNavMode.current\n                    remember(isFloating) {\n                        PaddingValues(\n                            start = 0.dp,\n                            top = 16.dp,\n                            end = 0.dp,\n                            bottom = if (isFloating) 16.dp + 16.dp + 56.dp else 16.dp\n                        )\n                    }\n                },\n            ) {\n                // Warning Banner\n                if (showMountWarning) {\n                    item {\n                        AnimatedVisibility(\n                            visible = true,\n                            enter = fadeIn() + expandVertically(),\n                            exit = fadeOut() + shrinkVertically()\n                        ) {\n                            WarningCard(\n                                modifier = Modifier.padding(horizontal = 16.dp),\n                                message = stringResource(R.string.apm_mount_warning_message),\n                                onClose = {\n                                    prefs.edit()\n                                        .putBoolean(\"apm_mount_warning_shown\", true)\n                                        .apply()\n                                    showMountWarning = false\n                                }\n                            )\n                        }\n                    }\n                }\n\n                when {\n                    modules.isEmpty() -> {\n                        item {\n                            Box(\n                                modifier = Modifier\n                                    .fillMaxWidth()\n                                    .fillParentMaxHeight(),\n                                contentAlignment = Alignment.Center\n                            ) {\n                                Text(\n                                    stringResource(R.string.apm_empty), textAlign = TextAlign.Center\n                                )\n                            }\n                        }\n                    }\n\n                    else -> {\n                        if (splicedCardGroup) {\n                            item { Spacer(Modifier.height(8.dp)) }\n                            splicedLazyColumnGroup(\n                                items = modules,\n                                key = { _, module -> module.id },\n                                contentType = { _, _ -> \"ModuleItem\" },\n                            ) { _, module ->\n                                var isChecked by rememberSaveable(module) { mutableStateOf(module.enabled) }\n                                val scope = rememberCoroutineScope()\n                                val updatedModule = viewModel.getCachedUpdate(module.id)\n\n                                ModuleItem(\n                                    navigator,\n                                    module,\n                                    isChecked,\n                                    updatedModule.first,\n                                    showMoreModuleInfo = showMoreModuleInfo,\n                                    foldSystemModule = foldSystemModule,\n                                    simpleListBottomBar = simpleListBottomBar,\n                                    enableModuleShortcutAdd = enableModuleShortcutAdd,\n                                    expanded = expandedModuleId == module.id,\n                                    onExpandToggle = {\n                                        expandedModuleId = if (expandedModuleId == module.id) null else module.id\n                                    },\n                                    onUninstall = {\n                                        scope.launch { onModuleUninstall(module) }\n                                    },\n                                    onUndoUninstall = {\n                                        scope.launch { onModuleUndoUninstall(module) }\n                                    },\n                                    onCheckChanged = { checked ->\n                                        scope.launch {\n                                            if (!checkStrongBiometric()) return@launch\n                                            val success = loadingDialog.withLoading {\n                                                withContext(Dispatchers.IO) {\n                                                    toggleModule(module.id, !isChecked)\n                                                }\n                                            }\n                                            if (success) {\n                                                isChecked = checked\n                                                viewModel.fetchModuleList()\n\n                                                val result = snackBarHost.showSnackbar(\n                                                    message = rebootToApply,\n                                                    actionLabel = reboot,\n                                                    duration = SnackbarDuration.Long\n                                                )\n                                                if (result == SnackbarResult.ActionPerformed) {\n                                                    reboot()\n                                                }\n                                            } else {\n                                                val message = if (isChecked) failedDisable else failedEnable\n                                                snackBarHost.showSnackbar(message.format(module.name))\n                                            }\n                                        }\n                                    },\n                                    onUpdate = {\n                                        scope.launch {\n                                            onModuleUpdate(\n                                                module,\n                                                updatedModule.third,\n                                                updatedModule.first,\n                                                \"${module.name}-${updatedModule.second}.zip\"\n                                            )\n                                        }\n                                    },\n                                    onClick = { clickedModule ->\n                                        onClickModule(clickedModule.id, clickedModule.name, clickedModule.hasWebUi)\n                                    })\n                            }\n                            item { Spacer(Modifier.height(if (LocalIsFloatingNavMode.current) 88.dp else 8.dp)) }\n                        } else {\n                            item { Spacer(Modifier.height(8.dp)) }\n                            itemsIndexed(modules, key = { _, module -> module.id }) { _, module ->\n                                Box(modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) {\n                                var isChecked by rememberSaveable(module) { mutableStateOf(module.enabled) }\n                                val scope = rememberCoroutineScope()\n                                val updatedModule = viewModel.getCachedUpdate(module.id)\n\n                                ModuleItem(\n                                    navigator,\n                                    module,\n                                    isChecked,\n                                    updatedModule.first,\n                                    showMoreModuleInfo = showMoreModuleInfo,\n                                    foldSystemModule = foldSystemModule,\n                                    simpleListBottomBar = simpleListBottomBar,\n                                    enableModuleShortcutAdd = enableModuleShortcutAdd,\n                                    expanded = expandedModuleId == module.id,\n                                    onExpandToggle = {\n                                        expandedModuleId = if (expandedModuleId == module.id) null else module.id\n                                    },\n                                    onUninstall = {\n                                        scope.launch { onModuleUninstall(module) }\n                                    },\n                                    onUndoUninstall = {\n                                        scope.launch { onModuleUndoUninstall(module) }\n                                    },\n                                    onCheckChanged = { checked ->\n                                        scope.launch {\n                                            if (!checkStrongBiometric()) return@launch\n                                            val success = loadingDialog.withLoading {\n                                                withContext(Dispatchers.IO) {\n                                                    toggleModule(module.id, !isChecked)\n                                                }\n                                            }\n                                            if (success) {\n                                                isChecked = checked\n                                                viewModel.fetchModuleList()\n\n                                                val result = snackBarHost.showSnackbar(\n                                                    message = rebootToApply,\n                                                    actionLabel = reboot,\n                                                    duration = SnackbarDuration.Long\n                                                )\n                                                if (result == SnackbarResult.ActionPerformed) {\n                                                    reboot()\n                                                }\n                                            } else {\n                                                val message = if (isChecked) failedDisable else failedEnable\n                                                snackBarHost.showSnackbar(message.format(module.name))\n                                            }\n                                        }\n                                    },\n                                    onUpdate = {\n                                        scope.launch {\n                                            onModuleUpdate(\n                                                module,\n                                                updatedModule.third,\n                                                updatedModule.first,\n                                                \"${module.name}-${updatedModule.second}.zip\"\n                                            )\n                                        }\n                                    },\n                                    onClick = { clickedModule ->\n                                        onClickModule(clickedModule.id, clickedModule.name, clickedModule.hasWebUi)\n                                    })\n\n                                }\n                            }\n                            item { Spacer(Modifier.height(if (LocalIsFloatingNavMode.current) 88.dp else 8.dp)) }\n                        }\n                    }\n                }\n            }\n        }\n\n        DownloadListener(context, onInstallModule)\n    }\n\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun TopBar(\n    navigator: DestinationsNavigator,\n    viewModel: APModuleViewModel,\n    snackBarHost: SnackbarHostState,\n    searchQuery: String,\n    checkStrongBiometric: suspend () -> Boolean,\n    onSearchQueryChange: (String) -> Unit,\n    onToggleModuleBanner: () -> Unit\n) {\n    val confirmDialog = rememberConfirmDialog()\n    val scope = rememberCoroutineScope()\n    val disableAllTitle = stringResource(R.string.apm_disable_all_title)\n    val disableAllConfirm = stringResource(R.string.apm_disable_all_confirm)\n    val confirm = stringResource(android.R.string.ok)\n    val cancel = stringResource(android.R.string.cancel)\n    val context = LocalContext.current\n\n    var showMenu by remember { mutableStateOf(false) }\n\n    val backupLauncher = rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument(\"application/gzip\")) { uri ->\n        uri?.let {\n            scope.launch {\n                ModuleBackupUtils.backupModules(context, snackBarHost, it)\n            }\n        }\n    }\n    val restoreLauncher = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->\n        uri?.let {\n            scope.launch {\n                ModuleBackupUtils.restoreModules(context, snackBarHost, it)\n                viewModel.fetchModuleList()\n            }\n        }\n    }\n\n    SearchAppBar(\n        title = { Text(stringResource(R.string.apm)) },\n        searchText = searchQuery,\n        onSearchTextChange = onSearchQueryChange,\n        onClearClick = { onSearchQueryChange(\"\") },\n        leadingActions = {\n            androidx.compose.material3.IconButton(onClick = {\n                navigator.navigate(OnlineModuleScreenDestination)\n            }) {\n                Icon(\n                    imageVector = Icons.Filled.Download,\n                    contentDescription = \"Online Modules\"\n                )\n            }\n        },\n        dropdownContent = {\n            androidx.compose.material3.IconButton(onClick = { showMenu = true }) {\n                Icon(Icons.Filled.MoreVert, contentDescription = \"More\")\n                WallpaperAwareDropdownMenu(\n                    expanded = showMenu,\n                    onDismissRequest = { showMenu = false }\n                ) {\n                    WallpaperAwareDropdownMenuItem(\n                        text = { Text(stringResource(R.string.apm_disable_all_title)) },\n                        onClick = {\n                            showMenu = false\n                            scope.launch {\n                                if (!checkStrongBiometric()) return@launch\n                                val result = confirmDialog.awaitConfirm(\n                                    title = disableAllTitle,\n                                    content = disableAllConfirm,\n                                    confirm = confirm,\n                                    dismiss = cancel\n                                )\n                                if (result == ConfirmResult.Confirmed) {\n                                    viewModel.disableAllModules()\n                                }\n                            }\n                        }\n                    )\n                    WallpaperAwareDropdownMenuItem(\n                        text = { Text(stringResource(R.string.apm_backup_title)) },\n                        onClick = {\n                            showMenu = false\n                            val timeStamp = SimpleDateFormat(\"yyyyMMdd_HHmmss\", Locale.getDefault()).format(Date())\n                            backupLauncher.launch(\"FolkPatch_Modules_Backup_$timeStamp.tar.gz\")\n                        }\n                    )\n                    WallpaperAwareDropdownMenuItem(\n                        text = { Text(stringResource(R.string.apm_restore_title)) },\n                        onClick = {\n                            showMenu = false\n                            restoreLauncher.launch(arrayOf(\"application/gzip\", \"application/x-gzip\", \"application/x-tar\"))\n                        }\n                    )\n                    WallpaperAwareDropdownMenuItem(\n                        text = { Text(stringResource(R.string.apm_copy_list_title)) },\n                        onClick = {\n                            showMenu = false\n                            val clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager\n                            val moduleNames = viewModel.moduleList.joinToString(\"\\n\") { it.name }\n                            val clip = ClipData.newPlainText(\"Module List\", moduleNames)\n                            clipboardManager.setPrimaryClip(clip)\n                            scope.launch {\n                                snackBarHost.showSnackbar(\n                                    message = context.getString(R.string.apm_copy_list_success),\n                                    duration = SnackbarDuration.Short\n                                )\n                            }\n                        }\n                    )\n                }\n            }\n        }\n    )\n}\n\n@Composable\nprivate fun ModuleLabel(\n    text: String,\n    containerColor: Color,\n    contentColor: Color\n) {\n    Surface(\n        color = containerColor,\n        contentColor = contentColor,\n        shape = RoundedCornerShape(8.dp),\n    ) {\n        Text(\n            text = text,\n            style = MaterialTheme.typography.labelSmall,\n            modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp),\n            maxLines = 1,\n            overflow = TextOverflow.Ellipsis\n        )\n    }\n}\n\nprivate const val FOLK_BANNER_FILE_NAME = \"FolkBanner\"\nprivate const val FOLK_BANNER_DIR_NAME = \"folk_banners\"\n\nprivate fun sanitizeBannerKey(raw: String): String {\n    return raw.replace(Regex(\"[^a-zA-Z0-9._-]\"), \"_\")\n}\n\nprivate fun getFolkBannerFile(context: Context, moduleId: String): File {\n    val dir = File(context.filesDir, FOLK_BANNER_DIR_NAME)\n    if (!dir.exists()) {\n        dir.mkdirs()\n    }\n    return File(dir, sanitizeBannerKey(moduleId))\n}\n\nprivate fun resolveModuleDir(rootShell: Shell, moduleId: String): String {\n    val suFile = { path: String ->\n        SuFile(path).apply { shell = rootShell }\n    }\n    val defaultDir = \"/data/adb/modules/$moduleId\"\n    return runCatching {\n        val direct = suFile(defaultDir)\n        if (direct.exists()) {\n            direct.path\n        } else {\n            val modulesRoot = suFile(\"/data/adb/modules\")\n            val dirs = modulesRoot.listFiles() ?: return@runCatching defaultDir\n            for (dir in dirs) {\n                if (!dir.isDirectory) continue\n                val propFile = suFile(\"${dir.path}/module.prop\")\n                if (!propFile.exists()) continue\n                val props = Properties()\n                props.load(propFile.newInputStream())\n                val id = props.getProperty(\"id\")?.trim()\n                if (id == moduleId) {\n                    return@runCatching dir.path\n                }\n            }\n            defaultDir\n        }\n    }.getOrDefault(defaultDir)\n}\n\nprivate fun readFolkBanner(context: Context, moduleId: String): ByteArray? {\n    return runCatching {\n        val file = getFolkBannerFile(context, moduleId)\n        if (file.exists()) {\n            file.readBytes().takeIf { it.isNotEmpty() }\n        } else {\n            null\n        }\n    }.getOrNull()\n}\n\nprivate fun readModulePropBanner(rootShell: Shell, resolvedDir: String): String? {\n    return runCatching {\n        val propFile = SuFile(\"$resolvedDir/module.prop\").apply { shell = rootShell }\n        if (propFile.exists()) {\n            val props = Properties()\n            props.load(propFile.newInputStream())\n            props.getProperty(\"banner\")?.trim()?.takeIf { it.isNotEmpty() }\n        } else {\n            null\n        }\n    }.getOrNull()\n}\n\nprivate fun writeFolkBanner(\n    context: Context,\n    moduleId: String,\n    uri: Uri\n): ByteArray? {\n    val data = SafeUriResolver.openInputStream(context, uri)?.use { it.readBytes() } ?: return null\n    val file = getFolkBannerFile(context, moduleId)\n    file.outputStream().use { it.write(data) }\n    return data\n}\n\nprivate fun clearFolkBanner(context: Context, moduleId: String): Boolean {\n    val file = getFolkBannerFile(context, moduleId)\n    return !file.exists() || file.delete()\n}\n\nprivate fun clearLegacyFolkBanner(rootShell: Shell, resolvedDir: String): Boolean {\n    val file = SuFile(\"$resolvedDir/$FOLK_BANNER_FILE_NAME\").apply { shell = rootShell }\n    return !file.exists() || file.delete()\n}\n\n@Composable\nprivate fun ModuleItem(\n    navigator: DestinationsNavigator,\n    module: APModuleViewModel.ModuleInfo,\n    isChecked: Boolean,\n    updateUrl: String,\n    showMoreModuleInfo: Boolean,\n    foldSystemModule: Boolean,\n    simpleListBottomBar: Boolean,\n    enableModuleShortcutAdd: Boolean,\n    expanded: Boolean,\n    onExpandToggle: () -> Unit,\n    onUninstall: (APModuleViewModel.ModuleInfo) -> Unit,\n    onUndoUninstall: (APModuleViewModel.ModuleInfo) -> Unit,\n    onCheckChanged: (Boolean) -> Unit,\n    onUpdate: (APModuleViewModel.ModuleInfo) -> Unit,\n    onClick: (APModuleViewModel.ModuleInfo) -> Unit,\n    modifier: Modifier = Modifier,\n    alpha: Float = 1f,\n) {\n    val context = LocalContext.current\n    val viewModel = viewModel<APModuleViewModel>()\n    val snackBarHost = LocalSnackbarHost.current\n    val scope = rememberCoroutineScope()\n    val loadingDialog = rememberLoadingDialog()\n    val shortcutAdd = stringResource(id = R.string.module_shortcut_add)\n    val folkBannerTitle = stringResource(R.string.apm_folk_banner_title)\n    val folkBannerSelect = stringResource(R.string.apm_folk_banner_select)\n    val folkBannerClear = stringResource(R.string.apm_folk_banner_clear)\n    val folkBannerSaved = stringResource(R.string.apm_folk_banner_saved)\n    val folkBannerCleared = stringResource(R.string.apm_folk_banner_cleared)\n    val folkBannerFailed = stringResource(R.string.apm_folk_banner_failed)\n    \n    var showFolkBannerDialog by remember { mutableStateOf(false) }\n    var hasFolkBanner by remember { mutableStateOf(false) }\n    var bannerReloadKey by rememberSaveable(module.id) { mutableStateOf(0) }\n    \n    LaunchedEffect(showFolkBannerDialog) {\n        if (showFolkBannerDialog) {\n            hasFolkBanner = withContext(Dispatchers.IO) {\n                readFolkBanner(context, module.id) != null\n            }\n        }\n    }\n    \n    val pickFolkBannerLauncher = rememberLauncherForActivityResult(\n        ActivityResultContracts.GetContent()\n    ) { uri: Uri? ->\n        uri?.let {\n            scope.launch {\n                loadingDialog.show()\n                val result = withContext(Dispatchers.IO) {\n                    runCatching {\n                        val data = writeFolkBanner(context, module.id, it)\n                        if (data != null) {\n                            runCatching {\n                                val rootShell = getRootShell(true)\n                                val resolvedDir = resolveModuleDir(rootShell, module.id)\n                                clearLegacyFolkBanner(rootShell, resolvedDir)\n                            }\n                        }\n                        data\n                    }.getOrNull()\n                }\n                loadingDialog.hide()\n                if (result != null) {\n                    viewModel.putBannerInfo(module.id, APModuleViewModel.BannerInfo(result, null))\n                    bannerReloadKey++\n                    snackBarHost.showSnackbar(folkBannerSaved.format(module.name))\n                } else {\n                    snackBarHost.showSnackbar(folkBannerFailed.format(module.name))\n                }\n            }\n        }\n    }\n\n    val isWallpaperMode = BackgroundConfig.isCustomBackgroundEnabled\n    val opacity = if (isWallpaperMode) {\n        BackgroundConfig.customBackgroundOpacity.coerceAtLeast(0.35f)\n    } else {\n        1f\n    }\n\n    val bannerImageAlpha = if (BackgroundConfig.isBannerCustomOpacityEnabled) {\n        BackgroundConfig.bannerCustomOpacity\n    } else {\n        if (isWallpaperMode) {\n            (0.35f + (opacity - 0.2f) * 0.5f).coerceIn(0.25f, 0.6f)\n        } else {\n            0.18f\n        }\n    }\n    \n    var showShortcutDialog by remember { mutableStateOf(false) }\n    var shortcutName by rememberSaveable(module.id) { mutableStateOf(module.name) }\n    var shortcutIconUri by remember { mutableStateOf<String?>(null) }\n    var shortcutType by rememberSaveable(module.id) { mutableStateOf(if (module.hasWebUi) \"webui\" else \"action\") }\n    val appIcon = remember(context) { context.packageManager.getApplicationIcon(context.packageName) }\n    val pickShortcutIconLauncher = rememberLauncherForActivityResult(\n        ActivityResultContracts.GetContent()\n    ) { uri: Uri? ->\n        shortcutIconUri = uri?.toString()\n    }\n\n    fun toSuIconUri(path: String?): String? {\n        val trimmed = path?.trim().orEmpty()\n        if (trimmed.isEmpty()) return null\n        return if (trimmed.startsWith(\"su://\", true)) trimmed else \"su://$trimmed\"\n    }\n\n    val moduleDefaultIconPath = remember(\n        module.id,\n        shortcutType,\n        module.webuiIcon,\n        module.actionIcon\n    ) {\n        val preferred = if (shortcutType == \"webui\") module.webuiIcon else module.actionIcon\n        preferred?.takeIf { it.isNotBlank() }\n            ?: module.webuiIcon?.takeIf { it.isNotBlank() }\n            ?: module.actionIcon?.takeIf { it.isNotBlank() }\n    }\n    val moduleDefaultIconUri = remember(moduleDefaultIconPath) { toSuIconUri(moduleDefaultIconPath) }\n    val effectiveShortcutIconUri = shortcutIconUri ?: moduleDefaultIconUri\n\n    val shortcutPreviewBitmap by produceState<Bitmap?>(initialValue = null, key1 = if (showShortcutDialog) effectiveShortcutIconUri else null) {\n        if (!showShortcutDialog || effectiveShortcutIconUri.isNullOrBlank()) {\n            value = null\n        } else {\n            value = withContext(Dispatchers.IO) {\n                ModuleShortcut.loadShortcutBitmap(context, effectiveShortcutIconUri)\n            }\n        }\n    }\n    \n    val sizeStr = if (showMoreModuleInfo) viewModel.getModuleSize(module.id) else \"0 KB\"\n\n    val bannerInfo by produceState<APModuleViewModel.BannerInfo?>(\n        initialValue = viewModel.getBannerInfo(module.id),\n        module.id,\n        BackgroundConfig.isBannerEnabled,\n        BackgroundConfig.isFolkBannerEnabled,\n        BackgroundConfig.isBannerApiModeEnabled,\n        BackgroundConfig.bannerApiSource,\n        bannerReloadKey\n    ) {\n        if (!BackgroundConfig.isBannerEnabled) {\n            value = null\n            return@produceState\n        }\n\n        viewModel.bannerSemaphore.withPermit {\n            val effectiveApiSource = BackgroundConfig.getEffectiveBannerApiSource()\n\n        if (BackgroundConfig.isBannerApiModeEnabled && effectiveApiSource.isNotBlank()) {\n            val apiBanner = withContext(Dispatchers.IO) {\n                BannerApiService.getModuleBanner(\n                    context = context,\n                    moduleId = module.id,\n                    source = effectiveApiSource\n                )\n            }\n            if (apiBanner != null) {\n                viewModel.putBannerInfo(module.id, APModuleViewModel.BannerInfo(apiBanner, null))\n                value = APModuleViewModel.BannerInfo(apiBanner, null)\n                return@produceState\n            }\n        }\n\n        val cached = viewModel.getBannerInfo(module.id)\n        if (cached != null && (cached.bytes != null || cached.url != null)) {\n            value = cached\n            return@produceState\n        }\n\n        val loaded = withContext(Dispatchers.IO) {\n            try {\n                val folkBanner = if (BackgroundConfig.isFolkBannerEnabled) readFolkBanner(context, module.id) else null\n                if (folkBanner != null) {\n                    return@withContext APModuleViewModel.BannerInfo(folkBanner, null)\n                }\n                val rootShell = getRootShell(true)\n                val suFile = { path: String ->\n                    SuFile(path).apply { shell = rootShell }\n                }\n                val resolvedDir = resolveModuleDir(rootShell, module.id)\n                val propBanner = readModulePropBanner(rootShell, resolvedDir)\n\n                if (!propBanner.isNullOrEmpty() && propBanner.startsWith(\"http\", true)) {\n                    return@withContext APModuleViewModel.BannerInfo(null, propBanner)\n                }\n\n                val candidates = buildList {\n                    if (!propBanner.isNullOrEmpty()) {\n                        add(propBanner)\n                    }\n                    addAll(listOf(\"banner\", \"banner.png\", \"banner.jpg\", \"banner.jpeg\", \"banner.webp\"))\n                }.distinct()\n\n                for (name in candidates) {\n                    val file = if (name.startsWith(\"/\")) {\n                        suFile(name)\n                    } else {\n                        suFile(\"$resolvedDir/$name\")\n                    }\n                    if (file.exists()) {\n                        return@withContext APModuleViewModel.BannerInfo(file.newInputStream().use { it.readBytes() }, null)\n                    }\n                }\n                null\n            } catch (e: Exception) {\n                null\n            }\n        }\n\n        if (loaded != null) {\n            viewModel.putBannerInfo(module.id, loaded)\n            value = loaded\n        } else if (cached != null) {\n            value = cached\n        } else {\n            viewModel.putBannerInfo(module.id, APModuleViewModel.BannerInfo(null, null))\n            value = APModuleViewModel.BannerInfo(null, null)\n        }\n        }\n    }\n\n    val insideSplicedGroup = LocalInsideSplicedGroup.current\n\n    val cardColor = if (isWallpaperMode) {\n        MaterialTheme.colorScheme.surface.copy(alpha = opacity)\n    } else {\n        MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.2f)\n    }\n\n    val cardShape = RoundedCornerShape(20.dp)\n\n    val clickModifier = Modifier\n        .fillMaxWidth()\n        .animateContentSize()\n        .combinedClickable(\n            onClick = {\n                if (foldSystemModule) {\n                    onExpandToggle()\n                } else {\n                    onClick(module)\n                }\n            },\n            onLongClick = {\n                if (BackgroundConfig.isBannerEnabled && BackgroundConfig.isFolkBannerEnabled) {\n                    showFolkBannerDialog = true\n                }\n            }\n        )\n\n    val contentBlock: @Composable () -> Unit = {\n        Box(modifier = Modifier.fillMaxWidth()) {\n            val bannerUrl = bannerInfo?.url\n            val bannerData = bannerInfo?.bytes\n            val hasBannerUrl = !bannerUrl.isNullOrEmpty()\n            if (bannerData != null || hasBannerUrl) {\n                val isDark = isSystemInDarkTheme()\n                val colorScheme = MaterialTheme.colorScheme\n                val isDynamic = colorScheme.primary != colorScheme.secondary\n                val fadeColor = when {\n                    isDynamic -> colorScheme.surface\n                    isDark -> Color(0xFF222222)\n                    else -> Color.White\n                }\n\n                Box(\n                    modifier = Modifier.matchParentSize(),\n                    contentAlignment = Alignment.Center\n                ) {\n                    AsyncImage(\n                        model = if (hasBannerUrl) {\n                            bannerUrl\n                        } else {\n                            ImageRequest.Builder(context)\n                                .data(bannerData)\n                                .build()\n                         },\n                         contentDescription = null,\n                         modifier = Modifier.fillMaxSize(),\n                         contentScale = ContentScale.Crop,\n                         alpha = bannerImageAlpha\n                     )\n                    val gradientAlpha = if (isWallpaperMode) 0.5f else 0.8f\n                    Box(\n                        modifier = Modifier\n                            .fillMaxSize()\n                            .background(\n                                Brush.verticalGradient(\n                                    colors = listOf(\n                                        fadeColor.copy(alpha = 0.0f),\n                                        fadeColor.copy(alpha = gradientAlpha)\n                                    ),\n                                    startY = 0f,\n                                    endY = Float.POSITIVE_INFINITY\n                                )\n                            )\n                    )\n                }\n            }\n\n            Column(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .padding(16.dp)\n            ) {\n                Row(\n                    modifier = Modifier.fillMaxWidth(),\n                    verticalAlignment = Alignment.Top\n                ) {\n                    Column(modifier = Modifier.weight(1f)) {\n                        val hasAnyLabel = showMoreModuleInfo || module.remove || (updateUrl.isNotEmpty() && !module.update) || module.update\n                        if (hasAnyLabel) {\n                            Row(\n                                horizontalArrangement = Arrangement.spacedBy(6.dp),\n                                modifier = Modifier.padding(bottom = 8.dp)\n                            ) {\n                                val labelOpacity = (opacity + 0.1f).coerceAtMost(1f)\n                                if (showMoreModuleInfo) {\n                                    ModuleLabel(\n                                        text = sizeStr,\n                                        containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = labelOpacity),\n                                        contentColor = MaterialTheme.colorScheme.onSecondaryContainer\n                                    )\n                                    ModuleLabel(\n                                        text = module.id,\n                                        containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = labelOpacity),\n                                        contentColor = MaterialTheme.colorScheme.onPrimaryContainer\n                                    )\n                                }\n                                if (module.remove) {\n                                    ModuleLabel(\n                                        text = stringResource(R.string.apm_remove),\n                                        containerColor = MaterialTheme.colorScheme.errorContainer.copy(alpha = labelOpacity),\n                                        contentColor = MaterialTheme.colorScheme.onErrorContainer\n                                    )\n                                } else if (updateUrl.isNotEmpty() && !module.update) {\n                                    ModuleLabel(\n                                        text = stringResource(R.string.apm_update),\n                                        containerColor = MaterialTheme.colorScheme.tertiaryContainer.copy(alpha = labelOpacity),\n                                        contentColor = MaterialTheme.colorScheme.onTertiaryContainer\n                                    )\n                                } else if (module.update) {\n                                    ModuleLabel(\n                                        text = \"Updated\",\n                                        containerColor = MaterialTheme.colorScheme.tertiaryContainer.copy(alpha = labelOpacity),\n                                        contentColor = MaterialTheme.colorScheme.onTertiaryContainer\n                                    )\n                                }\n                                \n                                if (showMoreModuleInfo && module.hasWebUi && module.enabled && !module.remove) {\n                                    ModuleLabel(\n                                        text = \"WebUI\",\n                                        containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = labelOpacity),\n                                        contentColor = MaterialTheme.colorScheme.onPrimaryContainer\n                                    )\n                                }\n                                if (showMoreModuleInfo && module.hasActionScript && module.enabled && !module.remove) {\n                                    ModuleLabel(\n                                        text = \"Action\",\n                                        containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = labelOpacity),\n                                        contentColor = MaterialTheme.colorScheme.onSecondaryContainer\n                                    )\n                                }\n                                \n                                if (module.isMetamodule && !module.remove) {\n                                    ModuleLabel(\n                                        text = \"META\",\n                                        containerColor = MaterialTheme.colorScheme.errorContainer.copy(alpha = labelOpacity),\n                                        contentColor = MaterialTheme.colorScheme.onErrorContainer\n                                    )\n                                }\n                            }\n                        }\n\n                        Text(\n                            text = module.name,\n                            style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold),\n                            textDecoration = if (module.remove) TextDecoration.LineThrough else TextDecoration.None\n                        )\n\n                        Text(\n                            text = module.version,\n                            style = MaterialTheme.typography.bodySmall,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant,\n                            textDecoration = if (module.remove) TextDecoration.LineThrough else TextDecoration.None\n                        )\n\n                        Text(\n                            text = module.author,\n                            style = MaterialTheme.typography.bodySmall,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant,\n                            textDecoration = if (module.remove) TextDecoration.LineThrough else TextDecoration.None\n                        )\n                    }\n\n                    ExpressiveSwitch(\n                        enabled = !module.update,\n                        checked = isChecked,\n                        onCheckedChange = onCheckChanged\n                    )\n                }\n\n                Spacer(modifier = Modifier.height(12.dp))\n\n                Text(\n                    text = module.description,\n                    style = MaterialTheme.typography.bodySmall,\n                    color = MaterialTheme.colorScheme.onSurfaceVariant,\n                    maxLines = 4,\n                    overflow = TextOverflow.Ellipsis\n                )\n\n                Spacer(modifier = Modifier.height(16.dp))\n\n                AnimatedVisibility(\n                    visible = !foldSystemModule || expanded,\n                    enter = fadeIn() + expandVertically(),\n                    exit = shrinkVertically() + fadeOut()\n                ) {\n           \n                    val buttons = mutableListOf<ModuleButtonConfig>()\n                    \n                    if (module.hasWebUi && module.enabled && !module.remove) {\n                        buttons.add(ModuleButtonConfig(\n                            icon = Icons.AutoMirrored.Outlined.Wysiwyg,\n                            text = stringResource(R.string.apm_webui_open),\n                            contentDescription = stringResource(R.string.apm_webui_open),\n                            onClick = { onClick(module) }\n                        ))\n                    }\n                    \n                    if (module.hasActionScript && module.enabled && !module.remove) {\n                        buttons.add(ModuleButtonConfig(\n                            icon = Icons.Outlined.Terminal,\n                            text = stringResource(R.string.apm_action),\n                            contentDescription = stringResource(R.string.apm_action),\n                            onClick = {\n                                navigator.navigate(ExecuteAPMActionScreenDestination(module.id))\n                                viewModel.markNeedRefresh()\n                            }\n                        ))\n                    }\n\n                    val hasUpdateButton = updateUrl.isNotEmpty() && !module.remove && !module.update\n                    \n                    if (enableModuleShortcutAdd && module.enabled && !module.remove && (module.hasWebUi || module.hasActionScript) && !hasUpdateButton) {\n                        buttons.add(ModuleButtonConfig(\n                            icon = Icons.Outlined.Add,\n                            text = shortcutAdd,\n                            contentDescription = shortcutAdd,\n                            onClick = { \n                                shortcutName = module.name\n                                shortcutIconUri = null\n                                shortcutType = if (module.hasWebUi) \"webui\" else \"action\"\n                                showShortcutDialog = true \n                            }\n                        ))\n                    }\n                    \n                    if (hasUpdateButton) {\n                        buttons.add(ModuleButtonConfig(\n                            icon = Icons.Outlined.Download,\n                            text = stringResource(R.string.apm_update),\n                            contentDescription = stringResource(R.string.apm_update),\n                            onClick = { onUpdate(module) }\n                        ))\n                    }\n                    \n\n                    val deleteButton = ModuleButtonConfig(\n                        icon = if (module.remove) Icons.Outlined.Restore else Icons.Outlined.Delete,\n                        text = if (module.remove) stringResource(R.string.apm_undo) else stringResource(R.string.apm_remove),\n                        contentDescription = if (module.remove) stringResource(R.string.apm_undo) else stringResource(R.string.apm_remove),\n                        onClick = {\n                            if (module.remove) {\n                                onUndoUninstall(module)\n                            } else {\n                                onUninstall(module)\n                            }\n                        },\n                        colors = if (simpleListBottomBar) ButtonDefaults.filledTonalButtonColors(\n                            containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = (opacity + 0.3f).coerceAtMost(1f))\n                        ) else if (module.remove) ButtonDefaults.filledTonalButtonColors(\n                            containerColor = MaterialTheme.colorScheme.tertiaryContainer.copy(alpha = (opacity + 0.3f).coerceAtMost(1f)),\n                            contentColor = MaterialTheme.colorScheme.onTertiaryContainer\n                        ) else ButtonDefaults.filledTonalButtonColors(\n                            containerColor = MaterialTheme.colorScheme.errorContainer.copy(alpha = (opacity + 0.3f).coerceAtMost(1f)),\n                            contentColor = MaterialTheme.colorScheme.onErrorContainer\n                        )\n                    )\n                    \n                    AdaptiveModuleButtonRow(\n                        buttons = buttons,\n                        trailingButton = deleteButton,\n                        simpleListBottomBar = simpleListBottomBar,\n                        spacing = if (simpleListBottomBar) 12 else 8,\n                        opacity = opacity\n                    )\n                }\n            }\n        }\n    }\n\n    // Render: inside spliced group → no Surface wrapper; standalone → Surface card\n    if (insideSplicedGroup) {\n        Box(modifier = modifier.then(clickModifier)) {\n            contentBlock()\n        }\n    } else {\n        Surface(\n            modifier = modifier.then(clickModifier),\n            shape = cardShape,\n            color = cardColor,\n            tonalElevation = 0.dp\n        ) {\n            contentBlock()\n        }\n    }\n\n    if (showShortcutDialog) {\n        AlertDialog(\n            onDismissRequest = { showShortcutDialog = false },\n            title = { Text(stringResource(R.string.module_shortcut_add)) },\n            text = {\n                Column {\n                    OutlinedTextField(\n                        value = shortcutName,\n                        onValueChange = { shortcutName = it },\n                        label = { Text(stringResource(R.string.module_shortcut_name)) },\n                        modifier = Modifier.fillMaxWidth()\n                    )\n                    Spacer(Modifier.height(12.dp))\n                    Row(verticalAlignment = Alignment.CenterVertically) {\n                        Text(stringResource(R.string.module_shortcut_icon))\n                        Spacer(Modifier.width(12.dp))\n                        if (shortcutPreviewBitmap != null) {\n                            Image(\n                                bitmap = shortcutPreviewBitmap!!.asImageBitmap(),\n                                contentDescription = null,\n                                modifier = Modifier.size(36.dp)\n                            )\n                        } else if (shortcutIconUri != null) {\n                            AsyncImage(\n                                model = shortcutIconUri,\n                                contentDescription = null,\n                                modifier = Modifier.size(36.dp)\n                            )\n                        } else {\n                            AsyncImage(\n                                model = appIcon,\n                                contentDescription = null,\n                                modifier = Modifier.size(36.dp)\n                            )\n                        }\n                    }\n                    Spacer(Modifier.height(8.dp))\n                    Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {\n                        TextButton(onClick = { pickShortcutIconLauncher.launch(\"image/*\") }) {\n                            Text(stringResource(R.string.module_shortcut_icon_select))\n                        }\n                        TextButton(onClick = { shortcutIconUri = null }) {\n                            Text(stringResource(R.string.module_shortcut_icon_default))\n                        }\n                    }\n                    if (module.hasWebUi && module.hasActionScript) {\n                        Spacer(Modifier.height(12.dp))\n                        Text(stringResource(R.string.module_shortcut_type))\n                        Row(verticalAlignment = Alignment.CenterVertically) {\n                            RadioButton(\n                                selected = shortcutType == \"webui\",\n                                onClick = { shortcutType = \"webui\" }\n                            )\n                            Text(stringResource(R.string.module_shortcut_type_webui))\n                            Spacer(Modifier.width(24.dp))\n                            RadioButton(\n                                selected = shortcutType == \"action\",\n                                onClick = { shortcutType = \"action\" }\n                            )\n                            Text(stringResource(R.string.module_shortcut_type_action))\n                        }\n                    }\n                }\n            },\n            confirmButton = {\n                TextButton(onClick = {\n                    if (shortcutType == \"webui\" && module.hasWebUi) {\n                        ModuleShortcut.createModuleWebUiShortcut(context, module.id, shortcutName.ifEmpty { module.name }, effectiveShortcutIconUri)\n                    } else if (module.hasActionScript) {\n                        ModuleShortcut.createModuleActionShortcut(context, module.id, shortcutName.ifEmpty { module.name }, effectiveShortcutIconUri)\n                    }\n                    showShortcutDialog = false\n                }) {\n                    Text(text = stringResource(id = android.R.string.ok))\n                }\n            },\n            dismissButton = {\n                TextButton(onClick = { showShortcutDialog = false }) {\n                    Text(text = stringResource(id = android.R.string.cancel))\n                }\n            }\n        )\n    }\n\n    if (showFolkBannerDialog) {\n        AlertDialog(\n            onDismissRequest = { showFolkBannerDialog = false },\n            title = { Text(folkBannerTitle) },\n            text = {\n                Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {\n                    Button(\n                        onClick = {\n                            showFolkBannerDialog = false\n                            pickFolkBannerLauncher.launch(\"image/*\")\n                        },\n                        modifier = Modifier.fillMaxWidth()\n                    ) {\n                        Text(folkBannerSelect)\n                    }\n                    if (hasFolkBanner) {\n                        Button(\n                            onClick = {\n                                showFolkBannerDialog = false\n                                scope.launch {\n                                    loadingDialog.show()\n                                    val success = withContext(Dispatchers.IO) {\n                                        runCatching {\n                                            val localCleared = clearFolkBanner(context, module.id)\n                                            val legacyCleared = runCatching {\n                                                val rootShell = getRootShell(true)\n                                                val resolvedDir = resolveModuleDir(rootShell, module.id)\n                                                clearLegacyFolkBanner(rootShell, resolvedDir)\n                                            }.getOrDefault(false)\n                                            localCleared || legacyCleared\n                                        }.getOrDefault(false)\n                                    }\n                                    loadingDialog.hide()\n                                    if (success) {\n                                        viewModel.removeBannerInfo(module.id)\n                                        bannerReloadKey++\n                                        snackBarHost.showSnackbar(folkBannerCleared.format(module.name))\n                                    } else {\n                                        snackBarHost.showSnackbar(folkBannerFailed.format(module.name))\n                                    }\n                                }\n                            },\n                            modifier = Modifier.fillMaxWidth()\n                        ) {\n                            Text(folkBannerClear)\n                        }\n                    }\n                }\n            },\n            confirmButton = {\n                TextButton(onClick = { showFolkBannerDialog = false }) {\n                    Text(stringResource(android.R.string.cancel))\n                }\n            }\n        )\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/AboutScreen.kt",
    "content": "package me.bmax.apatch.ui.screen\n\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight\nimport androidx.compose.material.icons.filled.Info\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Surface\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.scale\nimport androidx.compose.ui.draw.shadow\nimport androidx.compose.ui.platform.LocalUriHandler\nimport androidx.compose.ui.res.colorResource\nimport androidx.compose.ui.res.painterResource\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport androidx.lifecycle.compose.dropUnlessResumed\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport me.bmax.apatch.BuildConfig\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.component.ExpressiveCard\nimport me.bmax.apatch.ui.component.SplicedColumnGroup\nimport me.bmax.apatch.util.Version\n\n@Destination<RootGraph>\n@Composable\nfun AboutScreen(navigator: DestinationsNavigator) {\n    val uriHandler = LocalUriHandler.current\n\n    Scaffold(\n        topBar = {\n            TopBar(onBack = dropUnlessResumed { navigator.popBackStack() })\n        }\n    ) { innerPadding ->\n        Column(\n            modifier = Modifier\n                .padding(innerPadding)\n                .fillMaxWidth()\n                .verticalScroll(rememberScrollState()),\n            horizontalAlignment = Alignment.CenterHorizontally\n        ) {\n            Spacer(modifier = Modifier.height(24.dp))\n\n            // App icon with elevated shadow\n            Surface(\n                modifier = Modifier\n                    .size(100.dp)\n                    .shadow(8.dp, CircleShape),\n                color = colorResource(id = R.color.about_icon_background),\n                shape = CircleShape\n            ) {\n                Image(\n                    painter = painterResource(id = R.drawable.about),\n                    contentDescription = stringResource(R.string.app_name),\n                    modifier = Modifier.scale(0.7f)\n                )\n            }\n\n            Spacer(modifier = Modifier.height(20.dp))\n\n            Text(\n                text = stringResource(id = R.string.app_name),\n                style = MaterialTheme.typography.headlineSmall,\n                fontWeight = FontWeight.Bold,\n            )\n\n            Spacer(modifier = Modifier.height(4.dp))\n\n            Text(\n                text = stringResource(\n                    id = R.string.about_app_version,\n                    if (BuildConfig.VERSION_NAME.contains(BuildConfig.VERSION_CODE.toString())) \"${BuildConfig.VERSION_CODE}\" else \"${BuildConfig.VERSION_CODE} (${BuildConfig.VERSION_NAME})\"\n                ),\n                style = MaterialTheme.typography.bodyMedium,\n                color = MaterialTheme.colorScheme.onSurfaceVariant,\n            )\n\n            Text(\n                text = stringResource(\n                    id = R.string.about_powered_by,\n                    \"KernelPatch (${Version.buildKPVString()})\"\n                ),\n                style = MaterialTheme.typography.bodyMedium,\n                color = MaterialTheme.colorScheme.onSurfaceVariant,\n                modifier = Modifier.padding(top = 2.dp)\n            )\n\n            Spacer(modifier = Modifier.height(24.dp))\n\n            // Description card — M3E ExpressiveCard\n            ExpressiveCard(\n                modifier = Modifier.padding(horizontal = 16.dp),\n                flat = true,\n            ) {\n                Column(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(20.dp)\n                ) {\n                    Row(\n                        verticalAlignment = Alignment.CenterVertically,\n                    ) {\n                        Icon(\n                            imageVector = Icons.Filled.Info,\n                            contentDescription = null,\n                            tint = MaterialTheme.colorScheme.primary,\n                            modifier = Modifier.size(20.dp)\n                        )\n                        Spacer(modifier = Modifier.width(12.dp))\n                        Text(\n                            text = stringResource(id = R.string.about),\n                            style = MaterialTheme.typography.titleMedium,\n                            fontWeight = FontWeight.SemiBold,\n                        )\n                    }\n                    Spacer(modifier = Modifier.height(12.dp))\n                    Text(\n                        text = stringResource(id = R.string.about_app_desc),\n                        style = MaterialTheme.typography.bodyMedium,\n                        color = MaterialTheme.colorScheme.onSurfaceVariant,\n                    )\n                }\n            }\n\n            Spacer(modifier = Modifier.height(16.dp))\n\n            // Links — M3E SplicedColumnGroup\n            SplicedColumnGroup(\n                modifier = Modifier.padding(horizontal = 0.dp),\n            ) {\n                item {\n                    SplicedLinkItem(\n                        icon = {\n                            Icon(\n                                painter = painterResource(id = R.drawable.github),\n                                contentDescription = null,\n                                modifier = Modifier.size(20.dp)\n                            )\n                        },\n                        title = stringResource(id = R.string.about_github),\n                        onClick = { uriHandler.openUri(\"https://github.com/LyraVoid/FolkPatch\") }\n                    )\n                }\n                item {\n                    SplicedLinkItem(\n                        icon = {\n                            Icon(\n                                painter = painterResource(id = R.drawable.telegram),\n                                contentDescription = null,\n                                modifier = Modifier.size(20.dp)\n                            )\n                        },\n                        title = stringResource(id = R.string.about_telegram_channel),\n                        onClick = { uriHandler.openUri(\"https://t.me/FolkPatch\") }\n                    )\n                }\n            }\n\n            Spacer(modifier = Modifier.height(32.dp))\n        }\n    }\n}\n\n@Composable\nprivate fun SplicedLinkItem(\n    icon: @Composable () -> Unit,\n    title: String,\n    onClick: () -> Unit,\n) {\n    Row(\n        modifier = Modifier\n            .fillMaxWidth()\n            .clickable(onClick = onClick)\n            .padding(horizontal = 24.dp, vertical = 14.dp),\n        verticalAlignment = Alignment.CenterVertically,\n    ) {\n        icon()\n        Spacer(modifier = Modifier.width(20.dp))\n        Text(\n            text = title,\n            style = MaterialTheme.typography.bodyLarge,\n            modifier = Modifier.weight(1f),\n        )\n        Icon(\n            imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,\n            contentDescription = null,\n            tint = MaterialTheme.colorScheme.onSurfaceVariant,\n            modifier = Modifier.size(20.dp),\n        )\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun TopBar(onBack: () -> Unit = {}) {\n    TopAppBar(\n        title = { Text(stringResource(R.string.about)) },\n        navigationIcon = {\n            IconButton(\n                onClick = onBack\n            ) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) }\n        },\n    )\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/ApiMarketplace.kt",
    "content": "package me.bmax.apatch.ui.screen\n\nimport me.bmax.apatch.util.ui.showToast\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.filled.Check\nimport androidx.compose.material.icons.filled.Info\nimport androidx.compose.material3.*\nimport androidx.compose.runtime.*\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.layout.ContentScale\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.lifecycle.viewmodel.compose.viewModel\nimport coil.compose.AsyncImage\nimport coil.compose.AsyncImagePainter\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.component.AppLoadingIndicator\nimport me.bmax.apatch.ui.model.ApiMarketplaceItem\nimport me.bmax.apatch.ui.theme.BackgroundConfig\nimport me.bmax.apatch.ui.viewmodel.ApiMarketplaceViewModel\n\n@Destination<RootGraph>\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun ApiMarketplaceScreen(\n    navigator: DestinationsNavigator\n) {\n    val viewModel = viewModel<ApiMarketplaceViewModel>()\n    val context = LocalContext.current\n    val snackbarHostState = remember { SnackbarHostState() }\n\n    var selectedItem by remember { mutableStateOf<ApiMarketplaceItem?>(null) }\n    var showPreviewDialog by remember { mutableStateOf(false) }\n\n    // Fetch items on first load\n    LaunchedEffect(Unit) {\n        if (viewModel.items.isEmpty()) {\n            viewModel.fetchMarketplaceItems()\n        }\n    }\n\n    // Handle verification state changes\n    LaunchedEffect(viewModel.verificationState) {\n        when (val state = viewModel.verificationState) {\n            is ApiMarketplaceViewModel.VerificationState.Success -> {\n                showToast(\n                    context,\n                    context.getString(R.string.apm_api_apply_success)\n                )\n                navigator.popBackStack()\n            }\n            is ApiMarketplaceViewModel.VerificationState.Error -> {\n                // Error is shown in the dialog\n            }\n            else -> {}\n        }\n    }\n\n    // Preview Dialog\n    if (showPreviewDialog && selectedItem != null) {\n        ApiPreviewDialog(\n            item = selectedItem!!,\n            verificationState = viewModel.verificationState,\n            onDismiss = {\n                showPreviewDialog = false\n                selectedItem = null\n                viewModel.resetVerificationState()\n            },\n            onApply = { url ->\n                viewModel.verifyAndApplyApi(url)\n            }\n        )\n    }\n\n    Scaffold(\n        topBar = {\n            TopAppBar(\n                title = { Text(stringResource(R.string.apm_api_marketplace_title)) },\n                navigationIcon = {\n                    IconButton(onClick = { navigator.popBackStack() }) {\n                        Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = \"Back\")\n                    }\n                }\n            )\n        },\n        snackbarHost = { SnackbarHost(snackbarHostState) }\n    ) { paddingValues ->\n        when {\n            viewModel.isLoading -> {\n                Box(\n                    modifier = Modifier\n                        .fillMaxSize()\n                        .padding(paddingValues),\n                    contentAlignment = Alignment.Center\n                ) {\n                    AppLoadingIndicator(\n                        text = stringResource(R.string.loading_apis)\n                    )\n                }\n            }\n            viewModel.errorMessage != null -> {\n                Column(\n                    modifier = Modifier\n                        .fillMaxSize()\n                        .padding(paddingValues),\n                    verticalArrangement = Arrangement.Center,\n                    horizontalAlignment = Alignment.CenterHorizontally\n                ) {\n                    Text(\n                        text = viewModel.errorMessage ?: \"Unknown error\",\n                        color = MaterialTheme.colorScheme.error,\n                        modifier = Modifier.padding(16.dp)\n                    )\n                    Button(onClick = { viewModel.retry() }) {\n                        Text(stringResource(R.string.retry))\n                    }\n                }\n            }\n            viewModel.items.isEmpty() -> {\n                Box(\n                    modifier = Modifier\n                        .fillMaxSize()\n                        .padding(paddingValues),\n                    contentAlignment = Alignment.Center\n                ) {\n                    Text(\n                        text = stringResource(R.string.apm_api_empty),\n                        style = MaterialTheme.typography.bodyLarge,\n                        color = MaterialTheme.colorScheme.onSurfaceVariant\n                    )\n                }\n            }\n            else -> {\n                LazyColumn(\n                    modifier = Modifier\n                        .fillMaxSize()\n                        .padding(paddingValues),\n                    contentPadding = PaddingValues(16.dp),\n                    verticalArrangement = Arrangement.spacedBy(12.dp)\n                ) {\n                    items(\n                        items = viewModel.items,\n                        key = { it.url }\n                    ) { item ->\n                        ApiMarketplaceItemCard(\n                            item = item,\n                            onPreview = {\n                                selectedItem = item\n                                showPreviewDialog = true\n                            }\n                        )\n                    }\n                }\n            }\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun ApiMarketplaceItemCard(\n    item: ApiMarketplaceItem,\n    onPreview: () -> Unit\n) {\n    // Same styling as ScriptLibrary\n    val isWallpaperMode = BackgroundConfig.isCustomBackgroundEnabled\n    val opacity = if (isWallpaperMode) {\n        BackgroundConfig.customBackgroundOpacity.coerceAtLeast(0.2f)\n    } else {\n        1f\n    }\n\n    Surface(\n        modifier = Modifier.fillMaxWidth(),\n        shape = RoundedCornerShape(20.dp),\n        color = MaterialTheme.colorScheme.surface,\n        tonalElevation = 1.dp\n    ) {\n        Row(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(16.dp),\n            horizontalArrangement = Arrangement.SpaceBetween,\n            verticalAlignment = Alignment.CenterVertically\n        ) {\n            Column(\n                modifier = Modifier.weight(1f)\n            ) {\n                Text(\n                    text = item.name,\n                    style = MaterialTheme.typography.titleMedium,\n                    maxLines = 1,\n                    overflow = TextOverflow.Ellipsis\n                )\n                Spacer(modifier = Modifier.height(4.dp))\n                Text(\n                    text = item.getLocalizedDescription(),\n                    style = MaterialTheme.typography.bodyMedium,\n                    color = MaterialTheme.colorScheme.onSurfaceVariant,\n                    maxLines = 2,\n                    overflow = TextOverflow.Ellipsis\n                )\n            }\n            Spacer(modifier = Modifier.width(12.dp))\n\n            val buttonOpacity = (opacity + 0.3f).coerceAtMost(1f)\n\n            FilledTonalButton(\n                onClick = onPreview,\n                contentPadding = ButtonDefaults.TextButtonContentPadding,\n                modifier = Modifier.height(36.dp),\n                colors = ButtonDefaults.filledTonalButtonColors(\n                    containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = buttonOpacity)\n                )\n            ) {\n                Icon(\n                    imageVector = Icons.Default.Info,\n                    contentDescription = null,\n                    modifier = Modifier.size(18.dp)\n                )\n                Spacer(modifier = Modifier.width(8.dp))\n                Text(stringResource(R.string.apm_api_preview))\n            }\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun ApiPreviewDialog(\n    item: ApiMarketplaceItem,\n    verificationState: ApiMarketplaceViewModel.VerificationState,\n    onDismiss: () -> Unit,\n    onApply: (String) -> Unit\n) {\n    var imageLoadState by remember { mutableStateOf<AsyncImagePainter.State?>(null) }\n    // Add cache-busting parameter to ensure fresh API request each time\n    val cacheBuster = remember { System.currentTimeMillis() }\n    val imageUrlWithCacheBuster = remember(item.url, cacheBuster) {\n        val separator = if (item.url.contains(\"?\")) \"&\" else \"?\"\n        \"${item.url}${separator}t=$cacheBuster\"\n    }\n\n    // Same styling as ScriptLibrary\n    val isWallpaperMode = BackgroundConfig.isCustomBackgroundEnabled\n    val opacity = if (isWallpaperMode) {\n        BackgroundConfig.customBackgroundOpacity.coerceAtLeast(0.2f)\n    } else {\n        1f\n    }\n\n    AlertDialog(\n        onDismissRequest = onDismiss,\n        title = { Text(item.name) },\n        text = {\n            Column(\n                modifier = Modifier.fillMaxWidth()\n            ) {\n                // Description\n                Text(\n                    text = item.getLocalizedDescription(),\n                    style = MaterialTheme.typography.bodyMedium\n                )\n\n                Spacer(modifier = Modifier.height(8.dp))\n\n                // URL\n                Text(\n                    text = stringResource(R.string.apm_api_url_label),\n                    style = MaterialTheme.typography.labelMedium,\n                    color = MaterialTheme.colorScheme.primary\n                )\n                Text(\n                    text = item.url,\n                    style = MaterialTheme.typography.bodySmall,\n                    color = MaterialTheme.colorScheme.onSurfaceVariant\n                )\n\n                Spacer(modifier = Modifier.height(16.dp))\n\n                // API Preview Image\n                Text(\n                    text = stringResource(R.string.apm_api_preview_title),\n                    style = MaterialTheme.typography.labelMedium\n                )\n                Spacer(modifier = Modifier.height(8.dp))\n\n                Box(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .height(150.dp)\n                        .clip(MaterialTheme.shapes.medium),\n                    contentAlignment = Alignment.Center\n                ) {\n                    AsyncImage(\n                        model = imageUrlWithCacheBuster,\n                        contentDescription = stringResource(R.string.apm_api_preview_title),\n                        modifier = Modifier.fillMaxSize(),\n                        contentScale = ContentScale.Crop,\n                        onState = { state -> imageLoadState = state }\n                    )\n\n                    // Loading state\n                    if (imageLoadState is AsyncImagePainter.State.Loading) {\n                        CircularProgressIndicator(\n                            modifier = Modifier.size(32.dp)\n                        )\n                    }\n\n                    // Error state\n                    if (imageLoadState is AsyncImagePainter.State.Error) {\n                        Column(\n                            horizontalAlignment = Alignment.CenterHorizontally\n                        ) {\n                            Icon(\n                                imageVector = Icons.Default.Info,\n                                contentDescription = null,\n                                tint = MaterialTheme.colorScheme.error,\n                                modifier = Modifier.size(32.dp)\n                            )\n                            Spacer(modifier = Modifier.height(4.dp))\n                            Text(\n                                text = stringResource(R.string.apm_api_preview_failed),\n                                style = MaterialTheme.typography.bodySmall,\n                                color = MaterialTheme.colorScheme.error\n                            )\n                        }\n                    }\n                }\n\n                // Verification error message\n                if (verificationState is ApiMarketplaceViewModel.VerificationState.Error) {\n                    Spacer(modifier = Modifier.height(12.dp))\n                    Text(\n                        text = verificationState.message,\n                        style = MaterialTheme.typography.bodySmall,\n                        color = MaterialTheme.colorScheme.error\n                    )\n                }\n            }\n        },\n        confirmButton = {\n            val buttonOpacity = (opacity + 0.3f).coerceAtMost(1f)\n\n            when (verificationState) {\n                is ApiMarketplaceViewModel.VerificationState.Loading -> {\n                    FilledTonalButton(\n                        onClick = {},\n                        enabled = false,\n                        colors = ButtonDefaults.filledTonalButtonColors(\n                            containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = buttonOpacity)\n                        )\n                    ) {\n                        CircularProgressIndicator(\n                            modifier = Modifier.size(16.dp),\n                            strokeWidth = 2.dp\n                        )\n                        Spacer(modifier = Modifier.width(8.dp))\n                        Text(stringResource(R.string.apm_api_verifying))\n                    }\n                }\n                is ApiMarketplaceViewModel.VerificationState.Error -> {\n                    FilledTonalButton(\n                        onClick = { onApply(item.url) },\n                        colors = ButtonDefaults.filledTonalButtonColors(\n                            containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = buttonOpacity)\n                        )\n                    ) {\n                        Text(stringResource(R.string.apm_api_retry))\n                    }\n                }\n                is ApiMarketplaceViewModel.VerificationState.Success -> {\n                    FilledTonalButton(\n                        onClick = onDismiss,\n                        colors = ButtonDefaults.filledTonalButtonColors(\n                            containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = buttonOpacity)\n                        )\n                    ) {\n                        Icon(Icons.Default.Check, contentDescription = null)\n                        Spacer(modifier = Modifier.width(4.dp))\n                        Text(stringResource(R.string.apm_api_apply_success))\n                    }\n                }\n                else -> {\n                    FilledTonalButton(\n                        onClick = { onApply(item.url) },\n                        colors = ButtonDefaults.filledTonalButtonColors(\n                            containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = buttonOpacity)\n                        )\n                    ) {\n                        Text(stringResource(R.string.apm_api_apply))\n                    }\n                }\n            }\n        },\n        dismissButton = {\n            if (verificationState !is ApiMarketplaceViewModel.VerificationState.Success) {\n                TextButton(onClick = onDismiss) {\n                    Text(stringResource(R.string.close))\n                }\n            }\n        }\n    )\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/ApmBulkInstallScreen.kt",
    "content": "package me.bmax.apatch.ui.screen\n\nimport android.net.Uri\nimport android.provider.OpenableColumns\nimport android.content.Context\nimport androidx.activity.compose.rememberLauncherForActivityResult\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.filled.Delete\nimport androidx.compose.material.icons.filled.FolderOpen\nimport androidx.compose.material3.AlertDialog\nimport androidx.compose.material3.AlertDialogDefaults\nimport androidx.compose.material3.BasicAlertDialog\nimport androidx.compose.material3.Button\nimport androidx.compose.material3.Card\nimport androidx.compose.material3.CardDefaults\nimport androidx.compose.material3.Checkbox\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.ListItem\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Surface\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.window.DialogProperties\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.R\nimport me.bmax.apatch.util.installModule\nimport me.bmax.apatch.util.reboot\n\nimport me.bmax.apatch.util.BiometricUtils\nimport me.bmax.apatch.util.BulkInstallManager\nimport com.ramcosta.composedestinations.generated.destinations.InstallScreenDestination\n\n@Destination<RootGraph>\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun ApmBulkInstallScreen(navigator: DestinationsNavigator, initialUris: ArrayList<Uri>? = null) {\n    val context = LocalContext.current\n    val scope = rememberCoroutineScope()\n    var moduleUris by remember { mutableStateOf<List<Uri>>(initialUris ?: emptyList()) }\n    var isInstalling by remember { mutableStateOf(false) }\n    var installLog by remember { mutableStateOf(\"\") }\n    var showLogDialog by remember { mutableStateOf(false) }\n    \n    // Helper function to get filename from Uri\n    fun getFileName(context: Context, uri: Uri): String {\n        var result: String? = null\n        if (uri.scheme == \"content\") {\n            val cursor = context.contentResolver.query(uri, null, null, null, null)\n            try {\n                if (cursor != null && cursor.moveToFirst()) {\n                    val index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)\n                    if (index >= 0) {\n                        result = cursor.getString(index)\n                    }\n                }\n            } finally {\n                cursor?.close()\n            }\n        }\n        if (result == null) {\n            result = uri.path\n            val cut = result?.lastIndexOf('/')\n            if (cut != null && cut != -1) {\n                result = result?.substring(cut + 1)\n            }\n        }\n        return result ?: uri.toString()\n    }\n    \n    // First use dialog state\n    val prefs = remember { APApplication.sharedPreferences }\n    var showFirstTimeDialog by remember { \n        mutableStateOf(!prefs.getBoolean(\"apm_bulk_install_first_use_shown\", false)) \n    }\n    var dontShowAgain by remember { mutableStateOf(false) }\n\n    val filePickerLauncher = rememberLauncherForActivityResult(\n        contract = ActivityResultContracts.OpenMultipleDocuments()\n    ) { uris ->\n        moduleUris = moduleUris + uris\n    }\n\n    // First Use Dialog\n    if (showFirstTimeDialog) {\n        BasicAlertDialog(\n            onDismissRequest = {\n                if (dontShowAgain) {\n                    prefs.edit().putBoolean(\"apm_bulk_install_first_use_shown\", true).apply()\n                }\n                showFirstTimeDialog = false\n            },\n            properties = DialogProperties(\n                dismissOnClickOutside = false,\n                dismissOnBackPress = false\n            )\n        ) {\n            Surface(\n                modifier = Modifier\n                    .width(350.dp)\n                    .padding(16.dp),\n                shape = RoundedCornerShape(20.dp),\n                tonalElevation = AlertDialogDefaults.TonalElevation,\n                color = AlertDialogDefaults.containerColor,\n            ) {\n                Column(modifier = Modifier.padding(16.dp)) {\n                    Text(\n                        text = stringResource(R.string.apm_bulk_install_title),\n                        style = MaterialTheme.typography.titleMedium,\n                        modifier = Modifier.padding(bottom = 16.dp)\n                    )\n                    \n                    Text(\n                        text = stringResource(R.string.apm_bulk_install_first_use_text),\n                        style = MaterialTheme.typography.bodyMedium,\n                        color = MaterialTheme.colorScheme.onSurfaceVariant\n                    )\n                    \n                    Row(\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .padding(top = 16.dp),\n                        verticalAlignment = Alignment.CenterVertically\n                    ) {\n                        Checkbox(\n                            checked = dontShowAgain,\n                            onCheckedChange = { dontShowAgain = it }\n                        )\n                        Text(\n                            text = stringResource(R.string.kpm_autoload_do_not_show_again),\n                            style = MaterialTheme.typography.bodySmall,\n                            modifier = Modifier.padding(start = 8.dp)\n                        )\n                    }\n                    \n                    Row(\n                        modifier = Modifier.fillMaxWidth(),\n                        horizontalArrangement = Arrangement.End\n                    ) {\n                        Button(onClick = {\n                            if (dontShowAgain) {\n                                prefs.edit().putBoolean(\"apm_bulk_install_first_use_shown\", true).apply()\n                            }\n                            showFirstTimeDialog = false\n                        }) {\n                            Text(stringResource(R.string.kpm_autoload_first_time_confirm))\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    if (showLogDialog) {\n        AlertDialog(\n            onDismissRequest = { if (!isInstalling) showLogDialog = false },\n            title = { Text(stringResource(R.string.apm_bulk_install_log_title)) },\n            text = {\n                Column {\n                    Text(\n                        text = installLog,\n                        style = MaterialTheme.typography.bodySmall,\n                        modifier = Modifier\n                            .height(300.dp)\n                            .verticalScroll(rememberScrollState())\n                    )\n                }\n            },\n            confirmButton = {\n                if (!isInstalling) {\n                    Row {\n                        TextButton(onClick = { showLogDialog = false }) {\n                            Text(stringResource(android.R.string.ok))\n                        }\n                        TextButton(onClick = { \n                            scope.launch {\n                                withContext(Dispatchers.IO) {\n                                    reboot()\n                                }\n                            }\n                         }) {\n                            Text(stringResource(R.string.reboot))\n                        }\n                    }\n                }\n            }\n        )\n    }\n\n    Scaffold(\n        topBar = {\n            TopAppBar(\n                title = { Text(stringResource(R.string.apm_bulk_install_title)) },\n                navigationIcon = {\n                    IconButton(onClick = { navigator.navigateUp() }) {\n                        Icon(\n                            imageVector = Icons.AutoMirrored.Filled.ArrowBack,\n                            contentDescription = stringResource(android.R.string.cancel)\n                        )\n                    }\n                }\n            )\n        }\n    ) { innerPadding ->\n        Column(\n            modifier = Modifier\n                .fillMaxSize()\n                .padding(innerPadding)\n                .padding(16.dp)\n        ) {\n            Card(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .weight(1f),\n                colors = CardDefaults.cardColors(\n                    containerColor = MaterialTheme.colorScheme.surfaceContainer\n                )\n            ) {\n                Column(\n                    modifier = Modifier\n                        .fillMaxSize()\n                        .padding(16.dp)\n                ) {\n                    Row(\n                        modifier = Modifier.fillMaxWidth(),\n                        horizontalArrangement = Arrangement.SpaceBetween,\n                        verticalAlignment = Alignment.CenterVertically\n                    ) {\n                        Text(\n                            text = stringResource(R.string.apm_bulk_install_list_title),\n                            style = MaterialTheme.typography.titleMedium\n                        )\n                        Button(\n                            onClick = {\n                                filePickerLauncher.launch(arrayOf(\"*/*\"))\n                            }\n                        ) {\n                            Icon(\n                                imageVector = Icons.Default.FolderOpen,\n                                contentDescription = null,\n                                modifier = Modifier.padding(end = 8.dp)\n                            )\n                            Text(stringResource(R.string.apm_bulk_install_add))\n                        }\n                    }\n                    Spacer(modifier = Modifier.height(8.dp))\n                    if (moduleUris.isEmpty()) {\n                        Box(\n                            modifier = Modifier\n                                .fillMaxWidth()\n                                .weight(1f),\n                            contentAlignment = Alignment.Center\n                        ) {\n                            Text(\n                                text = stringResource(R.string.apm_bulk_install_empty),\n                                style = MaterialTheme.typography.bodyMedium,\n                                color = MaterialTheme.colorScheme.onSurfaceVariant\n                            )\n                        }\n                    } else {\n                        LazyColumn(\n                            modifier = Modifier.weight(1f)\n                        ) {\n                            items(moduleUris) { uri ->\n                                ListItem(\n                                    headlineContent = { \n                                        Text(\n                                            text = getFileName(context, uri),\n                                            style = MaterialTheme.typography.bodyMedium\n                                        )\n                                    },\n                                    trailingContent = {\n                                        IconButton(\n                                            onClick = {\n                                                moduleUris = moduleUris - uri\n                                            }\n                                        ) {\n                                            Icon(\n                                                imageVector = Icons.Default.Delete,\n                                                contentDescription = stringResource(R.string.apm_bulk_install_remove),\n                                                tint = MaterialTheme.colorScheme.error\n                                            )\n                                        }\n                                    }\n                                )\n                            }\n                        }\n                    }\n                }\n            }\n            \n            Spacer(modifier = Modifier.height(16.dp))\n            \n            Button(\n                onClick = {\n                    scope.launch {\n                        val prefs = APApplication.sharedPreferences\n                        if (prefs.getBoolean(\"strong_biometric\", false) && prefs.getBoolean(\"biometric_login\", false)) {\n                            val activity = context as? androidx.fragment.app.FragmentActivity\n                            if (activity != null) {\n                                if (!BiometricUtils.authenticate(activity)) return@launch\n                            }\n                        }\n\n                        val batchInstallFullProcess = prefs.getBoolean(\"apm_batch_install_full_process\", false)\n\n                        if (batchInstallFullProcess) {\n                            val uris = ArrayList(moduleUris)\n                            BulkInstallManager.setQueue(uris)\n                            val first = BulkInstallManager.popNext()\n                            if (first != null) {\n                                navigator.navigate(InstallScreenDestination(first, MODULE_TYPE.APM))\n                            }\n                        } else {\n                            showLogDialog = true\n                            isInstalling = true\n                            val logStart = context.getString(R.string.apm_bulk_install_log_start)\n                            installLog = \"$logStart\\n\"\n                            launch(Dispatchers.IO) {\n                                moduleUris.forEachIndexed { index, uri ->\n                                    val fileName = getFileName(context, uri)\n                                    val installingMsg = context.getString(R.string.apm_bulk_install_log_installing, fileName)\n                                    withContext(Dispatchers.Main) {\n                                        installLog += \"\\n>>> $installingMsg\\n\"\n                                    }\n\n                                    installModule(\n                                        uri = uri,\n                                        type = MODULE_TYPE.APM,\n                                        onFinish = { success ->\n                                            // handled via return value in blocking call\n                                        },\n                                        onStdout = { msg ->\n                                            // append log? might be too much text update on Main thread if fast\n                                            // for now let's skip detailed logs to avoid UI lag, or just append\n                                        },\n                                        onStderr = { msg ->\n                                            // same\n                                        }\n                                    )\n\n                                    val installedMsg = context.getString(R.string.apm_bulk_install_log_installed, fileName)\n                                    withContext(Dispatchers.Main) {\n                                        installLog += \"$installedMsg\\n\"\n                                    }\n                                }\n                                val doneMsg = context.getString(R.string.apm_bulk_install_log_done)\n                                withContext(Dispatchers.Main) {\n                                    installLog += \"\\n$doneMsg\"\n                                    isInstalling = false\n                                }\n                            }\n                        }\n                    }\n                },\n                modifier = Modifier.fillMaxWidth(),\n                enabled = moduleUris.isNotEmpty()\n            ) {\n                Text(stringResource(R.string.apm_bulk_install_action))\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/AppProfile.kt",
    "content": "package me.bmax.apatch.ui.screen\n\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.WindowInsetsSides\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.only\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.safeDrawing\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.automirrored.outlined.OpenInNew\nimport androidx.compose.material.icons.filled.Info\nimport androidx.compose.material.icons.filled.RemoveCircle\nimport androidx.compose.material.icons.filled.Security\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.ListItem\nimport androidx.compose.material3.ListItemDefaults\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.derivedStateOf\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.unit.dp\nimport androidx.lifecycle.viewmodel.compose.viewModel\nimport coil.compose.AsyncImage\nimport coil.request.ImageRequest\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport kotlinx.coroutines.launch\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.Natives\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.component.SegmentedControl\nimport me.bmax.apatch.ui.component.SwitchItem\nimport me.bmax.apatch.ui.viewmodel.SuperUserViewModel\nimport me.bmax.apatch.util.PkgConfig\nimport me.bmax.apatch.util.SuAuditLog\nimport me.bmax.apatch.util.ui.showToast\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Destination<RootGraph>\n@Composable\nfun AppProfileScreen(\n    navigator: DestinationsNavigator,\n    packageName: String,\n    uid: Int\n) {\n    val viewModel = viewModel<SuperUserViewModel>()\n    val scope = rememberCoroutineScope()\n    val context = LocalContext.current\n    \n    val appInfoState = remember(packageName, uid) {\n        derivedStateOf {\n            SuperUserViewModel.apps.find { it.packageName == packageName && it.uid == uid }\n        }\n    }\n    val appInfo = appInfoState.value\n    if (appInfo == null) {\n        navigator.popBackStack()\n        return\n    }\n\n    val config = appInfo.config\n    \n    // 0: ROOT, 1: NO ROOT, 2: Exclude\n    var selectedIndex by remember(config) { \n        mutableIntStateOf(\n            when {\n                config.allow == 1 -> 0\n                config.exclude == 1 -> 2\n                else -> 1\n            }\n        )\n    }\n\n    Scaffold(\n        containerColor = Color.Transparent,\n        topBar = {\n            TopAppBar(\n                title = { Text(stringResource(R.string.su_title)) },\n                navigationIcon = {\n                    IconButton(onClick = { navigator.popBackStack() }) {\n                        Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)\n                    }\n                },\n                actions = {\n                    IconButton(onClick = {\n                        val success = viewModel.launchApp(context, appInfo.packageName)\n                        scope.launch {\n                            showToast(\n                                context,\n                                if (success) {\n                                    context.getString(R.string.su_app_action_launch_success, appInfo.label)\n                                } else {\n                                    context.getString(R.string.su_app_action_failed, appInfo.label)\n                                }\n                            )\n                        }\n                    }) {\n                        Icon(Icons.AutoMirrored.Outlined.OpenInNew, contentDescription = stringResource(R.string.su_app_action_launch))\n                    }\n                },\n                windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)\n            )\n        }\n    ) { paddingValues ->\n        Column(\n            modifier = Modifier\n                .padding(paddingValues)\n                .verticalScroll(rememberScrollState())\n        ) {\n            ListItem(\n                colors = ListItemDefaults.colors(containerColor = Color.Transparent),\n                headlineContent = { Text(appInfo.label) },\n                supportingContent = {\n                    Column {\n                        Text(appInfo.packageName)\n                        Text(\"UID: ${appInfo.uid}\", color = MaterialTheme.colorScheme.outline)\n                    }\n                },\n                leadingContent = {\n                    AsyncImage(\n                        model = ImageRequest.Builder(LocalContext.current).data(appInfo.packageInfo)\n                            .crossfade(true).build(),\n                        contentDescription = appInfo.label,\n                        modifier = Modifier.size(48.dp)\n                    )\n                }\n            )\n\n            SegmentedControl(\n                items = listOf(\"ROOT\", \"NO ROOT\", \"Exclude\"),\n                selectedIndex = selectedIndex,\n                onItemSelection = { index ->\n                    selectedIndex = index\n                    \n                    // Update Logic\n                    when (index) {\n                        0 -> { // ROOT\n                            config.allow = 1\n                            config.exclude = 0\n                            config.profile.scontext = APApplication.MAGISK_SCONTEXT\n                            Natives.grantSu(appInfo.uid, 0, config.profile.scontext)\n                            Natives.setUidExclude(appInfo.uid, 0)\n                            SuAuditLog.logGrant(appInfo.packageName, appInfo.uid)\n                        }\n                        1 -> { // NO ROOT\n                            config.allow = 0\n                            config.exclude = 0\n                            Natives.revokeSu(appInfo.uid)\n                            Natives.setUidExclude(appInfo.uid, 0)\n                            SuAuditLog.logRevoke(appInfo.packageName, appInfo.uid)\n                        }\n                        2 -> { // Exclude\n                            config.allow = 0\n                            config.exclude = 1\n                            config.profile.scontext = APApplication.DEFAULT_SCONTEXT\n                            Natives.revokeSu(appInfo.uid)\n                            Natives.setUidExclude(appInfo.uid, 1)\n                            SuAuditLog.logExclude(appInfo.packageName, appInfo.uid)\n                        }\n                    }\n                    config.profile.uid = appInfo.uid\n                    PkgConfig.changeConfig(config)\n                }\n            )\n\n            // Description Cards\n            AnimatedVisibility(visible = selectedIndex == 0) {\n                ListItem(\n                    colors = ListItemDefaults.colors(containerColor = Color.Transparent),\n                    headlineContent = { Text(stringResource(id = R.string.su_pkg_root_setting_title)) },\n                    supportingContent = { Text(stringResource(id = R.string.su_pkg_root_setting_summary)) },\n                    leadingContent = { Icon(Icons.Filled.Security, contentDescription = null) }\n                )\n            }\n\n            AnimatedVisibility(visible = selectedIndex == 1) {\n                ListItem(\n                    colors = ListItemDefaults.colors(containerColor = Color.Transparent),\n                    headlineContent = { Text(stringResource(id = R.string.su_pkg_normal_setting_title)) },\n                    supportingContent = { Text(stringResource(id = R.string.su_pkg_normal_setting_summary)) },\n                    leadingContent = { Icon(Icons.Filled.Info, contentDescription = null) }\n                )\n            }\n\n            AnimatedVisibility(visible = selectedIndex == 2) {\n                ListItem(\n                    colors = ListItemDefaults.colors(containerColor = Color.Transparent),\n                    headlineContent = { Text(stringResource(id = R.string.su_pkg_excluded_setting_title)) },\n                    supportingContent = { Text(stringResource(id = R.string.su_pkg_excluded_setting_summary)) },\n                    leadingContent = { Icon(Icons.Filled.RemoveCircle, contentDescription = null) }\n                )\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/BannerApiService.kt",
    "content": "package me.bmax.apatch.ui.screen\n\nimport android.content.Context\nimport android.util.Log\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.apApp\nimport okhttp3.Request\nimport java.io.File\nimport java.security.MessageDigest\nimport java.util.Locale\n\n/**\n * 横幅API模式服务\n * 用于从随机图API或本地目录加载模块横幅图片\n */\nobject BannerApiService {\n    private const val TAG = \"BannerApiService\"\n    private const val API_BANNER_DIR_NAME = \"api_banners\"\n    private const val CACHE_TTL_MS = 24 * 60 * 60 * 1000L\n\n    // 支持的图片扩展名\n    private val IMAGE_EXTENSIONS = setOf(\".jpg\", \".jpeg\", \".png\", \".webp\", \".gif\", \".bmp\")\n\n    /**\n     * 获取模块的横幅图片（API模式）\n     * @param context Context\n     * @param moduleId 模块ID\n     * @param source API URL或本地目录路径\n     * @return 图片字节数组，失败返回null\n     */\n    suspend fun getModuleBanner(\n        context: Context,\n        moduleId: String,\n        source: String\n    ): ByteArray? {\n        if (source.isBlank()) {\n            Log.w(TAG, \"API source is empty\")\n            return null\n        }\n\n        val trimmedSource = source.trim()\n        // Generate source hash for cache key - each API source has its own cache\n        val sourceHash = getSourceHash(trimmedSource)\n\n        return try {\n            // 首先检查缓存（包含source hash）\n            val cachedBanner = getCachedBanner(context, moduleId, sourceHash)\n            if (cachedBanner != null) {\n                Log.d(TAG, \"Using cached banner for module: $moduleId (source: $sourceHash)\")\n                return cachedBanner\n            }\n\n            val bannerData = if (isLocalDirectory(trimmedSource)) {\n                getFromLocalDirectory(context, moduleId, trimmedSource)\n            } else {\n                getFromApi(context, moduleId, trimmedSource)\n            }\n\n            if (bannerData != null) {\n                // 缓存图片（包含source hash）\n                cacheBanner(context, moduleId, sourceHash, bannerData)\n            }\n\n            bannerData\n        } catch (e: Exception) {\n            Log.e(TAG, \"Failed to get banner for module $moduleId: ${e.message}\", e)\n            null\n        }\n    }\n\n    /**\n     * Generate a hash for the API source URL/path\n     * This ensures each API source has its own cache namespace\n     */\n    private fun getSourceHash(source: String): String {\n        val md = MessageDigest.getInstance(\"MD5\")\n        val digest = md.digest(source.toByteArray())\n        return digest.take(8).joinToString(\"\") { String.format(\"%02x\", it) }\n    }\n\n    /**\n     * 判断source是本地目录还是远程API\n     */\n    private fun isLocalDirectory(source: String): Boolean {\n        return source.startsWith(\"/\") || source.startsWith(\"file://\")\n    }\n\n    /**\n     * 从本地目录获取随机图片\n     * 为每个模块分配固定的随机索引，确保同一模块每次使用相同图片\n     */\n    private suspend fun getFromLocalDirectory(\n        context: Context,\n        moduleId: String,\n        directoryPath: String\n    ): ByteArray? = withContext(Dispatchers.IO) {\n        try {\n            val cleanPath = directoryPath.removePrefix(\"file://\")\n            val dir = File(cleanPath)\n\n            if (!dir.exists() || !dir.isDirectory) {\n                Log.w(TAG, \"Directory does not exist or is not a directory: $cleanPath\")\n                return@withContext null\n            }\n\n            // 获取目录中所有图片文件\n            val imageFiles = dir.listFiles()?.filter { file ->\n                file.isFile && file.extension.lowercase(Locale.getDefault()) in IMAGE_EXTENSIONS.map { it.removePrefix(\".\") }\n            }?.sortedBy { it.name } ?: emptyList()\n\n            if (imageFiles.isEmpty()) {\n                Log.w(TAG, \"No image files found in directory: $cleanPath\")\n                return@withContext null\n            }\n\n            // 使用模块ID的哈希值确定索引，确保同一模块每次获得相同图片\n            val index = getIndexFromModuleId(moduleId, imageFiles.size)\n            val selectedFile = imageFiles[index]\n\n            Log.d(TAG, \"Loading image from local directory for module $moduleId: ${selectedFile.name} (index $index of ${imageFiles.size})\")\n            selectedFile.readBytes()\n        } catch (e: Exception) {\n            Log.e(TAG, \"Failed to load image from local directory: ${e.message}\", e)\n            null\n        }\n    }\n\n    /**\n     * 从随机图API获取图片\n     * 使用模块ID作为随机种子，确保同一模块每次获得相同图片\n     */\n    private suspend fun getFromApi(\n        context: Context,\n        moduleId: String,\n        apiUrl: String\n    ): ByteArray? = withContext(Dispatchers.IO) {\n        try {\n            // 构建带种子的URL\n            val urlWithSeed = buildUrlWithSeed(apiUrl, moduleId)\n\n            Log.d(TAG, \"Fetching banner from API for module $moduleId: $urlWithSeed\")\n\n            val request = Request.Builder()\n                .url(urlWithSeed)\n                .build()\n\n            val response = apApp.okhttpClient.newCall(request).execute()\n\n            if (!response.isSuccessful) {\n                Log.w(TAG, \"API request failed with code: ${response.code}\")\n                return@withContext null\n            }\n\n            response.body?.bytes()?.also {\n                Log.d(TAG, \"Successfully loaded banner from API for module $moduleId, size: ${it.size}\")\n            }\n        } catch (e: Exception) {\n            Log.e(TAG, \"Failed to fetch banner from API: ${e.message}\", e)\n            null\n        }\n    }\n\n    /**\n     * 构建带种子的URL\n     * 根据URL格式智能添加种子参数\n     */\n    private fun buildUrlWithSeed(baseUrl: String, moduleId: String): String {\n        val seed = getSeedFromModuleId(moduleId)\n\n        // 检查URL是否已包含查询参数\n        return if (baseUrl.contains(\"?\")) {\n            // 已有查询参数，追加种子\n            \"$baseUrl&seed=$seed\"\n        } else {\n            // 无查询参数，添加种子\n            \"$baseUrl?seed=$seed\"\n        }\n    }\n\n    /**\n     * 使用模块ID生成确定的索引\n     */\n    private fun getIndexFromModuleId(moduleId: String, listSize: Int): Int {\n        val hash = moduleId.hashCode().let { if (it < 0) -it else it }\n        return hash % listSize\n    }\n\n    /**\n     * 使用模块ID生成种子值\n     */\n    private fun getSeedFromModuleId(moduleId: String): String {\n        val md = MessageDigest.getInstance(\"MD5\")\n        val digest = md.digest(moduleId.toByteArray())\n        return digest.take(4).joinToString(\"\") { String.format(\"%02x\", it) }\n    }\n\n    /**\n     * 获取API横幅缓存目录\n     */\n    private fun getApiBannerDir(context: Context): File {\n        val dir = File(context.filesDir, API_BANNER_DIR_NAME)\n        if (!dir.exists()) {\n            dir.mkdirs()\n        }\n        return dir\n    }\n\n    /**\n     * 获取模块的缓存横幅文件\n     * 包含source hash以确保不同API源的缓存隔离\n     */\n    private fun getCachedBannerFile(context: Context, moduleId: String, sourceHash: String): File {\n        val sanitizedId = sanitizeModuleId(moduleId)\n        // Include source hash in filename: sourceHash_moduleId\n        val fileName = \"${sourceHash}_$sanitizedId\"\n        return File(getApiBannerDir(context), fileName)\n    }\n\n    /**\n     * 获取缓存的横幅\n     */\n    private suspend fun getCachedBanner(context: Context, moduleId: String, sourceHash: String): ByteArray? = withContext(Dispatchers.IO) {\n        try {\n            val file = getCachedBannerFile(context, moduleId, sourceHash)\n            if (file.exists()) {\n                if (System.currentTimeMillis() - file.lastModified() > CACHE_TTL_MS) {\n                    file.delete()\n                    Log.d(TAG, \"Cache expired for module $moduleId (source: $sourceHash)\")\n                    return@withContext null\n                }\n                file.readBytes()\n            } else {\n                null\n            }\n        } catch (e: Exception) {\n            Log.e(TAG, \"Failed to read cached banner: ${e.message}\", e)\n            null\n        }\n    }\n\n    /**\n     * 缓存横幅图片\n     */\n    private suspend fun cacheBanner(context: Context, moduleId: String, sourceHash: String, data: ByteArray) = withContext(Dispatchers.IO) {\n        try {\n            val file = getCachedBannerFile(context, moduleId, sourceHash)\n            file.writeBytes(data)\n            Log.d(TAG, \"Cached banner for module $moduleId (source: $sourceHash)\")\n        } catch (e: Exception) {\n            Log.e(TAG, \"Failed to cache banner: ${e.message}\", e)\n        }\n    }\n\n    /**\n     * 清除所有API模式横幅缓存\n     */\n    suspend fun clearAllCache(context: Context) = withContext(Dispatchers.IO) {\n        try {\n            val dir = getApiBannerDir(context)\n            dir.listFiles()?.forEach { it.delete() }\n            Log.d(TAG, \"Cleared all API banner cache\")\n        } catch (e: Exception) {\n            Log.e(TAG, \"Failed to clear cache: ${e.message}\", e)\n        }\n    }\n\n    /**\n     * 清除指定模块的所有缓存（跨所有API源）\n     */\n    suspend fun clearModuleCache(context: Context, moduleId: String) = withContext(Dispatchers.IO) {\n        try {\n            val sanitizedId = sanitizeModuleId(moduleId)\n            val dir = getApiBannerDir(context)\n            // Find all cache files for this module (pattern: *_<moduleId>)\n            dir.listFiles()?.filter { file ->\n                file.name.endsWith(\"_$sanitizedId\")\n            }?.forEach { file ->\n                file.delete()\n                Log.d(TAG, \"Cleared cache file: ${file.name}\")\n            }\n            Log.d(TAG, \"Cleared all cache for module $moduleId\")\n        } catch (e: Exception) {\n            Log.e(TAG, \"Failed to clear module cache: ${e.message}\", e)\n        }\n    }\n\n    /**\n     * 清理模块ID，确保文件名安全\n     */\n    private fun sanitizeModuleId(raw: String): String {\n        return raw.replace(Regex(\"[^a-zA-Z0-9._-]\"), \"_\")\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/BottomBarDestination.kt",
    "content": "package me.bmax.apatch.ui.screen\n\nimport androidx.annotation.StringRes\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.Apps\nimport androidx.compose.material.icons.filled.Archive\nimport androidx.compose.material.icons.filled.AdminPanelSettings\nimport androidx.compose.material.icons.filled.Extension\nimport androidx.compose.material.icons.filled.Home\nimport androidx.compose.material.icons.filled.Settings\nimport androidx.compose.material.icons.outlined.Apps\nimport androidx.compose.material.icons.outlined.Archive\nimport androidx.compose.material.icons.outlined.AdminPanelSettings\nimport androidx.compose.material.icons.outlined.Extension\nimport androidx.compose.material.icons.outlined.Home\nimport androidx.compose.material.icons.outlined.Settings\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport com.ramcosta.composedestinations.generated.destinations.APModuleScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.HomeScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.KPModuleScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.OnlineKPMScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.SettingScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.SuperUserScreenDestination\nimport com.ramcosta.composedestinations.spec.DirectionDestinationSpec\nimport me.bmax.apatch.R\n\nenum class BottomBarDestination(\n    val direction: DirectionDestinationSpec,\n    @param:StringRes val label: Int,\n    val iconSelected: ImageVector,\n    val iconNotSelected: ImageVector,\n    val kPatchRequired: Boolean,\n    val aPatchRequired: Boolean,\n) {\n    Home(\n        HomeScreenDestination,\n        R.string.home,\n        Icons.Filled.Home,\n        Icons.Outlined.Home,\n        false,\n        false\n    ),\n    KModule(\n        KPModuleScreenDestination,\n        R.string.kpm,\n        Icons.Filled.Archive,\n        Icons.Outlined.Archive,\n        true,\n        false\n    ),\n    SuperUser(\n        SuperUserScreenDestination,\n        R.string.su_title,\n        Icons.Filled.AdminPanelSettings,\n        Icons.Outlined.AdminPanelSettings,\n        true,\n        false\n    ),\n    AModule(\n        APModuleScreenDestination,\n        R.string.apm,\n        Icons.Filled.Extension,\n        Icons.Outlined.Extension,\n        false,\n        true\n    ),\n    Settings(\n        SettingScreenDestination,\n        R.string.settings,\n        Icons.Filled.Settings,\n        Icons.Outlined.Settings,\n        false,\n        false\n    )\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/ExecuteAPMAction.kt",
    "content": "package me.bmax.apatch.ui.screen\n\nimport android.os.Environment\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.filled.Save\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.SnackbarHost\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.input.key.Key\nimport androidx.compose.ui.input.key.key\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontFamily\nimport androidx.compose.ui.unit.dp\nimport androidx.lifecycle.compose.dropUnlessResumed\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.component.KeyEventBlocker\nimport me.bmax.apatch.util.getSafeDownloadsDir\nimport me.bmax.apatch.util.runAPModuleAction\nimport me.bmax.apatch.util.ui.LocalSnackbarHost\nimport java.io.File\nimport java.text.SimpleDateFormat\nimport java.util.Date\nimport java.util.Locale\n\n@Composable\n@Destination<RootGraph>\nfun ExecuteAPMActionScreen(navigator: DestinationsNavigator, moduleId: String) {\n    var text by rememberSaveable { mutableStateOf(\"\") }\n    val displayBuffer = remember { StringBuffer() }\n    val fullLogBuffer = remember { StringBuffer() }\n    val snackBarHost = LocalSnackbarHost.current\n    val scope = rememberCoroutineScope()\n    val scrollState = rememberScrollState()\n    var actionResult: Boolean\n\n    LaunchedEffect(Unit) {\n        if (text.isNotEmpty()) {\n            return@LaunchedEffect\n        }\n\n        val updaterJob = launch {\n            while (true) {\n                kotlinx.coroutines.delay(100)\n                val newText = displayBuffer.toString()\n                if (text.length != newText.length) {\n                    text = newText\n                }\n            }\n        }\n\n        withContext(Dispatchers.IO) {\n            runAPModuleAction(\n                moduleId,\n                onStdout = {\n                    val tempText = \"$it\\n\"\n                    if (tempText.startsWith(\"\u001b[H\u001b[J\")) { // clear command\n                        displayBuffer.setLength(0)\n                        displayBuffer.append(tempText.substring(6))\n                    } else {\n                        displayBuffer.append(tempText)\n                    }\n                    fullLogBuffer.append(it).append(\"\\n\")\n                },\n                onStderr = {\n                    fullLogBuffer.append(it).append(\"\\n\")\n                }\n            ).let {\n                actionResult = it\n            }\n        }\n\n        updaterJob.cancel()\n        val finalText = displayBuffer.toString()\n        if (text.length != finalText.length) {\n            text = finalText\n        }\n\n        if (actionResult) {\n            if (!APApplication.sharedPreferences.getBoolean(\"apm_action_stay_on_page\", true)) {\n                navigator.popBackStack()\n            }\n        }\n    }\n\n    Scaffold(\n        topBar = {\n            TopBar(\n                onBack = dropUnlessResumed {\n                    navigator.popBackStack()\n                },\n                onSave = {\n                    scope.launch {\n                        val format = SimpleDateFormat(\"yyyy-MM-dd-HH-mm-ss\", Locale.getDefault())\n                        val date = format.format(Date())\n                        val file = File(\n                            getSafeDownloadsDir(me.bmax.apatch.apApp),\n                            \"APatch_apm_action_log_${date}.log\"\n                        )\n                        file.writeText(fullLogBuffer.toString())\n                        snackBarHost.showSnackbar(\"Log saved to ${file.absolutePath}\")\n                    }\n                }\n            )\n        },\n        snackbarHost = { SnackbarHost(snackBarHost) }\n    ) { innerPadding ->\n        KeyEventBlocker {\n            it.key == Key.VolumeDown || it.key == Key.VolumeUp\n        }\n        Column(\n            modifier = Modifier\n                .fillMaxSize(1f)\n                .padding(innerPadding)\n                .verticalScroll(scrollState),\n        ) {\n            LaunchedEffect(text) {\n                scrollState.animateScrollTo(scrollState.maxValue)\n            }\n            Text(\n                modifier = Modifier.padding(8.dp),\n                text = text,\n                fontSize = MaterialTheme.typography.bodySmall.fontSize,\n                fontFamily = FontFamily.Monospace,\n                lineHeight = MaterialTheme.typography.bodySmall.lineHeight,\n            )\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun TopBar(onBack: () -> Unit = {}, onSave: () -> Unit = {}) {\n    TopAppBar(\n        title = { Text(stringResource(R.string.apm_action)) },\n        navigationIcon = {\n            IconButton(\n                onClick = onBack\n            ) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) }\n        },\n        actions = {\n            IconButton(onClick = onSave) {\n                Icon(\n                    imageVector = Icons.Filled.Save,\n                    contentDescription = \"Save log\"\n                )\n            }\n        }\n    )\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/Home.kt",
    "content": "package me.bmax.apatch.ui.screen\n\nimport android.os.Build\nimport android.system.Os\nimport androidx.annotation.StringRes\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.aspectRatio\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.offset\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.layout.wrapContentHeight\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.foundation.text.KeyboardOptions\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.outlined.HelpOutline\nimport androidx.compose.material.icons.automirrored.outlined.RotateRight\nimport androidx.compose.material.icons.filled.CheckCircle\nimport androidx.compose.material.icons.filled.AutoFixHigh\nimport androidx.compose.material.icons.filled.InstallMobile\nimport androidx.compose.material.icons.filled.MoreVert\nimport androidx.compose.material.icons.filled.Pause\nimport androidx.compose.material.icons.filled.PowerSettingsNew\nimport androidx.compose.material.icons.filled.PlayArrow\nimport androidx.compose.material.icons.filled.Refresh\nimport androidx.compose.material.icons.filled.RadioButtonChecked\nimport androidx.compose.material.icons.filled.RadioButtonUnchecked\nimport androidx.compose.material.icons.filled.Fingerprint\nimport androidx.compose.material.icons.filled.Visibility\nimport androidx.compose.material.icons.filled.VisibilityOff\nimport androidx.compose.material.icons.filled.SwapVerticalCircle\nimport androidx.compose.material.icons.filled.SystemUpdate\nimport me.bmax.apatch.ui.theme.MusicConfig\nimport me.bmax.apatch.util.MusicManager\nimport androidx.compose.material.icons.filled.Warning\nimport androidx.compose.material.icons.outlined.Block\nimport androidx.compose.material.icons.outlined.Cached\nimport androidx.compose.material.icons.outlined.CheckCircle\nimport androidx.compose.material.icons.outlined.Clear\nimport androidx.compose.material.icons.outlined.Delete\nimport androidx.compose.material.icons.outlined.DeleteForever\nimport androidx.compose.material.icons.outlined.InstallMobile\nimport androidx.compose.material.icons.outlined.SystemUpdate\nimport androidx.compose.material.icons.outlined.Android\nimport androidx.compose.material.icons.outlined.Extension\nimport androidx.compose.material.icons.outlined.Terminal\nimport androidx.compose.material.icons.outlined.Code\nimport androidx.compose.material.icons.outlined.DeveloperBoard\nimport androidx.compose.material.icons.outlined.Info\nimport androidx.compose.material.icons.outlined.Layers\nimport androidx.compose.material.icons.outlined.PhoneAndroid\nimport androidx.compose.material.icons.outlined.Shield\nimport androidx.compose.material.icons.outlined.SdStorage\nimport androidx.compose.material.icons.outlined.DeveloperMode\nimport androidx.compose.material.icons.outlined.Download\nimport androidx.compose.material.icons.outlined.Memory\nimport androidx.compose.material.icons.outlined.RestartAlt\nimport androidx.compose.material3.AlertDialogDefaults\nimport androidx.compose.material3.AlertDialog\nimport androidx.compose.material3.BasicAlertDialog\nimport androidx.compose.material3.Button\nimport androidx.compose.material3.ButtonDefaults\nimport androidx.compose.material3.CardDefaults\nimport me.bmax.apatch.ui.theme.BackgroundConfig\nimport androidx.compose.material3.ElevatedCard\nimport androidx.compose.material3.Card\nimport androidx.compose.material3.surfaceColorAtElevation\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.OutlinedTextField\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.foundation.isSystemInDarkTheme\nimport androidx.compose.material3.Surface\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.MutableState\nimport androidx.compose.runtime.SideEffect\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.livedata.observeAsState\nimport androidx.compose.runtime.mutableStateOf\nimport me.bmax.apatch.ui.theme.refreshTheme\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.draw.alpha\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.ColorFilter\nimport androidx.compose.ui.graphics.ColorMatrix\nimport androidx.compose.ui.graphics.graphicsLayer\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport androidx.compose.ui.layout.ContentScale\nimport androidx.compose.ui.platform.LocalUriHandler\nimport androidx.compose.ui.platform.LocalView\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.input.KeyboardType\nimport androidx.compose.ui.text.input.PasswordVisualTransformation\nimport androidx.compose.ui.text.input.VisualTransformation\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.window.DialogProperties\nimport androidx.compose.ui.window.DialogWindowProvider\nimport androidx.compose.ui.window.SecureFlagPolicy\nimport androidx.lifecycle.compose.dropUnlessResumed\nimport coil.compose.AsyncImage\nimport coil.request.ImageRequest\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport com.ramcosta.composedestinations.generated.destinations.AboutScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.InstallModeSelectScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.PatchesDestination\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.Natives\nimport me.bmax.apatch.R\nimport me.bmax.apatch.apApp\nimport me.bmax.apatch.ui.component.ProvideMenuShape\nimport me.bmax.apatch.ui.component.WallpaperAwareDropdownMenu\nimport me.bmax.apatch.ui.component.WallpaperAwareDropdownMenuItem\nimport me.bmax.apatch.ui.component.rememberConfirmDialog\nimport me.bmax.apatch.ui.viewmodel.PatchesViewModel\nimport me.bmax.apatch.util.Version\nimport me.bmax.apatch.util.Version.getManagerVersion\nimport me.bmax.apatch.util.getSELinuxStatus\nimport me.bmax.apatch.util.reboot\nimport me.bmax.apatch.util.ui.APDialogBlurBehindUtils\nimport me.bmax.apatch.util.ui.HomeBottomSpacer\n\nprivate val managerVersion = getManagerVersion()\n\nprivate enum class ApatchUninstallOption(\n    @param:StringRes val titleRes: Int,\n    @param:StringRes val descRes: Int,\n    val icon: ImageVector,\n) {\n    PATCH_ONLY(\n        titleRes = R.string.home_dialog_uninstall_ap_only,\n        descRes = R.string.home_dialog_uninstall_ap_only_desc,\n        icon = Icons.Outlined.Delete\n    ),\n    FULL(\n        titleRes = R.string.home_dialog_uninstall_all,\n        descRes = R.string.home_dialog_uninstall_all_desc,\n        icon = Icons.Outlined.DeleteForever\n    ),\n}\n\n@Destination<RootGraph>(start = true)\n@Composable\nfun HomeScreen(navigator: DestinationsNavigator) {\n    var showPatchFloatAction by remember { mutableStateOf(true) }\n\n    val kpState by APApplication.kpStateLiveData.observeAsState(APApplication.State.UNKNOWN_STATE)\n    val apState by APApplication.apStateLiveData.observeAsState(APApplication.State.UNKNOWN_STATE)\n\n    SideEffect {\n        if (kpState != APApplication.State.UNKNOWN_STATE) {\n            showPatchFloatAction = false\n        }\n    }\n\n    var homeLayout by remember { mutableStateOf(APApplication.sharedPreferences.getString(\"home_layout_style\", \"circle\")) }\n    val homeRefreshObserver by refreshTheme.observeAsState(false)\n    if (homeRefreshObserver) {\n        homeLayout = APApplication.sharedPreferences.getString(\"home_layout_style\", \"circle\")\n    }\n\n    Scaffold(topBar = {\n        TopBar(onInstallClick = dropUnlessResumed {\n            navigator.navigate(InstallModeSelectScreenDestination)\n        }, navigator, kpState)\n    }) { innerPadding ->\n        when (homeLayout) {\n            \"kernelsu\" -> HomeScreenV2(innerPadding, navigator, kpState, apState)\n            \"focus\" -> HomeScreenV3(innerPadding, navigator, kpState, apState)\n            \"sign\" -> HomeScreenSign(innerPadding, navigator, kpState, apState)\n            \"circle\" -> HomeScreenCircle(innerPadding, navigator, kpState, apState)\n            \"dashboard_ui\" -> HomeScreenV4(innerPadding, navigator, kpState, apState)\n            \"stats\" -> HomeScreenStats(innerPadding, navigator, kpState, apState)\n            else -> HomeScreenV1(innerPadding, navigator, kpState, apState)\n        }\n    }\n}\n\n@Composable\nfun HomeScreenV1(\n    innerPadding: PaddingValues,\n    navigator: DestinationsNavigator,\n    kpState: APApplication.State,\n    apState: APApplication.State\n) {\n    Column(\n        modifier = Modifier\n            .padding(innerPadding)\n            .padding(horizontal = 16.dp)\n            .verticalScroll(rememberScrollState()),\n        verticalArrangement = Arrangement.spacedBy(16.dp)\n    ) {\n        Spacer(Modifier.height(0.dp))\n        KStatusCard(kpState, apState, navigator)\n        if (kpState != APApplication.State.UNKNOWN_STATE && apState != APApplication.State.UNKNOWN_STATE && apState != APApplication.State.ANDROIDPATCH_INSTALLED) {\n            AStatusCard(apState)\n        }\n        val hideApatchCard = APApplication.sharedPreferences.getBoolean(\"hide_apatch_card\", false)\n        if (!hideApatchCard) {\n            LearnMoreCard()\n        }\n        HomeBottomSpacer()\n    }\n}\n\n@Composable\nfun HomeScreenSign(\n    innerPadding: PaddingValues,\n    navigator: DestinationsNavigator,\n    kpState: APApplication.State,\n    apState: APApplication.State\n) {\n    Column(\n        modifier = Modifier\n            .padding(innerPadding)\n            .padding(horizontal = 16.dp)\n            .verticalScroll(rememberScrollState()),\n        verticalArrangement = Arrangement.spacedBy(16.dp)\n    ) {\n        Spacer(Modifier.height(0.dp))\n        KStatusCard(kpState, apState, navigator)\n        if (kpState != APApplication.State.UNKNOWN_STATE && apState != APApplication.State.UNKNOWN_STATE && apState != APApplication.State.ANDROIDPATCH_INSTALLED) {\n            AStatusCard(apState)\n        }\n        SignInfoCard(kpState, apState)\n        val hideApatchCard = APApplication.sharedPreferences.getBoolean(\"hide_apatch_card\", false)\n        if (!hideApatchCard) {\n            LearnMoreCard()\n        }\n        HomeBottomSpacer()\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun UninstallDialog(showDialog: MutableState<Boolean>, navigator: DestinationsNavigator) {\n    if (!showDialog.value) return\n\n    val options = remember { listOf(ApatchUninstallOption.PATCH_ONLY, ApatchUninstallOption.FULL) }\n    var selectedOption by remember { mutableStateOf<ApatchUninstallOption?>(null) }\n\n    MaterialTheme(\n        colorScheme = MaterialTheme.colorScheme.copy(\n            surface = MaterialTheme.colorScheme.surfaceContainerHigh\n        )\n    ) {\n        AlertDialog(\n            onDismissRequest = { showDialog.value = false },\n            title = {\n                Text(\n                    text = stringResource(R.string.home_dialog_uninstall_title),\n                    style = MaterialTheme.typography.headlineSmall,\n                )\n            },\n            text = {\n                Column(\n                    modifier = Modifier.padding(vertical = 8.dp),\n                    verticalArrangement = Arrangement.spacedBy(16.dp),\n                ) {\n                    options.forEach { option ->\n                        val isSelected = selectedOption == option\n                        val backgroundColor = if (isSelected) {\n                            MaterialTheme.colorScheme.primaryContainer\n                        } else {\n                            Color.Transparent\n                        }\n                        val subtitleColor = if (isSelected) {\n                            MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.8f)\n                        } else {\n                            MaterialTheme.colorScheme.onSurfaceVariant\n                        }\n\n                        Row(\n                            modifier = Modifier\n                                .fillMaxWidth()\n                                .clip(MaterialTheme.shapes.medium)\n                                .background(backgroundColor)\n                                .clickable { selectedOption = option }\n                                .padding(vertical = 12.dp, horizontal = 8.dp),\n                            verticalAlignment = Alignment.CenterVertically\n                        ) {\n                            Icon(\n                                imageVector = option.icon,\n                                contentDescription = null,\n                                tint = MaterialTheme.colorScheme.primary,\n                                modifier = Modifier\n                                    .padding(end = 16.dp)\n                                    .size(24.dp)\n                            )\n                            Column(modifier = Modifier.weight(1f)) {\n                                Text(\n                                    text = stringResource(option.titleRes),\n                                    style = MaterialTheme.typography.titleMedium,\n                                )\n                                Text(\n                                    text = stringResource(option.descRes),\n                                    style = MaterialTheme.typography.bodyMedium,\n                                    color = subtitleColor,\n                                )\n                            }\n                            Icon(\n                                imageVector = if (isSelected) {\n                                    Icons.Filled.RadioButtonChecked\n                                } else {\n                                    Icons.Filled.RadioButtonUnchecked\n                                },\n                                contentDescription = null,\n                                tint = if (isSelected) {\n                                    MaterialTheme.colorScheme.primary\n                                } else {\n                                    MaterialTheme.colorScheme.onSurfaceVariant\n                                },\n                                modifier = Modifier.size(24.dp)\n                            )\n                        }\n                    }\n                }\n\n                val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n                SideEffect {\n                    APDialogBlurBehindUtils.setupWindowBlurListener(dialogWindowProvider.window)\n                }\n            },\n            confirmButton = {\n                Button(\n                    onClick = {\n                        when (selectedOption) {\n                            ApatchUninstallOption.PATCH_ONLY -> {\n                                showDialog.value = false\n                                APApplication.uninstallApatch()\n                            }\n\n                            ApatchUninstallOption.FULL -> {\n                                showDialog.value = false\n                                APApplication.uninstallApatch()\n                                navigator.navigate(PatchesDestination(PatchesViewModel.PatchMode.UNPATCH))\n                            }\n\n                            null -> Unit\n                        }\n                    },\n                    enabled = selectedOption != null,\n                ) {\n                    Text(text = stringResource(android.R.string.ok))\n                }\n            },\n            dismissButton = {\n                TextButton(onClick = { showDialog.value = false }) {\n                    Text(text = stringResource(android.R.string.cancel))\n                }\n            },\n            shape = MaterialTheme.shapes.extraLarge,\n            tonalElevation = 4.dp,\n        )\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun AuthFailedTipDialog(showDialog: MutableState<Boolean>) {\n    BasicAlertDialog(\n        onDismissRequest = { showDialog.value = false }, properties = DialogProperties(\n            decorFitsSystemWindows = true,\n            usePlatformDefaultWidth = false,\n            securePolicy = SecureFlagPolicy.SecureOff\n        )\n    ) {\n        Surface(\n            modifier = Modifier\n                .width(320.dp)\n                .wrapContentHeight(),\n            shape = RoundedCornerShape(20.dp),\n            tonalElevation = AlertDialogDefaults.TonalElevation,\n            color = AlertDialogDefaults.containerColor,\n        ) {\n            Column(modifier = Modifier.padding(PaddingValues(all = 24.dp))) {\n                // Title\n                Box(\n                    Modifier\n                        .padding(PaddingValues(bottom = 16.dp))\n                        .align(Alignment.Start)\n                ) {\n                    Text(\n                        text = stringResource(id = R.string.home_dialog_auth_fail_title),\n                        style = MaterialTheme.typography.headlineSmall\n                    )\n                }\n\n                // Content\n                Box(\n                    Modifier\n                        .weight(weight = 1f, fill = false)\n                        .padding(PaddingValues(bottom = 24.dp))\n                        .align(Alignment.Start)\n                ) {\n                    Text(\n                        text = stringResource(id = R.string.home_dialog_auth_fail_content),\n                        style = MaterialTheme.typography.bodyMedium\n                    )\n                }\n\n                // Buttons\n                Row(\n                    modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End\n                ) {\n                    val uriHandler = LocalUriHandler.current\n                    TextButton(onClick = {\n                        uriHandler.openUri(\"https://fp.mysqil.com/\")\n                    }) {\n                        Text(text = stringResource(R.string.home_more_menu_document))\n                    }\n                    TextButton(onClick = { showDialog.value = false }) {\n                        Text(text = stringResource(id = android.R.string.ok))\n                    }\n                }\n            }\n            val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n            APDialogBlurBehindUtils.setupWindowBlurListener(dialogWindowProvider.window)\n        }\n    }\n\n}\n\nval checkSuperKeyValidation: (superKey: String) -> Boolean = { superKey ->\n    superKey.length in 8..63 && superKey.all { it.isLetterOrDigit() } && superKey.any { it.isDigit() } && superKey.any { it.isLetter() }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun AuthSuperKey(showDialog: MutableState<Boolean>, showFailedDialog: MutableState<Boolean>) {\n    var key by remember { mutableStateOf(\"\") }\n    var keyVisible by remember { mutableStateOf(false) }\n    var enable by remember { mutableStateOf(false) }\n\n    BasicAlertDialog(\n        onDismissRequest = { showDialog.value = false }, properties = DialogProperties(\n            decorFitsSystemWindows = true,\n            usePlatformDefaultWidth = false,\n            securePolicy = SecureFlagPolicy.SecureOff\n        )\n    ) {\n        Surface(\n            modifier = Modifier\n                .width(310.dp)\n                .wrapContentHeight(),\n            shape = RoundedCornerShape(30.dp),\n            tonalElevation = AlertDialogDefaults.TonalElevation,\n            color = AlertDialogDefaults.containerColor,\n        ) {\n            Column(modifier = Modifier.padding(PaddingValues(all = 24.dp))) {\n                // Title\n                Box(\n                    Modifier\n                        .padding(PaddingValues(bottom = 16.dp))\n                        .align(Alignment.Start)\n                ) {\n                    Text(\n                        text = stringResource(id = R.string.home_auth_key_title),\n                        style = MaterialTheme.typography.headlineSmall\n                    )\n                }\n\n                // Content\n                Box(\n                    Modifier\n                        .weight(weight = 1f, fill = false)\n                        .align(Alignment.Start)\n                ) {\n                    Text(\n                        text = stringResource(id = R.string.home_auth_key_desc),\n                        style = MaterialTheme.typography.bodyMedium\n                    )\n                }\n\n                // Content2\n                Box(\n                    contentAlignment = Alignment.CenterEnd,\n                ) {\n                    OutlinedTextField(\n                        value = key,\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .padding(top = 6.dp),\n                        onValueChange = {\n                            key = it\n                            enable = checkSuperKeyValidation(key)\n                        },\n                        shape = RoundedCornerShape(50.0f),\n                        label = { Text(stringResource(id = R.string.super_key)) },\n                        visualTransformation = if (keyVisible) VisualTransformation.None else PasswordVisualTransformation(),\n                        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)\n                    )\n                    IconButton(\n                        modifier = Modifier\n                            .size(40.dp)\n                            .padding(top = 15.dp, end = 5.dp),\n                        onClick = { keyVisible = !keyVisible }) {\n                        Icon(\n                            imageVector = if (keyVisible) Icons.Default.Visibility else Icons.Default.VisibilityOff,\n                            contentDescription = null,\n                            tint = Color.Gray\n                        )\n                    }\n                }\n\n                Spacer(modifier = Modifier.height(12.dp))\n                // Buttons\n                Row(\n                    modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End\n                ) {\n                    TextButton(onClick = { showDialog.value = false }) {\n                        Text(stringResource(id = android.R.string.cancel))\n                    }\n\n                    Button(onClick = {\n                        showDialog.value = false\n\n                        val preVerifyKey = Natives.nativeReady(key)\n                        if (preVerifyKey) {\n                            APApplication.setSuperKeyAndRefresh(key)\n                        } else {\n                            showFailedDialog.value = true\n                        }\n\n                    }, enabled = enable) {\n                        Text(stringResource(id = android.R.string.ok))\n                    }\n                }\n            }\n        }\n        val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n        APDialogBlurBehindUtils.setupWindowBlurListener(dialogWindowProvider.window)\n    }\n}\n\nprivate data class RebootOption(\n    @param:StringRes val titleRes: Int,\n    val reason: String,\n    val icon: ImageVector\n)\n\n@Composable\nprivate fun getRebootOptions(): List<RebootOption> = listOf(\n    RebootOption(R.string.reboot, \"\", Icons.Filled.Refresh),\n    RebootOption(R.string.reboot_recovery, \"recovery\", Icons.Outlined.SystemUpdate),\n    RebootOption(R.string.reboot_bootloader, \"bootloader\", Icons.Outlined.Memory),\n    RebootOption(R.string.reboot_download, \"download\", Icons.Outlined.Download),\n    RebootOption(R.string.reboot_edl, \"edl\", Icons.Outlined.DeveloperMode),\n    RebootOption(R.string.reboot_fastbootd, \"fastboot\", Icons.Outlined.RestartAlt),\n)\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun RebootDialog(\n    show: Boolean,\n    onDismiss: () -> Unit,\n    onReboot: (String) -> Unit\n) {\n    if (!show) return\n\n    val options = getRebootOptions()\n\n    BasicAlertDialog(onDismissRequest = onDismiss) {\n        Surface(\n            shape = RoundedCornerShape(28.dp),\n            color = MaterialTheme.colorScheme.surfaceContainerHigh\n        ) {\n            Column(modifier = Modifier.padding(24.dp)) {\n                Column(\n                    modifier = Modifier.padding(top = 4.dp),\n                    verticalArrangement = Arrangement.spacedBy(2.dp)\n                ) {\n                    options.forEach { option ->\n                        Surface(\n                            shape = RoundedCornerShape(16.dp),\n                            color = MaterialTheme.colorScheme.surfaceContainerLowest,\n                            onClick = {\n                                onDismiss()\n                                onReboot(option.reason)\n                            }\n                        ) {\n                            Row(\n                                modifier = Modifier\n                                    .fillMaxWidth()\n                                    .padding(horizontal = 16.dp, vertical = 12.dp),\n                                verticalAlignment = Alignment.CenterVertically\n                            ) {\n                                Box(\n                                    modifier = Modifier\n                                        .size(40.dp)\n                                        .background(\n                                            color = MaterialTheme.colorScheme.secondaryContainer,\n                                            shape = CircleShape\n                                        ),\n                                    contentAlignment = Alignment.Center\n                                ) {\n                                    Icon(\n                                        imageVector = option.icon,\n                                        contentDescription = null,\n                                        modifier = Modifier.size(20.dp),\n                                        tint = MaterialTheme.colorScheme.onSecondaryContainer\n                                    )\n                                }\n                                Spacer(modifier = Modifier.width(16.dp))\n                                Text(\n                                    text = stringResource(option.titleRes),\n                                    style = MaterialTheme.typography.bodyLarge\n                                )\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun TopBar(\n    onInstallClick: () -> Unit, navigator: DestinationsNavigator, kpState: APApplication.State\n) {\n    val uriHandler = LocalUriHandler.current\n    val context = androidx.compose.ui.platform.LocalContext.current\n    var showDropdownMoreOptions by remember { mutableStateOf(false) }\n    var showDropdownReboot by remember { mutableStateOf(false) }\n    val prefs = APApplication.sharedPreferences\n    val darkThemeFollowSys = prefs.getBoolean(\"night_mode_follow_sys\", false)\n    val nightModeEnabled = prefs.getBoolean(\"night_mode_enabled\", true)\n    val isDarkTheme = if (darkThemeFollowSys) {\n        isSystemInDarkTheme()\n    } else {\n        nightModeEnabled\n    }\n    \n    val currentTitle = prefs.getString(\"app_title\", \"folkpatch\") ?: \"folkpatch\"\n    val customAppTitle = prefs.getString(\"custom_app_title\", \"FolkPatch\") ?: \"FolkPatch\"\n    val isCustomTitle = currentTitle == \"custom\"\n    val titleResId = when (currentTitle) {\n        \"custom\" -> null\n        \"fpatch\" -> R.string.app_title_fpatch\n        \"apatch_folk\" -> R.string.app_title_apatch_folk\n        \"apatchx\" -> R.string.app_title_apatchx\n        \"apatch\" -> R.string.app_title_apatch\n        \"kernelpatch\" -> R.string.app_title_kernelpatch\n        \"kernelsu\" -> R.string.app_title_kernelsu\n        \"supersu\" -> R.string.app_title_supersu\n        \"folksu\" -> R.string.app_title_fpatch\n        \"superuser\" -> R.string.app_title_superuser\n        \"superpatch\" -> R.string.app_title_superpatch\n        \"magicpatch\" -> R.string.app_title_magicpatch\n        else -> R.string.app_title_folkpatch\n    }\n\n    val useAdvancedTitleStyle = BackgroundConfig.isAdvancedTitleStyleEnabled && \n                                !BackgroundConfig.titleImageUri.isNullOrEmpty()\n    val titleOpacity = if (useAdvancedTitleStyle) {\n        BackgroundConfig.getEffectiveTitleImageOpacity(isDarkTheme)\n    } else 1f\n    val titleDim = if (useAdvancedTitleStyle) {\n        BackgroundConfig.getEffectiveTitleImageDim(isDarkTheme)\n    } else 0f\n    val titleOffsetX = if (useAdvancedTitleStyle) {\n        BackgroundConfig.titleImageOffsetX * 100f\n    } else 0f\n\n    TopAppBar(title = {\n        if (useAdvancedTitleStyle) {\n            AsyncImage(\n                model = ImageRequest.Builder(context)\n                    .data(BackgroundConfig.titleImageUri)\n                    .crossfade(true)\n                    .build(),\n                contentDescription = titleResId?.let { stringResource(it) } ?: customAppTitle,\n                modifier = Modifier\n                    .height(40.dp)\n                    .offset(x = titleOffsetX.dp)\n                    .alpha(titleOpacity)\n                    .graphicsLayer {\n                        if (titleDim > 0f) {\n                            colorFilter = ColorFilter.colorMatrix(\n                                ColorMatrix().apply {\n                                    setToScale(\n                                        1f - titleDim,\n                                        1f - titleDim,\n                                        1f - titleDim,\n                                        1f\n                                    )\n                                }\n                            )\n                        }\n                    },\n                contentScale = ContentScale.Fit\n            )\n        } else {\n            Text(if (isCustomTitle) customAppTitle else stringResource(titleResId!!))\n        }\n    }, actions = {\n        if (MusicConfig.isMusicEnabled) {\n            val isPlaying by MusicManager.isPlaying.collectAsState()\n            IconButton(onClick = { MusicManager.toggle() }) {\n                Icon(\n                    imageVector = if (isPlaying) Icons.Filled.Pause else Icons.Filled.PlayArrow,\n                    contentDescription = \"Music Control\"\n                )\n            }\n        }\n\n        IconButton(onClick = onInstallClick) {\n            Icon(\n                imageVector = Icons.Filled.AutoFixHigh,\n                contentDescription = stringResource(id = R.string.mode_select_page_title)\n            )\n        }\n\n        if (kpState != APApplication.State.UNKNOWN_STATE) {\n            IconButton(onClick = { showDropdownReboot = true }) {\n                Icon(\n                    imageVector = Icons.Filled.PowerSettingsNew,\n                    contentDescription = stringResource(id = R.string.reboot)\n                )\n            }\n\n            RebootDialog(\n                show = showDropdownReboot,\n                onDismiss = { showDropdownReboot = false },\n                onReboot = { reason -> reboot(reason) }\n            )\n        }\n\n        Box {\n            IconButton(onClick = { showDropdownMoreOptions = true }) {\n                Icon(\n                    imageVector = Icons.Filled.MoreVert,\n                    contentDescription = stringResource(id = R.string.settings)\n                )\n                WallpaperAwareDropdownMenu(\n                    expanded = showDropdownMoreOptions,\n                    onDismissRequest = { showDropdownMoreOptions = false },\n                    shape = RoundedCornerShape(10.dp)\n                ) {\n                    WallpaperAwareDropdownMenuItem(\n                        text = { Text(stringResource(R.string.home_more_menu_feedback_or_suggestion)) },\n                        onClick = {\n                            showDropdownMoreOptions = false\n                            uriHandler.openUri(\"https://github.com/LyraVoid/FolkPatch/issues/new/choose\")\n                        }\n                    )\n                    WallpaperAwareDropdownMenuItem(\n                        text = { Text(stringResource(R.string.home_more_menu_about)) },\n                        onClick = {\n                            navigator.navigate(AboutScreenDestination)\n                            showDropdownMoreOptions = false\n                        }\n                    )\n                }\n            }\n        }\n    })\n}\n\n@Composable\nfun StatusBadge(\n    text: String,\n    containerColor: Color = MaterialTheme.colorScheme.onPrimary,\n    contentColor: Color = MaterialTheme.colorScheme.primary\n) {\n    Surface(\n        color = containerColor.copy(alpha = 1f),\n        shape = RoundedCornerShape(4.dp),\n        tonalElevation = 0.dp,\n        shadowElevation = 0.dp\n    ) {\n        Text(\n            text = text,\n            modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp),\n            style = MaterialTheme.typography.labelSmall,\n            color = contentColor.copy(alpha = 1f),\n            fontWeight = FontWeight.Bold\n        )\n    }\n}\n\n@Composable\nprivate fun KStatusCard(\n    kpState: APApplication.State, apState: APApplication.State, navigator: DestinationsNavigator\n) {\n\n    val showAuthFailedTipDialog = remember { mutableStateOf(false) }\n    if (showAuthFailedTipDialog.value) {\n        AuthFailedTipDialog(showDialog = showAuthFailedTipDialog)\n    }\n\n    val showAuthKeyDialog = remember { mutableStateOf(false) }\n    if (showAuthKeyDialog.value) {\n        AuthSuperKey(showDialog = showAuthKeyDialog, showFailedDialog = showAuthFailedTipDialog)\n    }\n\n    val showUninstallDialog = remember { mutableStateOf(false) }\n    if (showUninstallDialog.value) {\n        UninstallDialog(showDialog = showUninstallDialog, navigator)\n    }\n\n    val prefs = APApplication.sharedPreferences\n    \n    // Check if update notification is blocked\n    val kpState = if (kpState == APApplication.State.KERNELPATCH_NEED_UPDATE && apApp.isKernelPatchUpdateBlocked()) {\n        APApplication.State.KERNELPATCH_INSTALLED\n    } else {\n        kpState\n    }\n    \n    val apState = if (apState == APApplication.State.ANDROIDPATCH_NEED_UPDATE && apApp.isAndroidPatchUpdateBlocked()) {\n        APApplication.State.ANDROIDPATCH_INSTALLED\n    } else {\n        apState\n    }\n    \n    val darkThemeFollowSys = prefs.getBoolean(\"night_mode_follow_sys\", false)\n    val nightModeEnabled = prefs.getBoolean(\"night_mode_enabled\", true)\n    val isDarkTheme = if (darkThemeFollowSys) {\n        isSystemInDarkTheme()\n    } else {\n        nightModeEnabled\n    }\n\n    val (cardBackgroundColor, cardContentColor) = when (kpState) {\n        APApplication.State.KERNELPATCH_INSTALLED -> {\n            if (BackgroundConfig.isCustomBackgroundEnabled) {\n                val opacity = BackgroundConfig.customBackgroundOpacity\n                val contentColor = if (opacity <= 0.1f) {\n                    if (isDarkTheme) Color.White else Color.Black\n                } else {\n                    MaterialTheme.colorScheme.onPrimary\n                }\n                MaterialTheme.colorScheme.primary.copy(alpha = opacity) to contentColor\n            } else {\n                MaterialTheme.colorScheme.primary to MaterialTheme.colorScheme.onPrimary\n            }\n        }\n\n        APApplication.State.KERNELPATCH_NEED_UPDATE, APApplication.State.KERNELPATCH_NEED_REBOOT -> {\n            if (BackgroundConfig.isCustomBackgroundEnabled) {\n                MaterialTheme.colorScheme.secondary.copy(alpha = BackgroundConfig.customBackgroundOpacity) to MaterialTheme.colorScheme.onSecondary\n            } else {\n                MaterialTheme.colorScheme.secondary to MaterialTheme.colorScheme.onSecondary\n            }\n        }\n\n        else -> {\n            MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp) to MaterialTheme.colorScheme.onSurface\n        }\n    }\n\n    Card(\n        onClick = {\n            if (kpState != APApplication.State.KERNELPATCH_INSTALLED) {\n                navigator.navigate(InstallModeSelectScreenDestination)\n            }\n        },\n        shape = RoundedCornerShape(20.dp),\n        colors = CardDefaults.cardColors(\n            containerColor = cardBackgroundColor,\n            contentColor = cardContentColor\n        )\n    ) {\n        Column(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(12.dp),\n            horizontalAlignment = Alignment.CenterHorizontally\n        ) {\n            if (kpState == APApplication.State.KERNELPATCH_NEED_UPDATE) {\n                Row {\n                    Text(\n                        text = stringResource(R.string.kernel_patch),\n                        style = MaterialTheme.typography.titleMedium\n                    )\n                }\n            }\n            Row(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .padding(10.dp),\n                verticalAlignment = Alignment.CenterVertically\n            ) {\n                when (kpState) {\n                    APApplication.State.KERNELPATCH_INSTALLED -> {\n                        Icon(Icons.Filled.CheckCircle, stringResource(R.string.home_working))\n                    }\n\n                    APApplication.State.KERNELPATCH_NEED_UPDATE, APApplication.State.KERNELPATCH_NEED_REBOOT -> {\n                        Icon(Icons.Outlined.SystemUpdate, stringResource(R.string.home_kp_need_update))\n                    }\n\n                    else -> {\n                        Icon(Icons.AutoMirrored.Outlined.HelpOutline, \"Unknown\")\n                    }\n                }\n                Column(\n                    Modifier\n                        .weight(2f)\n                        .padding(start = 16.dp)\n                ) {\n                    when (kpState) {\n                        APApplication.State.KERNELPATCH_INSTALLED -> {\n                            Row(verticalAlignment = Alignment.CenterVertically) {\n                                Text(\n                                    text = if (BackgroundConfig.isListWorkingCardModeHidden) {\n                                        stringResource(R.string.home_working) + \"😋\"\n                                    } else {\n                                        stringResource(R.string.home_working)\n                                    },\n                                    style = MaterialTheme.typography.titleMedium\n                                )\n                                if (!BackgroundConfig.isListWorkingCardModeHidden) {\n                                    Spacer(Modifier.width(8.dp))\n                                    StatusBadge(\n                                        text = BackgroundConfig.getCustomBadgeText() ?: if (apState == APApplication.State.ANDROIDPATCH_INSTALLED) \"Full\" else \"Half\"\n                                    )\n                                }\n                            }\n                        }\n\n                        APApplication.State.KERNELPATCH_NEED_UPDATE, APApplication.State.KERNELPATCH_NEED_REBOOT -> {\n                            Text(\n                                text = stringResource(R.string.home_kp_need_update),\n                                style = MaterialTheme.typography.titleMedium\n                            )\n                            Spacer(Modifier.height(6.dp))\n                            Text(\n                                text = stringResource(\n                                    R.string.kpatch_version_update,\n                                    Version.installedKPVString(),\n                                    Version.buildKPVString()\n                                ), style = MaterialTheme.typography.bodyMedium\n                            )\n                        }\n\n                        else -> {\n                            Text(\n                                text = stringResource(R.string.home_install_unknown),\n                                style = MaterialTheme.typography.titleMedium\n                            )\n                            Text(\n                                text = stringResource(R.string.home_install_unknown_summary),\n                                style = MaterialTheme.typography.bodyMedium\n                            )\n                        }\n                    }\n                    if (kpState != APApplication.State.UNKNOWN_STATE && kpState != APApplication.State.KERNELPATCH_NEED_UPDATE && kpState != APApplication.State.KERNELPATCH_NEED_REBOOT) {\n                        Spacer(Modifier.height(4.dp))\n                        Text(\n                            text = \"${Version.installedKPVString()} (${managerVersion.second})\" + if (BackgroundConfig.isListWorkingCardModeHidden) \" - \" + (if (apState != APApplication.State.ANDROIDPATCH_NOT_INSTALLED) \"Full\" else \"KernelPatch\") else \"\",\n                            style = MaterialTheme.typography.bodyMedium\n                        )\n                    }\n                }\n\n                Column(\n                    modifier = Modifier.align(Alignment.CenterVertically)\n                ) {\n                    val onAction = {\n                        when (kpState) {\n                            APApplication.State.UNKNOWN_STATE -> {\n                                navigator.navigate(InstallModeSelectScreenDestination)\n                            }\n\n                            APApplication.State.KERNELPATCH_NEED_UPDATE -> {\n                                // todo: remove legacy compact for kp < 0.9.0\n                                if (Version.installedKPVUInt() < 0x900u) {\n                                    navigator.navigate(PatchesDestination(PatchesViewModel.PatchMode.PATCH_ONLY))\n                                } else {\n                                    navigator.navigate(InstallModeSelectScreenDestination)\n                                }\n                            }\n\n                            APApplication.State.KERNELPATCH_NEED_REBOOT -> {\n                                reboot()\n                            }\n\n                            APApplication.State.KERNELPATCH_UNINSTALLING -> {\n                                // Do nothing\n                            }\n\n                            else -> {\n                                if (apState == APApplication.State.ANDROIDPATCH_INSTALLED) {\n                                    showUninstallDialog.value = true\n                                } else {\n                                    navigator.navigate(PatchesDestination(PatchesViewModel.PatchMode.UNPATCH))\n                                }\n                            }\n                        }\n                    }\n\n                    Button(onClick = { onAction() }, colors = if (BackgroundConfig.isCustomBackgroundEnabled && kpState == APApplication.State.KERNELPATCH_INSTALLED) {\n                        val opacity = BackgroundConfig.customBackgroundOpacity\n                        val contentColor = if (opacity <= 0.1f) {\n                            if (isDarkTheme) Color.White else Color.Black\n                        } else {\n                            MaterialTheme.colorScheme.onPrimary\n                        }\n                        ButtonDefaults.buttonColors(\n                            containerColor = MaterialTheme.colorScheme.primary.copy(alpha = opacity),\n                            contentColor = contentColor\n                        )\n                    } else {\n                        ButtonDefaults.buttonColors()\n                    }, content = {\n                        when (kpState) {\n                            APApplication.State.UNKNOWN_STATE -> {\n                                Text(text = stringResource(id = R.string.home_ap_cando_install))\n                            }\n\n                            APApplication.State.KERNELPATCH_NEED_UPDATE -> {\n                                Text(text = stringResource(id = R.string.home_kp_cando_update))\n                            }\n\n                            APApplication.State.KERNELPATCH_NEED_REBOOT -> {\n                                Text(text = stringResource(id = R.string.home_ap_cando_reboot))\n                            }\n\n                            APApplication.State.KERNELPATCH_UNINSTALLING -> {\n                                Icon(Icons.Outlined.Cached, contentDescription = \"busy\")\n                            }\n\n                            else -> {\n                                Text(text = stringResource(id = R.string.home_ap_cando_uninstall))\n                            }\n                        }\n                    })\n                }\n            }\n        }\n    }\n}\n\n@Composable\nfun AStatusCard(apState: APApplication.State) {\n    Card(\n        shape = RoundedCornerShape(20.dp),\n        colors = CardDefaults.cardColors(containerColor = run {\n            if (BackgroundConfig.isCustomBackgroundEnabled) {\n                MaterialTheme.colorScheme.secondaryContainer.copy(alpha = BackgroundConfig.customBackgroundOpacity)\n            } else {\n                MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)\n            }\n        })\n    ) {\n        Column(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(12.dp),\n            horizontalAlignment = Alignment.CenterHorizontally\n        ) {\n            Row {\n                Text(\n                    text = stringResource(R.string.android_patch),\n                    style = MaterialTheme.typography.titleMedium\n                )\n            }\n            Row(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .padding(10.dp),\n                verticalAlignment = Alignment.CenterVertically\n            ) {\n                when (apState) {\n                    APApplication.State.ANDROIDPATCH_NOT_INSTALLED -> {\n                        Icon(Icons.Outlined.Block, stringResource(R.string.home_not_installed))\n                    }\n\n                    APApplication.State.ANDROIDPATCH_INSTALLING -> {\n                        Icon(Icons.Outlined.InstallMobile, stringResource(R.string.home_installing))\n                    }\n\n                    APApplication.State.ANDROIDPATCH_INSTALLED -> {\n                        Icon(Icons.Outlined.CheckCircle, stringResource(R.string.home_working))\n                    }\n\n                    APApplication.State.ANDROIDPATCH_NEED_UPDATE -> {\n                        Icon(Icons.Outlined.SystemUpdate, stringResource(R.string.home_kp_need_update))\n                    }\n\n                    else -> {\n                        Icon(\n                            Icons.AutoMirrored.Outlined.HelpOutline,\n                            stringResource(R.string.home_install_unknown)\n                        )\n                    }\n                }\n                Column(\n                    Modifier\n                        .weight(2f)\n                        .padding(start = 16.dp)\n                ) {\n\n                    when (apState) {\n                        APApplication.State.ANDROIDPATCH_NOT_INSTALLED -> {\n                            Text(\n                                text = stringResource(R.string.home_not_installed),\n                                style = MaterialTheme.typography.titleMedium\n                            )\n                        }\n\n                        APApplication.State.ANDROIDPATCH_INSTALLING -> {\n                            Text(\n                                text = stringResource(R.string.home_installing),\n                                style = MaterialTheme.typography.titleMedium\n                            )\n                        }\n\n                        APApplication.State.ANDROIDPATCH_INSTALLED -> {\n                            Text(\n                                text = stringResource(R.string.home_working),\n                                style = MaterialTheme.typography.titleMedium\n                            )\n                        }\n\n                        APApplication.State.ANDROIDPATCH_NEED_UPDATE -> {\n                            Text(\n                                text = stringResource(R.string.home_kp_need_update),\n                                style = MaterialTheme.typography.titleMedium\n                            )\n                        }\n\n                        else -> {\n                            Text(\n                                text = stringResource(R.string.home_install_unknown),\n                                style = MaterialTheme.typography.titleMedium\n                            )\n                        }\n                    }\n                }\n                if (apState != APApplication.State.UNKNOWN_STATE) {\n                    Column(\n                        modifier = Modifier.align(Alignment.CenterVertically)\n                    ) {\n                        Button(onClick = {\n                            when (apState) {\n                                APApplication.State.ANDROIDPATCH_NOT_INSTALLED -> {\n                                    APApplication.installApatch()\n                                }\n\n                                APApplication.State.ANDROIDPATCH_UNINSTALLING -> {\n                                    // Do nothing\n                                }\n\n                                APApplication.State.ANDROIDPATCH_NEED_UPDATE -> {\n                                    APApplication.installApatch()\n                                }\n\n                                else -> {\n                                    APApplication.uninstallApatch()\n                                }\n                            }\n                        }, content = {\n                            when (apState) {\n                                APApplication.State.ANDROIDPATCH_NOT_INSTALLED -> {\n                                    Text(text = stringResource(id = R.string.home_ap_cando_install))\n                                }\n\n                                APApplication.State.ANDROIDPATCH_UNINSTALLING -> {\n                                    Icon(Icons.Outlined.Cached, contentDescription = \"busy\")\n                                }\n\n                                APApplication.State.ANDROIDPATCH_NEED_UPDATE -> {\n                                    Text(text = stringResource(id = R.string.home_kp_cando_update))\n                                }\n\n                                else -> {\n                                    Text(text = stringResource(id = R.string.home_ap_cando_uninstall))\n                                }\n                            }\n                        })\n                    }\n                }\n            }\n        }\n    }\n}\n\n\n@Composable\nfun WarningCard() {\n    var show by rememberSaveable { mutableStateOf(apApp.getBackupWarningState()) }\n    if (show) {\n        ElevatedCard(\n            elevation = CardDefaults.cardElevation(\n                defaultElevation = 6.dp\n            ), colors = CardDefaults.elevatedCardColors(containerColor = run {\n                MaterialTheme.colorScheme.error\n            })\n        ) {\n            Row(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .padding(12.dp)\n            ) {\n                Column(\n                    modifier = Modifier.padding(12.dp),\n                    verticalArrangement = Arrangement.Center,\n                    horizontalAlignment = Alignment.CenterHorizontally\n                ) {\n                    Icon(Icons.Filled.Warning, contentDescription = \"warning\")\n                }\n                Column(\n                    modifier = Modifier.padding(12.dp),\n                    horizontalAlignment = Alignment.CenterHorizontally\n                ) {\n                    Row(\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .align(Alignment.CenterHorizontally),\n                        horizontalArrangement = Arrangement.SpaceBetween\n                    ) {\n                        Text(\n                            modifier = Modifier.weight(1f),\n                            text = stringResource(id = R.string.patch_warnning),\n                        )\n\n                        Spacer(Modifier.width(12.dp))\n\n                        Icon(\n                            Icons.Outlined.Clear,\n                            contentDescription = \"\",\n                            modifier = Modifier.clickable {\n                                show = false\n                                apApp.updateBackupWarningState(false)\n                            },\n                        )\n                    }\n                }\n            }\n        }\n    }\n}\n\nfun getSystemVersion(): String {\n    return \"${Build.VERSION.RELEASE} ${if (Build.VERSION.PREVIEW_SDK_INT != 0) \"Preview\" else \"\"} (API ${Build.VERSION.SDK_INT})\"\n}\n\nfun getDeviceInfo(): String {\n    var manufacturer =\n        Build.MANUFACTURER[0].uppercaseChar().toString() + Build.MANUFACTURER.substring(1)\n    if (!Build.BRAND.equals(Build.MANUFACTURER, ignoreCase = true)) {\n        manufacturer += \" \" + Build.BRAND[0].uppercaseChar() + Build.BRAND.substring(1)\n    }\n    manufacturer += \" \" + Build.MODEL + \" \"\n    return manufacturer\n}\n\n@Composable\nfun InfoCard(kpState: APApplication.State, apState: APApplication.State) {\n    // 隐藏设定状态\n    val hideSuPath = remember { mutableStateOf(APApplication.sharedPreferences.getBoolean(\"hide_su_path\", false)) }\n    val hideKpatchVersion = remember { mutableStateOf(APApplication.sharedPreferences.getBoolean(\"hide_kpatch_version\", false)) }\n    val hideFingerprint = remember { mutableStateOf(APApplication.sharedPreferences.getBoolean(\"hide_fingerprint\", false)) }\n    val hideZygisk = remember { mutableStateOf(APApplication.sharedPreferences.getBoolean(\"hide_zygisk\", false)) }\n    val hideMount = remember { mutableStateOf(APApplication.sharedPreferences.getBoolean(\"hide_mount\", false)) }\n\n    var zygiskImplement by remember { mutableStateOf(\"None\") }\n    var mountImplement by remember { mutableStateOf(\"None\") }\n    LaunchedEffect(Unit) {\n        withContext(kotlinx.coroutines.Dispatchers.IO) {\n            try {\n                zygiskImplement = me.bmax.apatch.util.getZygiskImplement()\n                mountImplement = me.bmax.apatch.util.getMountImplement()\n            } catch (e: Exception) {\n                e.printStackTrace()\n            }\n        }\n    }\n    \n    Card(\n        shape = RoundedCornerShape(20.dp),\n        colors = CardDefaults.cardColors(containerColor = if (BackgroundConfig.isCustomBackgroundEnabled) {\n            MaterialTheme.colorScheme.surface\n        } else {\n            MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)\n        })\n    ) {\n        Column(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(start = 24.dp, top = 24.dp, end = 24.dp, bottom = 16.dp)\n        ) {\n            val contents = StringBuilder()\n            val uname = Os.uname()\n\n            @Composable\n            fun InfoCardItem(label: String, content: String) {\n                contents.appendLine(label).appendLine(content).appendLine()\n                Text(text = label, style = MaterialTheme.typography.bodyLarge)\n                Text(text = content, style = MaterialTheme.typography.bodyMedium)\n            }\n\n            if (kpState != APApplication.State.UNKNOWN_STATE && !hideKpatchVersion.value) {\n                InfoCardItem(\n                    stringResource(R.string.home_kpatch_version), Version.installedKPVString()\n                )\n\n                Spacer(Modifier.height(16.dp))\n            }\n            \n            if (kpState != APApplication.State.UNKNOWN_STATE && !hideSuPath.value) {\n                InfoCardItem(stringResource(R.string.home_su_path), Natives.suPath())\n\n                Spacer(Modifier.height(16.dp))\n            }\n\n            if (apState != APApplication.State.UNKNOWN_STATE && apState != APApplication.State.ANDROIDPATCH_NOT_INSTALLED) {\n                InfoCardItem(\n                    stringResource(R.string.home_apatch_version), managerVersion.second.toString()\n                )\n                Spacer(Modifier.height(16.dp))\n            }\n\n            InfoCardItem(stringResource(R.string.home_device_info), getDeviceInfo())\n\n            Spacer(Modifier.height(16.dp))\n            InfoCardItem(stringResource(R.string.home_kernel), uname.release)\n\n            Spacer(Modifier.height(16.dp))\n            InfoCardItem(stringResource(R.string.home_system_version), getSystemVersion())\n\n            Spacer(Modifier.height(16.dp))\n            if (!hideFingerprint.value) {\n                InfoCardItem(stringResource(R.string.home_fingerprint), Build.FINGERPRINT)\n\n                Spacer(Modifier.height(16.dp))\n            }\n            \n            if (kpState != APApplication.State.UNKNOWN_STATE && zygiskImplement != \"None\" && !hideZygisk.value) {\n                InfoCardItem(stringResource(R.string.home_zygisk_implement), zygiskImplement)\n\n                Spacer(Modifier.height(16.dp))\n            }\n\n            if (kpState != APApplication.State.UNKNOWN_STATE && mountImplement != \"None\" && !hideMount.value) {\n                InfoCardItem(stringResource(R.string.home_mount_implement), mountImplement)\n\n                Spacer(Modifier.height(16.dp))\n            }\n\n            InfoCardItem(stringResource(R.string.home_selinux_status), getSELinuxStatus())\n\n        }\n    }\n}\n\n@Composable\nfun SignInfoCard(kpState: APApplication.State, apState: APApplication.State) {\n    val hideSuPath = remember { mutableStateOf(APApplication.sharedPreferences.getBoolean(\"hide_su_path\", false)) }\n    val hideKpatchVersion = remember { mutableStateOf(APApplication.sharedPreferences.getBoolean(\"hide_kpatch_version\", false)) }\n    val hideFingerprint = remember { mutableStateOf(APApplication.sharedPreferences.getBoolean(\"hide_fingerprint\", false)) }\n    val hideZygisk = remember { mutableStateOf(APApplication.sharedPreferences.getBoolean(\"hide_zygisk\", false)) }\n    val hideMount = remember { mutableStateOf(APApplication.sharedPreferences.getBoolean(\"hide_mount\", false)) }\n\n    var zygiskImplement by remember { mutableStateOf(\"None\") }\n    var mountImplement by remember { mutableStateOf(\"None\") }\n    LaunchedEffect(Unit) {\n        withContext(kotlinx.coroutines.Dispatchers.IO) {\n            try {\n                zygiskImplement = me.bmax.apatch.util.getZygiskImplement()\n                mountImplement = me.bmax.apatch.util.getMountImplement()\n            } catch (e: Exception) {\n                e.printStackTrace()\n            }\n        }\n    }\n\n    Card(\n        shape = RoundedCornerShape(20.dp),\n        colors = CardDefaults.cardColors(containerColor = if (BackgroundConfig.isCustomBackgroundEnabled) {\n            MaterialTheme.colorScheme.surface\n        } else {\n            MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)\n        })\n    ) {\n        Column(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(start = 24.dp, top = 24.dp, end = 24.dp, bottom = 16.dp)\n        ) {\n            val contents = StringBuilder()\n            val uname = Os.uname()\n\n            @Composable\n            fun InfoCardItem(icon: ImageVector, label: String, content: String) {\n                contents.appendLine(label).appendLine(content).appendLine()\n                Row(\n                    modifier = Modifier.fillMaxWidth(),\n                    verticalAlignment = Alignment.Top\n                ) {\n                    Icon(\n                        imageVector = icon,\n                        contentDescription = null,\n                        modifier = Modifier\n                            .padding(top = 2.dp)\n                            .size(20.dp),\n                        tint = MaterialTheme.colorScheme.onSurfaceVariant\n                    )\n                    Spacer(Modifier.width(12.dp))\n                    Column(modifier = Modifier.weight(1f)) {\n                        Text(text = label, style = MaterialTheme.typography.bodyLarge)\n                        Text(text = content, style = MaterialTheme.typography.bodyMedium)\n                    }\n                }\n            }\n\n            if (kpState != APApplication.State.UNKNOWN_STATE && !hideKpatchVersion.value) {\n                InfoCardItem(\n                    Icons.Outlined.Extension,\n                    stringResource(R.string.home_kpatch_version),\n                    Version.installedKPVString()\n                )\n                Spacer(Modifier.height(16.dp))\n            }\n\n            if (kpState != APApplication.State.UNKNOWN_STATE && !hideSuPath.value) {\n                InfoCardItem(\n                    Icons.Outlined.Code,\n                    stringResource(R.string.home_su_path),\n                    Natives.suPath()\n                )\n                Spacer(Modifier.height(16.dp))\n            }\n\n            if (apState != APApplication.State.UNKNOWN_STATE && apState != APApplication.State.ANDROIDPATCH_NOT_INSTALLED) {\n                InfoCardItem(\n                    Icons.Outlined.Android,\n                    stringResource(R.string.home_apatch_version),\n                    managerVersion.second.toString()\n                )\n                Spacer(Modifier.height(16.dp))\n            }\n\n            InfoCardItem(Icons.Outlined.PhoneAndroid, stringResource(R.string.home_device_info), getDeviceInfo())\n            Spacer(Modifier.height(16.dp))\n\n            InfoCardItem(Icons.Outlined.DeveloperBoard, stringResource(R.string.home_kernel), uname.release)\n            Spacer(Modifier.height(16.dp))\n\n            InfoCardItem(Icons.Outlined.Info, stringResource(R.string.home_system_version), getSystemVersion())\n            Spacer(Modifier.height(16.dp))\n\n            if (!hideFingerprint.value) {\n                InfoCardItem(Icons.Filled.Fingerprint, stringResource(R.string.home_fingerprint), Build.FINGERPRINT)\n                Spacer(Modifier.height(16.dp))\n            }\n\n            if (kpState != APApplication.State.UNKNOWN_STATE && zygiskImplement != \"None\" && !hideZygisk.value) {\n                InfoCardItem(Icons.Outlined.Layers, stringResource(R.string.home_zygisk_implement), zygiskImplement)\n                Spacer(Modifier.height(16.dp))\n            }\n\n            if (kpState != APApplication.State.UNKNOWN_STATE && mountImplement != \"None\" && !hideMount.value) {\n                InfoCardItem(Icons.Outlined.SdStorage, stringResource(R.string.home_mount_implement), mountImplement)\n                Spacer(Modifier.height(16.dp))\n            }\n\n            InfoCardItem(Icons.Outlined.Shield, stringResource(R.string.home_selinux_status), getSELinuxStatus())\n        }\n    }\n}\n\n@Composable\nfun WarningCard(\n    message: String, color: Color = MaterialTheme.colorScheme.error, onClick: (() -> Unit)? = null\n) {\n    ElevatedCard(\n        colors = CardDefaults.elevatedCardColors(\n            containerColor = color\n        ),\n        elevation = CardDefaults.cardElevation(\n            defaultElevation = if (BackgroundConfig.isCustomBackgroundEnabled) 0.dp else 6.dp\n        ),\n    ) {\n        Row(\n            modifier = Modifier\n                .fillMaxWidth()\n                .then(onClick?.let { Modifier.clickable { it() } } ?: Modifier)\n                .padding(24.dp)) {\n            Text(\n                text = message, style = MaterialTheme.typography.bodyMedium\n            )\n        }\n    }\n}\n\n@Composable\nfun LearnMoreCard() {\n    val uriHandler = LocalUriHandler.current\n\n    Card(\n        shape = RoundedCornerShape(20.dp),\n        colors = CardDefaults.cardColors(containerColor = if (BackgroundConfig.isCustomBackgroundEnabled) {\n            MaterialTheme.colorScheme.surface\n        } else {\n            MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)\n        })\n    ) {\n        Row(\n            modifier = Modifier\n                .fillMaxWidth()\n                .clickable {\n                    uriHandler.openUri(\"https://fp.mysqil.com/\")\n                }\n                .padding(24.dp), verticalAlignment = Alignment.CenterVertically) {\n            Column {\n                Text(\n                    text = stringResource(R.string.home_learn_apatch),\n                    style = MaterialTheme.typography.titleSmall\n                )\n                Spacer(Modifier.height(4.dp))\n                Text(\n                    text = stringResource(R.string.home_click_to_learn_apatch),\n                    style = MaterialTheme.typography.bodyMedium\n                )\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/HomeCircle.kt",
    "content": "package me.bmax.apatch.ui.screen\n\nimport android.content.Context\nimport android.os.Build\nimport android.system.Os\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.outlined.*\nimport androidx.compose.material.icons.automirrored.outlined.Help\nimport androidx.compose.material.icons.filled.CheckCircle\nimport androidx.compose.material.icons.filled.Warning\nimport androidx.compose.material3.*\nimport androidx.compose.runtime.*\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.Shape\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport androidx.compose.ui.text.TextStyle\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalUriHandler\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.compose.ui.tooling.preview.Preview\nimport androidx.core.content.pm.PackageInfoCompat\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport com.ramcosta.composedestinations.generated.NavGraphs\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.apApp\nimport me.bmax.apatch.Natives\nimport me.bmax.apatch.R\nimport me.bmax.apatch.util.Version\nimport me.bmax.apatch.util.getSELinuxStatus\nimport me.bmax.apatch.ui.screen.BottomBarDestination\nimport me.bmax.apatch.util.AppData\nimport me.bmax.apatch.util.Version.getManagerVersion\nimport me.bmax.apatch.ui.theme.BackgroundConfig\nimport androidx.compose.material3.surfaceColorAtElevation\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.util.ui.HomeBottomSpacer\n\n@Composable\nfun HomeScreenCircle(\n    innerPadding: PaddingValues,\n    navigator: DestinationsNavigator,\n    kpState: APApplication.State,\n    apState: APApplication.State\n) {\n    // Check if update notification is blocked\n    val kpState = if (kpState == APApplication.State.KERNELPATCH_NEED_UPDATE && apApp.isKernelPatchUpdateBlocked()) {\n        APApplication.State.KERNELPATCH_INSTALLED\n    } else {\n        kpState\n    }\n\n    val apState = if (apState == APApplication.State.ANDROIDPATCH_NEED_UPDATE && apApp.isAndroidPatchUpdateBlocked()) {\n        APApplication.State.ANDROIDPATCH_INSTALLED\n    } else {\n        apState\n    }\n\n    val showUninstallDialog = remember { mutableStateOf(false) }\n    val showAuthFailedTipDialog = remember { mutableStateOf(false) }\n    val showAuthKeyDialog = remember { mutableStateOf(false) }\n    if (showUninstallDialog.value) {\n        UninstallDialog(showDialog = showUninstallDialog, navigator)\n    }\n    if (showAuthFailedTipDialog.value) {\n        AuthFailedTipDialog(showDialog = showAuthFailedTipDialog)\n    }\n    if (showAuthKeyDialog.value) {\n        AuthSuperKey(showDialog = showAuthKeyDialog, showFailedDialog = showAuthFailedTipDialog)\n    }\n\n    Column(\n        modifier = Modifier\n            .padding(innerPadding)\n            .verticalScroll(rememberScrollState())\n            .padding(horizontal = 16.dp),\n        verticalArrangement = Arrangement.spacedBy(16.dp)\n    ) {\n        val context = LocalContext.current\n        \n        if (BackgroundConfig.isCustomBackgroundEnabled) {\n            Spacer(Modifier.height(0.dp))\n        }\n        \n        // Status Card\n        StatusCardCircle(kpState, apState, navigator, showUninstallDialog, showAuthKeyDialog)\n\n        // Superuser and Module Cards\n        val showCoreCards = kpState != APApplication.State.UNKNOWN_STATE\n        if (showCoreCards) {\n            LaunchedEffect(Unit) {\n                AppData.DataRefreshManager.ensureCountsLoaded()\n            }\n            \n            val superuserCount by AppData.DataRefreshManager.superuserCount.collectAsState()\n            val moduleCount by AppData.DataRefreshManager.apmModuleCount.collectAsState()\n            \n            Row(\n                modifier = Modifier.fillMaxWidth(),\n                horizontalArrangement = Arrangement.spacedBy(16.dp)\n            ) {\n                TonalCard(modifier = Modifier.weight(1f)) {\n                    Row(\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .clickable {\n                                navigator.navigate(BottomBarDestination.SuperUser.direction) {\n                                    popUpTo(NavGraphs.root) {\n                                        saveState = true\n                                    }\n                                    launchSingleTop = true\n                                    restoreState = true\n                                }\n                            }\n                            .padding(horizontal = 20.dp, vertical = 16.dp),\n                        verticalAlignment = Alignment.CenterVertically\n                    ) {\n                        Icon(\n                            imageVector = Icons.Outlined.Security,\n                            contentDescription = null,\n                            modifier = Modifier.size(20.dp),\n                            tint = MaterialTheme.colorScheme.onSurfaceVariant\n                        )\n                        Spacer(Modifier.width(16.dp))\n                        Column {\n                            Text(\n                                text = stringResource(R.string.superuser),\n                                style = MaterialTheme.typography.bodyLarge,\n                                maxLines = 1,\n                                overflow = TextOverflow.Ellipsis\n                            )\n                            Spacer(Modifier.height(4.dp))\n                            Text(\n                                text = superuserCount.toString(),\n                                style = MaterialTheme.typography.bodyMedium,\n                                color = MaterialTheme.colorScheme.outline\n                            )\n                        }\n                    }\n                }\n                \n                TonalCard(modifier = Modifier.weight(1f)) {\n                    Row(\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .clickable {\n                                navigator.navigate(BottomBarDestination.AModule.direction) {\n                                    popUpTo(NavGraphs.root) {\n                                        saveState = true\n                                    }\n                                    launchSingleTop = true\n                                    restoreState = true\n                                }\n                            }\n                            .padding(horizontal = 20.dp, vertical = 16.dp),\n                        verticalAlignment = Alignment.CenterVertically\n                    ) {\n                        Icon(\n                            imageVector = Icons.Outlined.Widgets,\n                            contentDescription = null,\n                            modifier = Modifier.size(20.dp),\n                            tint = MaterialTheme.colorScheme.onSurfaceVariant\n                        )\n                        Spacer(Modifier.width(16.dp))\n                        Column {\n                            Text(\n                                text = stringResource(R.string.module),\n                                style = MaterialTheme.typography.bodyLarge,\n                                maxLines = 1,\n                                overflow = TextOverflow.Ellipsis\n                            )\n                            Spacer(Modifier.height(4.dp))\n                            Text(\n                                text = moduleCount.toString(),\n                                style = MaterialTheme.typography.bodyMedium,\n                                color = MaterialTheme.colorScheme.outline\n                            )\n                        }\n                    }\n                }\n            }\n        }\n        \n        // System Patch Detection\n        if (kpState != APApplication.State.UNKNOWN_STATE && apState != APApplication.State.UNKNOWN_STATE && apState != APApplication.State.ANDROIDPATCH_INSTALLED) {\n             AStatusCardCircle(apState)\n        }\n\n        // Info Card\n        InfoCardCircle(kpState, apState)\n\n        // Learn More\n        val hideApatchCard = APApplication.sharedPreferences.getBoolean(\"hide_apatch_card\", false)\n        if (!hideApatchCard) {\n            LearnMoreCardCircle()\n        }\n        \n        HomeBottomSpacer()\n    }\n}\n\n@Composable\nfun StatusCardCircle(\n    kpState: APApplication.State,\n    apState: APApplication.State,\n    navigator: DestinationsNavigator,\n    showUninstallDialog: MutableState<Boolean>,\n    showAuthKeyDialog: MutableState<Boolean>\n) {\n    val isWorking = kpState == APApplication.State.KERNELPATCH_INSTALLED\n    val isUpdate = kpState == APApplication.State.KERNELPATCH_NEED_UPDATE || kpState == APApplication.State.KERNELPATCH_NEED_REBOOT\n    val classicEmojiEnabled = BackgroundConfig.isListWorkingCardModeHidden\n    \n    val finalContainerColor = if (isWorking) {\n        if (BackgroundConfig.isCustomBackgroundEnabled) {\n            MaterialTheme.colorScheme.primaryContainer.copy(alpha = BackgroundConfig.customBackgroundOpacity)\n        } else {\n            MaterialTheme.colorScheme.secondaryContainer\n        }\n    } else {\n        if (BackgroundConfig.isCustomBackgroundEnabled) {\n            MaterialTheme.colorScheme.errorContainer.copy(alpha = BackgroundConfig.customBackgroundOpacity)\n        } else {\n            MaterialTheme.colorScheme.errorContainer\n        }\n    }\n    \n    TonalCard(containerColor = finalContainerColor) {\n        Row(\n            modifier = Modifier\n                .fillMaxWidth()\n                .clickable { \n                    if (isWorking) {\n                        if (apState == APApplication.State.ANDROIDPATCH_INSTALLED) {\n                            showUninstallDialog.value = true\n                        }\n                    } else if (kpState == APApplication.State.UNKNOWN_STATE) {\n                        showAuthKeyDialog.value = true\n                    } else {\n                        navigator.navigate(com.ramcosta.composedestinations.generated.destinations.InstallModeSelectScreenDestination)\n                    }\n                }\n                .padding(24.dp), \n            verticalAlignment = Alignment.CenterVertically\n        ) {\n            if (isWorking) {\n                 Icon(Icons.Outlined.CheckCircle, stringResource(R.string.home_working))\n                 Column(Modifier.padding(start = 20.dp)) {\n                     val isFull = apState == APApplication.State.ANDROIDPATCH_INSTALLED\n                    val modeTextTitle = if (isFull) \"Full\" else \"Half\"\n                    val modeTextCaps = if (isFull) \"FULL\" else \"HALF\"\n                    val modeText = BackgroundConfig.getCustomBadgeText() ?: modeTextCaps\n\n                     Row(verticalAlignment = Alignment.CenterVertically) {\n                        Text(\n                            text = if (classicEmojiEnabled) {\n                                stringResource(R.string.home_working) + \"😋\"\n                            } else {\n                                stringResource(R.string.home_working)\n                            },\n                            style = MaterialTheme.typography.titleMedium\n                        )\n                        Spacer(Modifier.width(8.dp))\n                        \n                       // Full/Half Label\n                      if (!classicEmojiEnabled) {\n                          ModeLabelText(label = modeText)\n                      }\n                   }\n                   Spacer(Modifier.height(4.dp))\n                   Text(\n                       text = stringResource(R.string.home_version, getManagerVersion().second.toString()) +\n                               if (classicEmojiEnabled) \" - $modeTextTitle\" else \"\",\n                       style = MaterialTheme.typography.bodyMedium\n                   )\n                }\n            } else {\n                 // Not installed or error\n                 val icon = if (isUpdate) Icons.Outlined.SystemUpdate else Icons.Outlined.Warning\n                 val title = if (isUpdate) stringResource(R.string.home_kp_need_update) else stringResource(R.string.home_not_installed)\n                 \n                 Icon(icon, title)\n                 Column(Modifier.padding(start = 20.dp)) {\n                     Text(\n                         text = title,\n                         style = MaterialTheme.typography.titleMedium\n                     )\n                     Spacer(Modifier.height(4.dp))\n                     Text(\n                         text = stringResource(R.string.home_click_to_install),\n                         style = MaterialTheme.typography.bodyMedium\n                     )\n                 }\n            }\n        }\n    }\n}\n\n@Composable\nfun InfoCardCircle(kpState: APApplication.State, apState: APApplication.State) {\n    val context = LocalContext.current\n\n    TonalCard {\n        Column(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(horizontal = 20.dp, vertical = 24.dp)\n        ) {\n            val uname = Os.uname()\n            val prefs = APApplication.sharedPreferences\n\n            var hideSuPath by remember { mutableStateOf(prefs.getBoolean(\"hide_su_path\", false)) }\n            var hideKpatchVersion by remember { mutableStateOf(prefs.getBoolean(\"hide_kpatch_version\", false)) }\n            var hideFingerprint by remember { mutableStateOf(prefs.getBoolean(\"hide_fingerprint\", false)) }\n            var hideZygisk by remember { mutableStateOf(prefs.getBoolean(\"hide_zygisk\", false)) }\n            var hideMount by remember { mutableStateOf(prefs.getBoolean(\"hide_mount\", false)) }\n            \n\n            DisposableEffect(Unit) {\n                val listener = android.content.SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->\n                    when (key) {\n                        \"hide_su_path\" -> hideSuPath = sharedPreferences.getBoolean(\"hide_su_path\", false)\n                        \"hide_kpatch_version\" -> hideKpatchVersion = sharedPreferences.getBoolean(\"hide_kpatch_version\", false)\n                        \"hide_fingerprint\" -> hideFingerprint = sharedPreferences.getBoolean(\"hide_fingerprint\", false)\n                        \"hide_zygisk\" -> hideZygisk = sharedPreferences.getBoolean(\"hide_zygisk\", false)\n                        \"hide_mount\" -> hideMount = sharedPreferences.getBoolean(\"hide_mount\", false)\n                    }\n                }\n                prefs.registerOnSharedPreferenceChangeListener(listener)\n                onDispose {\n                    prefs.unregisterOnSharedPreferenceChangeListener(listener)\n                }\n            }\n\n            var zygiskImplement by remember { mutableStateOf(\"None\") }\n            var mountImplement by remember { mutableStateOf(\"None\") }\n            LaunchedEffect(Unit) {\n                withContext(Dispatchers.IO) {\n                    try {\n                        zygiskImplement = me.bmax.apatch.util.getZygiskImplement()\n                        mountImplement = me.bmax.apatch.util.getMountImplement()\n                    } catch (_: Exception) {\n                    }\n                }\n            }\n\n            @Composable\n            fun InfoCardItem(\n                label: String,\n                content: String,\n                icon: @Composable () -> Unit\n            ) {\n                Row(verticalAlignment = Alignment.CenterVertically) {\n                    icon()\n                    Spacer(Modifier.width(16.dp))\n                    Column {\n                        Text(text = label, style = MaterialTheme.typography.bodyLarge)\n                        Text(\n                            text = content,\n                            style = MaterialTheme.typography.bodyMedium,\n                            color = MaterialTheme.colorScheme.outline,\n                        )\n                    }\n                }\n            }\n\n            @Composable\n            fun InfoCardItem(icon: ImageVector, label: String, content: String) = InfoCardItem(\n                label = label,\n                content = content,\n                icon = {\n                    Icon(\n                        imageVector = icon,\n                        contentDescription = null,\n                        modifier = Modifier.size(20.dp),\n                        tint = MaterialTheme.colorScheme.onSurfaceVariant\n                    )\n                }\n            )\n\n            val managerVersion = getManagerVersion()\n            InfoCardItem(\n                icon = Icons.Outlined.Apps,\n                label = stringResource(R.string.home_manager_version),\n                content = managerVersion.first\n            )\n\n            Spacer(Modifier.height(16.dp))\n            if (kpState != APApplication.State.UNKNOWN_STATE && !hideKpatchVersion) {\n                InfoCardItem(\n                    icon = Icons.Outlined.Extension,\n                    label = stringResource(R.string.home_kpatch_version),\n                    content = Version.installedKPVString()\n                )\n                Spacer(Modifier.height(16.dp))\n            }\n\n            if (kpState != APApplication.State.UNKNOWN_STATE && !hideSuPath) {\n                InfoCardItem(\n                    icon = Icons.Outlined.Code,\n                    label = stringResource(R.string.home_su_path),\n                    content = Natives.suPath()\n                )\n                Spacer(Modifier.height(16.dp))\n            }\n\n            if (apState != APApplication.State.UNKNOWN_STATE && apState != APApplication.State.ANDROIDPATCH_NOT_INSTALLED) {\n                InfoCardItem(\n                    icon = Icons.Outlined.Android,\n                    label = stringResource(R.string.home_apatch_version),\n                    content = managerVersion.second.toString()\n                )\n                Spacer(Modifier.height(16.dp))\n            }\n\n            InfoCardItem(\n                icon = Icons.Outlined.PhoneAndroid,\n                label = stringResource(R.string.home_device_info),\n                content = getDeviceInfo()\n            )\n\n            Spacer(Modifier.height(16.dp))\n            InfoCardItem(\n                icon = Icons.Outlined.DeveloperBoard,\n                label = stringResource(R.string.home_kernel),\n                content = uname.release\n            )\n\n            Spacer(Modifier.height(16.dp))\n            InfoCardItem(\n                icon = Icons.Outlined.Info,\n                label = stringResource(R.string.home_system_version),\n                content = getSystemVersion()\n            )\n\n            Spacer(Modifier.height(16.dp))\n            if (!hideFingerprint) {\n                InfoCardItem(\n                    icon = Icons.Outlined.Fingerprint,\n                    label = stringResource(R.string.home_fingerprint),\n                    content = Build.FINGERPRINT\n                )\n                Spacer(Modifier.height(16.dp))\n            }\n\n            if (kpState != APApplication.State.UNKNOWN_STATE && zygiskImplement != \"None\" && !hideZygisk) {\n                InfoCardItem(\n                    icon = Icons.Outlined.Layers,\n                    label = stringResource(R.string.home_zygisk_implement),\n                    content = zygiskImplement\n                )\n                Spacer(Modifier.height(16.dp))\n            }\n\n            if (kpState != APApplication.State.UNKNOWN_STATE && mountImplement != \"None\" && !hideMount) {\n                InfoCardItem(\n                    icon = Icons.Outlined.SdStorage,\n                    label = stringResource(R.string.home_mount_implement),\n                    content = mountImplement\n                )\n                Spacer(Modifier.height(16.dp))\n            }\n\n            InfoCardItem(\n                icon = Icons.Outlined.Shield,\n                label = stringResource(R.string.home_selinux_status),\n                content = getSELinuxStatus()\n            )\n        }\n    }\n}\n\n@Composable\nfun ModeLabelText(\n    label: String,\n    modifier: Modifier = Modifier,\n    color: Color = MaterialTheme.colorScheme.onPrimary,\n    containerColor: Color = MaterialTheme.colorScheme.primary\n) {\n    Box(\n        modifier = modifier\n            .padding(end = 4.dp)\n            .background(\n                color = containerColor,\n                shape = RoundedCornerShape(4.dp)\n            )\n    ) {\n        Text(\n            text = label,\n            modifier = Modifier.padding(vertical = 2.dp, horizontal = 5.dp),\n            style = TextStyle(\n                fontSize = 10.sp,\n                fontWeight = FontWeight.SemiBold,\n                color = color,\n            )\n        )\n    }\n}\n\n@Composable\nfun AStatusCardCircle(apState: APApplication.State) {\n    TonalCard {\n        Column(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(20.dp),\n            horizontalAlignment = Alignment.CenterHorizontally\n        ) {\n            Text(\n                text = stringResource(R.string.android_patch),\n                style = MaterialTheme.typography.titleMedium\n            )\n            Spacer(Modifier.height(12.dp))\n            Row(\n                modifier = Modifier.fillMaxWidth(),\n                verticalAlignment = Alignment.CenterVertically\n            ) {\n                when (apState) {\n                    APApplication.State.ANDROIDPATCH_NOT_INSTALLED -> {\n                        Icon(Icons.Outlined.Block, stringResource(R.string.home_not_installed))\n                    }\n\n                    APApplication.State.ANDROIDPATCH_INSTALLING -> {\n                        Icon(Icons.Outlined.InstallMobile, stringResource(R.string.home_installing))\n                    }\n\n                    APApplication.State.ANDROIDPATCH_INSTALLED -> {\n                        Icon(Icons.Outlined.CheckCircle, stringResource(R.string.home_working))\n                    }\n\n                    APApplication.State.ANDROIDPATCH_NEED_UPDATE -> {\n                        Icon(Icons.Outlined.SystemUpdate, stringResource(R.string.home_kp_need_update))\n                    }\n\n                    else -> {\n                        Icon(\n                            Icons.AutoMirrored.Outlined.Help,\n                            stringResource(R.string.home_install_unknown)\n                        )\n                    }\n                }\n                Column(\n                    Modifier\n                        .weight(2f)\n                        .padding(start = 16.dp)\n                ) {\n                    when (apState) {\n                        APApplication.State.ANDROIDPATCH_NOT_INSTALLED -> {\n                            Text(\n                                text = stringResource(R.string.home_not_installed),\n                                style = MaterialTheme.typography.titleMedium\n                            )\n                        }\n\n                        APApplication.State.ANDROIDPATCH_INSTALLING -> {\n                            Text(\n                                text = stringResource(R.string.home_installing),\n                                style = MaterialTheme.typography.titleMedium\n                            )\n                        }\n\n                        APApplication.State.ANDROIDPATCH_INSTALLED -> {\n                            Text(\n                                text = stringResource(R.string.home_working),\n                                style = MaterialTheme.typography.titleMedium\n                            )\n                        }\n\n                        APApplication.State.ANDROIDPATCH_NEED_UPDATE -> {\n                            Text(\n                                text = stringResource(R.string.home_kp_need_update),\n                                style = MaterialTheme.typography.titleMedium\n                            )\n                        }\n\n                        else -> {\n                            Text(\n                                text = stringResource(R.string.home_install_unknown),\n                                style = MaterialTheme.typography.titleMedium\n                            )\n                        }\n                    }\n                }\n                if (apState != APApplication.State.UNKNOWN_STATE) {\n                    FilledTonalButton(onClick = {\n                        when (apState) {\n                            APApplication.State.ANDROIDPATCH_NOT_INSTALLED -> {\n                                APApplication.installApatch()\n                            }\n\n                            APApplication.State.ANDROIDPATCH_UNINSTALLING -> {\n                            }\n\n                            APApplication.State.ANDROIDPATCH_NEED_UPDATE -> {\n                                APApplication.installApatch()\n                            }\n\n                            else -> {\n                                APApplication.uninstallApatch()\n                            }\n                        }\n                    }) {\n                        when (apState) {\n                            APApplication.State.ANDROIDPATCH_NOT_INSTALLED -> {\n                                Text(text = stringResource(id = R.string.home_ap_cando_install))\n                            }\n\n                            APApplication.State.ANDROIDPATCH_UNINSTALLING -> {\n                                Icon(Icons.Outlined.Cached, contentDescription = \"busy\")\n                            }\n\n                            APApplication.State.ANDROIDPATCH_NEED_UPDATE -> {\n                                Text(text = stringResource(id = R.string.home_kp_cando_update))\n                            }\n\n                            else -> {\n                                Text(text = stringResource(id = R.string.home_ap_cando_uninstall))\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\n@Composable\nfun TonalCard(\n    modifier: Modifier = Modifier,\n    containerColor: Color? = null,\n    shape: Shape = RoundedCornerShape(20.dp),\n    content: @Composable () -> Unit\n) {\n    val finalContainerColor = containerColor ?: if (BackgroundConfig.isCustomBackgroundEnabled) {\n        MaterialTheme.colorScheme.surface.copy(alpha = BackgroundConfig.customBackgroundOpacity)\n    } else {\n        MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)\n    }\n    \n    Card(\n        modifier = modifier,\n        colors = CardDefaults.cardColors(containerColor = finalContainerColor),\n        shape = shape\n    ) {\n        content()\n    }\n}\n\n@Composable\nfun LearnMoreCardCircle() {\n    val uriHandler = LocalUriHandler.current\n\n    TonalCard {\n        Row(\n            modifier = Modifier\n                .fillMaxWidth()\n                .clickable {\n                    uriHandler.openUri(\"https://fp.mysqil.com/\")\n                }\n                .padding(24.dp),\n            verticalAlignment = Alignment.CenterVertically\n        ) {\n            Column {\n                Text(\n                    text = stringResource(R.string.home_learn_apatch),\n                    style = MaterialTheme.typography.titleSmall\n                )\n                Spacer(Modifier.height(4.dp))\n                Text(\n                    text = stringResource(R.string.home_click_to_learn_apatch),\n                    style = MaterialTheme.typography.bodyMedium,\n                    color = MaterialTheme.colorScheme.outline\n                )\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/HomeStats.kt",
    "content": "package me.bmax.apatch.ui.screen\n\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.outlined.*\nimport androidx.compose.material3.*\nimport androidx.compose.runtime.*\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport androidx.compose.ui.platform.LocalConfiguration\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport androidx.lifecycle.compose.LifecycleStartEffect\nimport androidx.lifecycle.viewmodel.compose.viewModel\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport com.ramcosta.composedestinations.generated.destinations.InstallModeSelectScreenDestination\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.component.chart.ModulePieChart\nimport me.bmax.apatch.ui.component.chart.rememberPieSliceDataFromCounts\nimport me.bmax.apatch.ui.component.chart.SystemAreaChart\nimport me.bmax.apatch.ui.component.chart.SystemLineChart\nimport me.bmax.apatch.util.Version\nimport me.bmax.apatch.util.Version.getManagerVersion\nimport me.bmax.apatch.ui.viewmodel.DashboardViewModel\nimport me.bmax.apatch.ui.viewmodel.SystemMonitorState\nimport me.bmax.apatch.ui.viewmodel.TimeSeriesData\nimport me.bmax.apatch.util.AppData\nimport me.bmax.apatch.ui.theme.BackgroundConfig\nimport kotlin.math.roundToInt\nimport me.bmax.apatch.util.ui.HomeBottomSpacer\n\n@Composable\nfun HomeScreenStats(\n    innerPadding: PaddingValues,\n    navigator: DestinationsNavigator,\n    kpState: APApplication.State,\n    apState: APApplication.State\n) {\n    val viewModel: DashboardViewModel = viewModel()\n    val uiState by viewModel.dashboardUiState.collectAsState()\n    val timeSeries by viewModel.timeSeriesData.collectAsState()\n\n    val showCoreCards = kpState != APApplication.State.UNKNOWN_STATE\n    if (showCoreCards) {\n        LaunchedEffect(Unit) {\n            AppData.DataRefreshManager.ensureCountsLoaded()\n        }\n    }\n    val superuserCount by AppData.DataRefreshManager.superuserCount.collectAsState()\n    val apmModuleCount by AppData.DataRefreshManager.apmModuleCount.collectAsState()\n    val kernelModuleCount by AppData.DataRefreshManager.kernelModuleCount.collectAsState()\n\n    LifecycleStartEffect(Unit) {\n        viewModel.startPeriodicPolling()\n        onStopOrDispose {\n            viewModel.stopPeriodicPolling()\n        }\n    }\n\n    val showUninstallDialog = remember { mutableStateOf(false) }\n    val showAuthFailedTipDialog = remember { mutableStateOf(false) }\n    val showAuthKeyDialog = remember { mutableStateOf(false) }\n    if (showUninstallDialog.value) {\n        UninstallDialog(showDialog = showUninstallDialog, navigator)\n    }\n    if (showAuthFailedTipDialog.value) {\n        AuthFailedTipDialog(showDialog = showAuthFailedTipDialog)\n    }\n    if (showAuthKeyDialog.value) {\n        AuthSuperKey(showDialog = showAuthKeyDialog, showFailedDialog = showAuthFailedTipDialog)\n    }\n\n    val hideApatchCard = APApplication.sharedPreferences.getBoolean(\"hide_apatch_card\", false)\n    val isInstalled = kpState != APApplication.State.UNKNOWN_STATE\n    val statsTopLayout = APApplication.sharedPreferences.getString(\"stats_top_layout\", \"list\") ?: \"list\"\n    val useGridTop = statsTopLayout == \"grid\"\n    val isWallpaperMode = BackgroundConfig.isCustomBackgroundEnabled &&\n        (BackgroundConfig.customBackgroundUri != null || BackgroundConfig.isMultiBackgroundEnabled)\n\n    var zygiskImplement by remember { mutableStateOf(\"None\") }\n    var mountImplement by remember { mutableStateOf(\"None\") }\n    if (isInstalled) {\n        LaunchedEffect(Unit) {\n            withContext(Dispatchers.IO) {\n                try {\n                    zygiskImplement = me.bmax.apatch.util.getZygiskImplement()\n                    mountImplement = me.bmax.apatch.util.getMountImplement()\n                } catch (_: Exception) {}\n            }\n        }\n    }\n\n    LifecycleStartEffect(isInstalled) {\n        if (isInstalled) {\n            viewModel.startPeriodicPolling()\n        }\n        onStopOrDispose {\n            viewModel.stopPeriodicPolling()\n        }\n    }\n\n    val configuration = LocalConfiguration.current\n    val isWideScreen = configuration.screenWidthDp >= 600\n\n    if (isWideScreen) {\n        Row(\n            modifier = Modifier\n                .padding(innerPadding)\n                .padding(horizontal = 16.dp)\n                .verticalScroll(rememberScrollState()),\n            horizontalArrangement = Arrangement.spacedBy(16.dp)\n        ) {\n            if (isWallpaperMode) { Spacer(Modifier.height(8.dp)) }\n            Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(16.dp)) {\n                if (useGridTop) {\n                    StatsGridTopSection(kpState, apState, navigator, showUninstallDialog, showAuthKeyDialog)\n                } else {\n                    StatusCardCircle(kpState, apState, navigator, showUninstallDialog, showAuthKeyDialog)\n                    if (kpState != APApplication.State.UNKNOWN_STATE && apState != APApplication.State.UNKNOWN_STATE && apState != APApplication.State.ANDROIDPATCH_INSTALLED) {\n                        AStatusCardCircle(apState)\n                    }\n                }\n                if (isInstalled) {\n                    SystemMonitoringSection(uiState.systemMonitor, timeSeries)\n                }\n            }\n            Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(16.dp)) {\n                if (isInstalled) {\n                    ModuleStatisticsSection(superuserCount, apmModuleCount, kernelModuleCount)\n                }\n                SystemInfoCard(kpState, apState, zygiskImplement, mountImplement)\n                if (!hideApatchCard) {\n                    LearnMoreCardV4()\n                }\n            }\n        }\n    } else {\n        Column(\n            modifier = Modifier\n                .padding(innerPadding)\n                .verticalScroll(rememberScrollState())\n                .padding(horizontal = 16.dp),\n            verticalArrangement = Arrangement.spacedBy(16.dp)\n        ) {\n            if (isWallpaperMode) { Spacer(Modifier.height(8.dp)) }\n            if (useGridTop) {\n                StatsGridTopSection(kpState, apState, navigator, showUninstallDialog, showAuthKeyDialog)\n            } else {\n                StatusCardCircle(kpState, apState, navigator, showUninstallDialog, showAuthKeyDialog)\n                if (kpState != APApplication.State.UNKNOWN_STATE && apState != APApplication.State.UNKNOWN_STATE && apState != APApplication.State.ANDROIDPATCH_INSTALLED) {\n                    AStatusCardCircle(apState)\n                }\n            }\n            if (isInstalled) {\n                SystemMonitoringSection(uiState.systemMonitor, timeSeries)\n                ModuleStatisticsSection(superuserCount, apmModuleCount, kernelModuleCount)\n            }\n            SystemInfoCard(kpState, apState, zygiskImplement, mountImplement)\n            if (!hideApatchCard) {\n                LearnMoreCardV4()\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun SystemMonitoringSection(\n    systemMonitor: SystemMonitorState,\n    timeSeries: TimeSeriesData,\n    modifier: Modifier = Modifier\n) {\n    val colors = MaterialTheme.colorScheme\n\n    val cpuDataPoints = timeSeries.cpuHistory\n    val gpuDataPoints = timeSeries.gpuHistory\n    val cpuTempDataPoints = timeSeries.cpuTempHistory\n\n    TonalCard(modifier = modifier) {\n        Column(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(16.dp),\n            verticalArrangement = Arrangement.spacedBy(16.dp)\n        ) {\n            Text(\n                text = stringResource(R.string.home_device_status_title),\n                style = MaterialTheme.typography.titleMedium,\n                fontWeight = FontWeight.SemiBold,\n                color = colors.onSurface\n            )\n\n            Row(\n                modifier = Modifier.fillMaxWidth(),\n                horizontalArrangement = Arrangement.spacedBy(12.dp)\n            ) {\n                SystemLineChart(\n                    title = stringResource(R.string.home_device_status_cpu_load),\n                    dataPoints = cpuDataPoints,\n                    modifier = Modifier.weight(1f),\n                    color = colors.primary\n                )\n                SystemLineChart(\n                    title = stringResource(R.string.home_device_status_gpu_load),\n                    dataPoints = gpuDataPoints,\n                    modifier = Modifier.weight(1f),\n                    color = colors.tertiary\n                )\n            }\n\n            if (cpuTempDataPoints.isNotEmpty() && cpuTempDataPoints.last() > 0f) {\n                SystemLineChart(\n                    title = stringResource(R.string.home_device_status_cpu_temp),\n                    dataPoints = cpuTempDataPoints,\n                    unit = \"°C\",\n                    modifier = Modifier.fillMaxWidth(),\n                    color = colors.error\n                )\n            }\n\n            if (timeSeries.ramHistory.isNotEmpty()) {\n                SystemAreaChart(\n                    title = stringResource(R.string.home_device_status_memory_trend),\n                    dataPoints = timeSeries.ramHistory,\n                    modifier = Modifier.fillMaxWidth(),\n                    color = colors.secondary\n                )\n            }\n\n            if (systemMonitor.cpuFrequencies.isNotEmpty()) {\n                CpuFrequencyBars(systemMonitor.cpuFrequencies)\n            }\n\n            systemMonitor.memoryInfo?.let { mem ->\n                val ramPercent = if (mem.ramTotal > 0) {\n                    (mem.ramUsed.toFloat() / mem.ramTotal.toFloat() * 100f)\n                } else 0f\n\n                Column(verticalArrangement = Arrangement.spacedBy(6.dp)) {\n                    Row(\n                        modifier = Modifier.fillMaxWidth(),\n                        horizontalArrangement = Arrangement.SpaceBetween,\n                        verticalAlignment = Alignment.CenterVertically\n                    ) {\n                        Row(verticalAlignment = Alignment.CenterVertically) {\n                            Icon(\n                                imageVector = Icons.Outlined.Memory,\n                                contentDescription = null,\n                                modifier = Modifier.size(16.dp),\n                                tint = colors.onSurfaceVariant\n                            )\n                            Spacer(Modifier.width(6.dp))\n                            Text(\n                                text = stringResource(R.string.home_storage_ram),\n                                style = MaterialTheme.typography.bodySmall,\n                                color = colors.onSurfaceVariant\n                            )\n                        }\n                        Text(\n                            text = formatBytes(mem.ramUsed) + \" / \" + formatBytes(mem.ramTotal),\n                            style = MaterialTheme.typography.labelSmall,\n                            color = colors.onSurfaceVariant\n                        )\n                    }\n                    LinearProgressIndicator(\n                        progress = { ramPercent.coerceIn(0f, 100f) / 100f },\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .height(8.dp)\n                            .clip(RoundedCornerShape(4.dp)),\n                        color = when {\n                            ramPercent > 85f -> colors.error\n                            ramPercent > 70f -> colors.tertiary\n                            else -> colors.primary\n                        },\n                        trackColor = colors.surfaceContainerHighest\n                    )\n                }\n\n                if (mem.zramTotal > 0) {\n                    val zramPercent = mem.zramUsed.toFloat() / mem.zramTotal.toFloat() * 100f\n                    Column(verticalArrangement = Arrangement.spacedBy(6.dp)) {\n                        Row(\n                            modifier = Modifier.fillMaxWidth(),\n                            horizontalArrangement = Arrangement.SpaceBetween,\n                            verticalAlignment = Alignment.CenterVertically\n                        ) {\n                            Row(verticalAlignment = Alignment.CenterVertically) {\n                                Icon(\n                                    imageVector = Icons.Outlined.SdStorage,\n                                    contentDescription = null,\n                                    modifier = Modifier.size(16.dp),\n                                    tint = colors.onSurfaceVariant\n                                )\n                                Spacer(Modifier.width(6.dp))\n                                Text(\n                                    text = stringResource(R.string.home_storage_zram),\n                                    style = MaterialTheme.typography.bodySmall,\n                                    color = colors.onSurfaceVariant\n                                )\n                            }\n                            Text(\n                                text = formatBytes(mem.zramUsed) + \" / \" + formatBytes(mem.zramTotal),\n                                style = MaterialTheme.typography.labelSmall,\n                                color = colors.onSurfaceVariant\n                            )\n                        }\n                        LinearProgressIndicator(\n                            progress = { zramPercent.coerceIn(0f, 100f) / 100f },\n                            modifier = Modifier\n                                .fillMaxWidth()\n                                .height(8.dp)\n                                .clip(RoundedCornerShape(4.dp)),\n                            color = colors.tertiary,\n                            trackColor = colors.surfaceContainerHighest\n                        )\n                    }\n                }\n\n                if (mem.swapTotal > 0) {\n                    val swapPercent = mem.swapUsed.toFloat() / mem.swapTotal.toFloat() * 100f\n                    Column(verticalArrangement = Arrangement.spacedBy(6.dp)) {\n                        Row(\n                            modifier = Modifier.fillMaxWidth(),\n                            horizontalArrangement = Arrangement.SpaceBetween,\n                            verticalAlignment = Alignment.CenterVertically\n                        ) {\n                            Row(verticalAlignment = Alignment.CenterVertically) {\n                                Icon(\n                                    imageVector = Icons.Outlined.Storage,\n                                    contentDescription = null,\n                                    modifier = Modifier.size(16.dp),\n                                    tint = colors.onSurfaceVariant\n                                )\n                                Spacer(Modifier.width(6.dp))\n                                Text(\n                                    text = stringResource(R.string.home_storage_swap),\n                                    style = MaterialTheme.typography.bodySmall,\n                                    color = colors.onSurfaceVariant\n                                )\n                            }\n                            Text(\n                                text = formatBytes(mem.swapUsed) + \" / \" + formatBytes(mem.swapTotal),\n                                style = MaterialTheme.typography.labelSmall,\n                                color = colors.onSurfaceVariant\n                            )\n                        }\n                        LinearProgressIndicator(\n                            progress = { swapPercent.coerceIn(0f, 100f) / 100f },\n                            modifier = Modifier\n                                .fillMaxWidth()\n                                .height(8.dp)\n                                .clip(RoundedCornerShape(4.dp)),\n                            color = colors.error,\n                            trackColor = colors.surfaceContainerHighest\n                        )\n                    }\n                }\n            }\n\n            Column(\n                modifier = Modifier.fillMaxWidth(),\n                verticalArrangement = Arrangement.spacedBy(4.dp)\n            ) {\n                Row(verticalAlignment = Alignment.CenterVertically) {\n                    Icon(\n                        imageVector = if (systemMonitor.batteryCharging) {\n                            Icons.Outlined.BatteryChargingFull\n                        } else {\n                            Icons.Outlined.BatteryStd\n                        },\n                        contentDescription = null,\n                        modifier = Modifier.size(14.dp),\n                        tint = if (systemMonitor.batteryCharging) colors.primary else colors.onSurfaceVariant\n                    )\n                    Spacer(Modifier.width(4.dp))\n                    Text(\n                        text = stringResource(R.string.home_device_status_battery_level),\n                        style = MaterialTheme.typography.labelSmall,\n                        color = colors.onSurfaceVariant\n                    )\n                }\n                Text(\n                    text = buildString {\n                        append(\"${systemMonitor.batteryLevel}%\")\n                        if (systemMonitor.batteryCharging) append(\" · ${stringResource(R.string.home_device_status_battery_charging)}\")\n                        if (systemMonitor.batteryTemp > 0) {\n                            append(\"  ${systemMonitor.batteryTemp.toInt()}°C\")\n                        }\n                    },\n                    style = MaterialTheme.typography.titleMedium,\n                    fontWeight = FontWeight.Bold,\n                    color = colors.onSurface\n                )\n                LinearProgressIndicator(\n                    progress = { systemMonitor.batteryLevel.coerceIn(0, 100) / 100f },\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .height(6.dp)\n                        .clip(RoundedCornerShape(3.dp)),\n                    color = when {\n                        systemMonitor.batteryCharging -> colors.primary\n                        systemMonitor.batteryLevel <= 20 -> colors.error\n                        systemMonitor.batteryLevel <= 50 -> colors.tertiary\n                        else -> colors.primary\n                    },\n                    trackColor = colors.surfaceContainerHighest\n                )\n            }\n\n            if (systemMonitor.networkRxBytes > 0 || systemMonitor.networkTxBytes > 0) {\n                Row(\n                    modifier = Modifier.fillMaxWidth(),\n                    horizontalArrangement = Arrangement.spacedBy(16.dp)\n                ) {\n                    Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(4.dp)) {\n                        Row(verticalAlignment = Alignment.CenterVertically) {\n                            Icon(\n                                imageVector = Icons.Outlined.Download,\n                                contentDescription = null,\n                                modifier = Modifier.size(14.dp),\n                                tint = colors.onSurfaceVariant\n                            )\n                            Spacer(Modifier.width(4.dp))\n                            Text(\n                                text = stringResource(R.string.home_network_rx),\n                                style = MaterialTheme.typography.labelSmall,\n                                color = colors.onSurfaceVariant\n                            )\n                        }\n                        Text(\n                            text = formatBytes(systemMonitor.networkRxBytes),\n                            style = MaterialTheme.typography.titleMedium,\n                            fontWeight = FontWeight.Bold,\n                            color = colors.onSurface\n                        )\n                    }\n                    Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(4.dp)) {\n                        Row(verticalAlignment = Alignment.CenterVertically) {\n                            Icon(\n                                imageVector = Icons.Outlined.Upload,\n                                contentDescription = null,\n                                modifier = Modifier.size(14.dp),\n                                tint = colors.onSurfaceVariant\n                            )\n                            Spacer(Modifier.width(4.dp))\n                            Text(\n                                text = stringResource(R.string.home_network_tx),\n                                style = MaterialTheme.typography.labelSmall,\n                                color = colors.onSurfaceVariant\n                            )\n                        }\n                        Text(\n                            text = formatBytes(systemMonitor.networkTxBytes),\n                            style = MaterialTheme.typography.titleMedium,\n                            fontWeight = FontWeight.Bold,\n                            color = colors.onSurface\n                        )\n                    }\n                }\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun ModuleStatisticsSection(\n    superuserCount: Int,\n    apmModuleCount: Int,\n    kernelModuleCount: Int,\n    modifier: Modifier = Modifier\n) {\n    TonalCard(modifier = modifier) {\n        Row(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(16.dp),\n            horizontalArrangement = Arrangement.spacedBy(16.dp)\n        ) {\n            val totalCount = kernelModuleCount + apmModuleCount + superuserCount\n            ModulePieChart(\n                data = rememberPieSliceDataFromCounts(\n                    kernelModules = kernelModuleCount,\n                    apmModules = apmModuleCount,\n                    superusers = superuserCount,\n                    apmLabel = stringResource(R.string.apm)\n                ),\n                centerLabel = if (totalCount > 0) totalCount.toString() else \"--\",\n                modifier = Modifier.size(140.dp)\n            )\n\n            Column(\n                verticalArrangement = Arrangement.spacedBy(8.dp),\n                modifier = Modifier.align(Alignment.CenterVertically)\n            ) {\n                StatRow(\n                    label = stringResource(R.string.home_stats_kernel_modules),\n                    value = kernelModuleCount.toString(),\n                    icon = Icons.Outlined.DeveloperBoard\n                )\n                StatRow(\n                    label = stringResource(R.string.home_stats_apm_modules),\n                    value = apmModuleCount.toString(),\n                    icon = Icons.Outlined.Extension\n                )\n                StatRow(\n                    label = stringResource(R.string.home_stats_superusers),\n                    value = superuserCount.toString(),\n                    icon = Icons.Outlined.Shield\n                )\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun StatRow(\n    label: String,\n    value: String,\n    icon: ImageVector,\n    modifier: Modifier = Modifier\n) {\n    Row(\n        modifier = modifier.fillMaxWidth(),\n        verticalAlignment = Alignment.CenterVertically\n    ) {\n        Icon(\n            imageVector = icon,\n            contentDescription = null,\n            tint = MaterialTheme.colorScheme.primary,\n            modifier = Modifier.size(20.dp)\n        )\n        Spacer(Modifier.width(12.dp))\n        Text(\n            text = label,\n            style = MaterialTheme.typography.bodyMedium,\n            color = MaterialTheme.colorScheme.onSurfaceVariant,\n            modifier = Modifier.weight(1f)\n        )\n        Text(\n            text = value,\n            style = MaterialTheme.typography.bodyMedium,\n            fontWeight = FontWeight.SemiBold,\n            color = MaterialTheme.colorScheme.onSurface\n        )\n    }\n}\n\n@Composable\nprivate fun CpuFrequencyBars(\n    cpuFrequencies: List<me.bmax.apatch.util.HardwareMonitor.CpuFreqInfo>,\n    modifier: Modifier = Modifier\n) {\n    val colors = MaterialTheme.colorScheme\n\n    val clusters = cpuFrequencies\n        .groupBy { it.maxFreqKhz }\n        .values\n        .map { cores ->\n            val label = if (cores.size == 1) \"CPU${cores[0].coreIndex}\" else \"CPU${cores.first().coreIndex}-${cores.last().coreIndex}\"\n            val avgFreq = cores.map { it.currentFreqKhz }.sum() / cores.size\n            val maxFreq = cores.first().maxFreqKhz\n            CpuCluster(label, avgFreq, maxFreq)\n        }\n\n    Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(4.dp)) {\n        Row(verticalAlignment = Alignment.CenterVertically) {\n            Icon(\n                imageVector = Icons.Outlined.DeveloperBoard,\n                contentDescription = null,\n                modifier = Modifier.size(14.dp),\n                tint = colors.onSurfaceVariant\n            )\n            Spacer(Modifier.width(6.dp))\n            Text(\n                text = stringResource(R.string.home_device_status_cpu_freq),\n                style = MaterialTheme.typography.labelSmall,\n                color = colors.onSurfaceVariant\n            )\n        }\n        clusters.forEach { cluster ->\n            val percent = if (cluster.maxFreq > 0) {\n                (cluster.avgFreq.toFloat() / cluster.maxFreq.toFloat() * 100f).coerceIn(0f, 100f)\n            } else 0f\n\n            Row(\n                modifier = Modifier.fillMaxWidth(),\n                verticalAlignment = Alignment.CenterVertically\n            ) {\n                Text(\n                    text = cluster.label,\n                    style = MaterialTheme.typography.labelSmall,\n                    color = colors.onSurfaceVariant,\n                    modifier = Modifier.width(52.dp)\n                )\n                LinearProgressIndicator(\n                    progress = { percent / 100f },\n                    modifier = Modifier\n                        .weight(1f)\n                        .height(6.dp)\n                        .clip(RoundedCornerShape(3.dp)),\n                    color = when {\n                        percent > 80f -> colors.error\n                        percent > 50f -> colors.tertiary\n                        else -> colors.primary\n                    },\n                    trackColor = colors.surfaceContainerHighest\n                )\n                Spacer(Modifier.width(8.dp))\n                Text(\n                    text = formatFreq(cluster.avgFreq),\n                    style = MaterialTheme.typography.labelSmall,\n                    color = colors.onSurface,\n                    modifier = Modifier.width(64.dp)\n                )\n            }\n        }\n    }\n}\n\nprivate data class CpuCluster(\n    val label: String,\n    val avgFreq: Long,\n    val maxFreq: Long\n)\n\nprivate fun formatBytes(bytes: Long): String {\n    return when {\n        bytes < 1024 -> \"${bytes}B\"\n        bytes < 1024 * 1024 -> \"${bytes / 1024}KB\"\n        bytes < 1024 * 1024 * 1024 -> String.format(\"%.1fMB\", bytes / (1024.0 * 1024))\n        else -> String.format(\"%.1fGB\", bytes / (1024.0 * 1024 * 1024))\n    }\n}\n\nprivate fun formatFreq(khz: Long): String {\n    return when {\n        khz >= 1_000_000 -> String.format(\"%.2fGHz\", khz / 1_000_000.0)\n        khz >= 1000 -> String.format(\"%.0fMHz\", khz / 1000.0)\n        else -> \"${khz}KHz\"\n    }\n}\n\n@Composable\nprivate fun StatsGridTopSection(\n    kpState: APApplication.State,\n    apState: APApplication.State,\n    navigator: DestinationsNavigator,\n    showUninstallDialog: MutableState<Boolean>,\n    showAuthKeyDialog: MutableState<Boolean>\n) {\n    val managerVersion = Version.getManagerVersion()\n\n    Row(\n        modifier = Modifier\n            .fillMaxWidth()\n            .height(IntrinsicSize.Min),\n        horizontalArrangement = Arrangement.spacedBy(12.dp)\n    ) {\n        StatusCardBig(\n            modifier = Modifier\n                .weight(1f)\n                .fillMaxHeight(),\n            kpState = kpState,\n            apState = apState,\n            onClick = {\n                when (kpState) {\n                    APApplication.State.UNKNOWN_STATE -> showAuthKeyDialog.value = true\n                    APApplication.State.KERNELPATCH_NEED_UPDATE -> navigator.navigate(InstallModeSelectScreenDestination)\n                    APApplication.State.KERNELPATCH_INSTALLED -> {}\n                    else -> navigator.navigate(InstallModeSelectScreenDestination)\n                }\n            }\n        )\n\n        Column(\n            modifier = Modifier\n                .weight(1f)\n                .fillMaxHeight(),\n            verticalArrangement = Arrangement.spacedBy(12.dp)\n        ) {\n            SmallInfoCard(\n                modifier = Modifier.weight(1f),\n                title = stringResource(R.string.kernel_patch),\n                value = if (kpState != APApplication.State.UNKNOWN_STATE) \"${Version.installedKPVString()} (${managerVersion.second})\" else \"N/A\",\n                icon = Icons.Outlined.Extension,\n                onClick = {\n                    if (kpState == APApplication.State.KERNELPATCH_NEED_UPDATE) {\n                        navigator.navigate(InstallModeSelectScreenDestination)\n                    }\n                }\n            )\n\n            SmallInfoCard(\n                modifier = Modifier.weight(1f),\n                title = stringResource(R.string.android_patch),\n                value = when (apState) {\n                    APApplication.State.ANDROIDPATCH_INSTALLED -> \"Active\"\n                    APApplication.State.ANDROIDPATCH_NEED_UPDATE -> \"Update\"\n                    APApplication.State.ANDROIDPATCH_INSTALLING -> \"...\"\n                    else -> \"Inactive\"\n                },\n                icon = Icons.Outlined.Android,\n                onClick = {\n                    if (apState == APApplication.State.ANDROIDPATCH_INSTALLED) {\n                        showUninstallDialog.value = true\n                    } else if (apState != APApplication.State.ANDROIDPATCH_NOT_INSTALLED && kpState == APApplication.State.KERNELPATCH_INSTALLED) {\n                        APApplication.installApatch()\n                    }\n                }\n            )\n        }\n    }\n\n    if (kpState != APApplication.State.UNKNOWN_STATE && apState != APApplication.State.UNKNOWN_STATE && apState != APApplication.State.ANDROIDPATCH_INSTALLED) {\n        AStatusCard(apState)\n    }\n\n    HomeBottomSpacer()\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/HomeV2.kt",
    "content": "package me.bmax.apatch.ui.screen\n\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.background\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.CheckCircle\nimport androidx.compose.material.icons.filled.Warning\nimport androidx.compose.material.icons.outlined.Android\nimport androidx.compose.material.icons.outlined.Extension\nimport androidx.compose.material3.*\nimport androidx.compose.material3.surfaceColorAtElevation\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.runtime.*\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport androidx.compose.ui.layout.ContentScale\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport coil.compose.rememberAsyncImagePainter\nimport com.ramcosta.composedestinations.generated.destinations.InstallModeSelectScreenDestination\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.theme.BackgroundConfig\nimport me.bmax.apatch.util.Version\nimport androidx.compose.foundation.isSystemInDarkTheme\n\nimport androidx.compose.ui.draw.alpha\nimport coil.ImageLoader\nimport coil.decode.GifDecoder\nimport coil.decode.ImageDecoderDecoder\nimport coil.request.ImageRequest\nimport android.os.Build\nimport me.bmax.apatch.apApp\nimport me.bmax.apatch.util.Version.getManagerVersion\nimport me.bmax.apatch.util.ui.HomeBottomSpacer\n\nprivate val managerVersion = getManagerVersion()\n\n@Composable\nfun HomeScreenV2(\n    paddingValues: PaddingValues,\n    navigator: DestinationsNavigator,\n    kpState: APApplication.State,\n    apState: APApplication.State\n) {\n    val scrollState = rememberScrollState()\n    \n    // Check if update notification is blocked\n    val kpState = if (kpState == APApplication.State.KERNELPATCH_NEED_UPDATE && apApp.isKernelPatchUpdateBlocked()) {\n        APApplication.State.KERNELPATCH_INSTALLED\n    } else {\n        kpState\n    }\n    \n    val apState = if (apState == APApplication.State.ANDROIDPATCH_NEED_UPDATE && apApp.isAndroidPatchUpdateBlocked()) {\n        APApplication.State.ANDROIDPATCH_INSTALLED\n    } else {\n        apState\n    }\n    \n    val showAuthKeyDialog = remember { mutableStateOf(false) }\n    val showUninstallDialog = remember { mutableStateOf(false) }\n    val showAuthFailedTipDialog = remember { mutableStateOf(false) }\n\n    if (showAuthFailedTipDialog.value) {\n        AuthFailedTipDialog(showDialog = showAuthFailedTipDialog)\n    }\n    if (showAuthKeyDialog.value) {\n        AuthSuperKey(showDialog = showAuthKeyDialog, showFailedDialog = showAuthFailedTipDialog)\n    }\n    if (showUninstallDialog.value) {\n        UninstallDialog(showDialog = showUninstallDialog, navigator)\n    }\n    \n    Column(\n        modifier = Modifier\n            .padding(paddingValues)\n            .padding(horizontal = 16.dp)\n            .verticalScroll(scrollState),\n        verticalArrangement = Arrangement.spacedBy(16.dp)\n    ) {\n        Spacer(Modifier.height(0.dp))\n        \n        // Top Section: Split into Left (Status) and Right (Details)\n        Row(\n            modifier = Modifier\n                .fillMaxWidth()\n                .height(IntrinsicSize.Min),\n            horizontalArrangement = Arrangement.spacedBy(12.dp)\n        ) {\n            // Left: Big Status Card\n            StatusCardBig(\n                modifier = Modifier\n                    .weight(1f)\n                    .fillMaxHeight(),\n                kpState = kpState,\n                apState = apState,\n                onClick = {\n                    when (kpState) {\n                        APApplication.State.UNKNOWN_STATE -> showAuthKeyDialog.value = true\n                        APApplication.State.KERNELPATCH_NEED_UPDATE -> navigator.navigate(InstallModeSelectScreenDestination)\n                        APApplication.State.KERNELPATCH_INSTALLED -> {} \n                        else -> navigator.navigate(InstallModeSelectScreenDestination)\n                    }\n                }\n            )\n            \n            // Right: Two Small Cards\n            Column(\n                modifier = Modifier\n                    .weight(1f)\n                    .fillMaxHeight(),\n                verticalArrangement = Arrangement.spacedBy(12.dp)\n            ) {\n                // Top Right: KP Version\n                SmallInfoCard(\n                    modifier = Modifier.weight(1f),\n                    title = stringResource(R.string.kernel_patch),\n                    value = if (kpState != APApplication.State.UNKNOWN_STATE) \"${Version.installedKPVString()} (${managerVersion.second})\" else \"N/A\",\n                    icon = Icons.Outlined.Extension,\n                    onClick = {\n                        if (kpState == APApplication.State.KERNELPATCH_NEED_UPDATE) {\n                             navigator.navigate(InstallModeSelectScreenDestination)\n                        }\n                    }\n                )\n                \n                // Bottom Right: AP Version\n                SmallInfoCard(\n                    modifier = Modifier.weight(1f),\n                    title = stringResource(R.string.android_patch),\n                    value = when(apState) {\n                        APApplication.State.ANDROIDPATCH_INSTALLED -> \"Active\"\n                        APApplication.State.ANDROIDPATCH_NEED_UPDATE -> \"Update\"\n                        APApplication.State.ANDROIDPATCH_INSTALLING -> \"...\"\n                        else -> \"Inactive\"\n                    },\n                    icon = Icons.Outlined.Android,\n                    onClick = {\n                        if (apState == APApplication.State.ANDROIDPATCH_INSTALLED) {\n                            showUninstallDialog.value = true\n                        } else if (apState != APApplication.State.ANDROIDPATCH_NOT_INSTALLED && kpState == APApplication.State.KERNELPATCH_INSTALLED) {\n                            // Only allow install/uninstall if NOT in \"Not Installed\" state (per user request to disable click)\n                            // Wait, if it is NOT installed, user wants NO trigger effect.\n                            // So we only allow action if INSTALLED.\n                            // But what about INSTALLING?\n                            // User said: \"When system patch (AP) is not installed... click... should be set to no trigger effect\"\n                            // So if apState == ANDROIDPATCH_NOT_INSTALLED -> No effect.\n                            APApplication.installApatch()\n                        }\n                    }\n                )\n            }\n        }\n\n        \n        // AndroidPatch Install Card (Only when not installed)\n        if (kpState != APApplication.State.UNKNOWN_STATE && apState != APApplication.State.UNKNOWN_STATE && apState != APApplication.State.ANDROIDPATCH_INSTALLED) {\n            AStatusCard(apState)\n        }\n        \n        // Info Card\n        InfoCard(kpState, apState)\n        \n        // Learn More\n        val hideApatchCard = APApplication.sharedPreferences.getBoolean(\"hide_apatch_card\", false)\n        if (!hideApatchCard) {\n            LearnMoreCard()\n        }\n        \n        HomeBottomSpacer()\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun StatusCardBig(\n    modifier: Modifier = Modifier,\n    kpState: APApplication.State,\n    apState: APApplication.State,\n    onClick: () -> Unit\n) {\n    val context = androidx.compose.ui.platform.LocalContext.current\n    val isWorking = kpState == APApplication.State.KERNELPATCH_INSTALLED\n    val isUpdate = kpState == APApplication.State.KERNELPATCH_NEED_UPDATE || kpState == APApplication.State.KERNELPATCH_NEED_REBOOT\n    \n    val prefs = APApplication.sharedPreferences\n    val darkThemeFollowSys = prefs.getBoolean(\"night_mode_follow_sys\", false)\n    val nightModeEnabled = prefs.getBoolean(\"night_mode_enabled\", true)\n    val isDark = if (darkThemeFollowSys) {\n        isSystemInDarkTheme()\n    } else {\n        nightModeEnabled\n    }\n    \n    // Colors\n    val useCustomGridBg = BackgroundConfig.isGridWorkingCardBackgroundEnabled && !BackgroundConfig.gridWorkingCardBackgroundUri.isNullOrEmpty()\n    \n    val (baseContainerColor, baseContentColor) = if (BackgroundConfig.isCustomBackgroundEnabled) {\n         val opacity = BackgroundConfig.customBackgroundOpacity\n         val container = MaterialTheme.colorScheme.primary.copy(alpha = opacity)\n         val content = if (opacity <= 0.1f) {\n             if (isDark) Color.White else Color.Black\n         } else {\n             MaterialTheme.colorScheme.onPrimary\n         }\n         container to content\n    } else {\n        if (isWorking) {\n             MaterialTheme.colorScheme.primary to MaterialTheme.colorScheme.onPrimary\n        } else if (isUpdate) {\n             MaterialTheme.colorScheme.secondaryContainer to MaterialTheme.colorScheme.onSecondaryContainer\n        } else {\n             // Use secondaryContainer for Unknown/Not Installed (Fixed/Neutral color like original layout)\n             MaterialTheme.colorScheme.secondaryContainer to MaterialTheme.colorScheme.onSecondaryContainer\n        }\n    }\n    \n    val containerColor = if (useCustomGridBg) Color.Transparent else baseContainerColor\n    // If using custom grid bg, force content color to white (or based on some logic), or keep base logic?\n    // Let's assume white text for image background with dimming\n    val contentColor = if (useCustomGridBg) Color.White else baseContentColor\n\n    Card(\n        onClick = onClick,\n        modifier = modifier,\n        shape = RoundedCornerShape(20.dp),\n        colors = CardDefaults.cardColors(containerColor = containerColor)\n    ) {\n        Box(modifier = Modifier.fillMaxSize()) {\n            if (useCustomGridBg) {\n                // Configure ImageLoader for GIF support explicitly\n                val imageLoader = ImageLoader.Builder(context)\n                    .components {\n                        if (Build.VERSION.SDK_INT >= 28) {\n                            add(ImageDecoderDecoder.Factory())\n                        } else {\n                            add(GifDecoder.Factory())\n                        }\n                    }\n                    .build()\n\n                val prefs = APApplication.sharedPreferences\n                val darkThemeFollowSys = prefs.getBoolean(\"night_mode_follow_sys\", false)\n                val nightModeEnabled = prefs.getBoolean(\"night_mode_enabled\", true)\n                val isDarkTheme = if (darkThemeFollowSys) {\n                    Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && context.resources.configuration.uiMode and\n                        android.content.res.Configuration.UI_MODE_NIGHT_MASK == android.content.res.Configuration.UI_MODE_NIGHT_YES\n                } else {\n                    nightModeEnabled\n                }\n\n                Image(\n                    painter = rememberAsyncImagePainter(\n                        model = ImageRequest.Builder(context)\n                            .data(BackgroundConfig.gridWorkingCardBackgroundUri)\n                            .crossfade(true)\n                            .build(),\n                        imageLoader = imageLoader,\n                        contentScale = ContentScale.Crop\n                    ),\n                    contentDescription = null,\n                    contentScale = ContentScale.Crop,\n                    modifier = Modifier\n                        .fillMaxSize()\n                        .alpha(BackgroundConfig.getEffectiveGridBackgroundOpacity(isDarkTheme))\n                )\n                // Add a dim layer for readability\n                Box(\n                    modifier = Modifier\n                        .fillMaxSize()\n                        .background(Color.Black.copy(alpha = BackgroundConfig.gridWorkingCardBackgroundDim))\n                )\n            }\n            \n            Box(modifier = Modifier.fillMaxSize().padding(16.dp)) {\n                Column(modifier = Modifier.align(Alignment.BottomStart)) {\n                    if (!BackgroundConfig.isGridWorkingCardTextHidden) {\n                        Text(\n                            text = when(kpState) {\n                                APApplication.State.KERNELPATCH_INSTALLED -> stringResource(R.string.home_working)\n                                APApplication.State.KERNELPATCH_NEED_UPDATE -> stringResource(R.string.home_kp_need_update)\n                                APApplication.State.KERNELPATCH_NEED_REBOOT -> stringResource(R.string.home_ap_cando_reboot)\n                                APApplication.State.UNKNOWN_STATE -> stringResource(R.string.home_install_unknown)\n                                else -> stringResource(R.string.home_not_installed)\n                            },\n                            style = MaterialTheme.typography.titleLarge,\n                            fontWeight = FontWeight.Bold,\n                            color = contentColor\n                        )\n                    }\n                    if (isWorking && !BackgroundConfig.isGridWorkingCardModeHidden) {\n                        Spacer(Modifier.height(4.dp))\n                        val customText = BackgroundConfig.getCustomBadgeText()\n                        Text(\n                            text = if (customText != null) \"<$customText>\" else if (apState == APApplication.State.ANDROIDPATCH_INSTALLED) \"<Full>\" else \"<Half>\",\n                            style = MaterialTheme.typography.bodyMedium,\n                            color = contentColor.copy(alpha = 0.8f)\n                        )\n                    }\n                } // End of Column\n            }\n            \n            // Icon\n            if (!BackgroundConfig.isGridWorkingCardCheckHidden) {\n                Icon(\n                    imageVector = if (isWorking) Icons.Filled.CheckCircle else Icons.Filled.Warning,\n                    contentDescription = null,\n                    modifier = Modifier\n                        .align(Alignment.TopEnd)\n                        .size(48.dp),\n                    tint = contentColor\n                )\n            }\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun SmallInfoCard(\n    modifier: Modifier = Modifier,\n    title: String,\n    value: String,\n    icon: ImageVector,\n    onClick: () -> Unit\n) {\n    val containerColor = if (BackgroundConfig.isCustomBackgroundEnabled) {\n        MaterialTheme.colorScheme.surface.copy(alpha = BackgroundConfig.customBackgroundOpacity)\n    } else {\n        MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)\n    }\n    \n    Card(\n        onClick = onClick,\n        modifier = modifier.fillMaxWidth(),\n        shape = RoundedCornerShape(20.dp),\n        colors = CardDefaults.cardColors(containerColor = containerColor)\n    ) {\n        Column(\n            modifier = Modifier.padding(16.dp),\n            verticalArrangement = Arrangement.Center\n        ) {\n            Row(verticalAlignment = Alignment.CenterVertically) {\n                Icon(\n                    imageVector = icon, \n                    contentDescription = null, \n                    modifier = Modifier.size(16.dp),\n                    tint = MaterialTheme.colorScheme.onSurfaceVariant\n                )\n                Spacer(Modifier.width(8.dp))\n                Text(\n                    text = title,\n                    style = MaterialTheme.typography.labelMedium,\n                    color = MaterialTheme.colorScheme.onSurfaceVariant\n                )\n            }\n            Spacer(Modifier.height(4.dp))\n            Text(\n                text = value,\n                style = MaterialTheme.typography.titleMedium,\n                fontWeight = FontWeight.SemiBold,\n                 color = MaterialTheme.colorScheme.onSurface\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/HomeV3.kt",
    "content": "package me.bmax.apatch.ui.screen\n\nimport android.os.Build\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.outlined.Android\nimport androidx.compose.material.icons.outlined.BatteryStd\nimport androidx.compose.material.icons.outlined.Extension\nimport androidx.compose.material.icons.outlined.Settings\nimport androidx.compose.material.icons.outlined.SdStorage\nimport androidx.compose.material3.*\nimport androidx.compose.runtime.*\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.surfaceColorAtElevation\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.lifecycle.compose.LocalLifecycleOwner\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.repeatOnLifecycle\nimport com.ramcosta.composedestinations.generated.destinations.InstallModeSelectScreenDestination\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.apApp\nimport me.bmax.apatch.Natives\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.theme.BackgroundConfig\nimport me.bmax.apatch.util.HardwareMonitor\nimport me.bmax.apatch.util.Version\nimport me.bmax.apatch.util.Version.getManagerVersion\nimport me.bmax.apatch.util.getSELinuxStatus\nimport me.bmax.apatch.util.rootShellForResult\nimport me.bmax.apatch.util.ui.HomeBottomSpacer\n\nprivate val managerVersion = getManagerVersion()\n\n@Composable\nfun HomeScreenV3(\n    paddingValues: PaddingValues,\n    navigator: DestinationsNavigator,\n    kpState: APApplication.State,\n    apState: APApplication.State\n) {\n    val scrollState = rememberScrollState()\n    \n    // Check if update notification is blocked\n    val kpState = if (kpState == APApplication.State.KERNELPATCH_NEED_UPDATE && apApp.isKernelPatchUpdateBlocked()) {\n        APApplication.State.KERNELPATCH_INSTALLED\n    } else {\n        kpState\n    }\n    \n    val apState = if (apState == APApplication.State.ANDROIDPATCH_NEED_UPDATE && apApp.isAndroidPatchUpdateBlocked()) {\n        APApplication.State.ANDROIDPATCH_INSTALLED\n    } else {\n        apState\n    }\n    \n    val context = LocalContext.current\n    // Only enable wallpaper mode (no card shadow) if custom background is actually enabled\n    val isWallpaperMode = BackgroundConfig.isCustomBackgroundEnabled && (BackgroundConfig.customBackgroundUri != null || BackgroundConfig.isMultiBackgroundEnabled)\n    \n    val showAuthKeyDialog = remember { mutableStateOf(false) }\n    val showUninstallDialog = remember { mutableStateOf(false) }\n    val showAuthFailedTipDialog = remember { mutableStateOf(false) }\n\n    val defaultSlot = stringResource(R.string.home_info_auth_na)\n    var deviceSlot by remember { mutableStateOf(defaultSlot) }\n    var zygiskImplement by remember { mutableStateOf(\"None\") }\n    var mountImplement by remember { mutableStateOf(\"None\") }\n\n    LaunchedEffect(Unit) {\n        withContext(Dispatchers.IO) {\n            try {\n                zygiskImplement = me.bmax.apatch.util.getZygiskImplement()\n                mountImplement = me.bmax.apatch.util.getMountImplement()\n\n                val result = rootShellForResult(\"getprop ro.boot.slot_suffix\")\n                if (result.isSuccess) {\n                    val slot = result.out.firstOrNull()?.trim()?.removePrefix(\"_\")\n                    if (!slot.isNullOrEmpty()) {\n                        deviceSlot = slot.uppercase()\n                    }\n                }\n            } catch (e: Exception) {\n                e.printStackTrace()\n            }\n        }\n    }\n\n    if (showAuthFailedTipDialog.value) {\n        AuthFailedTipDialog(showDialog = showAuthFailedTipDialog)\n    }\n    if (showAuthKeyDialog.value) {\n        AuthSuperKey(showDialog = showAuthKeyDialog, showFailedDialog = showAuthFailedTipDialog)\n    }\n    if (showUninstallDialog.value) {\n        UninstallDialog(showDialog = showUninstallDialog, navigator)\n    }\n    \n    BoxWithConstraints {\n        val isWide = maxWidth >= 600.dp && maxWidth > maxHeight\n        \n        Column(\n            modifier = Modifier\n                .padding(paddingValues)\n                .fillMaxSize()\n                .verticalScroll(scrollState)\n                .padding(16.dp),\n            verticalArrangement = Arrangement.spacedBy(16.dp)\n        ) {\n            if (isWide) {\n                // Row 1: KernelPatch + APP\n                Row(\n                    horizontalArrangement = Arrangement.spacedBy(16.dp),\n                    modifier = Modifier.height(IntrinsicSize.Max)\n                ) {\n                    KernelPatchCard(\n                        kpState = kpState,\n                        navigator = navigator,\n                        showAuthKeyDialog = showAuthKeyDialog,\n                        isWallpaperMode = isWallpaperMode,\n                        zygiskImplement = zygiskImplement,\n                        mountImplement = mountImplement,\n                        modifier = Modifier.weight(1f).fillMaxHeight()\n                    )\n                    AppCard(\n                        apState = apState,\n                        kpState = kpState,\n                        deviceSlot = deviceSlot,\n                        showUninstallDialog = showUninstallDialog,\n                        isWallpaperMode = isWallpaperMode,\n                        modifier = Modifier.weight(1f).fillMaxHeight()\n                    )\n                }\n                \n                // Row 2: Device + Storage\n                Row(\n                    horizontalArrangement = Arrangement.spacedBy(16.dp),\n                    modifier = Modifier.height(IntrinsicSize.Max)\n                ) {\n                    DeviceStatusCard(\n                        isWallpaperMode = isWallpaperMode,\n                        modifier = Modifier.weight(1f).fillMaxHeight()\n                    )\n                    StorageCard(\n                        isWallpaperMode = isWallpaperMode,\n                        modifier = Modifier.weight(1f).fillMaxHeight()\n                    )\n                }\n            } else {\n                // Vertical Stack\n                KernelPatchCard(\n                    kpState = kpState,\n                    navigator = navigator,\n                    showAuthKeyDialog = showAuthKeyDialog,\n                    isWallpaperMode = isWallpaperMode,\n                    zygiskImplement = zygiskImplement,\n                    mountImplement = mountImplement\n                )\n                AppCard(\n                    apState = apState,\n                    kpState = kpState,\n                    deviceSlot = deviceSlot,\n                    showUninstallDialog = showUninstallDialog,\n                    isWallpaperMode = isWallpaperMode\n                )\n                DeviceStatusCard(isWallpaperMode = isWallpaperMode)\n                StorageCard(isWallpaperMode = isWallpaperMode)\n            }\n\n            HomeBottomSpacer()\n        }\n    }\n}\n\n@Composable\nprivate fun KernelPatchCard(\n    kpState: APApplication.State,\n    navigator: DestinationsNavigator,\n    showAuthKeyDialog: MutableState<Boolean>,\n    isWallpaperMode: Boolean,\n    zygiskImplement: String,\n    mountImplement: String,\n    modifier: Modifier = Modifier\n) {\n    MagiskStyleCard(\n        title = \"KernelPatch\",\n        icon = Icons.Outlined.Extension,\n        actionText = when (kpState) {\n            APApplication.State.KERNELPATCH_NEED_UPDATE -> stringResource(R.string.home_kp_cando_update)\n            APApplication.State.UNKNOWN_STATE -> stringResource(R.string.kpm_install)\n            else -> stringResource(R.string.kpm_install)\n        },\n        showAction = kpState != APApplication.State.KERNELPATCH_INSTALLED,\n        isWallpaperMode = isWallpaperMode,\n        onActionClick = {\n            if (kpState == APApplication.State.UNKNOWN_STATE) {\n                showAuthKeyDialog.value = true\n            } else {\n                navigator.navigate(InstallModeSelectScreenDestination)\n            }\n        },\n        modifier = modifier\n    ) {\n        InfoRow(\n            label = stringResource(R.string.home_kpatch_version),\n            value = if (kpState != APApplication.State.UNKNOWN_STATE) Version.installedKPVString() else stringResource(R.string.home_not_installed)\n        )\n        if (kpState != APApplication.State.UNKNOWN_STATE && zygiskImplement != \"None\") {\n            InfoRow(\n                label = stringResource(R.string.home_zygisk_implement),\n                value = zygiskImplement\n            )\n        }\n        if (kpState != APApplication.State.UNKNOWN_STATE && mountImplement != \"None\") {\n            InfoRow(\n                label = stringResource(R.string.home_mount_implement),\n                value = mountImplement\n            )\n        }\n        InfoRow(\n            label = stringResource(R.string.home_info_kernel),\n            value = System.getProperty(\"os.version\") ?: stringResource(R.string.home_selinux_status_unknown)\n        )\n        if (kpState != APApplication.State.UNKNOWN_STATE) {\n            InfoRow(\n                label = stringResource(R.string.home_info_superkey),\n                value = if (APApplication.superKey.isNotEmpty()) stringResource(R.string.home_info_auth_auth) else stringResource(R.string.home_info_auth_na)\n            )\n        }\n    }\n}\n\n@Composable\nprivate fun AppCard(\n    apState: APApplication.State,\n    kpState: APApplication.State,\n    deviceSlot: String,\n    showUninstallDialog: MutableState<Boolean>,\n    isWallpaperMode: Boolean,\n    modifier: Modifier = Modifier\n) {\n    MagiskStyleCard(\n        title = stringResource(R.string.app_name),\n        icon = Icons.Outlined.Android,\n        actionText = if (apState == APApplication.State.ANDROIDPATCH_INSTALLED) stringResource(R.string.home_ap_cando_uninstall) else stringResource(R.string.kpm_install),\n        showAction = true,\n        actionEnabled = kpState == APApplication.State.KERNELPATCH_INSTALLED || kpState == APApplication.State.KERNELPATCH_NEED_UPDATE || apState == APApplication.State.ANDROIDPATCH_INSTALLED,\n        isWallpaperMode = isWallpaperMode,\n        onActionClick = {\n            if (apState == APApplication.State.ANDROIDPATCH_INSTALLED) {\n                showUninstallDialog.value = true\n            } else if (kpState == APApplication.State.KERNELPATCH_INSTALLED || kpState == APApplication.State.KERNELPATCH_NEED_UPDATE) {\n                APApplication.installApatch()\n            }\n        },\n        modifier = modifier\n    ) {\n        InfoRow(\n            label = stringResource(R.string.home_apatch_version),\n            value = \"${managerVersion.second} (${managerVersion.first})\"\n        )\n        InfoRow(\n            label = stringResource(R.string.home_info_device_slot),\n            value = deviceSlot\n        )\n        InfoRow(\n            label = stringResource(R.string.home_info_device_model),\n            value = Build.MODEL\n        )\n        InfoRow(\n            label = stringResource(R.string.home_info_running_mode),\n            value = if (apState == APApplication.State.ANDROIDPATCH_INSTALLED)\n                (BackgroundConfig.getCustomBadgeText() ?: stringResource(R.string.home_info_mode_full))\n            else if (kpState == APApplication.State.KERNELPATCH_INSTALLED || kpState == APApplication.State.KERNELPATCH_NEED_UPDATE)\n                (BackgroundConfig.getCustomBadgeText() ?: stringResource(R.string.home_info_mode_half))\n            else\n                stringResource(R.string.home_info_auth_na)\n        )\n        InfoRow(\n            label = stringResource(R.string.home_selinux_status),\n            value = getSELinuxStatus()\n        )\n        InfoRow(\n            label = stringResource(R.string.home_su_path),\n            value = if (kpState != APApplication.State.UNKNOWN_STATE) Natives.suPath() else stringResource(R.string.home_info_auth_na)\n        )\n    }\n}\n\n@Composable\nprivate fun DeviceStatusCard(isWallpaperMode: Boolean, modifier: Modifier = Modifier) {\n    val context = LocalContext.current\n    var batteryTemp by remember { mutableStateOf(0f) }\n    var batteryLevel by remember { mutableIntStateOf(0) }\n    var cpuUsage by remember { mutableIntStateOf(0) }\n\n    val lifecycleOwner = LocalLifecycleOwner.current\n\n    LaunchedEffect(lifecycleOwner) {\n        lifecycleOwner.lifecycle.repeatOnLifecycle(androidx.lifecycle.Lifecycle.State.RESUMED) {\n            withContext(Dispatchers.IO) {\n                // Battery Info\n                val intent = context.registerReceiver(null, android.content.IntentFilter(android.content.Intent.ACTION_BATTERY_CHANGED))\n                batteryTemp = (intent?.getIntExtra(android.os.BatteryManager.EXTRA_TEMPERATURE, 0) ?: 0) / 10f\n                val level = intent?.getIntExtra(android.os.BatteryManager.EXTRA_LEVEL, -1) ?: -1\n                val scale = intent?.getIntExtra(android.os.BatteryManager.EXTRA_SCALE, -1) ?: -1\n                if (level != -1 && scale != -1) {\n                    batteryLevel = (level * 100 / scale.toFloat()).toInt()\n                }\n\n                // CPU Usage (HardwareMonitor)\n                cpuUsage = HardwareMonitor.getCpuUsage()\n\n                kotlinx.coroutines.delay(10000)\n            }\n        }\n    }\n\n    MagiskStyleCard(\n        title = stringResource(R.string.home_device_status_title),\n        icon = Icons.Outlined.Settings,\n        actionText = \"\",\n        showAction = false,\n        isWallpaperMode = isWallpaperMode,\n        onActionClick = {},\n        modifier = modifier\n    ) {\n        Row(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(vertical = 8.dp),\n            horizontalArrangement = Arrangement.SpaceEvenly\n        ) {\n            StatusCircle(\n                value = \"${batteryTemp}°C\",\n                label = stringResource(R.string.home_device_status_battery_temp),\n                progress = (batteryTemp / 50f).coerceIn(0f, 1f),\n                color = MaterialTheme.colorScheme.primary\n            )\n            StatusCircle(\n                value = \"$cpuUsage%\",\n                label = stringResource(R.string.home_device_status_cpu_load),\n                progress = (cpuUsage / 100f).coerceIn(0f, 1f),\n                color = MaterialTheme.colorScheme.secondary\n            )\n            StatusCircle(\n                value = \"$batteryLevel%\",\n                label = stringResource(R.string.home_device_status_battery_level),\n                progress = (batteryLevel / 100f).coerceIn(0f, 1f),\n                color = MaterialTheme.colorScheme.tertiary\n            )\n        }\n    }\n}\n\n@Composable\nprivate fun StorageCard(isWallpaperMode: Boolean, modifier: Modifier = Modifier) {\n    var ramUsed by remember { mutableLongStateOf(0L) }\n    var ramTotal by remember { mutableLongStateOf(0L) }\n    var storageUsed by remember { mutableLongStateOf(0L) }\n    var storageTotal by remember { mutableLongStateOf(0L) }\n\n    // ZRAM & Swap\n    var zramUsed by remember { mutableLongStateOf(0L) }\n    var zramTotal by remember { mutableLongStateOf(0L) }\n    var swapUsed by remember { mutableLongStateOf(0L) }\n    var swapTotal by remember { mutableLongStateOf(0L) }\n\n    val lifecycleOwner = LocalLifecycleOwner.current\n\n    // [OPTIMIZE] 只在 RESUMED 状态时定期更新,并在 PAUSED 时自动停止\n    LaunchedEffect(lifecycleOwner) {\n        lifecycleOwner.lifecycle.repeatOnLifecycle(androidx.lifecycle.Lifecycle.State.RESUMED) {\n            withContext(Dispatchers.IO) {\n                // Internal Storage (Standard Java API)\n                val dataDir = android.os.Environment.getDataDirectory()\n                val stat = android.os.StatFs(dataDir.path)\n                val blockSize = stat.blockSizeLong\n                val totalBlocks = stat.blockCountLong\n                val availableBlocks = stat.availableBlocksLong\n                storageTotal = totalBlocks * blockSize\n                storageUsed = storageTotal - (availableBlocks * blockSize)\n\n                // Memory Info (HardwareMonitor)\n                val memInfo = HardwareMonitor.getMemoryInfo()\n                ramTotal = memInfo.ramTotal\n                ramUsed = memInfo.ramUsed\n                zramTotal = memInfo.zramTotal\n                zramUsed = memInfo.zramUsed\n                swapTotal = memInfo.swapTotal\n                swapUsed = memInfo.swapUsed\n\n                // [OPTIMIZE] 保持5秒更新频率,存储信息变化较慢\n                kotlinx.coroutines.delay(5000)\n            }\n        }\n    }\n    \n    MagiskStyleCard(\n        title = stringResource(R.string.home_storage_title),\n        icon = Icons.Outlined.SdStorage,\n        actionText = \"\",\n        showAction = false,\n        isWallpaperMode = isWallpaperMode,\n        onActionClick = {},\n        modifier = modifier\n    ) {\n        StorageRow(\n            label = stringResource(R.string.home_storage_internal),\n            used = storageUsed,\n            total = storageTotal,\n            color = MaterialTheme.colorScheme.primary\n        )\n        Spacer(modifier = Modifier.height(12.dp))\n        StorageRow(\n            label = stringResource(R.string.home_storage_ram),\n            used = ramUsed,\n            total = ramTotal,\n            color = MaterialTheme.colorScheme.secondary\n        )\n        if (zramTotal > 0) {\n            Spacer(modifier = Modifier.height(12.dp))\n            StorageRow(\n                label = stringResource(R.string.home_storage_zram),\n                used = zramUsed,\n                total = zramTotal,\n                color = MaterialTheme.colorScheme.tertiary\n            )\n        }\n        if (swapTotal > 0) {\n            Spacer(modifier = Modifier.height(12.dp))\n            StorageRow(\n                label = stringResource(R.string.home_storage_swap),\n                used = swapUsed,\n                total = swapTotal,\n                color = MaterialTheme.colorScheme.error\n            )\n        }\n    }\n}\n\n@Composable\nprivate fun StorageRow(\n    label: String,\n    used: Long,\n    total: Long,\n    color: androidx.compose.ui.graphics.Color\n) {\n    val progress = if (total > 0) used.toFloat() / total.toFloat() else 0f\n    val usedStr = android.text.format.Formatter.formatFileSize(LocalContext.current, used)\n    val totalStr = android.text.format.Formatter.formatFileSize(LocalContext.current, total)\n    \n    Column(modifier = Modifier.fillMaxWidth()) {\n        Row(\n            modifier = Modifier.fillMaxWidth(),\n            horizontalArrangement = Arrangement.SpaceBetween\n        ) {\n            Text(\n                text = label,\n                style = MaterialTheme.typography.bodyMedium,\n                color = MaterialTheme.colorScheme.onSurface\n            )\n            Text(\n                text = \"$usedStr / $totalStr\",\n                style = MaterialTheme.typography.bodyMedium,\n                color = MaterialTheme.colorScheme.onSurfaceVariant\n            )\n        }\n        Spacer(modifier = Modifier.height(8.dp))\n        LinearProgressIndicator(\n            progress = { progress },\n            modifier = Modifier\n                .fillMaxWidth()\n                .height(8.dp)\n                .clip(MaterialTheme.shapes.small),\n            color = color,\n            trackColor = color.copy(alpha = 0.2f),\n        )\n    }\n}\n\n@Composable\nprivate fun StatusCircle(\n    value: String,\n    label: String,\n    progress: Float,\n    color: androidx.compose.ui.graphics.Color\n) {\n    Column(\n        horizontalAlignment = Alignment.CenterHorizontally,\n        verticalArrangement = Arrangement.spacedBy(8.dp)\n    ) {\n        Box(\n            contentAlignment = Alignment.Center,\n            modifier = Modifier.size(80.dp)\n        ) {\n            CircularProgressIndicator(\n                progress = { 1f },\n                modifier = Modifier.fillMaxSize(),\n                color = color.copy(alpha = 0.2f),\n                strokeWidth = 8.dp,\n            )\n            CircularProgressIndicator(\n                progress = { progress },\n                modifier = Modifier.fillMaxSize(),\n                color = color,\n                strokeWidth = 8.dp,\n            )\n            Text(\n                text = value,\n                style = MaterialTheme.typography.titleMedium,\n                fontWeight = FontWeight.Bold\n            )\n        }\n        Text(\n            text = label,\n            style = MaterialTheme.typography.bodyMedium,\n            color = MaterialTheme.colorScheme.onSurfaceVariant\n        )\n    }\n}\n\n@Composable\nprivate fun MagiskStyleCard(\n    title: String,\n    icon: ImageVector,\n    actionText: String,\n    showAction: Boolean,\n    actionEnabled: Boolean = true,\n    isWallpaperMode: Boolean,\n    onActionClick: () -> Unit,\n    modifier: Modifier = Modifier,\n    content: @Composable ColumnScope.() -> Unit\n) {\n    Card(\n        modifier = modifier.fillMaxWidth(),\n        shape = RoundedCornerShape(20.dp),\n        colors = CardDefaults.cardColors(\n            containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp),\n            contentColor = MaterialTheme.colorScheme.onSurface\n        )\n    ) {\n        Column(\n            modifier = Modifier.padding(16.dp)\n        ) {\n            // Header Row\n            Row(\n                verticalAlignment = Alignment.CenterVertically,\n                modifier = Modifier.fillMaxWidth()\n            ) {\n                Icon(\n                    imageVector = icon,\n                    contentDescription = null,\n                    modifier = Modifier.size(32.dp),\n                    tint = MaterialTheme.colorScheme.primary\n                )\n                Spacer(modifier = Modifier.width(16.dp))\n                Text(\n                    text = title,\n                    style = MaterialTheme.typography.titleLarge,\n                    fontWeight = FontWeight.Bold,\n                    modifier = Modifier.weight(1f)\n                )\n                \n                if (showAction) {\n                    Button(\n                        onClick = onActionClick,\n                        enabled = actionEnabled,\n                        contentPadding = PaddingValues(horizontal = 24.dp)\n                    ) {\n                        Text(text = actionText)\n                    }\n                }\n            }\n            \n            HorizontalDivider(\n                modifier = Modifier.padding(vertical = 16.dp),\n                color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f)\n            )\n            \n            // Content Info\n            Column(\n                verticalArrangement = Arrangement.spacedBy(8.dp)\n            ) {\n                content()\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun InfoRow(\n    label: String,\n    value: String\n) {\n    Row(\n        verticalAlignment = Alignment.CenterVertically\n    ) {\n        Text(\n            text = \"$label: \",\n            style = MaterialTheme.typography.bodyMedium,\n            color = MaterialTheme.colorScheme.onSurfaceVariant\n        )\n        Text(\n            text = value,\n            style = MaterialTheme.typography.bodyMedium,\n            fontWeight = FontWeight.Medium,\n            color = MaterialTheme.colorScheme.onSurface\n        )\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/HomeV4.kt",
    "content": "package me.bmax.apatch.ui.screen\n\nimport android.os.BatteryManager\nimport android.os.Build\nimport android.system.Os\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.animation.animateColorAsState\nimport androidx.compose.animation.core.FastOutSlowInEasing\nimport androidx.compose.animation.core.RepeatMode\nimport androidx.compose.animation.core.animateFloat\nimport androidx.compose.animation.core.animateFloatAsState\nimport androidx.compose.animation.core.infiniteRepeatable\nimport androidx.compose.animation.core.rememberInfiniteTransition\nimport androidx.compose.animation.core.tween\nimport androidx.compose.animation.fadeIn\nimport androidx.compose.animation.fadeOut\nimport androidx.compose.animation.slideInVertically\nimport androidx.compose.animation.slideOutVertically\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.ColumnScope\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.heightIn\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.CheckCircle\nimport androidx.compose.material.icons.filled.Refresh\nimport androidx.compose.material.icons.outlined.Android\nimport androidx.compose.material.icons.outlined.CheckCircle\nimport androidx.compose.material.icons.outlined.Block\nimport androidx.compose.material.icons.outlined.Cached\nimport androidx.compose.material.icons.outlined.Delete\nimport androidx.compose.material.icons.outlined.Code\nimport androidx.compose.material.icons.outlined.DeveloperBoard\nimport androidx.compose.material.icons.outlined.Extension\nimport androidx.compose.material.icons.outlined.Fingerprint\nimport androidx.compose.material.icons.outlined.Info\nimport androidx.compose.material.icons.outlined.InstallMobile\nimport androidx.compose.material.icons.outlined.Layers\nimport androidx.compose.material.icons.outlined.PhoneAndroid\nimport androidx.compose.material.icons.outlined.SdStorage\nimport androidx.compose.material.icons.outlined.Security\nimport androidx.compose.material.icons.outlined.Settings\nimport androidx.compose.material.icons.outlined.Shield\nimport androidx.compose.material.icons.outlined.SystemUpdate\nimport androidx.compose.material.icons.outlined.Warning\nimport androidx.compose.material.icons.outlined.Widgets\nimport androidx.compose.material3.AlertDialog\nimport androidx.compose.material3.AssistChip\nimport androidx.compose.material3.AssistChipDefaults\nimport androidx.compose.material3.Button\nimport androidx.compose.material3.ButtonDefaults\nimport androidx.compose.material3.Card\nimport androidx.compose.material3.CardDefaults\nimport androidx.compose.material3.CircularProgressIndicator\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.FilledTonalButton\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.LinearProgressIndicator\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.OutlinedButton\nimport androidx.compose.material3.Surface\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.material3.surfaceColorAtElevation\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.MutableState\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.mutableLongStateOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.Brush\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport androidx.compose.ui.platform.LocalConfiguration\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.compose.LocalLifecycleOwner\nimport androidx.lifecycle.repeatOnLifecycle\nimport com.ramcosta.composedestinations.generated.NavGraphs\nimport com.ramcosta.composedestinations.generated.destinations.InstallModeSelectScreenDestination\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.Natives\nimport me.bmax.apatch.R\nimport me.bmax.apatch.apApp\nimport me.bmax.apatch.ui.theme.BackgroundConfig\nimport me.bmax.apatch.util.AppData\nimport me.bmax.apatch.util.HardwareMonitor\nimport me.bmax.apatch.util.Version\nimport me.bmax.apatch.util.Version.getManagerVersion\nimport me.bmax.apatch.util.getSELinuxStatus\nimport me.bmax.apatch.util.reboot\nimport me.bmax.apatch.util.rootShellForResult\nimport me.bmax.apatch.util.ui.HomeBottomSpacer\n\nprivate val managerVersion = getManagerVersion()\n\n/**\n * HomeV4 - Dashboard Pro 风格首页布局\n * \n * 特色功能：\n * - Hero状态卡：大型动态状态展示区，显示APatch状态和工作模式\n * - 内核补丁安装/卸载UI：完整的安装流程和进度显示\n * - 计数卡片组：超级用户、APM模块、内核补丁模块数量\n * - 快捷操作面板：重启菜单、SELinux切换等\n * - 系统信息网格：设备信息、内核版本、存储空间\n * - 响应式设计：宽屏双栏，窄屏单栏\n * - 动画效果：呼吸动画、颜色过渡、组件显示/隐藏动画\n */\n@Composable\nfun HomeScreenV4(\n    innerPadding: PaddingValues,\n    navigator: DestinationsNavigator,\n    kpState: APApplication.State,\n    apState: APApplication.State\n) {\n    // 检查是否屏蔽更新通知\n    val kpStateResolved = if (kpState == APApplication.State.KERNELPATCH_NEED_UPDATE && apApp.isKernelPatchUpdateBlocked()) {\n        APApplication.State.KERNELPATCH_INSTALLED\n    } else {\n        kpState\n    }\n\n    val apStateResolved = if (apState == APApplication.State.ANDROIDPATCH_NEED_UPDATE && apApp.isAndroidPatchUpdateBlocked()) {\n        APApplication.State.ANDROIDPATCH_INSTALLED\n    } else {\n        apState\n    }\n\n    // 对话框状态\n    val showUninstallDialog = remember { mutableStateOf(false) }\n    val showAuthFailedTipDialog = remember { mutableStateOf(false) }\n    val showAuthKeyDialog = remember { mutableStateOf(false) }\n    val showInstallDialog = remember { mutableStateOf(false) }\n\n    // 对话框显示\n    if (showUninstallDialog.value) {\n        UninstallDialog(showDialog = showUninstallDialog, navigator)\n    }\n    if (showAuthFailedTipDialog.value) {\n        AuthFailedTipDialog(showDialog = showAuthFailedTipDialog)\n    }\n    if (showAuthKeyDialog.value) {\n        AuthSuperKey(showDialog = showAuthKeyDialog, showFailedDialog = showAuthFailedTipDialog)\n    }\n    if (showInstallDialog.value) {\n        InstallProgressDialog(\n            showDialog = showInstallDialog,\n            kpState = kpStateResolved,\n            apState = apStateResolved\n        )\n    }\n\n    // 获取系统信息\n    val context = LocalContext.current\n    val prefs = APApplication.sharedPreferences\n    val isWallpaperMode = BackgroundConfig.isCustomBackgroundEnabled && \n        (BackgroundConfig.customBackgroundUri != null || BackgroundConfig.isMultiBackgroundEnabled)\n    \n    // 隐藏APatch卡片设置\n    val hideApatchCard = prefs.getBoolean(\"hide_apatch_card\", false)\n\n    // 系统信息状态\n    var zygiskImplement by remember { mutableStateOf(\"None\") }\n    var mountImplement by remember { mutableStateOf(\"None\") }\n    var deviceSlot by remember { mutableStateOf(context.getString(R.string.home_info_auth_na)) }\n\n    LaunchedEffect(Unit) {\n        withContext(Dispatchers.IO) {\n            try {\n                zygiskImplement = me.bmax.apatch.util.getZygiskImplement()\n                mountImplement = me.bmax.apatch.util.getMountImplement()\n                val result = rootShellForResult(\"getprop ro.boot.slot_suffix\")\n                if (result.isSuccess) {\n                    val slot = result.out.firstOrNull()?.trim()?.removePrefix(\"_\")\n                    if (!slot.isNullOrEmpty()) {\n                        deviceSlot = slot.uppercase()\n                    }\n                }\n            } catch (_: Exception) {}\n        }\n    }\n\n    // 加载计数数据\n    val showCoreCards = kpStateResolved != APApplication.State.UNKNOWN_STATE\n    if (showCoreCards) {\n        LaunchedEffect(Unit) {\n            AppData.DataRefreshManager.ensureCountsLoaded()\n        }\n    }\n    val superuserCount by AppData.DataRefreshManager.superuserCount.collectAsState()\n    val apmModuleCount by AppData.DataRefreshManager.apmModuleCount.collectAsState()\n    val kpmModuleCount by AppData.DataRefreshManager.kernelModuleCount.collectAsState()\n\n    // 响应式布局\n    val configuration = LocalConfiguration.current\n    val isWide = configuration.screenWidthDp >= 600\n\n    Column(\n        modifier = Modifier\n            .padding(innerPadding)\n            .fillMaxSize()\n            .verticalScroll(rememberScrollState())\n            .padding(horizontal = 16.dp),\n        verticalArrangement = Arrangement.spacedBy(16.dp)\n    ) {\n        Spacer(Modifier.height(0.dp))\n\n        // Hero状态卡\n        HeroStatusCard(\n            kpState = kpStateResolved,\n            apState = apStateResolved,\n            navigator = navigator,\n            showAuthKeyDialog = showAuthKeyDialog,\n            showUninstallDialog = showUninstallDialog,\n            showInstallDialog = showInstallDialog,\n            isWallpaperMode = isWallpaperMode\n        )\n\n        // 计数卡片组\n        AnimatedVisibility(\n            visible = showCoreCards,\n            enter = fadeIn() + slideInVertically(),\n            exit = fadeOut() + slideOutVertically()\n        ) {\n            CountCardsRow(\n                superuserCount = superuserCount,\n                apmModuleCount = apmModuleCount,\n                kpmModuleCount = kpmModuleCount,\n                navigator = navigator\n            )\n        }\n\n        // Android补丁状态卡片（Half模式时显示）\n        AnimatedVisibility(\n            visible = kpStateResolved != APApplication.State.UNKNOWN_STATE && \n                apStateResolved != APApplication.State.UNKNOWN_STATE &&\n                apStateResolved != APApplication.State.ANDROIDPATCH_INSTALLED,\n            enter = fadeIn(),\n            exit = fadeOut()\n        ) {\n            AndroidPatchCard(\n                apState = apStateResolved,\n                kpState = kpStateResolved,\n                showInstallDialog = showInstallDialog,\n                isWallpaperMode = isWallpaperMode\n            )\n        }\n\n        // 系统信息网格\n        if (isWide) {\n            Row(\n                modifier = Modifier.fillMaxWidth(),\n                horizontalArrangement = Arrangement.spacedBy(16.dp)\n            ) {\n                SystemInfoCard(\n                    kpState = kpStateResolved,\n                    apState = apStateResolved,\n                    zygiskImplement = zygiskImplement,\n                    mountImplement = mountImplement,\n                    modifier = Modifier.weight(1f)\n                )\n                Column(\n                    modifier = Modifier.weight(1f),\n                    verticalArrangement = Arrangement.spacedBy(16.dp)\n                ) {\n                    DeviceStatusCard(isWallpaperMode = isWallpaperMode)\n                    StorageInfoCard()\n                    // 了解更多卡片 - 只在平板布局右侧栏显示\n                    if (!hideApatchCard) {\n                        LearnMoreCardV4()\n                    }\n                }\n            }\n        } else {\n            SystemInfoCard(\n                kpState = kpStateResolved,\n                apState = apStateResolved,\n                zygiskImplement = zygiskImplement,\n                mountImplement = mountImplement\n            )\n            DeviceStatusCard(isWallpaperMode = isWallpaperMode)\n            StorageInfoCard()\n            // 窄屏布局在下方显示\n            if (!hideApatchCard) {\n                LearnMoreCardV4()\n            }\n        }\n\n        HomeBottomSpacer()\n    }\n}\n\n/**\n * Hero状态卡 - 大型动态状态展示区\n */\n@Composable\nprivate fun HeroStatusCard(\n    kpState: APApplication.State,\n    apState: APApplication.State,\n    navigator: DestinationsNavigator,\n    showAuthKeyDialog: MutableState<Boolean>,\n    showUninstallDialog: MutableState<Boolean>,\n    showInstallDialog: MutableState<Boolean>,\n    isWallpaperMode: Boolean\n) {\n    val isWorking = kpState == APApplication.State.KERNELPATCH_INSTALLED\n    val isUpdate = kpState == APApplication.State.KERNELPATCH_NEED_UPDATE || \n        kpState == APApplication.State.KERNELPATCH_NEED_REBOOT\n    val isUnknown = kpState == APApplication.State.UNKNOWN_STATE\n\n    // 呼吸动画\n    val infiniteTransition = rememberInfiniteTransition(label = \"breathing\")\n    val breathAlpha by infiniteTransition.animateFloat(\n        initialValue = 0.6f,\n        targetValue = 1f,\n        animationSpec = infiniteRepeatable(\n            animation = tween(2000, easing = FastOutSlowInEasing),\n            repeatMode = RepeatMode.Reverse\n        ),\n        label = \"breathAlpha\"\n    )\n\n    // 颜色状态动画\n    val containerColor by animateColorAsState(\n        targetValue = when {\n            isWorking -> MaterialTheme.colorScheme.primary\n            isUpdate -> MaterialTheme.colorScheme.secondary\n            else -> MaterialTheme.colorScheme.errorContainer\n        },\n        animationSpec = tween(500),\n        label = \"containerColor\"\n    )\n\n    val contentColor by animateColorAsState(\n        targetValue = when {\n            isWorking -> MaterialTheme.colorScheme.onPrimary\n            isUpdate -> MaterialTheme.colorScheme.onSecondary\n            else -> MaterialTheme.colorScheme.onErrorContainer\n        },\n        animationSpec = tween(500),\n        label = \"contentColor\"\n    )\n\n    // 渐变背景\n    val gradientBrush = Brush.linearGradient(\n        colors = listOf(\n            if (isWorking) containerColor.copy(alpha = breathAlpha) else containerColor,\n            containerColor.copy(alpha = 0.8f)\n        )\n    )\n\n    val classicEmojiEnabled = BackgroundConfig.isListWorkingCardModeHidden\n    val isFull = apState == APApplication.State.ANDROIDPATCH_INSTALLED\n    val modeText = BackgroundConfig.getCustomBadgeText() ?: if (isFull) \"Full\" else \"Half\"\n\n    if (isWorking) {\n        Card(\n            modifier = Modifier\n                .fillMaxWidth()\n                .heightIn(min = 160.dp),\n            shape = RoundedCornerShape(24.dp),\n            colors = CardDefaults.cardColors(\n                containerColor = Color.Transparent,\n                contentColor = contentColor\n            )\n        ) {\n            Box(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .background(gradientBrush)\n                    .padding(24.dp)\n            ) {\n                Column(\n                    modifier = Modifier.fillMaxWidth()\n                ) {\n                    Row(\n                        modifier = Modifier.fillMaxWidth(),\n                        horizontalArrangement = Arrangement.SpaceBetween,\n                        verticalAlignment = Alignment.CenterVertically\n                    ) {\n                        Row(\n                            modifier = Modifier.weight(1f),\n                            verticalAlignment = Alignment.CenterVertically\n                        ) {\n                            Icon(\n                                imageVector = Icons.Filled.CheckCircle,\n                                contentDescription = null,\n                                modifier = Modifier.size(32.dp),\n                                tint = contentColor\n                            )\n\n                            Spacer(Modifier.width(12.dp))\n\n                            Column {\n                                Text(\n                                    text = if (classicEmojiEnabled) \n                                        stringResource(R.string.home_working) + \"😋\" \n                                    else \n                                        stringResource(R.string.home_working),\n                                    style = MaterialTheme.typography.headlineSmall,\n                                    fontWeight = FontWeight.Bold\n                                )\n\n                                if (!classicEmojiEnabled) {\n                                    Spacer(Modifier.height(4.dp))\n                                    ModeLabelChip(label = modeText, contentColor = contentColor)\n                                }\n                            }\n                        }\n\n                        Spacer(Modifier.width(8.dp))\n\n                        OutlinedButton(\n                            onClick = { showUninstallDialog.value = true },\n                            colors = ButtonDefaults.outlinedButtonColors(\n                                contentColor = contentColor\n                            ),\n                            border = androidx.compose.foundation.BorderStroke(\n                                1.dp,\n                                contentColor.copy(alpha = 0.5f)\n                            ),\n                            contentPadding = PaddingValues(horizontal = 12.dp, vertical = 8.dp)\n                        ) {\n                            Icon(\n                                imageVector = Icons.Outlined.Delete,\n                                contentDescription = stringResource(R.string.home_ap_cando_uninstall),\n                                modifier = Modifier.size(20.dp)\n                            )\n                        }\n                    }\n\n                    Spacer(Modifier.height(20.dp))\n                    HorizontalDivider(\n                        color = contentColor.copy(alpha = 0.2f),\n                        modifier = Modifier.padding(vertical = 4.dp)\n                    )\n                    Spacer(Modifier.height(12.dp))\n\n                    Row(\n                        modifier = Modifier.fillMaxWidth(),\n                        horizontalArrangement = Arrangement.SpaceBetween\n                    ) {\n                        VersionInfoColumn(\n                            modifier = Modifier.weight(1f),\n                            label = stringResource(R.string.home_kpatch_version),\n                            value = Version.installedKPVString()\n                        )\n                        VersionInfoColumn(\n                            modifier = Modifier.weight(1f),\n                            label = stringResource(R.string.home_apatch_version),\n                            value = managerVersion.second.toString()\n                        )\n                        VersionInfoColumn(\n                            modifier = Modifier.weight(1f),\n                            label = stringResource(R.string.home_selinux_status),\n                            value = getSELinuxStatus()\n                        )\n                    }\n                }\n            }\n        }\n    } else {\n        val finalContainerColor = if (BackgroundConfig.isCustomBackgroundEnabled) {\n            MaterialTheme.colorScheme.errorContainer.copy(alpha = BackgroundConfig.customBackgroundOpacity)\n        } else {\n            MaterialTheme.colorScheme.errorContainer\n        }\n\n        Card(\n            modifier = Modifier\n                .fillMaxWidth()\n                .clickable {\n                    if (isUnknown) {\n                        showAuthKeyDialog.value = true\n                    } else {\n                        navigator.navigate(InstallModeSelectScreenDestination)\n                    }\n                },\n            shape = RoundedCornerShape(20.dp),\n            colors = CardDefaults.cardColors(\n                containerColor = finalContainerColor,\n                contentColor = MaterialTheme.colorScheme.onErrorContainer\n            )\n        ) {\n            Row(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .padding(24.dp),\n                verticalAlignment = Alignment.CenterVertically\n            ) {\n                when {\n                    isUpdate -> Icon(\n                        imageVector = Icons.Outlined.SystemUpdate,\n                        contentDescription = null,\n                        modifier = Modifier.size(32.dp)\n                    )\n                    isUnknown -> Icon(\n                        imageVector = Icons.Outlined.Warning,\n                        contentDescription = null,\n                        modifier = Modifier.size(32.dp)\n                    )\n                    else -> Icon(\n                        imageVector = Icons.Outlined.Block,\n                        contentDescription = null,\n                        modifier = Modifier.size(32.dp)\n                    )\n                }\n\n                Spacer(Modifier.width(20.dp))\n\n                Column {\n                    Text(\n                        text = when {\n                            isUpdate -> stringResource(R.string.home_kp_need_update)\n                            isUnknown -> stringResource(R.string.home_install_unknown)\n                            else -> stringResource(R.string.home_not_installed)\n                        },\n                        style = MaterialTheme.typography.titleMedium\n                    )\n                    Spacer(Modifier.height(4.dp))\n                    Text(\n                        text = if (isUnknown) stringResource(R.string.super_key) else stringResource(R.string.home_click_to_install),\n                        style = MaterialTheme.typography.bodyMedium\n                    )\n                }\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun DeviceStatusCard(isWallpaperMode: Boolean, modifier: Modifier = Modifier) {\n    val context = LocalContext.current\n    var batteryTemp by remember { mutableStateOf(0f) }\n    var batteryLevel by remember { mutableIntStateOf(0) }\n    var cpuUsage by remember { mutableIntStateOf(0) }\n\n    val lifecycleOwner = LocalLifecycleOwner.current\n\n    LaunchedEffect(lifecycleOwner) {\n        lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {\n            withContext(Dispatchers.IO) {\n                val intent = context.registerReceiver(null, android.content.IntentFilter(android.content.Intent.ACTION_BATTERY_CHANGED))\n                batteryTemp = (intent?.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0) ?: 0) / 10f\n                val level = intent?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1\n                val scale = intent?.getIntExtra(BatteryManager.EXTRA_SCALE, -1) ?: -1\n                if (level != -1 && scale != -1) {\n                    batteryLevel = (level * 100 / scale.toFloat()).toInt()\n                }\n\n                cpuUsage = HardwareMonitor.getCpuUsage()\n\n                kotlinx.coroutines.delay(10000)\n            }\n        }\n    }\n\n    MagiskStyleCard(\n        title = stringResource(R.string.home_device_status_title),\n        icon = Icons.Outlined.Settings,\n        actionText = \"\",\n        showAction = false,\n        isWallpaperMode = isWallpaperMode,\n        onActionClick = {},\n        modifier = modifier\n    ) {\n        Row(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(vertical = 8.dp),\n            horizontalArrangement = Arrangement.SpaceEvenly\n        ) {\n            StatusCircle(\n                value = \"${batteryTemp}°C\",\n                label = stringResource(R.string.home_device_status_battery_temp),\n                progress = (batteryTemp / 50f).coerceIn(0f, 1f),\n                color = MaterialTheme.colorScheme.primary\n            )\n            StatusCircle(\n                value = \"$cpuUsage%\",\n                label = stringResource(R.string.home_device_status_cpu_load),\n                progress = (cpuUsage / 100f).coerceIn(0f, 1f),\n                color = MaterialTheme.colorScheme.secondary\n            )\n            StatusCircle(\n                value = \"$batteryLevel%\",\n                label = stringResource(R.string.home_device_status_battery_level),\n                progress = (batteryLevel / 100f).coerceIn(0f, 1f),\n                color = MaterialTheme.colorScheme.tertiary\n            )\n        }\n    }\n}\n\n@Composable\nprivate fun StatusCircle(\n    value: String,\n    label: String,\n    progress: Float,\n    color: Color\n) {\n    Column(\n        horizontalAlignment = Alignment.CenterHorizontally,\n        verticalArrangement = Arrangement.spacedBy(8.dp)\n    ) {\n        Box(\n            contentAlignment = Alignment.Center,\n            modifier = Modifier.size(80.dp)\n        ) {\n            CircularProgressIndicator(\n                progress = { 1f },\n                modifier = Modifier.fillMaxSize(),\n                color = color.copy(alpha = 0.2f),\n                strokeWidth = 8.dp,\n            )\n            CircularProgressIndicator(\n                progress = { progress },\n                modifier = Modifier.fillMaxSize(),\n                color = color,\n                strokeWidth = 8.dp,\n            )\n            Text(\n                text = value,\n                style = MaterialTheme.typography.titleMedium,\n                fontWeight = FontWeight.Bold\n            )\n        }\n        Text(\n            text = label,\n            style = MaterialTheme.typography.bodyMedium,\n            color = MaterialTheme.colorScheme.onSurfaceVariant\n        )\n    }\n}\n\n@Composable\nprivate fun MagiskStyleCard(\n    title: String,\n    icon: ImageVector,\n    actionText: String,\n    showAction: Boolean,\n    actionEnabled: Boolean = true,\n    isWallpaperMode: Boolean,\n    onActionClick: () -> Unit,\n    modifier: Modifier = Modifier,\n    content: @Composable ColumnScope.() -> Unit\n) {\n    TonalCard(modifier = modifier) {\n        Column(\n            modifier = Modifier.padding(16.dp)\n        ) {\n            Row(\n                verticalAlignment = Alignment.CenterVertically,\n                modifier = Modifier.fillMaxWidth()\n            ) {\n                Icon(\n                    imageVector = icon,\n                    contentDescription = null,\n                    modifier = Modifier.size(32.dp),\n                    tint = MaterialTheme.colorScheme.primary\n                )\n                Spacer(modifier = Modifier.width(16.dp))\n                Text(\n                    text = title,\n                    style = MaterialTheme.typography.titleLarge,\n                    fontWeight = FontWeight.Bold,\n                    modifier = Modifier.weight(1f)\n                )\n                \n                if (showAction) {\n                    Button(\n                        onClick = onActionClick,\n                        enabled = actionEnabled,\n                        contentPadding = PaddingValues(horizontal = 24.dp)\n                    ) {\n                        Text(text = actionText)\n                    }\n                }\n            }\n            \n            HorizontalDivider(\n                modifier = Modifier.padding(vertical = 16.dp),\n                color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f)\n            )\n            \n            Column(\n                verticalArrangement = Arrangement.spacedBy(8.dp)\n            ) {\n                content()\n            }\n        }\n    }\n}\n\n/**\n * 模式标签芯片\n */\n@Composable\nprivate fun ModeLabelChip(label: String, contentColor: Color) {\n    Surface(\n        color = contentColor.copy(alpha = 0.2f),\n        shape = RoundedCornerShape(8.dp)\n    ) {\n        Text(\n            text = label,\n            modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),\n            style = MaterialTheme.typography.labelMedium,\n            fontWeight = FontWeight.Bold,\n            color = contentColor\n        )\n    }\n}\n\n/**\n * 版本信息列\n */\n@Composable\nprivate fun VersionInfoColumn(\n    modifier: Modifier = Modifier,\n    label: String,\n    value: String\n) {\n    Column(\n        modifier = modifier\n    ) {\n        Text(\n            text = label,\n            style = MaterialTheme.typography.labelMedium,\n            color = Color.White.copy(alpha = 0.7f),\n            maxLines = 1,\n            overflow = TextOverflow.Ellipsis\n        )\n        Text(\n            text = value,\n            style = MaterialTheme.typography.bodyLarge,\n            fontWeight = FontWeight.Medium,\n            maxLines = 1,\n            overflow = TextOverflow.Ellipsis\n        )\n    }\n}\n\n/**\n * 计数卡片组 - 超级用户、APM模块、内核补丁模块\n */\n@Composable\nprivate fun CountCardsRow(\n    superuserCount: Int,\n    apmModuleCount: Int,\n    kpmModuleCount: Int,\n    navigator: DestinationsNavigator\n) {\n    Row(\n        modifier = Modifier.fillMaxWidth(),\n        horizontalArrangement = Arrangement.spacedBy(12.dp)\n    ) {\n        CountCard(\n            modifier = Modifier.weight(1f),\n            icon = Icons.Outlined.Security,\n            label = stringResource(R.string.superuser),\n            count = superuserCount,\n            onClick = {\n                navigator.navigate(BottomBarDestination.SuperUser.direction) {\n                    popUpTo(NavGraphs.root) { saveState = true }\n                    launchSingleTop = true\n                    restoreState = true\n                }\n            }\n        )\n        CountCard(\n            modifier = Modifier.weight(1f),\n            icon = Icons.Outlined.Widgets,\n            label = stringResource(R.string.module),\n            count = apmModuleCount,\n            onClick = {\n                navigator.navigate(BottomBarDestination.AModule.direction) {\n                    popUpTo(NavGraphs.root) { saveState = true }\n                    launchSingleTop = true\n                    restoreState = true\n                }\n            }\n        )\n        CountCard(\n            modifier = Modifier.weight(1f),\n            icon = Icons.Outlined.Extension,\n            label = stringResource(R.string.kpm),\n            count = kpmModuleCount,\n            onClick = {\n                navigator.navigate(BottomBarDestination.KModule.direction) {\n                    popUpTo(NavGraphs.root) { saveState = true }\n                    launchSingleTop = true\n                    restoreState = true\n                }\n            }\n        )\n    }\n}\n\n/**\n * 单个计数卡片\n */\n@Composable\nprivate fun CountCard(\n    modifier: Modifier = Modifier,\n    icon: ImageVector,\n    label: String,\n    count: Int,\n    onClick: () -> Unit\n) {\n    val containerColor = if (BackgroundConfig.isCustomBackgroundEnabled) {\n        MaterialTheme.colorScheme.surface.copy(alpha = BackgroundConfig.customBackgroundOpacity)\n    } else {\n        MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)\n    }\n    Card(\n        modifier = modifier,\n        shape = RoundedCornerShape(16.dp),\n        colors = CardDefaults.cardColors(containerColor = containerColor),\n        onClick = onClick\n    ) {\n        Column(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(16.dp),\n            horizontalAlignment = Alignment.CenterHorizontally\n        ) {\n            Icon(\n                imageVector = icon,\n                contentDescription = null,\n                modifier = Modifier.size(24.dp),\n                tint = MaterialTheme.colorScheme.primary\n            )\n            Spacer(Modifier.height(8.dp))\n            Text(\n                text = count.toString(),\n                style = MaterialTheme.typography.headlineMedium,\n                fontWeight = FontWeight.Bold,\n                color = MaterialTheme.colorScheme.onSurface\n            )\n            Spacer(Modifier.height(4.dp))\n            Text(\n                text = label,\n                style = MaterialTheme.typography.labelMedium,\n                color = MaterialTheme.colorScheme.onSurfaceVariant,\n                maxLines = 1,\n                overflow = TextOverflow.Ellipsis\n            )\n        }\n    }\n}\n\n/**\n * Android补丁状态卡片\n */\n@Composable\nprivate fun AndroidPatchCard(\n    apState: APApplication.State,\n    kpState: APApplication.State,\n    showInstallDialog: MutableState<Boolean>,\n    isWallpaperMode: Boolean\n) {\n    val containerColor = when {\n        BackgroundConfig.isCustomBackgroundEnabled -> {\n            MaterialTheme.colorScheme.surface.copy(alpha = BackgroundConfig.customBackgroundOpacity)\n        }\n        else -> {\n            MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)\n        }\n    }\n\n    Card(\n        modifier = Modifier.fillMaxWidth(),\n        shape = RoundedCornerShape(16.dp),\n        colors = CardDefaults.cardColors(containerColor = containerColor)\n    ) {\n        Row(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(16.dp),\n            verticalAlignment = Alignment.CenterVertically\n        ) {\n            // 图标\n            when (apState) {\n                APApplication.State.ANDROIDPATCH_INSTALLED -> {\n                    Icon(\n                        imageVector = Icons.Outlined.CheckCircle,\n                        contentDescription = null,\n                        modifier = Modifier.size(28.dp),\n                        tint = MaterialTheme.colorScheme.onPrimaryContainer\n                    )\n                }\n                APApplication.State.ANDROIDPATCH_INSTALLING -> {\n                    CircularProgressIndicator(\n                        modifier = Modifier.size(28.dp),\n                        strokeWidth = 3.dp,\n                        color = MaterialTheme.colorScheme.onSecondaryContainer\n                    )\n                }\n                APApplication.State.ANDROIDPATCH_NEED_UPDATE -> {\n                    Icon(\n                        imageVector = Icons.Outlined.SystemUpdate,\n                        contentDescription = null,\n                        modifier = Modifier.size(28.dp),\n                        tint = MaterialTheme.colorScheme.onTertiaryContainer\n                    )\n                }\n                else -> {\n                    Icon(\n                        imageVector = Icons.Outlined.Android,\n                        contentDescription = null,\n                        modifier = Modifier.size(28.dp),\n                        tint = MaterialTheme.colorScheme.onSurfaceVariant\n                    )\n                }\n            }\n\n            Spacer(Modifier.width(16.dp))\n\n            // 状态文字\n            Column(modifier = Modifier.weight(1f)) {\n                Text(\n                    text = stringResource(R.string.android_patch),\n                    style = MaterialTheme.typography.titleMedium,\n                    fontWeight = FontWeight.Medium\n                )\n            }\n\n            // 操作按钮\n            FilledTonalButton(\n                onClick = {\n                    when (apState) {\n                        APApplication.State.ANDROIDPATCH_NOT_INSTALLED,\n                        APApplication.State.ANDROIDPATCH_NEED_UPDATE -> {\n                            APApplication.installApatch()\n                        }\n                        APApplication.State.ANDROIDPATCH_INSTALLED -> {\n                            APApplication.uninstallApatch()\n                        }\n                        else -> {}\n                    }\n                },\n                enabled = apState != APApplication.State.ANDROIDPATCH_INSTALLING &&\n                    apState != APApplication.State.ANDROIDPATCH_UNINSTALLING &&\n                    apState != APApplication.State.UNKNOWN_STATE\n            ) {\n                when (apState) {\n                    APApplication.State.ANDROIDPATCH_NOT_INSTALLED -> \n                        Text(stringResource(R.string.home_ap_cando_install))\n                    APApplication.State.ANDROIDPATCH_NEED_UPDATE -> \n                        Text(stringResource(R.string.home_kp_cando_update))\n                    APApplication.State.ANDROIDPATCH_INSTALLING,\n                    APApplication.State.ANDROIDPATCH_UNINSTALLING -> \n                        Icon(Icons.Outlined.Cached, contentDescription = \"busy\")\n                    else -> \n                        Text(stringResource(R.string.home_ap_cando_uninstall))\n                }\n            }\n        }\n    }\n}\n\n/**\n * 安装进度对话框\n */\n@Composable\nprivate fun InstallProgressDialog(\n    showDialog: MutableState<Boolean>,\n    kpState: APApplication.State,\n    apState: APApplication.State\n) {\n    if (!showDialog.value) return\n\n    var progress by remember { mutableStateOf(0f) }\n    var statusText by remember { mutableStateOf(\"准备安装...\") }\n\n    LaunchedEffect(Unit) {\n        // 模拟安装进度\n        for (i in 1..100) {\n            delay(50)\n            progress = i / 100f\n            statusText = when {\n                i < 20 -> \"正在准备...\"\n                i < 40 -> \"正在备份...\"\n                i < 60 -> \"正在写入...\"\n                i < 80 -> \"正在验证...\"\n                else -> \"正在完成...\"\n            }\n        }\n        showDialog.value = false\n    }\n\n    AlertDialog(\n        onDismissRequest = { },\n        title = { Text(stringResource(R.string.kpm_install)) },\n        text = {\n            Column {\n                Text(\n                    text = statusText,\n                    style = MaterialTheme.typography.bodyMedium\n                )\n                Spacer(Modifier.height(16.dp))\n                LinearProgressIndicator(\n                    progress = { progress },\n                    modifier = Modifier.fillMaxWidth(),\n                )\n            }\n        },\n        confirmButton = { }\n    )\n}\n\n/**\n * 系统信息卡片\n */\n@Composable\ninternal fun SystemInfoCard(\n    kpState: APApplication.State,\n    apState: APApplication.State,\n    zygiskImplement: String,\n    mountImplement: String,\n    modifier: Modifier = Modifier\n) {\n    val uname = Os.uname()\n    val prefs = APApplication.sharedPreferences\n    \n    var hideSuPath by remember { mutableStateOf(prefs.getBoolean(\"hide_su_path\", false)) }\n    var hideKpatchVersion by remember { mutableStateOf(prefs.getBoolean(\"hide_kpatch_version\", false)) }\n    var hideFingerprint by remember { mutableStateOf(prefs.getBoolean(\"hide_fingerprint\", false)) }\n    var hideZygisk by remember { mutableStateOf(prefs.getBoolean(\"hide_zygisk\", false)) }\n    var hideMount by remember { mutableStateOf(prefs.getBoolean(\"hide_mount\", false)) }\n\n    TonalCard(modifier = modifier) {\n        Column(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(16.dp)\n        ) {\n            // 标题\n            Row(\n                verticalAlignment = Alignment.CenterVertically,\n                modifier = Modifier.padding(bottom = 12.dp)\n            ) {\n                Icon(\n                    imageVector = Icons.Outlined.Info,\n                    contentDescription = null,\n                    modifier = Modifier.size(20.dp),\n                    tint = MaterialTheme.colorScheme.primary\n                )\n                Spacer(Modifier.width(8.dp))\n                Text(\n                    text = stringResource(R.string.home_kpatch_info_title),\n                    style = MaterialTheme.typography.titleMedium,\n                    fontWeight = FontWeight.Medium\n                )\n            }\n\n            HorizontalDivider(\n                color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f),\n                modifier = Modifier.padding(bottom = 12.dp)\n            )\n\n            // 信息列表\n            InfoItem(Icons.Outlined.PhoneAndroid, stringResource(R.string.home_device_info), getDeviceInfo())\n            if (kpState != APApplication.State.UNKNOWN_STATE && !hideKpatchVersion) {\n                InfoItem(Icons.Outlined.Extension, stringResource(R.string.home_kpatch_version), Version.installedKPVString())\n            }\n            if (kpState != APApplication.State.UNKNOWN_STATE && !hideSuPath) {\n                InfoItem(Icons.Outlined.Code, stringResource(R.string.home_su_path), Natives.suPath())\n            }\n            if (apState == APApplication.State.ANDROIDPATCH_INSTALLED) {\n                InfoItem(Icons.Outlined.Android, stringResource(R.string.home_apatch_version), managerVersion.second.toString())\n            }\n            InfoItem(Icons.Outlined.DeveloperBoard, stringResource(R.string.home_kernel), uname.release)\n            InfoItem(Icons.Outlined.Info, stringResource(R.string.home_system_version), getSystemVersion())\n            if (!hideFingerprint) {\n                InfoItem(Icons.Outlined.Fingerprint, stringResource(R.string.home_fingerprint), Build.FINGERPRINT)\n            }\n            if (kpState != APApplication.State.UNKNOWN_STATE && zygiskImplement != \"None\" && !hideZygisk) {\n                InfoItem(Icons.Outlined.Layers, stringResource(R.string.home_zygisk_implement), zygiskImplement)\n            }\n            if (kpState != APApplication.State.UNKNOWN_STATE && mountImplement != \"None\" && !hideMount) {\n                InfoItem(Icons.Outlined.SdStorage, stringResource(R.string.home_mount_implement), mountImplement)\n            }\n            InfoItem(Icons.Outlined.Shield, stringResource(R.string.home_selinux_status), getSELinuxStatus())\n        }\n    }\n}\n\n/**\n * 信息项\n */\n@Composable\nprivate fun InfoItem(icon: ImageVector, label: String, value: String) {\n    Row(\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(vertical = 6.dp),\n        verticalAlignment = Alignment.Top\n    ) {\n        Icon(\n            imageVector = icon,\n            contentDescription = null,\n            modifier = Modifier\n                .size(18.dp)\n                .padding(top = 2.dp),\n            tint = MaterialTheme.colorScheme.onSurfaceVariant\n        )\n        Spacer(Modifier.width(12.dp))\n        Column(modifier = Modifier.weight(1f)) {\n            Text(\n                text = label,\n                style = MaterialTheme.typography.bodySmall,\n                color = MaterialTheme.colorScheme.onSurfaceVariant\n            )\n            Text(\n                text = value,\n                style = MaterialTheme.typography.bodyMedium,\n                maxLines = 2,\n                overflow = TextOverflow.Ellipsis\n            )\n        }\n    }\n}\n\n/**\n * 存储信息卡片\n */\n@Composable\nprivate fun StorageInfoCard(modifier: Modifier = Modifier) {\n    val context = LocalContext.current\n    var ramUsed by remember { mutableStateOf(0L) }\n    var ramTotal by remember { mutableStateOf(0L) }\n    var storageUsed by remember { mutableStateOf(0L) }\n    var storageTotal by remember { mutableStateOf(0L) }\n\n    val lifecycleOwner = LocalLifecycleOwner.current\n\n    LaunchedEffect(lifecycleOwner) {\n        lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {\n            withContext(Dispatchers.IO) {\n                // 内部存储\n                val dataDir = android.os.Environment.getDataDirectory()\n                val stat = android.os.StatFs(dataDir.path)\n                val blockSize = stat.blockSizeLong\n                val totalBlocks = stat.blockCountLong\n                val availableBlocks = stat.availableBlocksLong\n                storageTotal = totalBlocks * blockSize\n                storageUsed = storageTotal - (availableBlocks * blockSize)\n\n                // 内存信息\n                val memInfo = HardwareMonitor.getMemoryInfo()\n                ramTotal = memInfo.ramTotal\n                ramUsed = memInfo.ramUsed\n\n                delay(5000)\n            }\n        }\n    }\n\n    TonalCard(modifier = modifier) {\n        Column(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(16.dp)\n        ) {\n            // 标题\n            Row(\n                verticalAlignment = Alignment.CenterVertically,\n                modifier = Modifier.padding(bottom = 12.dp)\n            ) {\n                Icon(\n                    imageVector = Icons.Outlined.SdStorage,\n                    contentDescription = null,\n                    modifier = Modifier.size(20.dp),\n                    tint = MaterialTheme.colorScheme.primary\n                )\n                Spacer(Modifier.width(8.dp))\n                Text(\n                    text = stringResource(R.string.home_storage_title),\n                    style = MaterialTheme.typography.titleMedium,\n                    fontWeight = FontWeight.Medium\n                )\n            }\n\n            HorizontalDivider(\n                color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f),\n                modifier = Modifier.padding(bottom = 12.dp)\n            )\n\n            // 存储进度\n            StorageProgressBar(\n                label = stringResource(R.string.home_storage_internal),\n                used = storageUsed,\n                total = storageTotal,\n                color = MaterialTheme.colorScheme.primary\n            )\n\n            Spacer(Modifier.height(12.dp))\n\n            StorageProgressBar(\n                label = stringResource(R.string.home_storage_ram),\n                used = ramUsed,\n                total = ramTotal,\n                color = MaterialTheme.colorScheme.secondary\n            )\n        }\n    }\n}\n\n/**\n * 存储进度条\n */\n@Composable\nprivate fun StorageProgressBar(\n    label: String,\n    used: Long,\n    total: Long,\n    color: Color\n) {\n    val context = LocalContext.current\n    val progress = if (total > 0) used.toFloat() / total.toFloat() else 0f\n    val usedStr = android.text.format.Formatter.formatFileSize(context, used)\n    val totalStr = android.text.format.Formatter.formatFileSize(context, total)\n\n    Column {\n        Row(\n            modifier = Modifier.fillMaxWidth(),\n            horizontalArrangement = Arrangement.SpaceBetween\n        ) {\n            Text(\n                text = label,\n                style = MaterialTheme.typography.bodyMedium,\n                color = MaterialTheme.colorScheme.onSurface\n            )\n            Text(\n                text = \"$usedStr / $totalStr\",\n                style = MaterialTheme.typography.bodySmall,\n                color = MaterialTheme.colorScheme.onSurfaceVariant\n            )\n        }\n        Spacer(Modifier.height(6.dp))\n        LinearProgressIndicator(\n            progress = { progress },\n            modifier = Modifier\n                .fillMaxWidth()\n                .height(6.dp)\n                .clip(RoundedCornerShape(3.dp)),\n            color = color,\n            trackColor = color.copy(alpha = 0.2f),\n        )\n    }\n}\n\n/**\n * 了解更多卡片 V4\n */\n@Composable\ninternal fun LearnMoreCardV4() {\n    val uriHandler = androidx.compose.ui.platform.LocalUriHandler.current\n\n    TonalCard(\n        modifier = Modifier\n            .fillMaxWidth()\n            .clickable { uriHandler.openUri(\"https://fp.mysqil.com/\") }\n    ) {\n        Row(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(16.dp),\n            verticalAlignment = Alignment.CenterVertically\n        ) {\n            Icon(\n                imageVector = Icons.Outlined.Info,\n                contentDescription = null,\n                modifier = Modifier.size(24.dp),\n                tint = MaterialTheme.colorScheme.primary\n            )\n            Spacer(Modifier.width(12.dp))\n            Column {\n                Text(\n                    text = stringResource(R.string.home_learn_apatch),\n                    style = MaterialTheme.typography.titleSmall,\n                    fontWeight = FontWeight.Medium\n                )\n                Text(\n                    text = stringResource(R.string.home_click_to_learn_apatch),\n                    style = MaterialTheme.typography.bodySmall,\n                    color = MaterialTheme.colorScheme.onSurfaceVariant\n                )\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/Install.kt",
    "content": "package me.bmax.apatch.ui.screen\n\nimport android.net.Uri\nimport android.os.Environment\nimport android.content.Intent\nimport androidx.activity.ComponentActivity\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.automirrored.filled.ArrowForward\nimport androidx.compose.material.icons.filled.Refresh\nimport androidx.compose.material.icons.filled.Save\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.ExtendedFloatingActionButton\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.SnackbarHost\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.input.key.Key\nimport androidx.compose.ui.input.key.key\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontFamily\nimport androidx.compose.ui.tooling.preview.Preview\nimport androidx.compose.ui.unit.dp\nimport androidx.lifecycle.compose.dropUnlessResumed\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport com.ramcosta.composedestinations.generated.destinations.InstallScreenDestination\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.component.KeyEventBlocker\nimport me.bmax.apatch.util.getSafeDownloadsDir\nimport me.bmax.apatch.util.installModule\nimport me.bmax.apatch.util.BulkInstallManager\nimport me.bmax.apatch.util.reboot\nimport me.bmax.apatch.util.ui.LocalSnackbarHost\nimport java.io.File\nimport java.text.SimpleDateFormat\nimport java.util.Date\nimport java.util.Locale\n\nenum class MODULE_TYPE {\n    KPM, APM\n}\n\n@Composable\n@Destination<RootGraph>\nfun InstallScreen(navigator: DestinationsNavigator, uri: Uri, type: MODULE_TYPE) {\n    var text by remember { mutableStateOf(\"\") }\n    val displayBuffer = remember { StringBuffer() }\n    val fullLogBuffer = remember { StringBuffer() }\n    var showFloatAction by rememberSaveable { mutableStateOf(false) }\n\n    val snackBarHost = LocalSnackbarHost.current\n    val scope = rememberCoroutineScope()\n    val scrollState = rememberScrollState()\n\n    val context = LocalContext.current\n    val activity = context as? ComponentActivity\n\n    val isExternalInstall = remember(activity) {\n        activity?.intent?.let { intent ->\n            intent.action == Intent.ACTION_VIEW || intent.action == Intent.ACTION_SEND\n        } ?: false\n    }\n\n    LaunchedEffect(Unit) {\n        if (text.isNotEmpty()) {\n            return@LaunchedEffect\n        }\n\n        val updaterJob = launch {\n            while (true) {\n                kotlinx.coroutines.delay(100)\n                val newText = displayBuffer.toString()\n                if (text.length != newText.length) {\n                    text = newText\n                }\n            }\n        }\n\n        withContext(Dispatchers.IO) {\n            installModule(uri, type, onFinish = { success ->\n                updaterJob.cancel()\n                val finalText = displayBuffer.toString()\n                if (text.length != finalText.length) {\n                    text = finalText\n                }\n                if (success) {\n                    showFloatAction = true\n                }\n            }, onStdout = {\n                val tempText = \"$it\\n\"\n                if (tempText.startsWith(\"\u001b[H\u001b[J\")) { // clear command\n                    displayBuffer.setLength(0)\n                    displayBuffer.append(tempText.substring(6))\n                } else {\n                    displayBuffer.append(tempText)\n                }\n                fullLogBuffer.append(it).append(\"\\n\")\n            }, onStderr = {\n                val tempText = \"$it\\n\"\n                if (tempText.startsWith(\"\u001b[H\u001b[J\")) { // clear command\n                    displayBuffer.setLength(0)\n                    displayBuffer.append(tempText.substring(6))\n                } else {\n                    displayBuffer.append(tempText)\n                }\n                fullLogBuffer.append(it).append(\"\\n\")\n            })\n        }\n    }\n\n    Scaffold(topBar = {\n        TopBar(onBack = dropUnlessResumed {\n            if (isExternalInstall) {\n                activity?.finish()\n            } else {\n                BulkInstallManager.clear()\n                navigator.popBackStack()\n            }\n        }, onSave = {\n            scope.launch {\n                val format = SimpleDateFormat(\"yyyy-MM-dd-HH-mm-ss\", Locale.getDefault())\n                val date = format.format(Date())\n                val file = File(\n                    getSafeDownloadsDir(context),\n                    \"APatch_install_${type}_log_${date}.log\"\n                )\n                file.writeText(fullLogBuffer.toString())\n                snackBarHost.showSnackbar(\"Log saved to ${file.absolutePath}\")\n            }\n        })\n    }, floatingActionButton = {\n        if (showFloatAction) {\n            if (BulkInstallManager.hasNext()) {\n                val nextText = stringResource(id = R.string.next_module)\n                ExtendedFloatingActionButton(\n                    onClick = {\n                        val nextUri = BulkInstallManager.popNext()\n                        if (nextUri != null) {\n                            navigator.popBackStack()\n                            navigator.navigate(InstallScreenDestination(nextUri, type))\n                        }\n                    },\n                    icon = { Icon(Icons.AutoMirrored.Filled.ArrowForward, nextText) },\n                    text = { Text(text = nextText) },\n                    containerColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                    contentColor = MaterialTheme.colorScheme.onPrimary.copy(alpha = 1f),\n                )\n            } else {\n                val reboot = stringResource(id = R.string.reboot)\n                ExtendedFloatingActionButton(\n                    onClick = {\n                        scope.launch {\n                            withContext(Dispatchers.IO) {\n                                reboot()\n                            }\n                        }\n                    },\n                    icon = { Icon(Icons.Filled.Refresh, reboot) },\n                    text = { Text(text = reboot) },\n                    containerColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                    contentColor = MaterialTheme.colorScheme.onPrimary.copy(alpha = 1f),\n                )\n            }\n        }\n\n    }, snackbarHost = { SnackbarHost(snackBarHost) }) { innerPadding ->\n        Column(\n            modifier = Modifier\n                .fillMaxSize(1f)\n                .padding(innerPadding)\n                .verticalScroll(scrollState),\n        ) {\n            LaunchedEffect(text) {\n                scrollState.animateScrollTo(scrollState.maxValue)\n            }\n            Text(\n                modifier = Modifier.padding(8.dp),\n                text = text,\n                fontSize = MaterialTheme.typography.bodySmall.fontSize,\n                fontFamily = FontFamily.Monospace,\n                lineHeight = MaterialTheme.typography.bodySmall.lineHeight,\n            )\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun TopBar(onBack: () -> Unit = {}, onSave: () -> Unit = {}) {\n    TopAppBar(title = { Text(stringResource(R.string.apm_install)) }, navigationIcon = {\n        IconButton(\n            onClick = onBack\n        ) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) }\n    }, actions = {\n        IconButton(onClick = onSave) {\n            Icon(\n                imageVector = Icons.Filled.Save, contentDescription = \"Localized description\"\n            )\n        }\n    })\n}\n\n@Preview\n@Composable\nfun InstallPreview() {\n//    InstallScreen(DestinationsNavigator(), uri = Uri.EMPTY)\n}"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/InstallModeSelect.kt",
    "content": "package me.bmax.apatch.ui.screen\n\nimport android.app.Activity\nimport android.content.Intent\nimport android.net.Uri\nimport androidx.activity.compose.rememberLauncherForActivityResult\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.annotation.StringRes\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.animation.expandVertically\nimport androidx.compose.animation.fadeIn\nimport androidx.compose.animation.fadeOut\nimport androidx.compose.animation.shrinkVertically\nimport androidx.compose.foundation.LocalIndication\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.interaction.MutableInteractionSource\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.selection.selectable\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.filled.AutoFixHigh\nimport androidx.compose.material.icons.filled.FileUpload\nimport androidx.compose.material3.Button\nimport androidx.compose.material3.ButtonDefaults\nimport androidx.compose.material3.CardDefaults\nimport androidx.compose.material3.ElevatedCard\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.ListItem\nimport androidx.compose.material3.ListItemDefaults\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.RadioButton\nimport androidx.compose.material3.RadioButtonDefaults\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Surface\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.semantics.Role\nimport androidx.compose.ui.unit.dp\nimport androidx.lifecycle.compose.dropUnlessResumed\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport com.ramcosta.composedestinations.generated.destinations.PatchesDestination\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.component.rememberConfirmDialog\nimport me.bmax.apatch.ui.theme.BackgroundConfig\nimport me.bmax.apatch.ui.viewmodel.PatchesViewModel\nimport me.bmax.apatch.util.isABDevice\nimport me.bmax.apatch.util.rootAvailable\n\nvar selectedBootImage: Uri? = null\nvar selectedKPImg: Uri? = null\n\n@Destination<RootGraph>\n@Composable\nfun InstallModeSelectScreen(navigator: DestinationsNavigator) {\n    var installMethod by remember {\n        mutableStateOf<InstallMethod?>(null)\n    }\n\n    Scaffold(topBar = {\n        TopBar(\n            onBack = dropUnlessResumed { navigator.popBackStack() },\n        )\n    }) {\n        Column(modifier = Modifier.padding(it)) {\n            SelectInstallMethod(\n                onSelected = { method ->\n                    installMethod = method\n                },\n                navigator = navigator\n            )\n\n        }\n    }\n}\n\nsealed class InstallMethod {\n    data class SelectFile(\n        val uri: Uri? = null,\n        @param:StringRes override val label: Int = R.string.mode_select_page_select_file,\n    ) : InstallMethod()\n\n    data class SelectKPImg(\n        val uri: Uri? = null,\n        @param:StringRes override val label: Int = R.string.mode_select_page_select_kpimg,\n    ) : InstallMethod()\n\n    data object DirectInstall : InstallMethod() {\n        override val label: Int\n            get() = R.string.mode_select_page_patch_and_install\n    }\n\n    data object DirectInstallToInactiveSlot : InstallMethod() {\n        override val label: Int\n            get() = R.string.mode_select_page_install_inactive_slot\n    }\n\n    // Placeholder for Restore functionality\n    data class Restore(\n        val uri: Uri? = null,\n        @param:StringRes override val label: Int = R.string.restore_select_file, // Reuse string or create new one\n    ) : InstallMethod()\n\n    abstract val label: Int\n    open val summary: String? = null\n}\n\n@Composable\nprivate fun SelectInstallMethod(\n    onSelected: (InstallMethod) -> Unit = {},\n    navigator: DestinationsNavigator\n) {\n    val rootAvailable = rootAvailable()\n    val isAbDevice = isABDevice()\n\n    // KP Install Options\n    val kpOptions = mutableListOf<InstallMethod>(InstallMethod.SelectFile(), InstallMethod.SelectKPImg())\n    if (rootAvailable) {\n        kpOptions.add(InstallMethod.DirectInstall)\n        if (isAbDevice) {\n            kpOptions.add(InstallMethod.DirectInstallToInactiveSlot)\n        }\n    }\n\n    // Restore Options\n    val restoreOptions = mutableListOf<InstallMethod>(InstallMethod.Restore())\n\n\n    var selectedOption by remember { mutableStateOf<InstallMethod?>(null) }\n    \n    // Launcher for KP Patching (SelectFile)\n    val selectImageLauncher = rememberLauncherForActivityResult(\n        contract = ActivityResultContracts.StartActivityForResult()\n    ) {\n        if (it.resultCode == Activity.RESULT_OK) {\n            it.data?.data?.let { uri ->\n                val option = InstallMethod.SelectFile(uri)\n                selectedOption = option\n                onSelected(option)\n                selectedBootImage = option.uri\n            }\n        }\n    }\n\n    // Launcher for custom KPimg selection\n    val selectKPImgLauncher = rememberLauncherForActivityResult(\n        contract = ActivityResultContracts.StartActivityForResult()\n    ) {\n        if (it.resultCode == Activity.RESULT_OK) {\n            it.data?.data?.let { uri ->\n                val option = InstallMethod.SelectKPImg(uri)\n                selectedOption = option\n                onSelected(option)\n                selectedKPImg = option.uri\n            }\n        }\n    }\n\n    // Launcher for Restore (SelectFile for now)\n    val selectRestoreImageLauncher = rememberLauncherForActivityResult(\n        contract = ActivityResultContracts.StartActivityForResult()\n    ) {\n        if (it.resultCode == Activity.RESULT_OK) {\n            it.data?.data?.let { uri ->\n                val option = InstallMethod.Restore(uri)\n                selectedOption = option\n                onSelected(option)\n                selectedBootImage = option.uri\n            }\n        }\n    }\n\n    val confirmDialog = rememberConfirmDialog(onConfirm = {\n        selectedOption = InstallMethod.DirectInstallToInactiveSlot\n        onSelected(InstallMethod.DirectInstallToInactiveSlot)\n    }, onDismiss = null)\n    val dialogTitle = stringResource(id = android.R.string.dialog_alert_title)\n    val dialogContent = stringResource(id = R.string.mode_select_page_install_inactive_slot_warning)\n\n    val onClick = { option: InstallMethod ->\n        when (option) {\n            is InstallMethod.SelectFile -> {\n                selectedBootImage = null\n                selectImageLauncher.launch(\n                    Intent(Intent.ACTION_GET_CONTENT).apply {\n                        type = \"application/octet-stream\"\n                        addCategory(Intent.CATEGORY_OPENABLE)\n                    }\n                )\n            }\n\n            is InstallMethod.SelectKPImg -> {\n                selectedKPImg = null\n                selectKPImgLauncher.launch(\n                    Intent(Intent.ACTION_GET_CONTENT).apply {\n                        type = \"application/octet-stream\"\n                        addCategory(Intent.CATEGORY_OPENABLE)\n                    }\n                )\n            }\n\n            is InstallMethod.DirectInstall -> {\n                selectedOption = option\n                onSelected(option)\n            }\n\n            is InstallMethod.DirectInstallToInactiveSlot -> {\n                confirmDialog.showConfirm(dialogTitle, dialogContent)\n            }\n            \n            is InstallMethod.Restore -> {\n                 selectedBootImage = null\n                selectRestoreImageLauncher.launch(\n                    Intent(Intent.ACTION_GET_CONTENT).apply {\n                        type = \"application/octet-stream\"\n                        addCategory(Intent.CATEGORY_OPENABLE)\n                    }\n                )\n            }\n        }\n    }\n\n    val onNext = {\n        selectedOption?.let { option ->\n            when (option) {\n                is InstallMethod.SelectFile -> {\n                    if (selectedBootImage != null) {\n                         navigator.navigate(PatchesDestination(PatchesViewModel.PatchMode.PATCH_ONLY))\n                    }\n                }\n                is InstallMethod.SelectKPImg -> {\n                    if (selectedKPImg != null) {\n                         navigator.navigate(PatchesDestination(PatchesViewModel.PatchMode.PATCH_ONLY))\n                    }\n                }\n                is InstallMethod.DirectInstall -> {\n                    navigator.navigate(PatchesDestination(PatchesViewModel.PatchMode.PATCH_AND_INSTALL))\n                }\n                 is InstallMethod.DirectInstallToInactiveSlot -> {\n                     navigator.navigate(PatchesDestination(PatchesViewModel.PatchMode.INSTALL_TO_NEXT_SLOT))\n                }\n                is InstallMethod.Restore -> {\n                    if (selectedBootImage != null) {\n                        navigator.navigate(PatchesDestination(PatchesViewModel.PatchMode.RESTORE))\n                    }\n                }\n            }\n        }\n    }\n\n    var kpExpanded by remember { mutableStateOf(false) }\n    var restoreExpanded by remember { mutableStateOf(false) }\n\n    val cardColors = CardDefaults.elevatedCardColors(\n        containerColor = if (BackgroundConfig.isCustomBackgroundEnabled) {\n            MaterialTheme.colorScheme.surfaceContainerLow.copy(alpha = BackgroundConfig.customBackgroundOpacity)\n        } else {\n            Color.Transparent\n        }\n    )\n\n    Column(\n        modifier = Modifier\n            .verticalScroll(rememberScrollState())\n            .padding(horizontal = 16.dp)\n    ) {\n        if (!rootAvailable) {\n            Box(Modifier.padding(vertical = 8.dp)) {\n                WarningCard(\n                    message = stringResource(R.string.home_install_unknown_summary),\n                    color = MaterialTheme.colorScheme.outlineVariant,\n                )\n            }\n        }\n\n        // KernelPatch Patching/Installing\n        ElevatedCard(\n            colors = cardColors,\n            elevation = CardDefaults.elevatedCardElevation(defaultElevation = 0.dp),\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(vertical = 8.dp)\n        ) {\n            ListItem(\n                colors = ListItemDefaults.colors(containerColor = Color.Transparent),\n                leadingContent = {\n                    Icon(\n                        Icons.Filled.AutoFixHigh,\n                        contentDescription = null,\n                        tint = MaterialTheme.colorScheme.primary\n                    )\n                },\n                headlineContent = {\n                    Text(\n                        stringResource(R.string.kp_install_methods),\n                        style = MaterialTheme.typography.titleMedium\n                    )\n                },\n                modifier = Modifier.clickable {\n                    kpExpanded = !kpExpanded\n                }\n            )\n\n            AnimatedVisibility(\n                visible = kpExpanded,\n                enter = fadeIn() + expandVertically(),\n                exit = shrinkVertically() + fadeOut()\n            ) {\n                Column(\n                    modifier = Modifier.padding(\n                        start = 16.dp,\n                        end = 16.dp,\n                        bottom = 16.dp\n                    )\n                ) {\n                    kpOptions.forEach { option ->\n                        InstallMethodOption(\n                            option = option,\n                            selectedOption = selectedOption,\n                            onClick = onClick\n                        )\n                    }\n                }\n            }\n        }\n\n        // Select a boot to restore to boot partition\n        ElevatedCard(\n            colors = cardColors,\n            elevation = CardDefaults.elevatedCardElevation(defaultElevation = 0.dp),\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(vertical = 8.dp)\n        ) {\n            ListItem(\n                colors = ListItemDefaults.colors(containerColor = Color.Transparent),\n                leadingContent = {\n                    Icon(\n                        Icons.Filled.FileUpload,\n                        contentDescription = null,\n                        tint = MaterialTheme.colorScheme.primary\n                    )\n                },\n                headlineContent = {\n                    Text(\n                        stringResource(R.string.restore_boot_methods),\n                        style = MaterialTheme.typography.titleMedium\n                    )\n                },\n                modifier = Modifier.clickable {\n                    restoreExpanded = !restoreExpanded\n                }\n            )\n\n            AnimatedVisibility(\n                visible = restoreExpanded,\n                enter = fadeIn() + expandVertically(),\n                exit = shrinkVertically() + fadeOut()\n            ) {\n                Column(\n                    modifier = Modifier.padding(\n                        start = 16.dp,\n                        end = 16.dp,\n                        bottom = 16.dp\n                    )\n                ) {\n                     restoreOptions.forEach { option ->\n                        InstallMethodOption(\n                            option = option,\n                            selectedOption = selectedOption,\n                            onClick = onClick\n                        )\n                    }\n                }\n            }\n        }\n\n        Button(\n            modifier = Modifier.fillMaxWidth(),\n            enabled = selectedOption != null,\n            onClick = {\n                onNext()\n            },\n            shape = MaterialTheme.shapes.medium,\n            colors = ButtonDefaults.buttonColors(\n                containerColor = MaterialTheme.colorScheme.primary,\n                contentColor = MaterialTheme.colorScheme.onPrimary,\n                disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.6f),\n                disabledContentColor = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f)\n            )\n        ) {\n            Text(\n                stringResource(id = R.string.home_patch_next_step),\n                style = MaterialTheme.typography.bodyMedium\n            )\n        }\n    }\n}\n\n@Composable\nfun InstallMethodOption(\n    option: InstallMethod,\n    selectedOption: InstallMethod?,\n    onClick: (InstallMethod) -> Unit\n) {\n    val interactionSource = remember { MutableInteractionSource() }\n    Surface(\n        color = Color.Transparent,\n        shape = MaterialTheme.shapes.medium,\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(vertical = 4.dp)\n            .clip(MaterialTheme.shapes.medium)\n    ) {\n        Row(\n            verticalAlignment = Alignment.CenterVertically,\n            modifier = Modifier\n                .fillMaxWidth()\n                .selectable(\n                    selected = option.javaClass == selectedOption?.javaClass,\n                    onClick = { onClick(option) },\n                    role = Role.RadioButton,\n                    indication = LocalIndication.current,\n                    interactionSource = interactionSource\n                )\n                .padding(vertical = 8.dp, horizontal = 12.dp)\n        ) {\n            RadioButton(\n                selected = option.javaClass == selectedOption?.javaClass,\n                onClick = null,\n                interactionSource = interactionSource,\n                colors = RadioButtonDefaults.colors(\n                    selectedColor = MaterialTheme.colorScheme.primary,\n                    unselectedColor = MaterialTheme.colorScheme.onSurfaceVariant\n                )\n            )\n            Column(\n                modifier = Modifier\n                    .padding(start = 10.dp)\n                    .weight(1f)\n            ) {\n                Text(\n                    text = stringResource(id = option.label),\n                    style = MaterialTheme.typography.bodyLarge\n                )\n                option.summary?.let {\n                    Text(\n                        text = it,\n                        style = MaterialTheme.typography.bodySmall,\n                        color = MaterialTheme.colorScheme.onSurfaceVariant\n                    )\n                }\n            }\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun TopBar(onBack: () -> Unit = {}) {\n    TopAppBar(\n        title = { Text(stringResource(R.string.mode_select_page_title)) },\n        navigationIcon = {\n            IconButton(\n                onClick = onBack\n            ) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) }\n        },\n    )\n}"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/KPM.kt",
    "content": "package me.bmax.apatch.ui.screen\n\nimport android.app.Activity.RESULT_OK\nimport android.content.Context\nimport android.content.Intent\nimport android.net.Uri\nimport android.util.Log\nimport me.bmax.apatch.util.ui.showToast\nimport androidx.activity.compose.rememberLauncherForActivityResult\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.animation.animateContentSize\nimport androidx.compose.animation.Crossfade\nimport androidx.compose.animation.core.animateDpAsState\nimport androidx.compose.animation.core.tween\nimport androidx.compose.animation.expandVertically\nimport androidx.compose.animation.fadeIn\nimport androidx.compose.animation.fadeOut\nimport androidx.compose.animation.shrinkVertically\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.defaultMinSize\nimport androidx.compose.foundation.layout.fillMaxHeight\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.layout.wrapContentHeight\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.LazyListState\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.lazy.itemsIndexed\nimport androidx.compose.foundation.lazy.rememberLazyListState\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.foundation.text.KeyboardOptions\nimport androidx.compose.foundation.text.KeyboardActions\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.Close\nimport androidx.compose.material.icons.filled.Download\nimport androidx.compose.material.icons.filled.Search\nimport androidx.compose.material.icons.outlined.Code\nimport androidx.compose.material.icons.outlined.Delete\nimport androidx.compose.material.icons.outlined.Settings\nimport androidx.compose.material.ExperimentalMaterialApi\nimport androidx.compose.material3.ExperimentalMaterial3ExpressiveApi\nimport androidx.compose.material3.*\nimport androidx.compose.material3.pulltorefresh.PullToRefreshBox\nimport androidx.compose.ui.text.input.KeyboardType\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.MutableState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.livedata.observeAsState\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.setValue\nimport androidx.compose.runtime.produceState\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.alpha\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.focus.FocusRequester\nimport androidx.compose.ui.focus.focusRequester\nimport androidx.compose.ui.focus.onFocusChanged\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalSoftwareKeyboardController\nimport androidx.compose.ui.platform.LocalView\nimport androidx.compose.ui.res.painterResource\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.input.ImeAction\nimport androidx.compose.ui.text.input.VisualTransformation\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.text.style.TextDecoration\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.window.DialogProperties\nimport androidx.compose.ui.window.DialogWindowProvider\nimport androidx.compose.ui.window.PopupProperties\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.combinedClickable\nimport androidx.compose.ui.graphics.Brush\nimport androidx.compose.ui.layout.ContentScale\nimport androidx.activity.compose.BackHandler\nimport androidx.lifecycle.viewmodel.compose.viewModel\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport com.ramcosta.composedestinations.generated.destinations.InstallScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.KpmAutoLoadConfigScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.OnlineKPMScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.PatchesDestination\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport com.topjohnwu.superuser.nio.ExtendedFile\nimport com.topjohnwu.superuser.nio.FileSystemManager\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.sync.withPermit\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.Natives\nimport me.bmax.apatch.R\nimport me.bmax.apatch.apApp\nimport androidx.compose.material3.pulltorefresh.PullToRefreshDefaults\nimport androidx.compose.material3.pulltorefresh.rememberPullToRefreshState\nimport me.bmax.apatch.ui.component.ConfirmResult\nimport me.bmax.apatch.ui.component.KpmAutoLoadManager\nimport me.bmax.apatch.ui.component.LoadingDialogHandle\nimport me.bmax.apatch.ui.component.TwoColumnGrid\nimport me.bmax.apatch.ui.component.splicedLazyColumnGroup\nimport me.bmax.apatch.ui.component.rememberConfirmDialog\nimport me.bmax.apatch.ui.component.rememberLoadingDialog\nimport me.bmax.apatch.ui.viewmodel.KPModel\nimport me.bmax.apatch.ui.viewmodel.KPModuleViewModel\nimport me.bmax.apatch.ui.viewmodel.PatchesViewModel\nimport me.bmax.apatch.util.inputStream\nimport me.bmax.apatch.util.ui.APDialogBlurBehindUtils\nimport me.bmax.apatch.util.writeTo\nimport java.io.IOException\nimport java.io.File\n\nimport androidx.compose.foundation.isSystemInDarkTheme\nimport androidx.compose.foundation.layout.offset\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.platform.LocalConfiguration\nimport me.bmax.apatch.ui.theme.BackgroundConfig\nimport me.bmax.apatch.ui.LocalBottomBarVisible\nimport me.bmax.apatch.ui.LocalIsFloatingNavMode\nimport androidx.compose.material3.ButtonDefaults\n\nimport android.content.SharedPreferences\nimport androidx.compose.runtime.DisposableEffect\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.compose.runtime.getValue\n\nimport me.bmax.apatch.util.BiometricUtils\nimport me.bmax.apatch.util.ModuleBackupUtils\nimport me.bmax.apatch.util.SafeUriResolver\nimport me.bmax.apatch.util.getFileNameFromUri\nimport kotlinx.coroutines.CoroutineScope\nimport coil.compose.AsyncImage\nimport coil.request.ImageRequest\n\nprivate const val TAG = \"KernelPatchModule\"\nprivate lateinit var targetKPMToControl: KPModel.KPMInfo\nprivate const val KPM_BANNER_DIR_NAME = \"kpm_banners\"\n\nprivate fun sanitizeKpmBannerKey(raw: String): String {\n    return raw.replace(Regex(\"[^a-zA-Z0-9._-]\"), \"_\")\n}\n\nprivate fun getKpmBannerFile(context: Context, moduleName: String): File {\n    val dir = File(context.filesDir, KPM_BANNER_DIR_NAME)\n    if (!dir.exists()) {\n        dir.mkdirs()\n    }\n    return File(dir, sanitizeKpmBannerKey(moduleName))\n}\n\nprivate fun readKpmBanner(context: Context, moduleName: String): ByteArray? {\n    return runCatching {\n        val file = getKpmBannerFile(context, moduleName)\n        if (file.exists()) {\n            file.readBytes().takeIf { it.isNotEmpty() }\n        } else {\n            null\n        }\n    }.getOrNull()\n}\n\nprivate fun writeKpmBanner(context: Context, moduleName: String, uri: Uri): ByteArray? {\n    val data = SafeUriResolver.openInputStream(context, uri)?.use { it.readBytes() } ?: return null\n    val file = getKpmBannerFile(context, moduleName)\n    file.outputStream().use { it.write(data) }\n    return data\n}\n\nprivate fun clearKpmBanner(context: Context, moduleName: String): Boolean {\n    val file = getKpmBannerFile(context, moduleName)\n    return !file.exists() || file.delete()\n}\n\n@Destination<RootGraph>\n@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)\n@Composable\nfun KPModuleScreen(navigator: DestinationsNavigator) {\n    val state by APApplication.apStateLiveData.observeAsState(APApplication.State.UNKNOWN_STATE)\n    if (state == APApplication.State.UNKNOWN_STATE) {\n        Column(\n            modifier = Modifier\n                .fillMaxSize()\n                .padding(12.dp),\n            verticalArrangement = Arrangement.Center,\n            horizontalAlignment = Alignment.CenterHorizontally\n        ) {\n            Row {\n                Text(\n                    text = stringResource(id = R.string.kpm_kp_not_installed),\n                    style = MaterialTheme.typography.titleMedium\n                )\n            }\n        }\n        return\n    }\n\n    val viewModel = viewModel<KPModuleViewModel>()\n\n    val context = LocalContext.current\n    var showFirstTimeDialog by remember { mutableStateOf(KpmAutoLoadManager.isFirstTimeKpmPage(context)) }\n    var dontShowAgain by remember { mutableStateOf(false) }\n\n    val prefs = remember { APApplication.sharedPreferences }\n    var showMoreModuleInfo by remember { mutableStateOf(prefs.getBoolean(\"show_more_module_info\", true)) }\n    var foldSystemModule by remember { mutableStateOf(prefs.getBoolean(\"fold_system_module\", true)) }\n    var simpleListBottomBar by remember { mutableStateOf(prefs.getBoolean(\"simple_list_bottom_bar\", false)) }\n    var splicedCardGroup by remember { mutableStateOf(prefs.getBoolean(\"spliced_card_group\", true)) }\n\n    DisposableEffect(Unit) {\n        val listener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPrefs, key ->\n            if (key == \"show_more_module_info\") {\n                showMoreModuleInfo = sharedPrefs.getBoolean(\"show_more_module_info\", true)\n            } else if (key == \"fold_system_module\") {\n                foldSystemModule = sharedPrefs.getBoolean(\"fold_system_module\", false)\n            } else if (key == \"simple_list_bottom_bar\") {\n                simpleListBottomBar = sharedPrefs.getBoolean(\"simple_list_bottom_bar\", false)\n            } else if (key == \"spliced_card_group\") {\n                splicedCardGroup = sharedPrefs.getBoolean(\"spliced_card_group\", true)\n            }\n        }\n        prefs.registerOnSharedPreferenceChangeListener(listener)\n        onDispose {\n            prefs.unregisterOnSharedPreferenceChangeListener(listener)\n        }\n    }\n\n    LaunchedEffect(Unit) {\n        if (viewModel.moduleList.isEmpty() || viewModel.isNeedRefresh) {\n            viewModel.fetchModuleList()\n        }\n    }\n\n    val kpModuleListState = rememberLazyListState()\n\n    var searchQuery by remember { mutableStateOf(\"\") }\n    val filteredModuleList = remember(viewModel.moduleList, searchQuery) {\n        if (searchQuery.isEmpty()) {\n            viewModel.moduleList\n        } else {\n            viewModel.moduleList.filter {\n                it.name.contains(searchQuery, ignoreCase = true) ||\n                it.description.contains(searchQuery, ignoreCase = true) ||\n                it.author.contains(searchQuery, ignoreCase = true)\n            }\n        }\n    }\n\n    val scope = rememberCoroutineScope()\n    suspend fun checkStrongBiometric(): Boolean {\n        val prefs = APApplication.sharedPreferences\n        if (prefs.getBoolean(\"strong_biometric\", false) && prefs.getBoolean(\"biometric_login\", false)) {\n            val activity = context as? androidx.fragment.app.FragmentActivity\n            return if (activity != null) {\n                BiometricUtils.authenticate(activity)\n            } else {\n                true\n            }\n        }\n        return true\n    }\n\n    Scaffold(topBar = {\n        TopBar(navigator, searchQuery) { searchQuery = it }\n    }, floatingActionButton = run {\n        {\n            val scope = rememberCoroutineScope()\n            val context = LocalContext.current\n\n            val moduleLoad = stringResource(id = R.string.kpm_load)\n            val moduleEmbed = stringResource(id = R.string.kpm_embed)\n            val autoLoadConfig = stringResource(id = R.string.kpm_autoload_title)\n            val successToastText = stringResource(id = R.string.kpm_load_toast_succ)\n            val failToastText = stringResource(id = R.string.kpm_load_toast_failed)\n            val loadingDialog = rememberLoadingDialog()\n\n            val selectZipLauncher = rememberLauncherForActivityResult(\n                contract = ActivityResultContracts.StartActivityForResult()\n            ) {\n                if (it.resultCode != RESULT_OK) {\n                    return@rememberLauncherForActivityResult\n                }\n                val data = it.data ?: return@rememberLauncherForActivityResult\n                val uri = data.data ?: return@rememberLauncherForActivityResult\n\n                Log.i(TAG, \"select zip result: $uri\")\n\n                navigator.navigate(InstallScreenDestination(uri, MODULE_TYPE.KPM))\n            }\n\n            val selectKpmLauncher = rememberLauncherForActivityResult(\n                contract = ActivityResultContracts.StartActivityForResult()\n            ) {\n                if (it.resultCode != RESULT_OK) {\n                    return@rememberLauncherForActivityResult\n                }\n                val data = it.data ?: return@rememberLauncherForActivityResult\n                val uri = data.data ?: return@rememberLauncherForActivityResult\n\n                // todo: args\n                scope.launch {\n                    val rc = loadModule(loadingDialog, uri, \"\")\n                    val toastText = if (rc == 0) successToastText else \"$failToastText: $rc\"\n                    withContext(Dispatchers.Main) {\n                        showToast(context, toastText)\n                    }\n                    viewModel.markNeedRefresh()\n                    viewModel.fetchModuleList()\n                }\n            }\n\n            var expanded by remember { mutableStateOf(false) }\n            val isFloatingMode = LocalIsFloatingNavMode.current\n\n            val fabContent: @Composable () -> Unit = {\n                FloatingActionButtonMenu(\n                    expanded = expanded,\n                    button = {\n                        FloatingActionButton(\n                            onClick = { expanded = !expanded },\n                            shape = CircleShape,\n                            contentColor = MaterialTheme.colorScheme.onPrimary.copy(alpha = 1f),\n                            containerColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                        ) {\n                            Crossfade(\n                                targetState = expanded,\n                                animationSpec = tween(durationMillis = 200),\n                                label = \"fabIconCrossfade\"\n                            ) { isExpanded ->\n                                if (isExpanded) {\n                                    Icon(\n                                        Icons.Filled.Close,\n                                        contentDescription = null,\n                                    )\n                                } else {\n                                    Icon(\n                                        painter = painterResource(id = R.drawable.package_import),\n                                        contentDescription = null,\n                                    )\n                                }\n                            }\n                        }\n                    },\n                ) {\n                    // 自动配置 (Auto Config) — top\n                    FloatingActionButtonMenuItem(\n                        onClick = {\n                            expanded = false\n                            navigator.navigate(KpmAutoLoadConfigScreenDestination)\n                        },\n                        icon = { Icon(Icons.Outlined.Settings, contentDescription = null, modifier = Modifier.size(18.dp)) },\n                        text = { Text(text = autoLoadConfig, style = MaterialTheme.typography.bodyMedium) },\n                    )\n                    // 嵌入 (Embed)\n                    FloatingActionButtonMenuItem(\n                        onClick = {\n                            expanded = false\n                            scope.launch {\n                                if (!checkStrongBiometric()) return@launch\n                                navigator.navigate(PatchesDestination(PatchesViewModel.PatchMode.PATCH_AND_INSTALL))\n                            }\n                        },\n                        icon = { Icon(Icons.Outlined.Code, contentDescription = null, modifier = Modifier.size(18.dp)) },\n                        text = { Text(text = moduleEmbed, style = MaterialTheme.typography.bodyMedium) },\n                    )\n                    // 加载 (Load)\n                    FloatingActionButtonMenuItem(\n                        onClick = {\n                            expanded = false\n                            scope.launch {\n                                if (!checkStrongBiometric()) return@launch\n                                val intent = Intent(Intent.ACTION_GET_CONTENT)\n                                intent.type = \"*/*\"\n                                intent.addCategory(Intent.CATEGORY_OPENABLE)\n                                selectKpmLauncher.launch(intent)\n                            }\n                        },\n                        icon = { Icon(Icons.Filled.Download, contentDescription = null, modifier = Modifier.size(18.dp)) },\n                        text = { Text(text = moduleLoad, style = MaterialTheme.typography.bodyMedium) },\n                    )\n                }\n            }\n            val bottomBarVisible = LocalBottomBarVisible.current.value\n            val configuration = LocalConfiguration.current\n            val isLandscape = configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE\n            val animatedOffset by animateDpAsState(\n                targetValue = if (isFloatingMode && bottomBarVisible && !isLandscape) (-88).dp else 0.dp,\n                animationSpec = tween(durationMillis = 300),\n                label = \"fabOffset\"\n            )\n            if (isFloatingMode) {\n                Box(modifier = Modifier.offset(y = animatedOffset)) {\n                    fabContent()\n                }\n            } else {\n                fabContent()\n            }\n        }\n    }) { innerPadding ->\n\n        KPModuleList(\n            viewModel = viewModel,\n            moduleList = filteredModuleList,\n            modifier = Modifier\n                .padding(innerPadding)\n                .fillMaxSize(),\n            state = kpModuleListState,\n            showMoreModuleInfo = showMoreModuleInfo,\n            foldSystemModule = foldSystemModule,\n            simpleListBottomBar = simpleListBottomBar,\n            splicedCardGroup = splicedCardGroup,\n            checkStrongBiometric = ::checkStrongBiometric\n        )\n    }\n\n    if (showFirstTimeDialog) {\n        BasicAlertDialog(\n            onDismissRequest = {\n                if (dontShowAgain) {\n                    KpmAutoLoadManager.setFirstTimeKpmPageShown(context)\n                }\n                showFirstTimeDialog = false\n            },\n            properties = androidx.compose.ui.window.DialogProperties(\n                dismissOnClickOutside = false,\n                dismissOnBackPress = false\n            )\n        ) {\n            Surface(\n                modifier = Modifier\n                    .width(350.dp)\n                    .padding(16.dp),\n                shape = RoundedCornerShape(20.dp),\n                tonalElevation = AlertDialogDefaults.TonalElevation,\n                color = AlertDialogDefaults.containerColor,\n            ) {\n                Column(modifier = Modifier.padding(16.dp)) {\n                    Text(\n                        text = stringResource(R.string.kpm_page_first_time_title),\n                        style = MaterialTheme.typography.titleMedium,\n                        modifier = Modifier.padding(bottom = 12.dp)\n                    )\n\n                    Text(\n                        text = stringResource(R.string.kpm_page_first_time_message),\n                        style = MaterialTheme.typography.bodyMedium,\n                        modifier = Modifier.padding(bottom = 16.dp)\n                    )\n\n                    Row(\n                        modifier = Modifier.fillMaxWidth(),\n                        verticalAlignment = Alignment.CenterVertically\n                    ) {\n                        androidx.compose.material3.Checkbox(\n                            checked = dontShowAgain,\n                            onCheckedChange = { dontShowAgain = it }\n                        )\n                        Spacer(modifier = Modifier.width(8.dp))\n                        Text(\n                            text = stringResource(R.string.kpm_autoload_do_not_show_again),\n                            style = MaterialTheme.typography.bodySmall\n                        )\n                    }\n\n                    Spacer(modifier = Modifier.height(16.dp))\n\n                    Row(\n                        modifier = Modifier.fillMaxWidth(),\n                        horizontalArrangement = Arrangement.End\n                    ) {\n                        Button(onClick = {\n                            if (dontShowAgain) {\n                                KpmAutoLoadManager.setFirstTimeKpmPageShown(context)\n                            }\n                            showFirstTimeDialog = false\n                        }) {\n                            Text(stringResource(R.string.kpm_autoload_first_time_confirm))\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\nsuspend fun loadModule(loadingDialog: LoadingDialogHandle, uri: Uri, args: String): Int {\n    val rc = loadingDialog.withLoading {\n        withContext(Dispatchers.IO) {\n            run {\n                val kpmDir: ExtendedFile =\n                    FileSystemManager.getLocal().getFile(apApp.filesDir.parent, \"kpm\")\n                kpmDir.deleteRecursively()\n                kpmDir.mkdirs()\n                val rand = (1..4).map { ('a'..'z').random() }.joinToString(\"\")\n                val kpm = kpmDir.getChildFile(\"${rand}.kpm\")\n                Log.d(TAG, \"save tmp kpm: ${kpm.path}\")\n                var rc = -1\n                try {\n                    uri.inputStream().buffered().writeTo(kpm)\n\n                    // Auto Backup Logic for KPM Load\n                    val fileName = getFileNameFromUri(apApp, uri)\n                    // Launch backup asynchronously\n                    CoroutineScope(Dispatchers.IO).launch {\n                        try {\n                            val result = ModuleBackupUtils.autoBackupModule(apApp, kpm, fileName, \"KPM\")\n                            if (result != null && !result.startsWith(\"Duplicate\")) {\n                                Log.e(TAG, \"KPM Auto backup failed: $result\")\n                            } else {\n                                Log.d(TAG, \"KPM Auto backup success\")\n                            }\n                        } catch (e: Exception) {\n                            Log.e(TAG, \"KPM Auto backup error: ${e.message}\")\n                        }\n                    }\n\n                    rc = Natives.loadKernelPatchModule(kpm.path, args).toInt()\n                } catch (e: IOException) {\n                    Log.e(TAG, \"Copy kpm error: $e\")\n                }\n                Log.d(TAG, \"load ${kpm.path} rc: $rc\")\n                rc\n            }\n        }\n    }\n    return rc\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun KPMControlDialog(showDialog: MutableState<Boolean>) {\n    var controlParam by remember { mutableStateOf(\"\") }\n    var enable by remember { mutableStateOf(false) }\n    val scope = rememberCoroutineScope()\n    val loadingDialog = rememberLoadingDialog()\n    val context = LocalContext.current\n    val outMsgStringRes = stringResource(id = R.string.kpm_control_outMsg)\n    val okStringRes = stringResource(id = R.string.kpm_control_ok)\n    val failedStringRes = stringResource(id = R.string.kpm_control_failed)\n\n    lateinit var controlResult: Natives.KPMCtlRes\n\n    suspend fun onModuleControl(module: KPModel.KPMInfo) {\n        loadingDialog.withLoading {\n            withContext(Dispatchers.IO) {\n                controlResult = Natives.kernelPatchModuleControl(module.name, controlParam)\n            }\n        }\n\n        if (controlResult.rc >= 0) {\n            showToast(context, \"$okStringRes\\n${outMsgStringRes}: ${controlResult.outMsg}\")\n        } else {\n            showToast(context, \"$failedStringRes\\n${outMsgStringRes}: ${controlResult.outMsg}\")\n        }\n    }\n\n    BasicAlertDialog(\n        onDismissRequest = { showDialog.value = false }, properties = DialogProperties(\n            decorFitsSystemWindows = true,\n            usePlatformDefaultWidth = false,\n        )\n    ) {\n        Surface(\n            modifier = Modifier\n                .width(310.dp)\n                .wrapContentHeight(),\n            shape = RoundedCornerShape(30.dp),\n            tonalElevation = AlertDialogDefaults.TonalElevation,\n            color = AlertDialogDefaults.containerColor,\n        ) {\n            Column(modifier = Modifier.padding(PaddingValues(all = 24.dp))) {\n                Box(\n                    Modifier\n                        .padding(PaddingValues(bottom = 16.dp))\n                        .align(Alignment.Start)\n                ) {\n                    Text(\n                        text = stringResource(id = R.string.kpm_control_dialog_title),\n                        style = MaterialTheme.typography.headlineSmall\n                    )\n                }\n\n                Box(\n                    Modifier\n                        .weight(weight = 1f, fill = false)\n                        .align(Alignment.Start)\n                ) {\n                    Text(\n                        text = stringResource(id = R.string.kpm_control_dialog_content),\n                        style = MaterialTheme.typography.bodyMedium\n                    )\n                }\n\n                Box(\n                    contentAlignment = Alignment.CenterEnd,\n                ) {\n                    OutlinedTextField(\n                        value = controlParam,\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .padding(top = 6.dp),\n                        onValueChange = {\n                            controlParam = it\n                            enable = controlParam.isNotBlank()\n                        },\n                        shape = RoundedCornerShape(50.0f),\n                        label = { Text(stringResource(id = R.string.kpm_control_paramters)) },\n                        visualTransformation = VisualTransformation.None,\n                    )\n                }\n\n                Spacer(modifier = Modifier.height(12.dp))\n                Row(\n                    modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End\n                ) {\n                    TextButton(onClick = { showDialog.value = false }) {\n                        Text(stringResource(id = android.R.string.cancel))\n                    }\n\n                    Button(onClick = {\n                        showDialog.value = false\n\n                        scope.launch { onModuleControl(targetKPMToControl) }\n\n                    }, enabled = enable) {\n                        Text(stringResource(id = android.R.string.ok))\n                    }\n                }\n            }\n        }\n        val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n        APDialogBlurBehindUtils.setupWindowBlurListener(dialogWindowProvider.window)\n    }\n}\n\n@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)\n@Composable\nprivate fun KPModuleList(\n    viewModel: KPModuleViewModel,\n    moduleList: List<KPModel.KPMInfo>,\n    modifier: Modifier = Modifier,\n    state: LazyListState,\n    showMoreModuleInfo: Boolean,\n    foldSystemModule: Boolean,\n    simpleListBottomBar: Boolean,\n    splicedCardGroup: Boolean,\n    checkStrongBiometric: suspend () -> Boolean\n) {\n    val moduleStr = stringResource(id = R.string.kpm)\n    val moduleUninstallConfirm = stringResource(id = R.string.kpm_unload_confirm)\n    val uninstall = stringResource(id = R.string.kpm_unload)\n    val cancel = stringResource(id = android.R.string.cancel)\n\n    var expandedModuleId by remember { mutableStateOf<String?>(null) }\n\n    val confirmDialog = rememberConfirmDialog()\n    val loadingDialog = rememberLoadingDialog()\n\n    val showKPMControlDialog = remember { mutableStateOf(false) }\n    if (showKPMControlDialog.value) {\n        KPMControlDialog(showDialog = showKPMControlDialog)\n    }\n\n    suspend fun onModuleUninstall(module: KPModel.KPMInfo) {\n        if (!checkStrongBiometric()) return\n        val confirmResult = confirmDialog.awaitConfirm(\n            moduleStr,\n            content = moduleUninstallConfirm.format(module.name),\n            confirm = uninstall,\n            dismiss = cancel\n        )\n        if (confirmResult != ConfirmResult.Confirmed) {\n            return\n        }\n\n        val success = loadingDialog.withLoading {\n            withContext(Dispatchers.IO) {\n                Natives.unloadKernelPatchModule(module.name) == 0L\n            }\n        }\n\n        if (success) {\n            viewModel.fetchModuleList()\n        }\n    }\n\n    val pullToRefreshState = rememberPullToRefreshState()\n    PullToRefreshBox(\n        modifier = modifier,\n        onRefresh = { viewModel.fetchModuleList() },\n        isRefreshing = viewModel.isRefreshing,\n        state = pullToRefreshState,\n        indicator = { PullToRefreshDefaults.LoadingIndicator(state = pullToRefreshState, isRefreshing = viewModel.isRefreshing, modifier = Modifier.align(Alignment.TopCenter)) }\n    ) {\n        val configuration = LocalConfiguration.current\n        val isWideScreen = configuration.screenWidthDp >= 600\n\n        if (isWideScreen) {\n            TwoColumnGrid(\n                modifier = Modifier.fillMaxSize(),\n                items = if (moduleList.isEmpty()) emptyList() else moduleList,\n                key = { module -> module.name },\n                verticalSpacing = 16.dp,\n                horizontalSpacing = 16.dp,\n                contentPadding = run {\n                    val isFloating = LocalIsFloatingNavMode.current\n                    remember(isFloating) {\n                        PaddingValues(\n                            start = 16.dp,\n                            top = 16.dp,\n                            end = 16.dp,\n                            bottom = if (isFloating) 16.dp + 16.dp + 56.dp else 16.dp\n                        )\n                    }\n                },\n                beforeItems = {\n                    if (moduleList.isEmpty()) {\n                        Box(\n                            modifier = Modifier\n                                .fillMaxWidth()\n                                .defaultMinSize(minHeight = 300.dp),\n                            contentAlignment = Alignment.Center\n                        ) {\n                            Text(\n                                stringResource(R.string.kpm_apm_empty), textAlign = TextAlign.Center\n                            )\n                        }\n                    }\n                },\n                itemContent = { module ->\n                    val scope = rememberCoroutineScope()\n                    KPModuleItem(\n                        module,\n                        onUninstall = {\n                            scope.launch { onModuleUninstall(module) }\n                        },\n                        onControl = {\n                            scope.launch {\n                                if (checkStrongBiometric()) {\n                                    targetKPMToControl = module\n                                    showKPMControlDialog.value = true\n                                }\n                            }\n                        },\n                        showMoreModuleInfo = showMoreModuleInfo,\n                        simpleListBottomBar = simpleListBottomBar,\n                        foldSystemModule = foldSystemModule,\n                        expanded = expandedModuleId == module.name,\n                        onExpandToggle = {\n                            expandedModuleId = if (expandedModuleId == module.name) null else module.name\n                        }\n                    )\n                }\n            )\n        } else {\n            LazyColumn(\n                modifier = Modifier.fillMaxSize(),\n                state = state,\n                contentPadding = run {\n                    val isFloating = LocalIsFloatingNavMode.current\n                    remember(isFloating) {\n                        PaddingValues(\n                            start = 0.dp,\n                            top = 16.dp,\n                            end = 0.dp,\n                            bottom = if (isFloating) 16.dp + 16.dp + 56.dp else 16.dp\n                        )\n                    }\n                },\n            ) {\n                when {\n                    moduleList.isEmpty() -> {\n                        item {\n                            Box(\n                                modifier = Modifier\n                                    .fillMaxWidth()\n                                    .fillParentMaxHeight(),\n                                contentAlignment = Alignment.Center\n                            ) {\n                                Text(\n                                    stringResource(R.string.kpm_apm_empty), textAlign = TextAlign.Center\n                                )\n                            }\n                        }\n                    }\n\n                    else -> {\n                        if (splicedCardGroup) {\n                            item { Spacer(Modifier.height(8.dp)) }\n                            splicedLazyColumnGroup(\n                                items = moduleList,\n                                key = { _, module -> module.name },\n                                contentType = { _, _ -> \"KPModuleItem\" },\n                            ) { _, module ->\n                                val scope = rememberCoroutineScope()\n                                KPModuleItem(\n                                    module,\n                                    onUninstall = {\n                                        scope.launch { onModuleUninstall(module) }\n                                    },\n                                    onControl = {\n                                        scope.launch {\n                                            if (checkStrongBiometric()) {\n                                                targetKPMToControl = module\n                                                showKPMControlDialog.value = true\n                                            }\n                                        }\n                                    },\n                                    showMoreModuleInfo = showMoreModuleInfo,\n                                    simpleListBottomBar = simpleListBottomBar,\n                                    foldSystemModule = foldSystemModule,\n                                    expanded = expandedModuleId == module.name,\n                                    onExpandToggle = {\n                                        expandedModuleId = if (expandedModuleId == module.name) null else module.name\n                                    }\n                                )\n                            }\n                            item { Spacer(Modifier.height(if (LocalIsFloatingNavMode.current) 88.dp else 8.dp)) }\n                        } else {\n                            item { Spacer(Modifier.height(8.dp)) }\n                            itemsIndexed(moduleList, key = { _, module -> module.name }) { _, module ->\n                                Box(modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) {\n                                val scope = rememberCoroutineScope()\n                                KPModuleItem(\n                                    module,\n                                    onUninstall = {\n                                        scope.launch { onModuleUninstall(module) }\n                                    },\n                                    onControl = {\n                                        scope.launch {\n                                            if (checkStrongBiometric()) {\n                                                targetKPMToControl = module\n                                                showKPMControlDialog.value = true\n                                            }\n                                        }\n                                    },\n                                    showMoreModuleInfo = showMoreModuleInfo,\n                                    simpleListBottomBar = simpleListBottomBar,\n                                    foldSystemModule = foldSystemModule,\n                                    expanded = expandedModuleId == module.name,\n                                    onExpandToggle = {\n                                        expandedModuleId = if (expandedModuleId == module.name) null else module.name\n                                    }\n                                )\n                                }\n                            }\n                            item { Spacer(Modifier.height(if (LocalIsFloatingNavMode.current) 88.dp else 8.dp)) }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun TopBar(\n    navigator: DestinationsNavigator,\n    searchQuery: String,\n    onSearchQueryChange: (String) -> Unit\n) {\n    val keyboardController = LocalSoftwareKeyboardController.current\n    val focusRequester = remember { FocusRequester() }\n    var onSearch by remember { mutableStateOf(false) }\n\n    if (onSearch) {\n        LaunchedEffect(Unit) { focusRequester.requestFocus() }\n    }\n\n    BackHandler(\n        enabled = onSearch,\n        onBack = {\n            keyboardController?.hide()\n            onSearchQueryChange(\"\")\n            onSearch = false\n        }\n    )\n\n    TopAppBar(\n        title = {\n            Box {\n                // 标题（搜索框未显示时）\n                AnimatedVisibility(\n                    modifier = Modifier.align(Alignment.CenterStart),\n                    visible = !onSearch,\n                    enter = fadeIn(),\n                    exit = fadeOut(),\n                    content = { Text(stringResource(R.string.kpm)) }\n                )\n\n                // 搜索框（搜索时显示）\n                AnimatedVisibility(\n                    visible = onSearch,\n                    enter = fadeIn(),\n                    exit = fadeOut()\n                ) {\n                    OutlinedTextField(\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .padding(top = 2.dp, bottom = 2.dp, end = 14.dp)\n                            .focusRequester(focusRequester)\n                            .onFocusChanged { focusState ->\n                                if (focusState.isFocused) onSearch = true\n                            },\n                        value = searchQuery,\n                        onValueChange = onSearchQueryChange,\n                        shape = RoundedCornerShape(15.dp),\n                        trailingIcon = {\n                            IconButton(\n                                onClick = {\n                                    onSearch = false\n                                    keyboardController?.hide()\n                                    onSearchQueryChange(\"\")\n                                },\n                                content = { Icon(Icons.Filled.Close, null) }\n                            )\n                        },\n                        maxLines = 1,\n                        singleLine = true,\n                        keyboardOptions = KeyboardOptions(\n                            keyboardType = KeyboardType.Text,\n                            imeAction = ImeAction.Search\n                        ),\n                        keyboardActions = KeyboardActions {\n                            keyboardController?.hide()\n                        },\n                    )\n                }\n            }\n        },\n        actions = {\n            AnimatedVisibility(\n                visible = !onSearch\n            ) {\n                Row(verticalAlignment = Alignment.CenterVertically) {\n                    // 搜索按钮\n                    IconButton(onClick = { onSearch = true }) {\n                        Icon(\n                            imageVector = Icons.Filled.Search,\n                            contentDescription = \"Search\"\n                        )\n                    }\n                    // 下载按钮\n                    IconButton(onClick = {\n                        navigator.navigate(OnlineKPMScreenDestination)\n                    }) {\n                        Icon(\n                            imageVector = Icons.Filled.Download,\n                            contentDescription = \"Online KPM\"\n                        )\n                    }\n                }\n            }\n        }\n    )\n}\n\n@Composable\nprivate fun KPModuleLabel(\n    text: String,\n    containerColor: Color,\n    contentColor: Color\n) {\n    Surface(\n        color = containerColor,\n        contentColor = contentColor,\n        shape = RoundedCornerShape(8.dp),\n    ) {\n        Text(\n            text = text,\n            style = MaterialTheme.typography.labelSmall,\n            modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp),\n            maxLines = 1,\n            overflow = TextOverflow.Ellipsis\n        )\n    }\n}\n\n@Composable\nprivate fun KPModuleItem(\n    module: KPModel.KPMInfo,\n    onUninstall: (KPModel.KPMInfo) -> Unit,\n    onControl: (KPModel.KPMInfo) -> Unit,\n    modifier: Modifier = Modifier,\n    alpha: Float = 1f,\n    showMoreModuleInfo: Boolean,\n    simpleListBottomBar: Boolean,\n    foldSystemModule: Boolean,\n    expanded: Boolean,\n    onExpandToggle: () -> Unit\n) {\n    val moduleAuthor = stringResource(id = R.string.kpm_author)\n    val moduleArgs = stringResource(id = R.string.kpm_args)\n    val decoration = TextDecoration.None\n    val context = LocalContext.current\n    val scope = rememberCoroutineScope()\n    val loadingDialog = rememberLoadingDialog()\n    val folkBannerTitle = stringResource(R.string.apm_folk_banner_title)\n    val folkBannerSelect = stringResource(R.string.apm_folk_banner_select)\n    val folkBannerClear = stringResource(R.string.apm_folk_banner_clear)\n    val folkBannerSaved = stringResource(R.string.apm_folk_banner_saved)\n    val folkBannerCleared = stringResource(R.string.apm_folk_banner_cleared)\n    val folkBannerFailed = stringResource(R.string.apm_folk_banner_failed)\n    var showFolkBannerDialog by remember { mutableStateOf(false) }\n    var hasFolkBanner by remember { mutableStateOf(false) }\n    var bannerReloadKey by remember { mutableStateOf(0) }\n    \n    LaunchedEffect(showFolkBannerDialog) {\n        if (showFolkBannerDialog) {\n            hasFolkBanner = withContext(Dispatchers.IO) {\n                readKpmBanner(context, module.name) != null\n            }\n        }\n    }\n\n    val pickFolkBannerLauncher = rememberLauncherForActivityResult(\n        ActivityResultContracts.GetContent()\n    ) { uri: Uri? ->\n        uri?.let {\n            scope.launch {\n                loadingDialog.show()\n                val result = withContext(Dispatchers.IO) {\n                    runCatching { writeKpmBanner(context, module.name, it) }.getOrNull()\n                }\n                loadingDialog.hide()\n                val message = if (result != null) {\n                    bannerReloadKey++\n                    folkBannerSaved.format(module.name)\n                } else {\n                    folkBannerFailed.format(module.name)\n                }\n                showToast(context, message)\n            }\n        }\n    }\n\n    val isWallpaperMode = BackgroundConfig.isCustomBackgroundEnabled\n    val opacity = if (isWallpaperMode) {\n        BackgroundConfig.customBackgroundOpacity.coerceAtLeast(0.35f)\n    } else {\n        1f\n    }\n    \n    val isDark = isSystemInDarkTheme()\n    val cardColor = if (isWallpaperMode) {\n        MaterialTheme.colorScheme.surface.copy(alpha = opacity)\n    } else {\n        MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.2f)\n    }\n\n    val bannerImageAlpha = if (BackgroundConfig.isBannerCustomOpacityEnabled) {\n        BackgroundConfig.bannerCustomOpacity\n    } else {\n        if (isWallpaperMode) {\n            (0.35f + (opacity - 0.2f) * 0.5f).coerceIn(0.25f, 0.6f)\n        } else {\n            0.18f\n        }\n    }\n\n    val bannerData by produceState<ByteArray?>(\n        initialValue = null,\n        module.name,\n        BackgroundConfig.isBannerEnabled,\n        BackgroundConfig.isFolkBannerEnabled,\n        BackgroundConfig.isBannerApiModeEnabled,\n        BackgroundConfig.bannerApiSource,\n        bannerReloadKey\n    ) {\n        if (!BackgroundConfig.isBannerEnabled) {\n            value = null\n            return@produceState\n        }\n\n        KPModuleViewModel.bannerSemaphore.withPermit {\n            val effectiveApiSource = BackgroundConfig.getEffectiveBannerApiSource()\n\n        if (BackgroundConfig.isBannerApiModeEnabled && effectiveApiSource.isNotBlank()) {\n            val apiBanner = withContext(Dispatchers.IO) {\n                BannerApiService.getModuleBanner(\n                    context = context,\n                    moduleId = \"kpm_${module.name}\",\n                    source = effectiveApiSource\n                )\n            }\n            if (apiBanner != null) {\n                value = apiBanner\n                return@produceState\n            }\n        }\n\n        value = if (BackgroundConfig.isFolkBannerEnabled) {\n            withContext(Dispatchers.IO) { readKpmBanner(context, module.name) }\n        } else {\n            null\n        }\n        }\n    }\n\n    val insideSplicedGroup = me.bmax.apatch.ui.component.LocalInsideSplicedGroup.current\n\n    val cardShape = RoundedCornerShape(20.dp)\n\n    val clickModifier = Modifier\n        .fillMaxWidth()\n        .animateContentSize()\n        .combinedClickable(\n            onClick = {\n                if (foldSystemModule) {\n                    onExpandToggle()\n                }\n            },\n            onLongClick = {\n                if (BackgroundConfig.isBannerEnabled && BackgroundConfig.isFolkBannerEnabled) {\n                    showFolkBannerDialog = true\n                }\n            }\n        )\n\n    val contentBlock: @Composable () -> Unit = {\n        Box(modifier = Modifier.fillMaxWidth()) {\n            if (bannerData != null) {\n                val colorScheme = MaterialTheme.colorScheme\n                val isDynamic = colorScheme.primary != colorScheme.secondary\n                val fadeColor = when {\n                    isDynamic -> colorScheme.surface\n                    isDark -> Color(0xFF222222)\n                    else -> Color.White\n                }\n\n                Box(\n                    modifier = Modifier.matchParentSize(),\n                    contentAlignment = Alignment.Center\n                ) {\n                    AsyncImage(\n                        model = ImageRequest.Builder(context)\n                            .data(bannerData)\n                            .build(),\n                        contentDescription = null,\n                        modifier = Modifier.fillMaxSize(),\n                        contentScale = ContentScale.Crop,\n                        alpha = bannerImageAlpha\n                    )\n                    val gradientAlpha = if (isWallpaperMode) 0.5f else 0.8f\n                    Box(\n                        modifier = Modifier\n                            .fillMaxSize()\n                            .background(\n                                Brush.verticalGradient(\n                                    colors = listOf(\n                                        fadeColor.copy(alpha = 0.0f),\n                                        fadeColor.copy(alpha = gradientAlpha)\n                                    ),\n                                    startY = 0f,\n                                    endY = Float.POSITIVE_INFINITY\n                                )\n                            )\n                    )\n                }\n            }\n\n            Column(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .padding(16.dp)\n            ) {\n                Row(\n                    modifier = Modifier.fillMaxWidth(),\n                    verticalAlignment = Alignment.Top\n                ) {\n                    Column(modifier = Modifier.weight(1f)) {\n                        val hasAnyLabel = showMoreModuleInfo\n                        if (hasAnyLabel) {\n                            Row(\n                                horizontalArrangement = Arrangement.spacedBy(6.dp),\n                                modifier = Modifier.padding(bottom = 8.dp)\n                            ) {\n                                 val labelOpacity = (opacity + 0.1f).coerceAtMost(1f)\n                                 \n                                 KPModuleLabel(\n                                    text = \"KPM\",\n                                    containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = labelOpacity),\n                                    contentColor = MaterialTheme.colorScheme.onPrimaryContainer\n                                 )\n                                 \n                                 if (module.args.isNotBlank()) {\n                                     KPModuleLabel(\n                                        text = \"Args\",\n                                        containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = labelOpacity),\n                                        contentColor = MaterialTheme.colorScheme.onSecondaryContainer\n                                     )\n                                 }\n                            }\n                        }\n                    \n                        Text(\n                            text = module.name,\n                            style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold),\n                            textDecoration = decoration\n                        )\n\n                        Text(\n                            text = module.version,\n                            style = MaterialTheme.typography.bodySmall,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant,\n                            textDecoration = decoration\n                        )\n\n                        Text(\n                            text = module.author,\n                            style = MaterialTheme.typography.bodySmall,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant,\n                            textDecoration = decoration\n                        )\n                        \n                        if (showMoreModuleInfo && module.args.isNotBlank()) {\n                             Text(\n                                text = \"$moduleArgs: ${module.args}\",\n                                style = MaterialTheme.typography.bodySmall,\n                                color = MaterialTheme.colorScheme.onSurfaceVariant,\n                                textDecoration = decoration,\n                                modifier = Modifier.padding(top = 2.dp)\n                            )\n                        }\n                    }\n                }\n\n                Spacer(modifier = Modifier.height(12.dp))\n\n                Text(\n                    text = module.description,\n                    style = MaterialTheme.typography.bodySmall,\n                    color = MaterialTheme.colorScheme.onSurfaceVariant,\n                    maxLines = 4,\n                    overflow = TextOverflow.Ellipsis\n                )\n\n                Spacer(modifier = Modifier.height(16.dp))\n\n                AnimatedVisibility(\n                    visible = !foldSystemModule || expanded,\n                    enter = fadeIn() + expandVertically(),\n                    exit = shrinkVertically() + fadeOut()\n                ) {\n                    Row(\n                        modifier = Modifier.fillMaxWidth(),\n                        horizontalArrangement = Arrangement.spacedBy(if (simpleListBottomBar) 12.dp else 8.dp)\n                    ) {\n                        FilledTonalButton(\n                            onClick = { onControl(module) },\n                            enabled = true,\n                            contentPadding = if (simpleListBottomBar) PaddingValues(12.dp) else PaddingValues(horizontal = 12.dp),\n                            modifier = if (simpleListBottomBar) Modifier else Modifier.height(36.dp),\n                            colors = ButtonDefaults.filledTonalButtonColors(\n                                containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = (opacity + 0.3f).coerceAtMost(1f))\n                            )\n                        ) {\n                            Icon(\n                                modifier = Modifier.size(20.dp),\n                                painter = painterResource(id = R.drawable.settings),\n                                contentDescription = stringResource(id = R.string.kpm_control)\n                            )\n                            if (!simpleListBottomBar) {\n                                Spacer(modifier = Modifier.width(8.dp))\n                                Text(stringResource(id = R.string.kpm_control))\n                            }\n                        }\n\n                        Spacer(modifier = Modifier.weight(1f))\n\n                        FilledTonalButton(\n                            onClick = { onUninstall(module) },\n                            enabled = true,\n                            contentPadding = if (simpleListBottomBar) PaddingValues(12.dp) else PaddingValues(horizontal = 12.dp),\n                            modifier = if (simpleListBottomBar) Modifier else Modifier.height(36.dp),\n                            colors = if (simpleListBottomBar) ButtonDefaults.filledTonalButtonColors(\n                                containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = (opacity + 0.3f).coerceAtMost(1f))\n                            ) else ButtonDefaults.filledTonalButtonColors(\n                                containerColor = MaterialTheme.colorScheme.errorContainer.copy(alpha = (opacity + 0.3f).coerceAtMost(1f)),\n                                contentColor = MaterialTheme.colorScheme.onErrorContainer\n                            )\n                        ) {\n                            Icon(\n                                modifier = Modifier.size(20.dp),\n                                painter = painterResource(id = R.drawable.trash),\n                                contentDescription = stringResource(id = R.string.kpm_unload)\n                            )\n                            if (!simpleListBottomBar) {\n                                Spacer(modifier = Modifier.width(8.dp))\n                                Text(stringResource(id = R.string.kpm_unload))\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    // Render: inside spliced group → no Surface wrapper; standalone → Surface card\n    if (insideSplicedGroup) {\n        Box(modifier = modifier.then(clickModifier)) {\n            contentBlock()\n        }\n    } else {\n        Surface(\n            modifier = modifier.then(clickModifier),\n            shape = cardShape,\n            color = cardColor,\n            tonalElevation = 0.dp\n        ) {\n            contentBlock()\n        }\n    }\n\n    if (showFolkBannerDialog) {\n        AlertDialog(\n            onDismissRequest = { showFolkBannerDialog = false },\n            title = { Text(folkBannerTitle) },\n            text = {\n                Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {\n                    Button(\n                        onClick = {\n                            showFolkBannerDialog = false\n                            pickFolkBannerLauncher.launch(\"image/*\")\n                        },\n                        modifier = Modifier.fillMaxWidth()\n                    ) {\n                        Text(folkBannerSelect)\n                    }\n                    if (hasFolkBanner) {\n                        Button(\n                            onClick = {\n                                showFolkBannerDialog = false\n                                scope.launch {\n                                    loadingDialog.show()\n                                    val success = withContext(Dispatchers.IO) {\n                                        runCatching { clearKpmBanner(context, module.name) }.getOrDefault(false)\n                                    }\n                                    loadingDialog.hide()\n                                    val message = if (success) {\n                                        bannerReloadKey++\n                                        folkBannerCleared.format(module.name)\n                                    } else {\n                                        folkBannerFailed.format(module.name)\n                                    }\n                                    showToast(context, message)\n                                }\n                            },\n                            modifier = Modifier.fillMaxWidth()\n                        ) {\n                            Text(folkBannerClear)\n                        }\n                    }\n                }\n            },\n            confirmButton = {\n                TextButton(onClick = { showFolkBannerDialog = false }) {\n                    Text(stringResource(android.R.string.cancel))\n                }\n            }\n        )\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/KpmAutoLoadConfigScreen.kt",
    "content": "package me.bmax.apatch.ui.screen\n\nimport androidx.activity.compose.rememberLauncherForActivityResult\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.foundation.text.KeyboardOptions\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material3.AlertDialogDefaults\nimport androidx.compose.material3.BasicAlertDialog\nimport androidx.compose.material3.Button\nimport androidx.compose.material3.Card\nimport androidx.compose.material3.CardDefaults\nimport me.bmax.apatch.ui.component.WallpaperAwareDropdownMenu\nimport me.bmax.apatch.ui.component.WallpaperAwareDropdownMenuItem\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.OutlinedTextField\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Surface\nimport me.bmax.apatch.ui.component.ExpressiveSwitch\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.Checkbox\nimport androidx.compose.material3.CircularProgressIndicator\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.filled.Delete\nimport androidx.compose.material.icons.filled.Edit\nimport androidx.compose.material.icons.filled.FolderOpen\nimport androidx.compose.material.icons.filled.KeyboardArrowDown\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontFamily\nimport androidx.compose.ui.text.input.KeyboardType\nimport androidx.compose.ui.unit.dp\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.component.KpmAutoLoadConfig\nimport me.bmax.apatch.ui.component.KpmAutoLoadEntry\nimport me.bmax.apatch.ui.component.KpmAutoLoadManager\nimport me.bmax.apatch.util.ui.showToast\n\n@Destination<RootGraph>\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun KpmAutoLoadConfigScreen(navigator: DestinationsNavigator) {\n    val context = LocalContext.current\n    var isEnabled by remember { mutableStateOf(KpmAutoLoadManager.isEnabled.value) }\n    var jsonString by remember { mutableStateOf(\"\") }\n    var showSaveDialog by remember { mutableStateOf(false) }\n    var isValidJson by remember { mutableStateOf(true) }\n    var isVisualMode by remember { mutableStateOf(true) }\n    var kpmEntriesList by remember { mutableStateOf(KpmAutoLoadManager.entries.value.toList()) }\n    var showFirstTimeDialog by remember { mutableStateOf(KpmAutoLoadManager.isFirstTime(context)) }\n    var dontShowAgain by remember { mutableStateOf(false) }\n    var isLoading by remember { mutableStateOf(true) }\n\n    var editingEntry by remember { mutableStateOf<KpmAutoLoadEntry?>(null) }\n    var showEditDialog by remember { mutableStateOf(false) }\n\n    val scope = rememberCoroutineScope()\n    \n    // 根据路径列表更新JSON字符串\n    fun updateJsonString(entries: List<KpmAutoLoadEntry>, enabled: Boolean, onUpdate: (String) -> Unit) {\n        val config = KpmAutoLoadConfig(enabled, entries)\n        onUpdate(KpmAutoLoadManager.getConfigJson(config))\n    }\n    \n    // 文件选择器\n    val filePickerLauncher = rememberLauncherForActivityResult(\n        contract = ActivityResultContracts.GetContent()\n    ) { uri ->\n        uri?.let {\n            scope.launch {\n                val importedPath = withContext(Dispatchers.IO) {\n                    KpmAutoLoadManager.importKpm(context, it)\n                }\n\n                if (importedPath != null && importedPath.endsWith(\".kpm\", ignoreCase = true) && importedPath !in kpmEntriesList.map { it.path }) {\n                    kpmEntriesList = kpmEntriesList + KpmAutoLoadEntry(path = importedPath)\n                    updateJsonString(kpmEntriesList, isEnabled) { newJson ->\n                        jsonString = newJson\n                    }\n                    showToast(context, context.getString(R.string.kpm_autoload_save_success))\n                } else if (importedPath == null) {\n                    showToast(context, context.getString(R.string.kpm_autoload_file_not_found))\n                }\n            }\n        }\n    }\n    \n    LaunchedEffect(Unit) {\n        withContext(Dispatchers.IO) {\n            val config = KpmAutoLoadManager.loadConfig(context)\n            isEnabled = config.enabled\n            jsonString = KpmAutoLoadManager.getConfigJson()\n            kpmEntriesList = config.entries\n        }\n        isLoading = false\n    }\n\n    Scaffold(\n        topBar = {\n            TopAppBar(\n                title = { Text(stringResource(R.string.kpm_autoload_title)) },\n                navigationIcon = {\n                    IconButton(onClick = { navigator.navigateUp() }) {\n                        Icon(\n                            imageVector = Icons.AutoMirrored.Default.ArrowBack,\n                            contentDescription = stringResource(android.R.string.cancel)\n                        )\n                    }\n                }\n            )\n        }\n    ) { innerPadding ->\n        if (isLoading) {\n            Box(\n                modifier = Modifier\n                    .fillMaxSize()\n                    .padding(innerPadding),\n                contentAlignment = Alignment.Center\n            ) {\n                CircularProgressIndicator(modifier = Modifier.size(48.dp))\n            }\n        } else {\n        Column(\n            modifier = Modifier\n                .fillMaxSize()\n                .padding(innerPadding)\n                .padding(16.dp)\n                .verticalScroll(rememberScrollState()),\n            verticalArrangement = Arrangement.spacedBy(16.dp)\n        ) {\n            // 功能启用开关\n            Card(\n                modifier = Modifier.fillMaxWidth(),\n                colors = CardDefaults.cardColors(\n                    containerColor = MaterialTheme.colorScheme.surfaceContainer\n                )\n            ) {\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(16.dp),\n                    verticalAlignment = Alignment.CenterVertically,\n                    horizontalArrangement = Arrangement.SpaceBetween\n                ) {\n                    Column(modifier = Modifier.weight(1f)) {\n                        Text(\n                            text = stringResource(R.string.kpm_autoload_enabled),\n                            style = MaterialTheme.typography.titleMedium\n                        )\n                        Text(\n                            text = stringResource(R.string.kpm_autoload_enabled_summary),\n                            style = MaterialTheme.typography.bodySmall,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant\n                        )\n                    }\n                    ExpressiveSwitch(\n                        checked = isEnabled,\n                        onCheckedChange = { \n                            isEnabled = it\n                            updateJsonString(kpmEntriesList, it) { newJson ->\n                                jsonString = newJson\n                            }\n                        }\n                    )\n                }\n            }\n\n            // 可视化模式或JSON模式\n            if (isVisualMode) {\n                // 可视化模式\n                Card(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .weight(1f),\n                    colors = CardDefaults.cardColors(\n                        containerColor = MaterialTheme.colorScheme.surfaceContainer\n                    )\n                ) {\n                    Column(\n                        modifier = Modifier\n                            .fillMaxSize()\n                            .padding(16.dp)\n                    ) {\n                        Row(\n                            modifier = Modifier.fillMaxWidth(),\n                            horizontalArrangement = Arrangement.SpaceBetween,\n                            verticalAlignment = Alignment.CenterVertically\n                        ) {\n                            Text(\n                                text = stringResource(R.string.kpm_autoload_kpm_list_title),\n                                style = MaterialTheme.typography.titleMedium\n                            )\n                            Button(\n                                onClick = {\n                                    filePickerLauncher.launch(\"application/octet-stream\")\n                                }\n                            ) {\n                                Icon(\n                                    imageVector = Icons.Default.FolderOpen,\n                                    contentDescription = null,\n                                    modifier = Modifier.padding(end = 8.dp)\n                                )\n                                Text(stringResource(R.string.kpm_autoload_add_kpm))\n                            }\n                        }\n                        \n                        Spacer(modifier = Modifier.height(8.dp))\n                        \n                        if (kpmEntriesList.isEmpty()) {\n                            Box(\n                                modifier = Modifier\n                                    .fillMaxWidth()\n                                    .weight(1f),\n                                contentAlignment = Alignment.Center\n                            ) {\n                                Text(\n                                    text = stringResource(R.string.kpm_autoload_no_kpm_added),\n                                    style = MaterialTheme.typography.bodyMedium,\n                                    color = MaterialTheme.colorScheme.onSurfaceVariant\n                                )\n                            }\n                        } else {\n                            LazyColumn(\n                                modifier = Modifier.weight(1f),\n                                verticalArrangement = Arrangement.spacedBy(8.dp)\n                            ) {\n                                items(kpmEntriesList, key = { it.path }) { entry ->\n                                    Row(\n                                        modifier = Modifier\n                                            .fillMaxWidth()\n                                            .clip(RoundedCornerShape(16.dp))\n                                            .background(MaterialTheme.colorScheme.surfaceContainerHighest)\n                                            .padding(horizontal = 16.dp, vertical = 12.dp),\n                                        verticalAlignment = Alignment.CenterVertically\n                                    ) {\n                                        Column(modifier = Modifier.weight(1f)) {\n                                            Text(\n                                                text = entry.path.substringAfterLast(\"/\"),\n                                                style = MaterialTheme.typography.bodyMedium\n                                            )\n                                            Text(\n                                                text = entry.path,\n                                                style = MaterialTheme.typography.bodySmall,\n                                                color = MaterialTheme.colorScheme.onSurfaceVariant\n                                            )\n                                            if (entry.event != \"service\" || entry.args.isNotEmpty()) {\n                                                Text(\n                                                    text = buildString {\n                                                        if (entry.event != \"service\") append(\"${stringResource(R.string.kpm_autoload_event_label).trimEnd(':')} ${entry.event}\")\n                                                        if (entry.args.isNotEmpty()) {\n                                                            if (entry.event != \"service\") append(\" | \")\n                                                            append(\"${stringResource(R.string.kpm_autoload_args_label).trimEnd(':')} ${entry.args}\")\n                                                        }\n                                                    },\n                                                    style = MaterialTheme.typography.bodySmall,\n                                                    color = MaterialTheme.colorScheme.primary\n                                                )\n                                            }\n                                        }\n                                        Row(\n                                            horizontalArrangement = Arrangement.spacedBy(4.dp)\n                                        ) {\n                                            IconButton(\n                                                onClick = {\n                                                    editingEntry = entry\n                                                    showEditDialog = true\n                                                }\n                                            ) {\n                                                Icon(\n                                                    imageVector = Icons.Default.Edit,\n                                                    contentDescription = stringResource(R.string.kpm_autoload_edit_kpm),\n                                                    tint = MaterialTheme.colorScheme.onSurfaceVariant\n                                                )\n                                            }\n                                            IconButton(\n                                                onClick = {\n                                                    kpmEntriesList = kpmEntriesList.filter { it.path != entry.path }\n                                                    updateJsonString(kpmEntriesList, isEnabled) { newJson ->\n                                                        jsonString = newJson\n                                                    }\n                                                }\n                                            ) {\n                                                Icon(\n                                                    imageVector = Icons.Default.Delete,\n                                                    contentDescription = stringResource(R.string.kpm_autoload_remove_kpm),\n                                                    tint = MaterialTheme.colorScheme.error\n                                                )\n                                            }\n                                        }\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            } else {\n                // JSON配置编辑框\n                Card(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .weight(1f),\n                    colors = CardDefaults.cardColors(\n                        containerColor = MaterialTheme.colorScheme.surfaceContainer\n                    )\n                ) {\n                    Column(\n                        modifier = Modifier\n                            .fillMaxSize()\n                            .padding(16.dp)\n                    ) {\n                        Text(\n                            text = stringResource(R.string.kpm_autoload_json_config),\n                            style = MaterialTheme.typography.titleMedium,\n                            modifier = Modifier.padding(bottom = 8.dp)\n                        )\n                        \n                        OutlinedTextField(\n                            value = jsonString,\n                            onValueChange = { \n                                jsonString = it\n                                isValidJson = KpmAutoLoadManager.parseConfigFromJson(it) != null\n                                if (isValidJson) {\n                                    KpmAutoLoadManager.parseConfigFromJson(it)?.let { config ->\n                                        kpmEntriesList = config.entries\n                                    }\n                                }\n                            },\n                            modifier = Modifier\n                                .fillMaxWidth()\n                                .weight(1f),\n                            label = { Text(stringResource(R.string.kpm_autoload_json_label)) },\n                            placeholder = { Text(stringResource(R.string.kpm_autoload_json_placeholder)) },\n                            isError = !isValidJson,\n                            supportingText = {\n                                if (!isValidJson) {\n                                    Text(stringResource(R.string.kpm_autoload_json_error))\n                                } else {\n                                    Text(stringResource(R.string.kpm_autoload_json_helper))\n                                }\n                            },\n                            keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text),\n                            textStyle = MaterialTheme.typography.bodySmall.copy(fontFamily = FontFamily.Monospace)\n                        )\n                    }\n                }\n            }\n\n            Row(\n                modifier = Modifier.fillMaxWidth(),\n                horizontalArrangement = Arrangement.spacedBy(8.dp)\n            ) {\n                // 可视化模式/JSON模式切换按钮\n                Button(\n                    onClick = {\n                        isVisualMode = !isVisualMode\n                    },\n                    modifier = Modifier.weight(1f)\n                ) {\n                    Text(if (isVisualMode) stringResource(R.string.kpm_autoload_json_mode) else stringResource(R.string.kpm_autoload_visual_mode))\n                }\n                \n                // 保存按钮\n                Button(\n                    onClick = {\n                        showSaveDialog = true\n                    },\n                    modifier = Modifier.weight(1f),\n                    enabled = if (isVisualMode) kpmEntriesList.isNotEmpty() else isValidJson\n                ) {\n                    Text(stringResource(R.string.kpm_autoload_save))\n                }\n            }\n        }\n        }\n    }\n\n    // 保存确认对话框\n    if (showSaveDialog) {\n        BasicAlertDialog(\n            onDismissRequest = { showSaveDialog = false }\n        ) {\n            Surface(\n                modifier = Modifier\n                    .width(320.dp)\n                    .padding(16.dp),\n                shape = RoundedCornerShape(20.dp),\n                tonalElevation = AlertDialogDefaults.TonalElevation,\n                color = AlertDialogDefaults.containerColor,\n            ) {\n                Column(modifier = Modifier.padding(16.dp)) {\n                    Text(\n                        text = stringResource(R.string.kpm_autoload_save_confirm),\n                        style = MaterialTheme.typography.titleMedium,\n                        modifier = Modifier.padding(bottom = 16.dp)\n                    )\n                    \n                    Row(\n                        modifier = Modifier.fillMaxWidth(),\n                        horizontalArrangement = Arrangement.End\n                    ) {\n                        TextButton(onClick = { showSaveDialog = false }) {\n                            Text(stringResource(android.R.string.cancel))\n                        }\n                        Spacer(modifier = Modifier.width(8.dp))\n                        Button(onClick = {\n                            val config = if (isVisualMode) {\n                                KpmAutoLoadConfig(enabled = isEnabled, entries = kpmEntriesList)\n                            } else {\n                                KpmAutoLoadConfig(enabled = isEnabled, entries =\n                                    KpmAutoLoadManager.parseConfigFromJson(jsonString)?.entries ?: emptyList()\n                                )\n                            }\n\n                            showSaveDialog = false\n                            scope.launch {\n                                val success = withContext(Dispatchers.IO) {\n                                    KpmAutoLoadManager.saveConfig(context, config)\n                                }\n                                if (success) {\n                                    showToast(context, context.getString(R.string.kpm_autoload_save_success))\n                                    navigator.navigateUp()\n                                } else {\n                                    showToast(context, context.getString(R.string.kpm_autoload_save_failed))\n                                }\n                            }\n                        }) {\n                            Text(stringResource(android.R.string.ok))\n                        }\n                    }\n                }\n            }\n        }\n    }\n    \n    // 编辑 KPM 对话框\n    if (showEditDialog && editingEntry != null) {\n        KpmEditDialog(\n            entry = editingEntry!!,\n            onDismiss = { showEditDialog = false },\n            onConfirm = { updatedEntry ->\n                kpmEntriesList = kpmEntriesList.map { if (it.path == updatedEntry.path) updatedEntry else it }\n                updateJsonString(kpmEntriesList, isEnabled) { jsonString = it }\n                showEditDialog = false\n            }\n        )\n    }\n\n    // 首次使用提示对话框\n    if (showFirstTimeDialog) {\n        BasicAlertDialog(\n            onDismissRequest = {\n                if (dontShowAgain) {\n                    KpmAutoLoadManager.setFirstTimeShown(context)\n                }\n                showFirstTimeDialog = false\n            },\n            properties = androidx.compose.ui.window.DialogProperties(\n                dismissOnClickOutside = false,\n                dismissOnBackPress = false\n            )\n        ) {\n            Surface(\n                modifier = Modifier\n                    .width(350.dp)\n                    .padding(16.dp),\n                shape = RoundedCornerShape(20.dp),\n                tonalElevation = AlertDialogDefaults.TonalElevation,\n                color = AlertDialogDefaults.containerColor,\n            ) {\n                Column(modifier = Modifier.padding(16.dp)) {\n                    Text(\n                        text = stringResource(R.string.kpm_autoload_first_time_title),\n                        style = MaterialTheme.typography.titleMedium,\n                        modifier = Modifier.padding(bottom = 12.dp)\n                    )\n                    \n                    Text(\n                        text = stringResource(R.string.kpm_autoload_first_time_message),\n                        style = MaterialTheme.typography.bodyMedium,\n                        modifier = Modifier.padding(bottom = 16.dp)\n                    )\n                    \n                    Row(\n                        modifier = Modifier.fillMaxWidth(),\n                        verticalAlignment = Alignment.CenterVertically\n                    ) {\n                        androidx.compose.material3.Checkbox(\n                            checked = dontShowAgain,\n                            onCheckedChange = { dontShowAgain = it }\n                        )\n                        Spacer(modifier = Modifier.width(8.dp))\n                        Text(\n                            text = stringResource(R.string.kpm_autoload_do_not_show_again),\n                            style = MaterialTheme.typography.bodySmall\n                        )\n                    }\n                    \n                    Spacer(modifier = Modifier.height(16.dp))\n                    \n                    Row(\n                        modifier = Modifier.fillMaxWidth(),\n                        horizontalArrangement = Arrangement.End\n                    ) {\n                        Button(onClick = {\n                            if (dontShowAgain) {\n                                KpmAutoLoadManager.setFirstTimeShown(context)\n                            }\n                            showFirstTimeDialog = false\n                        }) {\n                            Text(stringResource(R.string.kpm_autoload_first_time_confirm))\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun KpmEditDialog(\n    entry: KpmAutoLoadEntry,\n    onDismiss: () -> Unit,\n    onConfirm: (KpmAutoLoadEntry) -> Unit\n) {\n    var selectedEvent by remember { mutableStateOf(entry.event) }\n    var argsValue by remember { mutableStateOf(entry.args) }\n    var showEventDropdown by remember { mutableStateOf(false) }\n\n    val eventOptions = listOf(\"service\", \"post-fs-data\")\n    val eventLabels = mapOf(\n        \"service\" to stringResource(R.string.kpm_autoload_event_service),\n        \"post-fs-data\" to stringResource(R.string.kpm_autoload_event_post_fs_data)\n    )\n\n    BasicAlertDialog(onDismissRequest = onDismiss) {\n        Surface(\n            modifier = Modifier.width(320.dp).padding(16.dp),\n            shape = RoundedCornerShape(20.dp),\n            tonalElevation = AlertDialogDefaults.TonalElevation,\n            color = AlertDialogDefaults.containerColor,\n        ) {\n            Column(modifier = Modifier.padding(16.dp)) {\n                Text(\n                    text = \"${stringResource(R.string.kpm_autoload_edit_dialog_title)}: ${entry.path.substringAfterLast(\"/\")}\",\n                    style = MaterialTheme.typography.titleMedium,\n                    modifier = Modifier.padding(bottom = 16.dp)\n                )\n\n                Text(\n                    text = stringResource(R.string.kpm_autoload_event_label),\n                    style = MaterialTheme.typography.bodySmall,\n                    color = MaterialTheme.colorScheme.onSurfaceVariant,\n                    modifier = Modifier.padding(bottom = 4.dp)\n                )\n\n                Box(modifier = Modifier.fillMaxWidth()) {\n                    OutlinedTextField(\n                        value = eventLabels[selectedEvent] ?: selectedEvent,\n                        onValueChange = {},\n                        readOnly = true,\n                        modifier = Modifier.fillMaxWidth(),\n                        trailingIcon = {\n                            IconButton(onClick = { showEventDropdown = true }) {\n                                Icon(\n                                    imageVector = Icons.Filled.KeyboardArrowDown,\n                                    contentDescription = stringResource(R.string.kpm_autoload_edit_kpm)\n                                )\n                            }\n                        },\n                        singleLine = true\n                    )\n                    WallpaperAwareDropdownMenu(\n                        expanded = showEventDropdown,\n                        onDismissRequest = { showEventDropdown = false }\n                    ) {\n                        eventOptions.forEach { option ->\n                            WallpaperAwareDropdownMenuItem(\n                                text = { Text(eventLabels[option] ?: option) },\n                                onClick = {\n                                    selectedEvent = option\n                                    showEventDropdown = false\n                                }\n                            )\n                        }\n                    }\n                }\n\n                Spacer(modifier = Modifier.height(12.dp))\n\n                Text(\n                    text = stringResource(R.string.kpm_autoload_args_label),\n                    style = MaterialTheme.typography.bodySmall,\n                    color = MaterialTheme.colorScheme.onSurfaceVariant,\n                    modifier = Modifier.padding(bottom = 4.dp)\n                )\n                OutlinedTextField(\n                    value = argsValue,\n                    onValueChange = { argsValue = it },\n                    modifier = Modifier.fillMaxWidth(),\n                    singleLine = true,\n                    shape = RoundedCornerShape(12.dp)\n                )\n\n                Spacer(modifier = Modifier.height(20.dp))\n\n                Row(\n                    modifier = Modifier.fillMaxWidth(),\n                    horizontalArrangement = Arrangement.End\n                ) {\n                    TextButton(onClick = onDismiss) {\n                        Text(stringResource(android.R.string.cancel))\n                    }\n                    Spacer(modifier = Modifier.width(8.dp))\n                    Button(onClick = {\n                        onConfirm(entry.copy(event = selectedEvent, args = argsValue))\n                    }) {\n                        Text(stringResource(android.R.string.ok))\n                    }\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/MyThemesScreen.kt",
    "content": "package me.bmax.apatch.ui.screen\n\nimport android.net.Uri\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.combinedClickable\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid\nimport androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells\nimport androidx.compose.foundation.lazy.staggeredgrid.items\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.filled.Delete\nimport androidx.compose.material.icons.filled.Info\nimport androidx.compose.material.icons.filled.Refresh\nimport androidx.compose.material.icons.filled.Search\nimport androidx.compose.material.icons.filled.Close\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.window.DialogProperties\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.*\nimport androidx.compose.runtime.*\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.layout.ContentScale\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.unit.dp\nimport androidx.lifecycle.viewmodel.compose.viewModel\nimport coil.compose.AsyncImage\nimport coil.request.ImageRequest\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport kotlinx.coroutines.launch\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.viewmodel.ThemeStoreViewModel\nimport java.io.File\n\n@Destination<RootGraph>\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun MyThemesScreen(\n    navigator: DestinationsNavigator\n) {\n    val viewModel = viewModel<ThemeStoreViewModel>(\n        factory = ThemeStoreViewModel.Factory(LocalContext.current)\n    )\n    val context = LocalContext.current\n    val scope = rememberCoroutineScope()\n    val snackbarHostState = remember { SnackbarHostState() }\n    \n    var selectedTheme by remember { mutableStateOf<ThemeStoreViewModel.LocalTheme?>(null) }\n    var showDeleteDialog by remember { mutableStateOf<ThemeStoreViewModel.LocalTheme?>(null) }\n    var refreshing by remember { mutableStateOf(false) }\n    var isSearchActive by remember { mutableStateOf(false) }\n\n    // 刷新本地主题列表\n    LaunchedEffect(Unit) {\n        if (viewModel.localThemes.isEmpty()) {\n            viewModel.loadLocalThemes()\n        }\n    }\n\n    // 主题详情对话框\n    if (selectedTheme != null) {\n        val theme = selectedTheme!!\n        val typeString = if (theme.type == \"tablet\") stringResource(R.string.theme_type_tablet) else stringResource(R.string.theme_type_phone)\n        val sourceString = if (theme.source == \"official\") stringResource(R.string.theme_source_official) else stringResource(R.string.theme_source_third_party)\n\n        AlertDialog(\n            onDismissRequest = { selectedTheme = null },\n            title = { Text(text = theme.name) },\n            text = {\n                Column {\n                    Text(\n                        text = stringResource(R.string.theme_store_author, theme.author),\n                        style = MaterialTheme.typography.bodyMedium\n                    )\n                    Text(\n                        text = stringResource(R.string.theme_store_version, theme.version),\n                        style = MaterialTheme.typography.bodyMedium\n                    )\n                    Text(\n                        text = \"${stringResource(R.string.theme_type)}: $typeString\",\n                        style = MaterialTheme.typography.bodyMedium\n                    )\n                    Text(\n                        text = \"${stringResource(R.string.theme_source)}: $sourceString\",\n                        style = MaterialTheme.typography.bodyMedium\n                    )\n                    Spacer(modifier = Modifier.height(8.dp))\n                    Text(\n                        text = theme.description.ifEmpty { \"No description\" },\n                        style = MaterialTheme.typography.bodySmall,\n                        color = MaterialTheme.colorScheme.onSurfaceVariant\n                    )\n                }\n            },\n            confirmButton = {\n                Button(\n                    onClick = {\n                        scope.launch {\n                            val success = viewModel.applyTheme(theme)\n                            if (success) {\n                                snackbarHostState.showSnackbar(context.getString(R.string.my_themes_applied))\n                            } else {\n                                snackbarHostState.showSnackbar(context.getString(R.string.my_themes_apply_failed))\n                            }\n                        }\n                        selectedTheme = null\n                    }\n                ) {\n                    Text(stringResource(R.string.my_themes_apply))\n                }\n            },\n            dismissButton = {\n                TextButton(onClick = { selectedTheme = null }) {\n                    Text(stringResource(android.R.string.cancel))\n                }\n            }\n        )\n    }\n\n    // 删除确认对话框\n    if (showDeleteDialog != null) {\n        val theme = showDeleteDialog!!\n        AlertDialog(\n            onDismissRequest = { showDeleteDialog = null },\n            title = { Text(stringResource(R.string.my_themes_delete)) },\n            text = {\n                Column {\n                    Text(text = theme.name)\n                    Spacer(modifier = Modifier.height(8.dp))\n                    Text(\n                        text = stringResource(R.string.my_themes_delete_confirm),\n                        style = MaterialTheme.typography.bodyMedium\n                    )\n                }\n            },\n            confirmButton = {\n                Button(\n                    onClick = {\n                        scope.launch {\n                            val success = viewModel.deleteTheme(theme)\n                            if (success) {\n                                snackbarHostState.showSnackbar(context.getString(R.string.my_themes_deleted))\n                            } else {\n                                snackbarHostState.showSnackbar(\"Failed to delete theme\")\n                            }\n                        }\n                        showDeleteDialog = null\n                    }\n                ) {\n                    Text(stringResource(android.R.string.ok))\n                }\n            },\n            dismissButton = {\n                TextButton(onClick = { showDeleteDialog = null }) {\n                    Text(stringResource(android.R.string.cancel))\n                }\n            }\n        )\n    }\n\n    Scaffold(\n        topBar = {\n            TopAppBar(\n                title = {\n                    if (isSearchActive) {\n                        TextField(\n                            value = viewModel.localSearchQuery,\n                            onValueChange = { viewModel.onLocalSearchQueryChange(it) },\n                            placeholder = { Text(stringResource(R.string.theme_store_search_hint)) },\n                            singleLine = true,\n                            colors = TextFieldDefaults.colors(\n                                focusedContainerColor = Color.Transparent,\n                                unfocusedContainerColor = Color.Transparent,\n                                disabledContainerColor = Color.Transparent,\n                                focusedIndicatorColor = Color.Transparent,\n                                unfocusedIndicatorColor = Color.Transparent,\n                            ),\n                            modifier = Modifier.fillMaxWidth()\n                        )\n                    } else {\n                        Text(stringResource(R.string.my_themes_title))\n                    }\n                },\n                navigationIcon = {\n                    IconButton(onClick = {\n                        if (isSearchActive) {\n                            isSearchActive = false\n                            viewModel.onLocalSearchQueryChange(\"\")\n                        } else {\n                            navigator.popBackStack()\n                        }\n                    }) {\n                        Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = \"Back\")\n                    }\n                },\n                actions = {\n                    if (isSearchActive) {\n                        if (viewModel.localSearchQuery.isNotEmpty()) {\n                            IconButton(onClick = { viewModel.onLocalSearchQueryChange(\"\") }) {\n                                Icon(Icons.Filled.Close, contentDescription = \"Clear\")\n                            }\n                        }\n                    } else {\n                        IconButton(onClick = { isSearchActive = true }) {\n                            Icon(Icons.Filled.Search, contentDescription = \"Search\")\n                        }\n                        IconButton(onClick = {\n                            refreshing = true\n                            viewModel.loadLocalThemes()\n                            refreshing = false\n                        }) {\n                            Icon(Icons.Filled.Refresh, contentDescription = \"Refresh\")\n                        }\n                    }\n                }\n            )\n        },\n        snackbarHost = { SnackbarHost(snackbarHostState) }\n    ) { paddingValues ->\n        if (viewModel.localThemes.isEmpty()) {\n            // 空状态\n            Column(\n                modifier = Modifier.fillMaxSize().padding(paddingValues),\n                verticalArrangement = Arrangement.Center,\n                horizontalAlignment = Alignment.CenterHorizontally\n            ) {\n                Text(\n                    text = stringResource(R.string.my_themes_empty),\n                    style = MaterialTheme.typography.titleMedium,\n                    color = MaterialTheme.colorScheme.onSurfaceVariant\n                )\n                Spacer(modifier = Modifier.height(16.dp))\n                Button(\n                    onClick = { navigator.popBackStack() }\n                ) {\n                    Text(stringResource(R.string.my_themes_empty_action))\n                }\n            }\n        } else {\n            // 主题瀑布流（和主题商店一致）\n            LazyVerticalStaggeredGrid(\n                columns = StaggeredGridCells.Adaptive(minSize = 128.dp),\n                modifier = Modifier.fillMaxSize().padding(paddingValues),\n                contentPadding = PaddingValues(16.dp),\n                verticalItemSpacing = 16.dp,\n                horizontalArrangement = Arrangement.spacedBy(16.dp)\n            ) {\n                items(\n                    items = viewModel.localThemes,\n                    key = { it.id }\n                ) { theme ->\n                    MyThemeGridItem(\n                        theme = theme,\n                        onClick = { selectedTheme = theme },\n                        onLongClick = { showDeleteDialog = theme }\n                    )\n                }\n            }\n        }\n    }\n}\n\n@Composable\nfun MyThemeGridItem(\n    theme: ThemeStoreViewModel.LocalTheme,\n    onClick: () -> Unit,\n    onLongClick: () -> Unit\n) {\n    val context = LocalContext.current\n    \n    Card(\n        modifier = Modifier\n            .fillMaxWidth()\n            .combinedClickable(\n                onClick = onClick,\n                onLongClick = onLongClick\n            ),\n        elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)\n    ) {\n        Box {\n            // 图片加载：本地优先，回退网络\n            val previewImageFile = File(theme.previewImagePath)\n            val imageModel = if (previewImageFile.exists()) {\n                // 使用本地文件\n                Uri.fromFile(previewImageFile)\n            } else if (theme.previewUrl.isNotEmpty()) {\n                // 回退到网络 URL\n                theme.previewUrl\n            } else {\n                null\n            }\n            \n            // 保持图片原始比例（和主题商店一致）\n            AsyncImage(\n                model = ImageRequest.Builder(context)\n                    .data(imageModel)\n                    .crossfade(true)\n                    .build(),\n                contentDescription = theme.name,\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .wrapContentHeight(),\n                contentScale = ContentScale.FillWidth\n            )\n            \n            // 底部信息（渐变背景）\n            Box(\n                modifier = Modifier\n                    .align(Alignment.BottomCenter)\n                    .fillMaxWidth()\n                    .padding(8.dp)\n            ) {\n                Text(\n                    text = theme.name,\n                    style = MaterialTheme.typography.bodyMedium,\n                    color = Color.White,\n                    modifier = Modifier.padding(4.dp),\n                    textAlign = TextAlign.Center\n                )\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/OnlineKPMScreen.kt",
    "content": "package me.bmax.apatch.ui.screen\n\nimport android.content.Context\nimport me.bmax.apatch.util.ui.showToast\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid\nimport androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells\nimport androidx.compose.foundation.lazy.staggeredgrid.items\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.filled.Close\nimport androidx.compose.material.icons.filled.Download\nimport androidx.compose.material.icons.filled.Search\nimport androidx.compose.material3.*\nimport androidx.compose.runtime.*\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.platform.LocalConfiguration\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport androidx.lifecycle.viewmodel.compose.viewModel\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.component.AppLoadingIndicator\nimport me.bmax.apatch.ui.viewmodel.OnlineKPMViewModel\nimport me.bmax.apatch.util.download\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Destination<RootGraph>\n@Composable\nfun OnlineKPMScreen(navigator: DestinationsNavigator) {\n    val viewModel = viewModel<OnlineKPMViewModel>()\n    val context = LocalContext.current\n    var isSearchActive by remember { mutableStateOf(false) }\n\n    LaunchedEffect(Unit) {\n        if (viewModel.modules.isEmpty()) {\n            viewModel.fetchModules()\n        }\n    }\n\n    Scaffold(\n        topBar = {\n            TopAppBar(\n                title = {\n                    if (isSearchActive) {\n                        TextField(\n                            value = viewModel.searchQuery,\n                            onValueChange = { viewModel.onSearchQueryChange(it) },\n                            placeholder = { Text(stringResource(R.string.search_modules)) },\n                            singleLine = true,\n                            colors = TextFieldDefaults.colors(\n                                focusedContainerColor =  Color.Transparent,\n                                unfocusedContainerColor = Color.Transparent,\n                                disabledContainerColor = Color.Transparent,\n                                focusedIndicatorColor = Color.Transparent,\n                                unfocusedIndicatorColor = Color.Transparent,\n                            ),\n                            modifier = Modifier.fillMaxWidth()\n                        )\n                    } else {\n                        Text(stringResource(R.string.online_kpm_title))\n                    }\n                },\n                navigationIcon = {\n                    IconButton(onClick = {\n                        if (isSearchActive) {\n                            isSearchActive = false\n                            viewModel.onSearchQueryChange(\"\")\n                        } else {\n                            navigator.popBackStack()\n                        }\n                    }) {\n                        Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = \"Back\")\n                    }\n                },\n                actions = {\n                    if (isSearchActive) {\n                        if (viewModel.searchQuery.isNotEmpty()) {\n                            IconButton(onClick = { viewModel.onSearchQueryChange(\"\") }) {\n                                Icon(Icons.Filled.Close, contentDescription = \"Clear\")\n                            }\n                        }\n                    } else {\n                        IconButton(onClick = { isSearchActive = true }) {\n                            Icon(Icons.Filled.Search, contentDescription = \"Search\")\n                        }\n                    }\n                }\n            )\n        }\n    ) { innerPadding ->\n        Box(modifier = Modifier\n            .fillMaxSize()\n            .padding(innerPadding)) {\n            if (viewModel.isRefreshing) {\n                AppLoadingIndicator(\n                    text = stringResource(R.string.loading_modules),\n                    modifier = Modifier.align(Alignment.Center)\n                )\n            } else if (viewModel.errorMessage != null) {\n                Column(\n                    modifier = Modifier.fillMaxSize(),\n                    verticalArrangement = Arrangement.Center,\n                    horizontalAlignment = Alignment.CenterHorizontally\n                ) {\n                    Text(\n                        text = viewModel.errorMessage ?: \"Unknown error\",\n                        color = MaterialTheme.colorScheme.error,\n                        modifier = Modifier.padding(16.dp)\n                    )\n                    Button(onClick = { viewModel.fetchModules() }) {\n                        Text(stringResource(R.string.retry))\n                    }\n                }\n            } else {\n                val configuration = LocalConfiguration.current\n                val isWideScreen = configuration.screenWidthDp >= 600\n\n                if (isWideScreen) {\n                    LazyVerticalStaggeredGrid(\n                        modifier = Modifier.fillMaxSize(),\n                        columns = StaggeredGridCells.Fixed(2),\n                        verticalItemSpacing = 8.dp,\n                        horizontalArrangement = Arrangement.spacedBy(8.dp),\n                        contentPadding = PaddingValues(16.dp)\n                    ) {\n                        items(viewModel.modules, key = { module -> module.name }) { module ->\n                            OnlineKPMItem(module, context)\n                        }\n                    }\n                } else {\n                    LazyColumn(\n                        modifier = Modifier.fillMaxSize(),\n                        contentPadding = PaddingValues(16.dp),\n                        verticalArrangement = Arrangement.spacedBy(8.dp)\n                    ) {\n                        items(viewModel.modules) { module ->\n                            OnlineKPMItem(module, context)\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\n@Composable\nfun OnlineKPMItem(module: OnlineKPMViewModel.OnlineKPM, context: Context) {\n    val downloadStartText = stringResource(R.string.online_kpm_download_start, module.name)\n    val downloadNotificationText = stringResource(R.string.online_kpm_download_notification, module.name)\n\n    Surface(\n        modifier = Modifier.fillMaxWidth().fillMaxHeight(),\n        color = MaterialTheme.colorScheme.surface,\n        tonalElevation = 1.dp,\n        shape = RoundedCornerShape(20.dp)\n    ) {\n        Row(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(16.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.SpaceBetween\n        ) {\n            Column(\n                modifier = Modifier.weight(1f)\n            ) {\n                Text(\n                    text = module.name,\n                    style = MaterialTheme.typography.titleMedium,\n                    fontWeight = FontWeight.Bold\n                )\n                Row(verticalAlignment = Alignment.CenterVertically) {\n                    Text(\n                        text = \"Version: ${module.version}\",\n                        style = MaterialTheme.typography.bodyMedium\n                    )\n                    Spacer(modifier = Modifier.width(12.dp))\n                    Text(\n                        text = \"Args: ${if (module.needControl) \"Control\" else \"NoControl\"}\",\n                        style = MaterialTheme.typography.bodyMedium,\n                        color = MaterialTheme.colorScheme.onSurfaceVariant\n                    )\n                }\n                Spacer(modifier = Modifier.height(4.dp))\n                Text(\n                    text = module.description,\n                    style = MaterialTheme.typography.bodySmall\n                )\n            }\n\n            IconButton(\n                onClick = {\n                    // Show toast message with download info\n                    showToast(context, downloadNotificationText)\n\n                    // Start the download\n                    download(\n                        context = context,\n                        url = module.url,\n                        fileName = \"${module.name}-${module.version}.kpm\",\n                        description = downloadStartText,\n                        onDownloading = {},\n                        onDownloaded = {}\n                    )\n                }\n            ) {\n                Icon(\n                    imageVector = Icons.Filled.Download,\n                    contentDescription = \"Download\"\n                )\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/OnlineModuleScreen.kt",
    "content": "package me.bmax.apatch.ui.screen\n\nimport android.content.Context\nimport me.bmax.apatch.util.ui.showToast\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid\nimport androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells\nimport androidx.compose.foundation.lazy.staggeredgrid.items\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.filled.Close\nimport androidx.compose.material.icons.filled.Download\nimport androidx.compose.material.icons.filled.Search\nimport androidx.compose.material3.*\nimport androidx.compose.runtime.*\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.platform.LocalConfiguration\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport androidx.lifecycle.viewmodel.compose.viewModel\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport com.ramcosta.composedestinations.generated.destinations.InstallScreenDestination\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.component.AppLoadingIndicator\nimport me.bmax.apatch.ui.viewmodel.OnlineModuleViewModel\nimport me.bmax.apatch.util.download\nimport me.bmax.apatch.util.DownloadListener\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Destination<RootGraph>\n@Composable\nfun OnlineModuleScreen(navigator: DestinationsNavigator) {\n    val viewModel = viewModel<OnlineModuleViewModel>()\n    val context = LocalContext.current\n    var isSearchActive by remember { mutableStateOf(false) }\n\n    LaunchedEffect(Unit) {\n        if (viewModel.modules.isEmpty()) {\n            viewModel.fetchModules()\n        }\n    }\n\n    Scaffold(\n        topBar = {\n            TopAppBar(\n                title = {\n                    if (isSearchActive) {\n                        TextField(\n                            value = viewModel.searchQuery,\n                            onValueChange = { viewModel.onSearchQueryChange(it) },\n                            placeholder = { Text(stringResource(R.string.search_modules)) },\n                            singleLine = true,\n                            colors = TextFieldDefaults.colors(\n                                focusedContainerColor =  Color.Transparent,\n                                unfocusedContainerColor = Color.Transparent,\n                                disabledContainerColor = Color.Transparent,\n                                focusedIndicatorColor = Color.Transparent,\n                                unfocusedIndicatorColor = Color.Transparent,\n                            ),\n                            modifier = Modifier.fillMaxWidth()\n                        )\n                    } else {\n                        Text(stringResource(R.string.online_module_title))\n                    }\n                },\n                navigationIcon = {\n                    IconButton(onClick = {\n                        if (isSearchActive) {\n                            isSearchActive = false\n                            viewModel.onSearchQueryChange(\"\")\n                        } else {\n                            navigator.popBackStack()\n                        }\n                    }) {\n                        Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = \"Back\")\n                    }\n                },\n                actions = {\n                    if (isSearchActive) {\n                        if (viewModel.searchQuery.isNotEmpty()) {\n                            IconButton(onClick = { viewModel.onSearchQueryChange(\"\") }) {\n                                Icon(Icons.Filled.Close, contentDescription = \"Clear\")\n                            }\n                        }\n                    } else {\n                        IconButton(onClick = { isSearchActive = true }) {\n                            Icon(Icons.Filled.Search, contentDescription = \"Search\")\n                        }\n                    }\n                }\n            )\n        }\n    ) { innerPadding ->\n        Box(modifier = Modifier\n            .fillMaxSize()\n            .padding(innerPadding)) {\n            if (viewModel.isRefreshing) {\n                AppLoadingIndicator(\n                    text = stringResource(R.string.loading_modules),\n                    modifier = Modifier.align(Alignment.Center)\n                )\n            } else if (viewModel.errorMessage != null) {\n                Column(\n                    modifier = Modifier.fillMaxSize(),\n                    verticalArrangement = Arrangement.Center,\n                    horizontalAlignment = Alignment.CenterHorizontally\n                ) {\n                    Text(\n                        text = viewModel.errorMessage ?: \"Unknown error\",\n                        color = MaterialTheme.colorScheme.error,\n                        modifier = Modifier.padding(16.dp)\n                    )\n                    Button(onClick = { viewModel.fetchModules() }) {\n                        Text(stringResource(R.string.retry))\n                    }\n                }\n            } else {\n                val configuration = LocalConfiguration.current\n                val isWideScreen = configuration.screenWidthDp >= 600\n\n                if (isWideScreen) {\n                    LazyVerticalStaggeredGrid(\n                        modifier = Modifier.fillMaxSize(),\n                        columns = StaggeredGridCells.Fixed(2),\n                        verticalItemSpacing = 8.dp,\n                        horizontalArrangement = Arrangement.spacedBy(8.dp),\n                        contentPadding = PaddingValues(16.dp)\n                    ) {\n                        items(viewModel.modules, key = { module -> module.name }) { module ->\n                            OnlineModuleItem(module, context)\n                        }\n                    }\n                } else {\n                    LazyColumn(\n                        modifier = Modifier.fillMaxSize(),\n                        contentPadding = PaddingValues(16.dp),\n                        verticalArrangement = Arrangement.spacedBy(8.dp)\n                    ) {\n                        items(viewModel.modules) { module ->\n                            OnlineModuleItem(module, context)\n                        }\n                    }\n                }\n            }\n        }\n        \n        DownloadListener(context) { uri ->\n            navigator.navigate(InstallScreenDestination(uri, MODULE_TYPE.APM))\n        }\n    }\n}\n\n@Composable\nfun OnlineModuleItem(module: OnlineModuleViewModel.OnlineModule, context: Context) {\n    val downloadStartText = stringResource(R.string.online_module_download_start, module.name)\n    val downloadNotificationText = stringResource(R.string.online_module_download_notification, module.name)\n\n    Surface(\n        modifier = Modifier.fillMaxWidth().fillMaxHeight(),\n        color = MaterialTheme.colorScheme.surface,\n        tonalElevation = 1.dp,\n        shape = RoundedCornerShape(20.dp)\n    ) {\n        Row(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(16.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.SpaceBetween\n        ) {\n            Column(\n                modifier = Modifier.weight(1f)\n            ) {\n                Text(\n                    text = module.name, \n                    style = MaterialTheme.typography.titleMedium, \n                    fontWeight = FontWeight.Bold\n                )\n                Text(\n                    text = \"Version: ${module.version}\", \n                    style = MaterialTheme.typography.bodyMedium\n                )\n                Spacer(modifier = Modifier.height(4.dp))\n                Text(\n                    text = module.description, \n                    style = MaterialTheme.typography.bodySmall\n                )\n            }\n            \n            IconButton(\n                onClick = {\n                    // Show toast message with download info\n                    showToast(context, downloadNotificationText)\n                    \n                    // Start the download\n                    download(\n                        context = context,\n                        url = module.url,\n                        fileName = \"${module.name}-${module.version}.zip\",\n                        description = downloadStartText,\n                        onDownloading = {},\n                        onDownloaded = {}\n                    )\n                }\n            ) {\n                Icon(\n                    imageVector = Icons.Filled.Download,\n                    contentDescription = \"Download\"\n                )\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/OnlineScriptScreen.kt",
    "content": "package me.bmax.apatch.ui.screen\n\nimport android.content.ContentResolver\nimport android.content.Context\nimport android.database.Cursor\nimport android.net.Uri\nimport android.os.Environment\nimport android.provider.OpenableColumns\nimport me.bmax.apatch.util.ui.showToast\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.filled.Close\nimport androidx.compose.material.icons.filled.Download\nimport androidx.compose.material.icons.filled.Search\nimport androidx.compose.material3.*\nimport androidx.compose.runtime.*\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport androidx.lifecycle.viewmodel.compose.viewModel\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.component.AppLoadingIndicator\nimport me.bmax.apatch.ui.viewmodel.OnlineScriptViewModel\nimport me.bmax.apatch.util.SafeUriResolver\nimport me.bmax.apatch.util.download\nimport java.io.File\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Destination<RootGraph>\n@Composable\nfun OnlineScriptScreen(navigator: DestinationsNavigator) {\n    val viewModel = viewModel<OnlineScriptViewModel>()\n    val context = LocalContext.current\n    var isSearchActive by remember { mutableStateOf(false) }\n    var downloadedFile by remember { mutableStateOf<File?>(null) }\n    val scope = rememberCoroutineScope()\n\n    LaunchedEffect(Unit) {\n        if (viewModel.modules.isEmpty()) {\n            viewModel.fetchModules()\n        }\n    }\n\n    Scaffold(\n        topBar = {\n            TopAppBar(\n                title = {\n                    if (isSearchActive) {\n                        TextField(\n                            value = viewModel.searchQuery,\n                            onValueChange = { viewModel.onSearchQueryChange(it) },\n                            placeholder = { Text(stringResource(R.string.search_scripts)) },\n                            singleLine = true,\n                            colors = TextFieldDefaults.colors(\n                                focusedContainerColor =  Color.Transparent,\n                                unfocusedContainerColor = Color.Transparent,\n                                disabledContainerColor = Color.Transparent,\n                                focusedIndicatorColor = Color.Transparent,\n                                unfocusedIndicatorColor = Color.Transparent,\n                            ),\n                            modifier = Modifier.fillMaxWidth()\n                        )\n                    } else {\n                        Text(stringResource(R.string.online_script_title))\n                    }\n                },\n                navigationIcon = {\n                    IconButton(onClick = {\n                        if (isSearchActive) {\n                            isSearchActive = false\n                            viewModel.onSearchQueryChange(\"\")\n                        } else {\n                            navigator.popBackStack()\n                        }\n                    }) {\n                        Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = \"Back\")\n                    }\n                },\n                actions = {\n                    if (isSearchActive) {\n                        if (viewModel.searchQuery.isNotEmpty()) {\n                            IconButton(onClick = { viewModel.onSearchQueryChange(\"\") }) {\n                                Icon(Icons.Filled.Close, contentDescription = \"Clear\")\n                            }\n                        }\n                    } else {\n                        IconButton(onClick = { isSearchActive = true }) {\n                            Icon(Icons.Filled.Search, contentDescription = \"Search\")\n                        }\n                    }\n                }\n            )\n        }\n    ) { innerPadding ->\n        Box(modifier = Modifier\n            .fillMaxSize()\n            .padding(innerPadding)) {\n            if (viewModel.isRefreshing) {\n                AppLoadingIndicator(\n                    text = stringResource(R.string.loading_scripts),\n                    modifier = Modifier.align(Alignment.Center)\n                )\n            } else if (viewModel.errorMessage != null) {\n                Column(\n                    modifier = Modifier.fillMaxSize(),\n                    verticalArrangement = Arrangement.Center,\n                    horizontalAlignment = Alignment.CenterHorizontally\n                ) {\n                    Text(\n                        text = viewModel.errorMessage ?: \"Unknown error\",\n                        color = MaterialTheme.colorScheme.error,\n                        modifier = Modifier.padding(16.dp)\n                    )\n                    Button(onClick = { viewModel.fetchModules() }) {\n                        Text(stringResource(R.string.retry))\n                    }\n                }\n            } else {\n                LazyColumn(\n                    modifier = Modifier.fillMaxSize(),\n                    contentPadding = PaddingValues(16.dp),\n                    verticalArrangement = Arrangement.spacedBy(8.dp)\n                ) {\n                    items(viewModel.modules) { script ->\n                        OnlineScriptItem(\n                            script = script,\n                            context = context\n                        )\n                    }\n                }\n            }\n        }\n    }\n}\n\n@Composable\nfun OnlineScriptItem(\n    script: OnlineScriptViewModel.OnlineScript,\n    context: Context\n) {\n    val downloadStartText = stringResource(R.string.online_script_download_start, script.name)\n    val downloadNotificationText = stringResource(R.string.online_script_download_notification, script.name)\n    var fileName by remember { mutableStateOf(\"\") }\n    var isDownloading by remember { mutableStateOf(false) }\n    var isDownloaded by remember { mutableStateOf(false) }\n    val scope = rememberCoroutineScope()\n\n    val fileNameWithoutExt = \"${script.name}-${script.version}\"\n    val scriptFileName = \"$fileNameWithoutExt.sh\"\n\n    fun handleDownloadComplete(uri: Uri) {\n        scope.launch {\n            try {\n                val targetFile = withContext(Dispatchers.IO) {\n                    val scriptDir = File(\"/storage/emulated/0/Download/FolkPatch/script\")\n                    if (!scriptDir.exists()) {\n                        scriptDir.mkdirs()\n                    }\n\n                    val outputFile = File(scriptDir, scriptFileName)\n                    when (uri.scheme?.lowercase()) {\n                        \"content\" -> {\n                            SafeUriResolver.openInputStream(context, uri)?.use { input ->\n                                outputFile.outputStream().use { output ->\n                                    input.copyTo(output)\n                                }\n                            } ?: throw IllegalStateException(\"无法读取下载文件\")\n                        }\n                        \"file\" -> {\n                            val downloadFile = File(uri.path ?: \"\")\n                            if (!downloadFile.exists()) {\n                                throw IllegalStateException(\"Downloaded file not found\")\n                            }\n                            downloadFile.copyTo(outputFile, overwrite = true)\n                        }\n                        else -> {\n                            val downloadPath = uri.path?.replace(\"file://\", \"\") ?: \"\"\n                            val downloadFile = File(downloadPath)\n                            if (!downloadFile.exists()) {\n                                throw IllegalStateException(\"Downloaded file not found\")\n                            }\n                            downloadFile.copyTo(outputFile, overwrite = true)\n                        }\n                    }\n                    outputFile.setExecutable(true)\n                    outputFile\n                }\n\n                withContext(Dispatchers.Main) {\n                    showToast(context, \"Downloaded to: ${targetFile.absolutePath}\")\n                }\n            } catch (e: Exception) {\n                withContext(Dispatchers.Main) {\n                    showToast(context, \"Download failed: ${e.message}\")\n                }\n            }\n        }\n    }\n\n    Surface(\n        modifier = Modifier.fillMaxWidth(),\n        color = MaterialTheme.colorScheme.surface,\n        tonalElevation = 1.dp,\n        shape = RoundedCornerShape(20.dp)\n    ) {\n        Row(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(16.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.SpaceBetween\n        ) {\n            Column(\n                modifier = Modifier.weight(1f)\n            ) {\n                Text(\n                    text = script.name,\n                    style = MaterialTheme.typography.titleMedium,\n                    fontWeight = FontWeight.Bold\n                )\n                Text(\n                    text = \"Version: ${script.version}\",\n                    style = MaterialTheme.typography.bodyMedium\n                )\n                Spacer(modifier = Modifier.height(4.dp))\n                Text(\n                    text = script.description,\n                    style = MaterialTheme.typography.bodySmall\n                )\n            }\n\n            IconButton(\n                onClick = {\n                    showToast(context, downloadNotificationText)\n                    \n                    download(\n                        context = context,\n                        url = script.url,\n                        fileName = scriptFileName,\n                        description = downloadStartText,\n                        onDownloaded = { uri ->\n                            handleDownloadComplete(uri)\n                        }\n                    )\n                }\n            ) {\n                Icon(\n                    imageVector = Icons.Filled.Download,\n                    contentDescription = \"Download\"\n                )\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/Patches.kt",
    "content": "package me.bmax.apatch.ui.screen\n\nimport android.Manifest\nimport android.app.Activity\nimport android.content.Intent\nimport android.content.pm.PackageManager\nimport android.net.Uri\nimport android.util.Log\nimport androidx.activity.compose.rememberLauncherForActivityResult\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.animation.expandVertically\nimport androidx.compose.animation.fadeIn\nimport androidx.compose.animation.fadeOut\nimport androidx.compose.animation.shrinkVertically\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.layout.wrapContentHeight\nimport androidx.compose.foundation.layout.wrapContentWidth\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.foundation.text.BasicTextField\nimport androidx.compose.foundation.text.KeyboardOptions\nimport androidx.compose.foundation.text.selection.SelectionContainer\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.Delete\nimport androidx.compose.material.icons.filled.Key\nimport androidx.compose.material.icons.filled.Refresh\nimport androidx.compose.material.icons.filled.Settings\nimport androidx.compose.material.icons.filled.Visibility\nimport androidx.compose.material.icons.filled.VisibilityOff\nimport androidx.compose.material.icons.outlined.Extension\nimport androidx.compose.material3.AlertDialogDefaults\nimport androidx.compose.material3.BasicAlertDialog\nimport androidx.compose.material3.Button\nimport androidx.compose.material3.ButtonDefaults\nimport androidx.compose.material3.CircularProgressIndicator\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.ExtendedFloatingActionButton\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.OutlinedTextField\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Surface\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.SideEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalView\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.input.KeyboardType\nimport androidx.compose.ui.text.input.PasswordVisualTransformation\nimport androidx.compose.ui.text.input.VisualTransformation\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.window.DialogProperties\nimport androidx.compose.ui.window.DialogWindowProvider\nimport androidx.core.app.ActivityCompat\nimport androidx.core.content.ContextCompat\nimport androidx.lifecycle.viewmodel.compose.viewModel\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.R\nimport me.bmax.apatch.APApplication\nimport androidx.compose.material3.HorizontalDivider\nimport me.bmax.apatch.ui.component.ExpressiveCard\nimport me.bmax.apatch.ui.component.SwitchItem\nimport me.bmax.apatch.ui.viewmodel.KPModel\nimport me.bmax.apatch.ui.viewmodel.PatchesViewModel\nimport me.bmax.apatch.util.Version\nimport me.bmax.apatch.util.reboot\nimport me.bmax.apatch.util.ui.APDialogBlurBehindUtils\n\nprivate const val TAG = \"Patches\"\n\n@Destination<RootGraph>\n@Composable\nfun Patches(mode: PatchesViewModel.PatchMode) {\n    val scrollState = rememberScrollState()\n    val scope = rememberCoroutineScope()\n    var needKey by remember { mutableStateOf(APApplication.sharedPreferences.getBoolean(\"patch_custom_superkey_enabled\", false)) }\n    val viewModel = viewModel<PatchesViewModel>()\n    LaunchedEffect(mode) {\n        viewModel.prepare(mode)\n    }\n\n    Scaffold(topBar = {\n        TopBar()\n    }, floatingActionButton = {\n        if (viewModel.needReboot) {\n            val reboot = stringResource(id = R.string.reboot)\n            ExtendedFloatingActionButton(\n                onClick = {\n                    scope.launch {\n                        withContext(Dispatchers.IO) {\n                            reboot()\n                        }\n                    }\n                },\n                icon = { Icon(Icons.Filled.Refresh, reboot) },\n                text = { Text(text = reboot) },\n                containerColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                contentColor = MaterialTheme.colorScheme.onPrimary.copy(alpha = 1f),\n            )\n        }\n    }) { innerPadding ->\n        Column(\n            modifier = Modifier\n                .padding(innerPadding)\n                .padding(horizontal = 16.dp)\n                .padding(top = 16.dp)\n                .verticalScroll(scrollState),\n            verticalArrangement = Arrangement.spacedBy(16.dp)\n        ) {\n            val context = LocalContext.current\n\n            LaunchedEffect(Unit) {\n                val permissions = arrayOf(\n                    Manifest.permission.WRITE_EXTERNAL_STORAGE,\n                    Manifest.permission.READ_EXTERNAL_STORAGE\n                )\n                val permissionsToRequest = permissions.filter {\n                    ContextCompat.checkSelfPermission(context, it) != PackageManager.PERMISSION_GRANTED\n                }\n                if (permissionsToRequest.isNotEmpty()) {\n                    ActivityCompat.requestPermissions(\n                        context as Activity,\n                        permissionsToRequest.toTypedArray(),\n                        1001\n                    )\n                }\n            }\n\n            PatchMode(mode)\n            ErrorView(viewModel.error)\n            KernelPatchImageView(viewModel.kpimgInfo)\n            CustomKPImgView(viewModel)\n\n            if ((mode == PatchesViewModel.PatchMode.PATCH_ONLY || mode == PatchesViewModel.PatchMode.RESTORE) && selectedBootImage != null && viewModel.kimgInfo.banner.isEmpty() && !viewModel.running) {\n                viewModel.copyAndParseBootimg(selectedBootImage!!)\n            }\n\n            // select boot.img\n            if ((mode == PatchesViewModel.PatchMode.PATCH_ONLY || mode == PatchesViewModel.PatchMode.RESTORE) && viewModel.kimgInfo.banner.isEmpty()) {\n                SelectFileButton(\n                    text = stringResource(id = R.string.patch_select_bootimg_btn),\n                    onSelected = { data, uri ->\n                        Log.d(TAG, \"select boot.img, data: $data, uri: $uri\")\n                        viewModel.copyAndParseBootimg(uri)\n                    }\n                )\n            }\n\n            if (viewModel.bootSlot.isNotEmpty() || viewModel.bootDev.isNotEmpty()) {\n                BootimgView(slot = viewModel.bootSlot, boot = viewModel.bootDev)\n            }\n\n            if (viewModel.kimgInfo.banner.isNotEmpty()) {\n                KernelImageView(viewModel.kimgInfo)\n            }\n\n            if (mode != PatchesViewModel.PatchMode.UNPATCH && mode != PatchesViewModel.PatchMode.RESTORE && viewModel.kimgInfo.banner.isNotEmpty()) {\n                ExpressiveCard(\n                    modifier = Modifier.fillMaxWidth(),\n                    flat = true,\n                ) {\n                    Column {\n                        SwitchItem(\n                            icon = Icons.Default.Key,\n                            title = stringResource(R.string.patch_custom_superkey),\n                            summary = stringResource(R.string.patch_custom_superkey_summary),\n                            checked = needKey,\n                            onCheckedChange = { checked ->\n                                needKey = checked\n                                APApplication.sharedPreferences.edit().putBoolean(\"patch_custom_superkey_enabled\", checked).apply()\n                            }\n                        )\n\n                        AnimatedVisibility(\n                            visible = needKey,\n                            enter = expandVertically() + fadeIn(),\n                            exit = shrinkVertically() + fadeOut()\n                        ) {\n                            Column {\n                                HorizontalDivider(modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 4.dp))\n                                SetSuperKeyViewContent(viewModel)\n                                Spacer(modifier = Modifier.height(12.dp))\n                            }\n                        }\n                    }\n                }\n            }\n\n            if (viewModel.useCustomKPImg && !viewModel.patching && !viewModel.patchdone) {\n                SelectFileButton(\n                    text = stringResource(id = R.string.patch_select_kpimg_btn),\n                    onSelected = { _, uri ->\n                        viewModel.setCustomKPImg(uri)\n                    }\n                )\n            }\n\n            // existed extras\n            if (mode == PatchesViewModel.PatchMode.PATCH_AND_INSTALL || mode == PatchesViewModel.PatchMode.INSTALL_TO_NEXT_SLOT) {\n                viewModel.existedExtras.forEach(action = {\n                    ExtraItem(extra = it, true, onDelete = {\n                        viewModel.existedExtras.remove(it)\n                    })\n                })\n            }\n\n            // add new extras\n            if (mode != PatchesViewModel.PatchMode.UNPATCH && mode != PatchesViewModel.PatchMode.RESTORE) {\n                viewModel.newExtras.forEach(action = {\n                    ExtraItem(extra = it, false, onDelete = {\n                        val idx = viewModel.newExtras.indexOf(it)\n                        viewModel.newExtras.remove(it)\n                        viewModel.newExtrasFileName.removeAt(idx)\n                    })\n                })\n            }\n\n            // add new KPM\n            if (!viewModel.patching && !viewModel.patchdone && mode != PatchesViewModel.PatchMode.UNPATCH && mode != PatchesViewModel.PatchMode.RESTORE) {\n\n                SelectFileButton(\n                    text = stringResource(id = R.string.patch_embed_kpm_btn),\n                    onSelected = { data, uri ->\n                        Log.d(TAG, \"select kpm, data: $data, uri: $uri\")\n                        viewModel.embedKPM(uri)\n                    }\n                )\n            }\n\n            // do patch, update, unpatch\n            if (!viewModel.patching && !viewModel.patchdone) {\n                // patch start\n                if (mode != PatchesViewModel.PatchMode.UNPATCH && mode != PatchesViewModel.PatchMode.RESTORE) {\n                    val isKeyReady = !needKey || viewModel.superkey.isNotEmpty()\n                    if (isKeyReady && viewModel.kimgInfo.banner.isNotEmpty()) {\n                        StartButton(stringResource(id = R.string.patch_start_patch_btn)) {\n                            viewModel.doPatch(mode, needKey)\n                        }\n                    }\n                }\n                // unpatch\n                if (mode == PatchesViewModel.PatchMode.UNPATCH && viewModel.kimgInfo.banner.isNotEmpty()) {\n                    StartButton(stringResource(id = R.string.patch_start_unpatch_btn)) { viewModel.doUnpatch() }\n                }\n            }\n\n            // patch log\n            if (viewModel.patching || viewModel.patchdone) {\n                SelectionContainer {\n                    Text(\n                        modifier = Modifier.padding(8.dp),\n                        text = viewModel.patchLog,\n                        fontSize = MaterialTheme.typography.bodySmall.fontSize,\n                        fontFamily = MaterialTheme.typography.bodySmall.fontFamily,\n                        lineHeight = MaterialTheme.typography.bodySmall.lineHeight,\n                    )\n                }\n                LaunchedEffect(viewModel.patchLog) {\n                    scrollState.animateScrollTo(scrollState.maxValue)\n                }\n            }\n\n            Spacer(modifier = Modifier.height(12.dp))\n\n            // loading progress\n            if (viewModel.running) {\n                Box(\n                    modifier = Modifier\n                        .padding(innerPadding)\n                        .align(Alignment.CenterHorizontally)\n                ) {\n                    CircularProgressIndicator(\n                        modifier = Modifier\n                            .size(50.dp)\n                            .padding(16.dp)\n                            .align(Alignment.BottomCenter)\n                    )\n                }\n            }\n        }\n    }\n}\n\n\n@Composable\nprivate fun StartButton(text: String, onClick: () -> Unit) {\n    Column(\n        modifier = Modifier\n            .fillMaxWidth(),\n        horizontalAlignment = Alignment.End\n    ) {\n        Button(\n            onClick = onClick,\n            colors = ButtonDefaults.buttonColors(\n                containerColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                contentColor = MaterialTheme.colorScheme.onPrimary.copy(alpha = 1f)\n            ),\n            content = {\n                Text(text = text)\n            }\n        )\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun ExtraConfigDialog(kpmInfo: KPModel.KPMInfo, onDismiss: () -> Unit) {\n    var event by remember { mutableStateOf(kpmInfo.event) }\n    var args by remember { mutableStateOf(kpmInfo.args) }\n\n    BasicAlertDialog(\n        onDismissRequest = onDismiss,\n        properties = DialogProperties(\n            decorFitsSystemWindows = true,\n            usePlatformDefaultWidth = false,\n        )\n    ) {\n        Surface(\n            modifier = Modifier\n                .width(310.dp)\n                .wrapContentHeight(),\n            shape = RoundedCornerShape(30.dp),\n            tonalElevation = AlertDialogDefaults.TonalElevation,\n            color = AlertDialogDefaults.containerColor,\n        ) {\n            Column(modifier = Modifier.padding(PaddingValues(all = 24.dp))) {\n                Text(\n                    text = stringResource(id = R.string.kpm_control_dialog_title),\n                    style = MaterialTheme.typography.headlineSmall,\n                    modifier = Modifier.padding(bottom = 16.dp)\n                )\n\n                OutlinedTextField(\n                    value = event,\n                    onValueChange = {\n                        event = it\n                        kpmInfo.event = it\n                    },\n                    label = { Text(stringResource(id = R.string.patch_item_extra_event)) },\n                    modifier = Modifier.fillMaxWidth()\n                )\n\n                Spacer(modifier = Modifier.height(16.dp))\n\n                OutlinedTextField(\n                    value = args,\n                    onValueChange = {\n                        args = it\n                        kpmInfo.args = it\n                    },\n                    label = { Text(stringResource(id = R.string.patch_item_extra_args)) },\n                    modifier = Modifier.fillMaxWidth()\n                )\n\n                Spacer(modifier = Modifier.height(24.dp))\n\n                Row(\n                    modifier = Modifier.fillMaxWidth(),\n                    horizontalArrangement = Arrangement.End\n                ) {\n                    TextButton(onClick = onDismiss) {\n                        Text(stringResource(id = android.R.string.ok))\n                    }\n                }\n            }\n        }\n        val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n        APDialogBlurBehindUtils.setupWindowBlurListener(dialogWindowProvider.window)\n    }\n}\n\n@Composable\nprivate fun ExtraItem(extra: KPModel.IExtraInfo, existed: Boolean, onDelete: () -> Unit) {\n    var showConfigDialog by remember { mutableStateOf(false) }\n\n    if (showConfigDialog && extra is KPModel.KPMInfo) {\n        ExtraConfigDialog(extra, onDismiss = { showConfigDialog = false })\n    }\n\n    ExpressiveCard(flat = true) {\n        Column(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(12.dp),\n        ) {\n            Row(modifier = Modifier.align(Alignment.CenterHorizontally)) {\n                Text(\n                    text = stringResource(\n                        id =\n                            if (existed) R.string.patch_item_existed_extra_kpm else R.string.patch_item_new_extra_kpm\n                    ) +\n                            \" \" + extra.type.toString().uppercase(),\n                    style = MaterialTheme.typography.bodyLarge,\n                    modifier = Modifier\n                        .weight(1f)\n                        .wrapContentWidth(Alignment.CenterHorizontally)\n                )\n\n                if (extra.type == KPModel.ExtraType.KPM) {\n                    Icon(\n                        imageVector = Icons.Default.Settings,\n                        contentDescription = \"Config\",\n                        modifier = Modifier\n                            .padding(end = 8.dp)\n                            .clickable { showConfigDialog = true }\n                    )\n                }\n\n                Icon(\n                    imageVector = Icons.Default.Delete,\n                    contentDescription = \"Delete\",\n                    modifier = Modifier\n                        .padding(end = 8.dp)\n                        .clickable { onDelete() })\n            }\n            if (extra.type == KPModel.ExtraType.KPM) {\n                val kpmInfo: KPModel.KPMInfo = extra as KPModel.KPMInfo\n                Text(\n                    text = \"${stringResource(id = R.string.patch_item_extra_name) + \" \"} ${kpmInfo.name}\",\n                    style = MaterialTheme.typography.bodyMedium\n                )\n                Text(\n                    text = \"${stringResource(id = R.string.patch_item_extra_version) + \" \"} ${kpmInfo.version}\",\n                    style = MaterialTheme.typography.bodyMedium\n                )\n                Text(\n                    text = \"${stringResource(id = R.string.patch_item_extra_kpm_license) + \" \"} ${kpmInfo.license}\",\n                    style = MaterialTheme.typography.bodyMedium\n                )\n                Text(\n                    text = \"${stringResource(id = R.string.patch_item_extra_author) + \" \"} ${kpmInfo.author}\",\n                    style = MaterialTheme.typography.bodyMedium\n                )\n                Text(\n                    text = \"${stringResource(id = R.string.patch_item_extra_kpm_desciption) + \" \"} ${kpmInfo.description}\",\n                    style = MaterialTheme.typography.bodyMedium\n                )\n            }\n        }\n    }\n}\n\n\n@Composable\nprivate fun SetSuperKeyViewContent(viewModel: PatchesViewModel) {\n    var skey by remember { mutableStateOf(viewModel.superkey) }\n    var skeyConfirm by remember { mutableStateOf(\"\") }\n    var showWarn by remember { mutableStateOf(!viewModel.checkSuperKeyValidation(skey)) }\n    var showMismatch by remember { mutableStateOf(false) }\n    var keyVisible by remember { mutableStateOf(false) }\n    var keyConfirmVisible by remember { mutableStateOf(false) }\n    Column(\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(horizontal = 16.dp, vertical = 4.dp),\n    ) {\n        if (showWarn) {\n            Text(\n                color = Color.Red,\n                text = stringResource(id = R.string.patch_item_set_skey_label),\n                style = MaterialTheme.typography.bodyMedium\n            )\n            Spacer(modifier = Modifier.height(4.dp))\n        }\n        Box(\n            contentAlignment = Alignment.CenterEnd,\n        ) {\n            OutlinedTextField(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .padding(top = 6.dp),\n                value = skey,\n                label = { Text(stringResource(id = R.string.patch_set_superkey)) },\n                visualTransformation = if (keyVisible) VisualTransformation.None else PasswordVisualTransformation(),\n                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),\n                shape = RoundedCornerShape(50.0f),\n                onValueChange = {\n                    skey = it\n                    showMismatch = skeyConfirm.isNotEmpty() && skey != skeyConfirm\n                    if (viewModel.checkSuperKeyValidation(it)) {\n                        if (skeyConfirm.isNotEmpty() && skey == skeyConfirm) {\n                            viewModel.superkey = it\n                        } else {\n                            viewModel.superkey = \"\"\n                        }\n                        showWarn = false\n                    } else {\n                        viewModel.superkey = \"\"\n                        showWarn = true\n                    }\n                },\n            )\n            IconButton(\n                modifier = Modifier\n                    .size(40.dp)\n                    .padding(top = 15.dp, end = 5.dp),\n                onClick = { keyVisible = !keyVisible }\n            ) {\n                Icon(\n                    imageVector = if (keyVisible) Icons.Default.Visibility else Icons.Default.VisibilityOff,\n                    contentDescription = null,\n                    tint = Color.Gray\n                )\n            }\n        }\n        Box(\n            contentAlignment = Alignment.CenterEnd,\n        ) {\n            OutlinedTextField(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .padding(top = 6.dp),\n                value = skeyConfirm,\n                label = { Text(stringResource(id = R.string.patch_confirm_superkey)) },\n                visualTransformation = if (keyConfirmVisible) VisualTransformation.None else PasswordVisualTransformation(),\n                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),\n                shape = RoundedCornerShape(50.0f),\n                onValueChange = {\n                    skeyConfirm = it\n                    showMismatch = skeyConfirm.isNotEmpty() && skey != skeyConfirm\n                    if (viewModel.checkSuperKeyValidation(skey) && skey == skeyConfirm) {\n                        viewModel.superkey = skey\n                    } else {\n                        viewModel.superkey = \"\"\n                    }\n                },\n            )\n            IconButton(\n                modifier = Modifier\n                    .size(40.dp)\n                    .padding(top = 15.dp, end = 5.dp),\n                onClick = { keyConfirmVisible = !keyConfirmVisible }\n            ) {\n                Icon(\n                    imageVector = if (keyConfirmVisible) Icons.Default.Visibility else Icons.Default.VisibilityOff,\n                    contentDescription = null,\n                    tint = Color.Gray\n                )\n            }\n        }\n        if (showMismatch) {\n            Spacer(modifier = Modifier.height(3.dp))\n            Text(\n                color = Color.Red,\n                text = stringResource(id = R.string.patch_skey_mismatch),\n                style = MaterialTheme.typography.bodyMedium\n            )\n        }\n    }\n}\n\n@Composable\nprivate fun KernelPatchImageView(kpImgInfo: KPModel.KPImgInfo) {\n    if (kpImgInfo.version.isEmpty()) return\n    ExpressiveCard(flat = true) {\n        Column(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(start = 12.dp, top = 12.dp, end = 12.dp, bottom = 12.dp),\n        ) {\n            Column(\n                modifier = Modifier\n                    .fillMaxWidth(),\n                horizontalAlignment = Alignment.CenterHorizontally\n            ) {\n                Text(\n                    text = stringResource(id = R.string.patch_item_kpimg),\n                    style = MaterialTheme.typography.bodyLarge\n                )\n            }\n            Text(\n                text = stringResource(id = R.string.patch_item_kpimg_version) + \" \" + Version.uInt2String(\n                    kpImgInfo.version.substring(2).toUInt(16)\n                ), style = MaterialTheme.typography.bodyMedium\n            )\n            Text(\n                text = stringResource(id = R.string.patch_item_kpimg_comile_time) + \" \" + kpImgInfo.compileTime,\n                style = MaterialTheme.typography.bodyMedium\n            )\n            Text(\n                text = stringResource(id = R.string.patch_item_kpimg_config) + \" \" + kpImgInfo.config,\n                style = MaterialTheme.typography.bodyMedium\n            )\n        }\n    }\n}\n\n@Composable\nprivate fun CustomKPImgView(viewModel: PatchesViewModel) {\n    if (!viewModel.useCustomKPImg) return\n    ExpressiveCard(flat = true) {\n        Column(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(12.dp),\n        ) {\n            Column(\n                modifier = Modifier\n                    .fillMaxWidth(),\n                horizontalAlignment = Alignment.CenterHorizontally\n            ) {\n                Text(\n                    text = stringResource(id = R.string.patch_custom_kpimg_label),\n                    style = MaterialTheme.typography.bodyLarge\n                )\n            }\n            if (viewModel.customKPImgFileName.isNotEmpty()) {\n                Text(\n                    text = stringResource(id = R.string.patch_custom_kpimg_file, viewModel.customKPImgFileName),\n                    style = MaterialTheme.typography.bodyMedium\n                )\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun BootimgView(slot: String, boot: String) {\n    ExpressiveCard(flat = true) {\n        Column(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(12.dp),\n        ) {\n            Column(\n                modifier = Modifier\n                    .fillMaxWidth(),\n                horizontalAlignment = Alignment.CenterHorizontally\n            ) {\n                Text(\n                    text = stringResource(id = R.string.patch_item_bootimg),\n                    style = MaterialTheme.typography.bodyLarge\n                )\n            }\n            if (slot.isNotEmpty()) {\n                Text(\n                    text = stringResource(id = R.string.patch_item_bootimg_slot) + \" \" + slot,\n                    style = MaterialTheme.typography.bodyMedium\n                )\n            }\n            Text(\n                text = stringResource(id = R.string.patch_item_bootimg_dev) + \" \" + boot,\n                style = MaterialTheme.typography.bodyMedium\n            )\n        }\n    }\n}\n\n@Composable\nprivate fun KernelImageView(kImgInfo: KPModel.KImgInfo) {\n    ExpressiveCard(flat = true) {\n        Column(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(12.dp),\n        ) {\n            Column(\n                modifier = Modifier\n                    .fillMaxWidth(),\n                horizontalAlignment = Alignment.CenterHorizontally\n            ) {\n                Text(\n                    text = stringResource(id = R.string.patch_item_kernel),\n                    style = MaterialTheme.typography.bodyLarge\n                )\n            }\n            Text(text = kImgInfo.banner, style = MaterialTheme.typography.bodyMedium)\n        }\n    }\n}\n\n\n@Composable\nprivate fun SelectFileButton(text: String, onSelected: (data: Intent, uri: Uri) -> Unit) {\n    val selectFileLauncher = rememberLauncherForActivityResult(\n        contract = ActivityResultContracts.StartActivityForResult()\n    ) {\n        if (it.resultCode != Activity.RESULT_OK) {\n            return@rememberLauncherForActivityResult\n        }\n        val data = it.data ?: return@rememberLauncherForActivityResult\n        val uri = data.data ?: return@rememberLauncherForActivityResult\n        onSelected(data, uri)\n    }\n\n    Column(\n        modifier = Modifier\n            .fillMaxWidth(),\n        horizontalAlignment = Alignment.End\n    ) {\n        Button(\n            onClick = {\n                val intent = Intent(Intent.ACTION_GET_CONTENT)\n                intent.type = \"*/*\"\n                intent.addCategory(Intent.CATEGORY_OPENABLE)\n                selectFileLauncher.launch(intent)\n            },\n            colors = ButtonDefaults.buttonColors(\n                containerColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                contentColor = MaterialTheme.colorScheme.onPrimary.copy(alpha = 1f)\n            ),\n            content = { Text(text = text) }\n        )\n    }\n}\n\n@Composable\nprivate fun ErrorView(error: String) {\n    if (error.isEmpty()) return\n    ExpressiveCard(flat = true) {\n        Column(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(start = 12.dp, top = 12.dp, end = 12.dp, bottom = 12.dp),\n            horizontalAlignment = Alignment.CenterHorizontally\n        ) {\n            Text(\n                text = stringResource(id = R.string.patch_item_error),\n                style = MaterialTheme.typography.bodyLarge\n            )\n            Text(text = error, style = MaterialTheme.typography.bodyMedium)\n        }\n    }\n}\n\n@Composable\nprivate fun PatchMode(mode: PatchesViewModel.PatchMode) {\n    ExpressiveCard(flat = true) {\n        Column(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(12.dp),\n            horizontalAlignment = Alignment.CenterHorizontally\n        ) {\n            Text(text = stringResource(id = mode.sId), style = MaterialTheme.typography.bodyLarge)\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun TopBar() {\n    TopAppBar(title = { Text(stringResource(R.string.patch_config_title)) })\n}"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/ScriptExecutionLogScreen.kt",
    "content": "package me.bmax.apatch.ui.screen\n\nimport android.os.Environment\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.lazy.rememberLazyListState\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.automirrored.filled.Send\nimport androidx.compose.material.icons.filled.Save\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.OutlinedTextField\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.SnackbarHost\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.DisposableEffect\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateListOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.AnnotatedString\nimport androidx.compose.ui.text.font.FontFamily\nimport androidx.compose.ui.unit.dp\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.R\nimport me.bmax.apatch.apApp\nimport me.bmax.apatch.data.ScriptInfo\nimport me.bmax.apatch.util.getSafeDownloadsDir\nimport me.bmax.apatch.util.ui.AnsiUtils\nimport me.bmax.apatch.util.ui.LocalSnackbarHost\nimport java.io.File\nimport java.io.OutputStream\nimport java.text.SimpleDateFormat\nimport java.util.Date\nimport java.util.Locale\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Destination<RootGraph>\n@Composable\nfun ScriptExecutionLogScreen(\n    navigator: DestinationsNavigator,\n    scriptInfo: ScriptInfo\n) {\n    val logLines = remember { mutableStateListOf<AnnotatedString>() }\n    val fullLogBuffer = remember { StringBuffer() }\n\n    val snackBarHost = LocalSnackbarHost.current\n    val scope = rememberCoroutineScope()\n    val listState = rememberLazyListState()\n\n    val context = androidx.compose.ui.platform.LocalContext.current\n    \n    var process by remember { mutableStateOf<Process?>(null) }\n    var inputCmd by remember { mutableStateOf(\"\") }\n\n    LaunchedEffect(Unit) {\n        withContext(Dispatchers.IO) {\n            var p: Process? = null\n            try {\n                // Try to start root shell with multiple strategies\n                \n                if (p == null) {\n                    try {\n                        val pb = ProcessBuilder(\n                            APApplication.SUPERCMD,\n                            APApplication.superKey,\n                            \"-Z\",\n                            APApplication.MAGISK_SCONTEXT\n                        )\n                        pb.redirectErrorStream(true)\n                        pb.environment()?.apply {\n                            set(\"PATH\", System.getenv(\"PATH\") ?: \"\" + \":/system_ext/bin:/vendor/bin:${APApplication.APATCH_FOLDER}bin\")\n                            set(\"BUSYBOX\", \"${APApplication.APATCH_FOLDER}bin/busybox\")\n                        }\n                        p = pb.start()\n                    } catch (e: Exception) {\n                        // Continue\n                    }\n                }\n\n                if (p == null) {\n                    try {\n                        val pb = ProcessBuilder(\n                            APApplication.SUPERCMD,\n                            APApplication.superKey,\n                            \"su\",\n                            \"-Z\",\n                            APApplication.MAGISK_SCONTEXT\n                        )\n                        pb.redirectErrorStream(true)\n                        pb.environment()?.apply {\n                            set(\"PATH\", System.getenv(\"PATH\") ?: \"\" + \":/system_ext/bin:/vendor/bin:${APApplication.APATCH_FOLDER}bin\")\n                            set(\"BUSYBOX\", \"${APApplication.APATCH_FOLDER}bin/busybox\")\n                        }\n                        p = pb.start()\n                    } catch (e: Exception) {\n                        // Continue\n                    }\n                }\n\n              \n                if (p == null) {\n                    try {\n                        val pb = ProcessBuilder(\"su\")\n                        pb.redirectErrorStream(true)\n                        pb.environment()?.apply {\n                            set(\"PATH\", System.getenv(\"PATH\") ?: \"\" + \":/system_ext/bin:/vendor/bin:${APApplication.APATCH_FOLDER}bin\")\n                            set(\"BUSYBOX\", \"${APApplication.APATCH_FOLDER}bin/busybox\")\n                        }\n                        p = pb.start()\n                    } catch (e: Exception) {\n                        // Continue\n                    }\n                }\n\n              \n                if (p == null) {\n                    try {\n                        val kpatchPath = apApp.applicationInfo.nativeLibraryDir + File.separator + \"libkpatch.so\"\n                        val pb = ProcessBuilder(\n                            kpatchPath,\n                            APApplication.superKey,\n                            \"su\",\n                            \"-Z\",\n                            APApplication.MAGISK_SCONTEXT\n                        )\n                        pb.redirectErrorStream(true)\n                        val env = pb.environment()\n                        env?.set(\"PATH\", System.getenv(\"PATH\") ?: \"\" + \":/system_ext/bin:/vendor/bin:${APApplication.APATCH_FOLDER}bin\")\n                        env?.set(\"BUSYBOX\", \"${APApplication.APATCH_FOLDER}bin/busybox\")\n                        p = pb.start()\n                    } catch (e: Exception) {\n                        // Continue\n                    }\n                }\n\n               \n                if (p == null) {\n                    try {\n                        val pb = ProcessBuilder(\"su\")\n                        pb.redirectErrorStream(true)\n                        val env = pb.environment()\n                        env?.set(\"PATH\", System.getenv(\"PATH\") ?: \"\" + \":/system_ext/bin:/vendor/bin:${APApplication.APATCH_FOLDER}bin\")\n                        env?.set(\"BUSYBOX\", \"${APApplication.APATCH_FOLDER}bin/busybox\")\n                        p = pb.start()\n                    } catch (e: Exception) {\n                        throw e // Rethrow if all failed\n                    }\n                }\n\n                process = p\n                \n                if (p != null) {\n                    val os = p!!.outputStream\n                    // Start the script\n                    os.write(\"sh \\\"${scriptInfo.path}\\\"\\n\".toByteArray())\n                    os.flush()\n\n                    val reader = p!!.inputStream.bufferedReader()\n                    val buffer = CharArray(1024)\n                    while (true) {\n                        val count = reader.read(buffer)\n                        if (count == -1) break\n                        if (count > 0) {\n                            val chunk = String(buffer, 0, count)\n                            fullLogBuffer.append(chunk)\n                            \n                            val lines = chunk.split(\"\\n\")\n                            withContext(Dispatchers.Main) {\n                                if (lines.isNotEmpty()) {\n                                    lines.forEach { line ->\n                                        if (line.isNotEmpty()) {\n                                             // Handle clear screen\n                                            if (line.contains(\"\\u001B[H\") || line.contains(\"\\u001B[2J\")) {\n                                                logLines.clear()\n                                            }\n                                            logLines.add(AnsiUtils.parseAnsi(line))\n                                        }\n                                    }\n                                }\n                            }\n                        }\n                    }\n                    p!!.waitFor()\n                }\n            } catch (e: Exception) {\n                e.printStackTrace()\n                withContext(Dispatchers.Main) {\n                    logLines.add(AnnotatedString(\"Error: ${e.message}\"))\n                }\n            } finally {\n                try {\n                    p?.destroy()\n                } catch (e: Exception) {\n                    e.printStackTrace()\n                }\n                process = null\n            }\n        }\n    }\n\n    Scaffold(\n        topBar = {\n            TopAppBar(\n                title = { Text(stringResource(R.string.script_library_output)) },\n                navigationIcon = {\n                    IconButton(\n                        onClick = { navigator.popBackStack() }\n                    ) {\n                        Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)\n                    }\n                },\n                actions = {\n                    IconButton(\n                        onClick = {\n                            scope.launch {\n                                try {\n                                    val format = SimpleDateFormat(\"yyyy-MM-dd-HH-mm-ss\", Locale.getDefault())\n                                    val date = format.format(Date())\n                                    val file = File(\n                                        getSafeDownloadsDir(context),\n                                        \"FolkPatch/${scriptInfo.alias}_${date}.log\"\n                                    )\n                                    file.writeText(fullLogBuffer.toString())\n                                    snackBarHost.showSnackbar(\"Log saved to ${file.absolutePath}\")\n                                } catch (e: Exception) {\n                                    snackBarHost.showSnackbar(\"Failed to save log: ${e.message}\")\n                                }\n                            }\n                        }\n                    ) {\n                        Icon(\n                            imageVector = Icons.Default.Save,\n                            contentDescription = stringResource(R.string.script_library_save_log)\n                        )\n                    }\n                }\n            )\n        },\n        snackbarHost = { SnackbarHost(snackBarHost) },\n        bottomBar = {\n            Row(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .padding(start = 8.dp, end = 8.dp, top = 8.dp, bottom = 30.dp),\n                verticalAlignment = Alignment.CenterVertically\n            ) {\n                OutlinedTextField(\n                    value = inputCmd,\n                    onValueChange = { inputCmd = it },\n                    modifier = Modifier.weight(1f),\n                    placeholder = { Text(\"Type command...\") },\n                    singleLine = true\n                )\n                IconButton(\n                    onClick = {\n                        scope.launch(Dispatchers.IO) {\n                            process?.outputStream?.let { out ->\n                                try {\n                                    out.write((inputCmd + \"\\n\").toByteArray())\n                                    out.flush()\n                                    withContext(Dispatchers.Main) {\n                                        inputCmd = \"\"\n                                    }\n                                } catch (e: Exception) {\n                                    e.printStackTrace()\n                                }\n                            }\n                        }\n                    }\n                ) {\n                    Icon(Icons.AutoMirrored.Filled.Send, contentDescription = \"Send\")\n                }\n            }\n        }\n    ) { innerPadding ->\n        LazyColumn(\n            modifier = Modifier\n                .fillMaxSize()\n                .padding(innerPadding),\n            state = listState\n        ) {\n            items(logLines) { line ->\n                Text(\n                    text = line,\n                    modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp),\n                    fontSize = MaterialTheme.typography.bodySmall.fontSize,\n                    fontFamily = FontFamily.Monospace,\n                    lineHeight = MaterialTheme.typography.bodySmall.lineHeight,\n                )\n            }\n        }\n        LaunchedEffect(logLines.size) {\n            if (logLines.isNotEmpty()) {\n                listState.animateScrollToItem(logLines.size - 1)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/ScriptLibrary.kt",
    "content": "package me.bmax.apatch.ui.screen\n\nimport android.content.SharedPreferences\nimport android.net.Uri\nimport androidx.activity.compose.rememberLauncherForActivityResult\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.*\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.outlined.Add\nimport androidx.compose.material.icons.outlined.Delete\nimport androidx.compose.material.icons.outlined.PlayArrow\nimport androidx.compose.material3.*\nimport androidx.compose.material3.pulltorefresh.PullToRefreshBox\nimport androidx.compose.runtime.*\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.asImageBitmap\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.lifecycle.viewmodel.compose.viewModel\nimport androidx.compose.ui.platform.LocalView\nimport androidx.compose.ui.window.DialogWindowProvider\nimport coil.compose.AsyncImage\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport com.ramcosta.composedestinations.generated.destinations.ScriptExecutionLogScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.OnlineScriptScreenDestination\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.R\nimport me.bmax.apatch.data.ScriptInfo\nimport androidx.compose.material3.pulltorefresh.PullToRefreshDefaults\nimport androidx.compose.material3.pulltorefresh.rememberPullToRefreshState\nimport me.bmax.apatch.ui.component.FilePickerDialog\nimport me.bmax.apatch.ui.component.rememberConfirmDialog\nimport me.bmax.apatch.ui.component.rememberLoadingDialog\nimport me.bmax.apatch.ui.theme.BackgroundConfig\nimport me.bmax.apatch.ui.viewmodel.ScriptLibraryViewModel\nimport me.bmax.apatch.util.ModuleShortcut\nimport me.bmax.apatch.util.ui.LocalSnackbarHost\nimport java.io.File\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Destination<RootGraph>\n@Composable\nfun ScriptLibraryScreen(navigator: DestinationsNavigator) {\n    val viewModel = viewModel<ScriptLibraryViewModel>()\n    val snackBarHost = LocalSnackbarHost.current\n    val scope = rememberCoroutineScope()\n    val context = LocalContext.current\n    val prefs = remember { APApplication.sharedPreferences }\n\n    val scripts by viewModel.scripts.collectAsState()\n    val isLoading by viewModel.isLoading.collectAsState()\n\n    var showAddDialog by remember { mutableStateOf(false) }\n    var selectedFile by remember { mutableStateOf<File?>(null) }\n    var scriptAlias by remember { mutableStateOf(\"\") }\n    var selectedScript by remember { mutableStateOf<ScriptInfo?>(null) }\n\n    val confirmDialog = rememberConfirmDialog()\n\n    val confirmDeleteTitle = stringResource(R.string.script_library_confirm_delete)\n    val confirmDeleteLabel = stringResource(R.string.script_library_delete)\n    val dismissLabel = stringResource(android.R.string.cancel)\n    val deleteSuccessMsg = context.getString(R.string.script_library_delete_success)\n\n    var enableModuleShortcutAdd by remember {\n        mutableStateOf(prefs.getBoolean(\"enable_module_shortcut_add\", true))\n    }\n\n    DisposableEffect(Unit) {\n        val listener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->\n            if (key == \"enable_module_shortcut_add\") {\n                enableModuleShortcutAdd = sharedPreferences.getBoolean(\"enable_module_shortcut_add\", true)\n            }\n        }\n        prefs.registerOnSharedPreferenceChangeListener(listener)\n        onDispose {\n            prefs.unregisterOnSharedPreferenceChangeListener(listener)\n        }\n    }\n\n    Scaffold(\n        topBar = {\n            TopAppBar(\n                title = { Text(stringResource(R.string.script_library_title)) },\n                navigationIcon = {\n                    IconButton(onClick = { navigator.navigateUp() }) {\n                        Icon(Icons.AutoMirrored.Default.ArrowBack, contentDescription = null)\n                    }\n                },\n                actions = {\n                    IconButton(onClick = { navigator.navigate(OnlineScriptScreenDestination) }) {\n                        Icon(Icons.Default.Download, contentDescription = stringResource(R.string.online_script_title))\n                    }\n                    IconButton(onClick = { showAddDialog = true }) {\n                        Icon(Icons.Default.Add, contentDescription = stringResource(R.string.script_library_add))\n                    }\n                }\n            )\n        }\n    ) { innerPadding ->\n        val pullToRefreshState = rememberPullToRefreshState()\n        PullToRefreshBox(\n            modifier = Modifier.padding(innerPadding),\n            onRefresh = { viewModel.loadScripts() },\n            isRefreshing = isLoading,\n            state = pullToRefreshState,\n            indicator = { PullToRefreshDefaults.LoadingIndicator(state = pullToRefreshState, isRefreshing = isLoading, modifier = Modifier.align(Alignment.TopCenter)) }\n        ) {\n            if (scripts.isEmpty()) {\n                Box(\n                    modifier = Modifier.fillMaxSize(),\n                    contentAlignment = Alignment.Center\n                ) {\n                    Text(\n                        text = stringResource(R.string.script_library_empty),\n                        style = MaterialTheme.typography.bodyLarge,\n                        color = MaterialTheme.colorScheme.onSurfaceVariant\n                    )\n                }\n            } else {\n                LazyColumn(\n                    modifier = Modifier.fillMaxSize(),\n                    contentPadding = PaddingValues(16.dp),\n                    verticalArrangement = Arrangement.spacedBy(12.dp)\n                ) {\n                    items(scripts) { script ->\n                        ScriptItem(\n                            script = script,\n                            enableShortcut = enableModuleShortcutAdd,\n                            onRun = {\n                                navigator.navigate(ScriptExecutionLogScreenDestination(script))\n                            },\n                            onDelete = {\n                                selectedScript = script\n                                val confirmContent = \"${script.alias}\\n${script.path}\"\n\n                                scope.launch {\n                                    val confirmResult = confirmDialog.awaitConfirm(\n                                        title = confirmDeleteTitle,\n                                        content = confirmContent,\n                                        confirm = confirmDeleteLabel,\n                                        dismiss = dismissLabel\n                                    )\n                                    if (confirmResult == me.bmax.apatch.ui.component.ConfirmResult.Confirmed) {\n                                        viewModel.removeScript(\n                                            script,\n                                            onSuccess = {\n                                                scope.launch {\n                                                    snackBarHost.showSnackbar(deleteSuccessMsg)\n                                                }\n                                            },\n                                            onError = { error ->\n                                                scope.launch {\n                                                    snackBarHost.showSnackbar(context.getString(R.string.script_library_delete_failed, error))\n                                                }\n                                            }\n                                        )\n                                    }\n                                }\n                            }\n                        )\n                    }\n                }\n            }\n        }\n    }\n\n    if (showAddDialog) {\n        AddScriptDialog(\n            onDismiss = {\n                showAddDialog = false\n                selectedFile = null\n                scriptAlias = \"\"\n            },\n            onConfirm = { file, alias ->\n                showAddDialog = false\n                viewModel.addScript(\n                    file,\n                    alias,\n                    onSuccess = {\n                        scope.launch {\n                            snackBarHost.showSnackbar(context.getString(R.string.script_library_add_success))\n                        }\n                    },\n                    onError = { error ->\n                        scope.launch {\n                            snackBarHost.showSnackbar(context.getString(R.string.script_library_add_failed, error))\n                        }\n                    }\n                )\n                selectedFile = null\n                scriptAlias = \"\"\n            },\n            selectedFile = selectedFile,\n            onFileSelected = { selectedFile = it },\n            scriptAlias = scriptAlias,\n            onAliasChange = { scriptAlias = it }\n        )\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun ScriptLabel(\n    text: String,\n    containerColor: Color,\n    contentColor: Color\n) {\n    Surface(\n        color = containerColor,\n        contentColor = contentColor,\n        shape = RoundedCornerShape(8.dp),\n    ) {\n        Text(\n            text = text,\n            style = MaterialTheme.typography.labelSmall,\n            modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp),\n            maxLines = 1,\n            overflow = TextOverflow.Ellipsis\n        )\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun ScriptItem(\n    script: ScriptInfo,\n    enableShortcut: Boolean,\n    onRun: () -> Unit,\n    onDelete: () -> Unit\n) {\n    val context = LocalContext.current\n    val scope = rememberCoroutineScope()\n\n    var showShortcutDialog by remember { mutableStateOf(false) }\n    var shortcutName by rememberSaveable(script.id) { mutableStateOf(script.alias) }\n    var shortcutIconUri by remember { mutableStateOf<String?>(null) }\n    val appIcon = remember(context) { context.packageManager.getApplicationIcon(context.packageName) }\n    val pickShortcutIconLauncher = rememberLauncherForActivityResult(\n        ActivityResultContracts.GetContent()\n    ) { uri: Uri? ->\n        shortcutIconUri = uri?.toString()\n    }\n\n    val shortcutPreviewBitmap by produceState<android.graphics.Bitmap?>(initialValue = null, key1 = shortcutIconUri) {\n        value = if (shortcutIconUri.isNullOrBlank()) {\n            null\n        } else {\n            withContext(Dispatchers.IO) {\n                ModuleShortcut.loadShortcutBitmap(context, shortcutIconUri)\n            }\n        }\n    }\n\n    val isWallpaperMode = BackgroundConfig.isCustomBackgroundEnabled\n    val opacity = if (isWallpaperMode) {\n        BackgroundConfig.customBackgroundOpacity.coerceAtLeast(0.35f)\n    } else {\n        1f\n    }\n\n    val cardColor = if (isWallpaperMode) {\n        MaterialTheme.colorScheme.surface.copy(alpha = opacity)\n    } else {\n        MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.2f)\n    }\n\n    val labelOpacity = (opacity + 0.1f).coerceAtMost(1f)\n\n    Surface(\n        modifier = Modifier.fillMaxWidth(),\n        shape = RoundedCornerShape(20.dp),\n        color = cardColor,\n        tonalElevation = 0.dp,\n    ) {\n        Box(modifier = Modifier.fillMaxWidth()) {\n            Column(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .padding(16.dp)\n            ) {\n                // 第一行：标签 + 脚本名称\n                Row(\n                    modifier = Modifier.fillMaxWidth(),\n                    horizontalArrangement = Arrangement.SpaceBetween,\n                    verticalAlignment = Alignment.CenterVertically\n                ) {\n                    Text(\n                        text = script.alias,\n                        style = MaterialTheme.typography.titleMedium\n                    )\n\n                    ScriptLabel(\n                        text = \"Shell\",\n                        containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = labelOpacity),\n                        contentColor = MaterialTheme.colorScheme.onPrimaryContainer\n                    )\n                }\n\n                // 第二行：脚本路径\n                Text(\n                    text = script.path,\n                    style = MaterialTheme.typography.bodySmall,\n                    color = MaterialTheme.colorScheme.onSurfaceVariant\n                )\n\n                // 按钮区域\n                Spacer(modifier = Modifier.height(12.dp))\n\n                Row(\n                    modifier = Modifier.fillMaxWidth(),\n                    horizontalArrangement = Arrangement.spacedBy(8.dp)\n                ) {\n                    FilledTonalButton(\n                        onClick = onRun,\n                        contentPadding = ButtonDefaults.TextButtonContentPadding,\n                        modifier = Modifier.height(36.dp),\n                        colors = ButtonDefaults.filledTonalButtonColors(\n                            containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = (opacity + 0.3f).coerceAtMost(1f))\n                        )\n                    ) {\n                        Icon(Icons.Outlined.PlayArrow, contentDescription = null, modifier = Modifier.size(18.dp))\n                        Spacer(Modifier.width(8.dp))\n                        Text(stringResource(R.string.script_library_run))\n                    }\n\n                    if (enableShortcut) {\n                        FilledTonalButton(\n                            onClick = {\n                                shortcutName = script.alias\n                                shortcutIconUri = null\n                                showShortcutDialog = true\n                            },\n                            contentPadding = ButtonDefaults.TextButtonContentPadding,\n                            modifier = Modifier.height(36.dp),\n                            colors = ButtonDefaults.filledTonalButtonColors(\n                                containerColor = MaterialTheme.colorScheme.tertiaryContainer.copy(alpha = (opacity + 0.3f).coerceAtMost(1f)),\n                                contentColor = MaterialTheme.colorScheme.onTertiaryContainer\n                            )\n                        ) {\n                            Icon(Icons.Outlined.Add, contentDescription = null, modifier = Modifier.size(18.dp))\n                            Spacer(Modifier.width(8.dp))\n                            Text(stringResource(R.string.module_shortcut_add))\n                        }\n                    }\n\n                    Spacer(modifier = Modifier.weight(1f))\n\n                    FilledTonalButton(\n                        onClick = onDelete,\n                        contentPadding = ButtonDefaults.TextButtonContentPadding,\n                        modifier = Modifier.height(36.dp),\n                        colors = ButtonDefaults.filledTonalButtonColors(\n                            containerColor = MaterialTheme.colorScheme.errorContainer.copy(alpha = (opacity + 0.3f).coerceAtMost(1f)),\n                            contentColor = MaterialTheme.colorScheme.onErrorContainer\n                        )\n                    ) {\n                        Icon(Icons.Outlined.Delete, contentDescription = null, modifier = Modifier.size(18.dp))\n                        Spacer(Modifier.width(8.dp))\n                        Text(stringResource(R.string.script_library_delete))\n                    }\n                }\n            }\n        }\n    }\n\n    if (showShortcutDialog) {\n        AlertDialog(\n            onDismissRequest = { showShortcutDialog = false },\n            title = { Text(stringResource(R.string.module_shortcut_add)) },\n            text = {\n                Column {\n                    OutlinedTextField(\n                        value = shortcutName,\n                        onValueChange = { shortcutName = it },\n                        label = { Text(stringResource(R.string.module_shortcut_name)) },\n                        modifier = Modifier.fillMaxWidth()\n                    )\n                    Spacer(Modifier.height(12.dp))\n                    Row(verticalAlignment = Alignment.CenterVertically) {\n                        Text(stringResource(R.string.module_shortcut_icon))\n                        Spacer(Modifier.width(12.dp))\n                        if (shortcutPreviewBitmap != null) {\n                            Image(\n                                bitmap = shortcutPreviewBitmap!!.asImageBitmap(),\n                                contentDescription = null,\n                                modifier = Modifier.size(36.dp)\n                            )\n                        } else if (shortcutIconUri != null) {\n                            AsyncImage(\n                                model = shortcutIconUri,\n                                contentDescription = null,\n                                modifier = Modifier.size(36.dp)\n                            )\n                        } else {\n                            AsyncImage(\n                                model = appIcon,\n                                contentDescription = null,\n                                modifier = Modifier.size(36.dp)\n                            )\n                        }\n                    }\n                    Spacer(Modifier.height(8.dp))\n                    Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {\n                        TextButton(onClick = { pickShortcutIconLauncher.launch(\"image/*\") }) {\n                            Text(stringResource(R.string.module_shortcut_icon_select))\n                        }\n                        TextButton(onClick = { shortcutIconUri = null }) {\n                            Text(stringResource(R.string.module_shortcut_icon_default))\n                        }\n                    }\n                }\n            },\n            confirmButton = {\n                TextButton(onClick = {\n                    val name = shortcutName.ifBlank { script.alias }\n                    ModuleShortcut.createScriptShortcut(\n                        context,\n                        script.id,\n                        name,\n                        shortcutIconUri\n                    )\n                    showShortcutDialog = false\n                }) {\n                    Text(text = stringResource(id = android.R.string.ok))\n                }\n            },\n            dismissButton = {\n                TextButton(onClick = { showShortcutDialog = false }) {\n                    Text(text = stringResource(id = android.R.string.cancel))\n                }\n            }\n        )\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun AddScriptDialog(\n    onDismiss: () -> Unit,\n    onConfirm: (File, String) -> Unit,\n    selectedFile: File?,\n    onFileSelected: (File) -> Unit,\n    scriptAlias: String,\n    onAliasChange: (String) -> Unit\n) {\n    var showFilePicker by remember { mutableStateOf(false) }\n\n    AnimatedVisibility(visible = showFilePicker) {\n        FilePickerDialog(\n            initialPath = null,\n            allowedExtensions = listOf(\"sh\"),\n            onDismissRequest = { showFilePicker = false },\n            onFileSelected = { file ->\n                onFileSelected(file)\n                showFilePicker = false\n            }\n        )\n    }\n\n    AlertDialog(\n        onDismissRequest = onDismiss,\n        title = { Text(stringResource(R.string.script_library_add_title)) },\n        text = {\n            Column {\n                OutlinedButton(\n                    onClick = { showFilePicker = true },\n                    modifier = Modifier.fillMaxWidth()\n                ) {\n                    Icon(Icons.Default.FolderOpen, contentDescription = null)\n                    Spacer(modifier = Modifier.width(8.dp))\n                    Text(stringResource(R.string.script_library_select_file))\n                }\n\n                if (selectedFile != null) {\n                    Spacer(modifier = Modifier.height(8.dp))\n                    Text(\n                        text = selectedFile?.absolutePath ?: \"\",\n                        style = MaterialTheme.typography.bodySmall,\n                        color = MaterialTheme.colorScheme.primary\n                    )\n                }\n\n                Spacer(modifier = Modifier.height(16.dp))\n\n                OutlinedTextField(\n                    value = scriptAlias,\n                    onValueChange = onAliasChange,\n                    label = { Text(stringResource(R.string.script_library_alias)) },\n                    placeholder = { Text(stringResource(R.string.script_library_alias_hint)) },\n                    singleLine = true,\n                    modifier = Modifier.fillMaxWidth()\n                )\n            }\n        },\n        confirmButton = {\n            Button(\n                onClick = {\n                    selectedFile?.let { onConfirm(it, scriptAlias) }\n                },\n                enabled = selectedFile != null\n            ) {\n                Text(stringResource(android.R.string.ok))\n            }\n        },\n        dismissButton = {\n            TextButton(onClick = onDismiss) {\n                Text(stringResource(android.R.string.cancel))\n            }\n        }\n    )\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/Settings.kt",
    "content": "package me.bmax.apatch.ui.screen\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight\nimport androidx.compose.material.icons.filled.Cloud\nimport androidx.compose.material.icons.filled.Coffee\nimport androidx.compose.material.icons.filled.Extension\nimport androidx.compose.material.icons.filled.Palette\nimport androidx.compose.material.icons.filled.Security\nimport androidx.compose.material.icons.filled.Settings\nimport androidx.compose.material.icons.filled.Tune\nimport androidx.compose.material.icons.filled.MusicNote\nimport androidx.compose.material.icons.filled.Visibility\nimport androidx.compose.material.icons.outlined.Info\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.FilledTonalButton\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.ModalBottomSheet\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.material3.rememberModalBottomSheetState\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.livedata.observeAsState\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.draw.shadow\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport androidx.compose.ui.input.nestedscroll.nestedScroll\nimport androidx.compose.ui.layout.ContentScale\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalUriHandler\nimport androidx.compose.ui.res.painterResource\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport coil.compose.AsyncImage\nimport coil.request.CachePolicy\nimport coil.request.ImageRequest\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.component.SplicedColumnGroup\nimport me.bmax.apatch.util.ui.NavigationBarsSpacer\n\nimport com.ramcosta.composedestinations.generated.destinations.GeneralSettingsScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.AppearanceSettingsScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.BehaviorSettingsScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.SecuritySettingsScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.BackupSettingsScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.ModuleSettingsScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.FunctionSettingsScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.MultimediaSettingsScreenDestination\n\n@Destination<RootGraph>\n@Composable\n@OptIn(ExperimentalMaterial3Api::class)\nfun SettingScreen(navigator: DestinationsNavigator) {\n    val state by APApplication.apStateLiveData.observeAsState(APApplication.State.UNKNOWN_STATE)\n    val aPatchReady =\n        (state == APApplication.State.ANDROIDPATCH_INSTALLING || state == APApplication.State.ANDROIDPATCH_INSTALLED || state == APApplication.State.ANDROIDPATCH_NEED_UPDATE)\n\n    var showDevDialog by rememberSaveable { mutableStateOf(false) }\n\n    DeveloperInfo(\n        showDialog = showDevDialog\n    ) {\n        showDevDialog = false\n    }\n\n    val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()\n\n    Scaffold(\n        topBar = {\n            TopAppBar(\n                title = {\n                    Text(\n                        text = stringResource(R.string.settings),\n                        style = MaterialTheme.typography.titleLarge,\n                        fontWeight = FontWeight.SemiBold,\n                    )\n                },\n                actions = {\n                    IconButton(onClick = { showDevDialog = true }) {\n                        Icon(Icons.Outlined.Info, contentDescription = null)\n                    }\n                    IconButton(onClick = { navigator.navigate(FunctionSettingsScreenDestination) }) {\n                        Icon(Icons.Filled.Tune, contentDescription = null)\n                    }\n                },\n                scrollBehavior = scrollBehavior,\n            )\n        },\n        containerColor = Color.Transparent,\n    ) { paddingValues ->\n        LazyColumn(\n            modifier = Modifier\n                .fillMaxSize()\n                .padding(paddingValues)\n                .nestedScroll(scrollBehavior.nestedScrollConnection),\n        ) {\n            item { Spacer(Modifier.height(8.dp)) }\n\n            item {\n                SplicedColumnGroup {\n                    item {\n                        SplicedSettingsItem(\n                            icon = Icons.Filled.Settings,\n                            title = stringResource(R.string.settings_category_general),\n                            summary = stringResource(R.string.settings_category_general_summary),\n                            onClick = { navigator.navigate(GeneralSettingsScreenDestination) },\n                        )\n                    }\n                    item {\n                        SplicedSettingsItem(\n                            icon = Icons.Filled.Palette,\n                            title = stringResource(R.string.settings_category_appearance),\n                            summary = stringResource(R.string.settings_category_appearance_summary),\n                            onClick = { navigator.navigate(AppearanceSettingsScreenDestination) },\n                        )\n                    }\n                    item {\n                        SplicedSettingsItem(\n                            icon = Icons.Filled.Visibility,\n                            title = stringResource(R.string.settings_category_behavior),\n                            summary = stringResource(R.string.settings_category_behavior_summary),\n                            onClick = { navigator.navigate(BehaviorSettingsScreenDestination) },\n                        )\n                    }\n                    item {\n                        SplicedSettingsItem(\n                            icon = Icons.Filled.Security,\n                            title = stringResource(R.string.settings_category_security),\n                            summary = stringResource(R.string.settings_category_security_summary),\n                            onClick = { navigator.navigate(SecuritySettingsScreenDestination) },\n                        )\n                    }\n                    item(visible = aPatchReady) {\n                        SplicedSettingsItem(\n                            icon = Icons.Filled.Cloud,\n                            title = stringResource(R.string.settings_category_backup),\n                            summary = stringResource(R.string.settings_category_backup_summary),\n                            onClick = { navigator.navigate(BackupSettingsScreenDestination) },\n                        )\n                    }\n                    item(visible = aPatchReady) {\n                        SplicedSettingsItem(\n                            icon = Icons.Filled.Extension,\n                            title = stringResource(R.string.settings_category_module),\n                            summary = stringResource(R.string.settings_category_module_summary),\n                            onClick = { navigator.navigate(ModuleSettingsScreenDestination) },\n                        )\n                    }\n                    item {\n                        SplicedSettingsItem(\n                            icon = Icons.Filled.MusicNote,\n                            title = stringResource(R.string.settings_category_multimedia),\n                            summary = stringResource(R.string.settings_category_multimedia_summary),\n                            onClick = { navigator.navigate(MultimediaSettingsScreenDestination) },\n                        )\n                    }\n                }\n            }\n\n            item {\n                Spacer(Modifier.height(16.dp))\n                NavigationBarsSpacer()\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun SplicedSettingsItem(\n    icon: ImageVector,\n    title: String,\n    summary: String,\n    onClick: () -> Unit,\n) {\n    Row(\n        modifier = Modifier\n            .fillMaxWidth()\n            .clickable(onClick = onClick)\n            .padding(horizontal = 24.dp, vertical = 14.dp),\n        verticalAlignment = Alignment.CenterVertically,\n    ) {\n        Icon(\n            imageVector = icon,\n            contentDescription = null,\n            tint = MaterialTheme.colorScheme.onSurfaceVariant,\n            modifier = Modifier.size(24.dp),\n        )\n\n        Spacer(Modifier.width(20.dp))\n\n        Column(modifier = Modifier.weight(1f)) {\n            Text(\n                text = title,\n                style = MaterialTheme.typography.bodyLarge,\n                color = MaterialTheme.colorScheme.onSurface,\n            )\n            Text(\n                text = summary,\n                style = MaterialTheme.typography.bodySmall,\n                color = MaterialTheme.colorScheme.onSurfaceVariant,\n            )\n        }\n\n        Icon(\n            imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,\n            contentDescription = null,\n            tint = MaterialTheme.colorScheme.onSurfaceVariant,\n            modifier = Modifier.size(20.dp),\n        )\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun DeveloperInfo(\n    showDialog: Boolean,\n    onDismissRequest: () -> Unit\n) {\n    val context = LocalContext.current\n    val uriHandler = LocalUriHandler.current\n    val githubUrl = \"https://github.com/LyraVoid/FolkPatch\"\n    val telegramUrl = \"https://t.me/FolkPatch\"\n    val sociabuzzUrl = \"https://ifdian.net/a/matsuzaka_yuki\"\n\n    if (showDialog) {\n        ModalBottomSheet(\n            sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),\n            containerColor = MaterialTheme.colorScheme.surfaceContainerLowest,\n            onDismissRequest = onDismissRequest\n        ) {\n            Column(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .padding(24.dp),\n                horizontalAlignment = Alignment.CenterHorizontally,\n                verticalArrangement = Arrangement.Center\n            ) {\n                Box(\n                    modifier = Modifier\n                        .size(110.dp)\n                        .shadow(8.dp, CircleShape)\n                        .clip(CircleShape)\n                        .background(MaterialTheme.colorScheme.surfaceContainerHighest),\n                    contentAlignment = Alignment.Center\n                ) {\n                    AsyncImage(\n                        model = ImageRequest.Builder(context)\n                            .data(\"http://q.qlogo.cn/headimg_dl?dst_uin=3231515355&spec=640&img_type=jpg\")\n                            .crossfade(true)\n                            .memoryCachePolicy(CachePolicy.DISABLED)\n                            .diskCachePolicy(CachePolicy.DISABLED)\n                            .build(),\n                        contentDescription = \"Developer Profile Picture\",\n                        contentScale = ContentScale.Crop,\n                        modifier = Modifier\n                            .fillMaxSize()\n                            .clip(CircleShape)\n                            .background(MaterialTheme.colorScheme.surfaceContainerHighest)\n                    )\n                }\n\n                Spacer(modifier = Modifier.height(16.dp))\n\n                Text(\n                    text = \"Matsuzaka Yuki\",\n                    fontSize = 20.sp,\n                    fontWeight = FontWeight.Bold\n                )\n                Text(\n                    text = stringResource(R.string.developer_and_maintainer),\n                    fontSize = 15.sp,\n                    color = MaterialTheme.colorScheme.primary\n                )\n\n                Spacer(modifier = Modifier.height(16.dp))\n\n                Text(\n                    text = \"\\\"美しい世界を見てきましょう\\\"\",\n                    textAlign = TextAlign.Center,\n                    style = MaterialTheme.typography.bodyMedium,\n                    color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.8f),\n                    modifier = Modifier.padding(horizontal = 16.dp)\n                )\n\n                Spacer(modifier = Modifier.height(24.dp))\n\n                Row(\n                    modifier = Modifier.padding(top = 16.dp),\n                    horizontalArrangement = Arrangement.spacedBy(16.dp)\n                ) {\n                    FilledTonalButton(\n                        onClick = { uriHandler.openUri(githubUrl) },\n                        modifier = Modifier.height(38.dp)\n                    ) {\n                        Icon(\n                            painter = painterResource(id = R.drawable.github),\n                            contentDescription = stringResource(R.string.github),\n                            modifier = Modifier.size(20.dp)\n                        )\n                        Spacer(Modifier.width(8.dp))\n                        Text(stringResource(R.string.github))\n                    }\n\n                    FilledTonalButton(\n                        onClick = { uriHandler.openUri(telegramUrl) },\n                        modifier = Modifier.height(38.dp)\n                    ) {\n                        Icon(\n                            painter = painterResource(id = R.drawable.telegram),\n                            contentDescription = stringResource(R.string.telegram),\n                            modifier = Modifier.size(20.dp)\n                        )\n                        Spacer(Modifier.width(8.dp))\n                        Text(stringResource(R.string.telegram))\n                    }\n                }\n                Spacer(modifier = Modifier.height(16.dp))\n\n                FilledTonalButton(\n                    onClick = { uriHandler.openUri(sociabuzzUrl) },\n                    modifier = Modifier.height(38.dp)\n                ) {\n                    Icon(\n                        imageVector = Icons.Filled.Coffee,\n                        contentDescription = stringResource(R.string.support_or_donate),\n                        modifier = Modifier.size(20.dp)\n                    )\n                    Spacer(Modifier.width(8.dp))\n                    Text(stringResource(R.string.support_or_donate))\n                }\n\n                Spacer(modifier = Modifier.height(16.dp))\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/SuAuditLogScreen.kt",
    "content": "package me.bmax.apatch.ui.screen\n\nimport androidx.compose.animation.Crossfade\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.filled.DeleteOutline\nimport androidx.compose.material3.BasicAlertDialog\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Surface\nimport androidx.compose.material3.Tab\nimport androidx.compose.material3.TabRow\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.SpanStyle\nimport androidx.compose.ui.text.buildAnnotatedString\nimport androidx.compose.ui.text.font.FontFamily\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.withStyle\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.window.DialogProperties\nimport coil.compose.AsyncImage\nimport coil.request.ImageRequest\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.component.splicedLazyColumnGroup\nimport me.bmax.apatch.util.SuAuditLog\nimport java.text.SimpleDateFormat\nimport java.util.Date\nimport java.util.Locale\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Destination<RootGraph>\n@Composable\nfun SuAuditLogScreen(navigator: DestinationsNavigator) {\n    val context = LocalContext.current\n    var showClearDialog by remember { mutableStateOf(false) }\n    var selectedTab by remember { mutableIntStateOf(0) }\n\n    var kernelEntries by remember { mutableStateOf(SuAuditLog.getKernelEntries()) }\n    var appEntries by remember { mutableStateOf(SuAuditLog.getAppEntries()) }\n\n    if (showClearDialog) {\n        SuAuditClearDialog(\n            onDismiss = { showClearDialog = false },\n            onConfirm = {\n                SuAuditLog.clearEntries()\n                kernelEntries = SuAuditLog.getKernelEntries()\n                appEntries = SuAuditLog.getAppEntries()\n                showClearDialog = false\n            }\n        )\n    }\n\n    val tabs = listOf(\n        stringResource(R.string.su_audit_tab_usage),\n        stringResource(R.string.su_audit_tab_operations),\n    )\n\n    Scaffold(\n        containerColor = Color.Transparent,\n        topBar = {\n            TopAppBar(\n                title = {\n                    Text(\n                        stringResource(R.string.su_audit_log_title),\n                        style = MaterialTheme.typography.titleLarge,\n                        fontWeight = FontWeight.SemiBold,\n                    )\n                },\n                navigationIcon = {\n                    IconButton(onClick = { navigator.popBackStack() }) {\n                        Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)\n                    }\n                },\n                actions = {\n                    if (kernelEntries.isNotEmpty() || appEntries.isNotEmpty()) {\n                        IconButton(onClick = { showClearDialog = true }) {\n                            Icon(Icons.Filled.DeleteOutline, contentDescription = stringResource(R.string.su_audit_log_clear))\n                        }\n                    }\n                },\n            )\n        },\n    ) { innerPadding ->\n        Column(\n            modifier = Modifier\n                .fillMaxSize()\n                .padding(innerPadding),\n        ) {\n            TabRow(selectedTabIndex = selectedTab) {\n                tabs.forEachIndexed { index, title ->\n                    Tab(\n                        selected = selectedTab == index,\n                        onClick = { selectedTab = index },\n                        text = { Text(title, style = MaterialTheme.typography.bodyMedium) },\n                    )\n                }\n            }\n\n            Crossfade(targetState = selectedTab, label = \"auditTab\") { tab ->\n                when (tab) {\n                    0 -> KernelAuditList(entries = kernelEntries)\n                    1 -> AppAuditList(entries = appEntries)\n                }\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun KernelAuditList(entries: List<SuAuditLog.AuditEntry.KernelEntry>) {\n    if (entries.isEmpty()) {\n        EmptyAuditView()\n    } else {\n        LazyColumn(\n            modifier = Modifier.fillMaxSize(),\n            contentPadding = PaddingValues(top = 8.dp, bottom = 16.dp),\n        ) {\n            splicedLazyColumnGroup(\n                items = entries,\n                key = { _, entry -> entry.timestamp },\n                contentType = { _, _ -> \"KernelEntry\" },\n            ) { _, entry ->\n                val pm = LocalContext.current.packageManager\n                val appLabel = remember(entry.uid) {\n                    try {\n                        val packages = pm.getPackagesForUid(entry.uid)\n                        if (packages != null && packages.isNotEmpty()) {\n                            pm.getApplicationInfo(packages[0], 0).loadLabel(pm).toString()\n                        } else {\n                            \"UID ${entry.uid}\"\n                        }\n                    } catch (_: Exception) {\n                        \"UID ${entry.uid}\"\n                    }\n                }\n\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(horizontal = 16.dp, vertical = 12.dp),\n                    verticalAlignment = Alignment.CenterVertically,\n                ) {\n                    // Icon\n                    val appInfo = remember(entry.uid) {\n                        try {\n                            val packages = pm.getPackagesForUid(entry.uid)\n                            if (packages != null && packages.isNotEmpty()) {\n                                pm.getPackageInfo(packages[0], 0)\n                            } else null\n                        } catch (_: Exception) {\n                            null\n                        }\n                    }\n                    if (appInfo != null) {\n                        AsyncImage(\n                            model = ImageRequest.Builder(LocalContext.current).data(appInfo)\n                                .crossfade(true).build(),\n                            contentDescription = null,\n                            modifier = Modifier.size(40.dp).clip(CircleShape),\n                        )\n                    } else {\n                        Box(\n                            modifier = Modifier\n                                .size(40.dp)\n                                .clip(CircleShape)\n                                .background(MaterialTheme.colorScheme.surfaceContainerHigh),\n                            contentAlignment = Alignment.Center,\n                        ) {\n                            Text(\n                                text = entry.uid.toString().take(2),\n                                style = MaterialTheme.typography.labelMedium,\n                                color = MaterialTheme.colorScheme.onSurfaceVariant,\n                            )\n                        }\n                    }\n\n                    Spacer(modifier = Modifier.width(16.dp))\n\n                    Column(modifier = Modifier.weight(1f)) {\n                        Text(\n                            buildAnnotatedString {\n                                append(appLabel)\n                                append(\"  \")\n                                withStyle(SpanStyle(color = MaterialTheme.colorScheme.primary)) {\n                                    append(if (entry.toUid == 0) \"root\" else \"uid ${entry.toUid}\")\n                                }\n                            },\n                            style = MaterialTheme.typography.bodyMedium,\n                            fontWeight = FontWeight.Medium,\n                        )\n                        Text(\n                            \"${entry.comm}  PID ${entry.pid}\",\n                            style = MaterialTheme.typography.bodySmall.copy(fontFamily = FontFamily.Monospace),\n                            color = MaterialTheme.colorScheme.onSurfaceVariant,\n                        )\n                    }\n                }\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun AppAuditList(entries: List<SuAuditLog.AuditEntry.AppEntry>) {\n    if (entries.isEmpty()) {\n        EmptyAuditView()\n    } else {\n        LazyColumn(\n            modifier = Modifier.fillMaxSize(),\n            contentPadding = PaddingValues(top = 8.dp, bottom = 16.dp),\n        ) {\n            splicedLazyColumnGroup(\n                items = entries,\n                key = { _, entry -> \"${entry.packageName}_${entry.timestamp}\" },\n                contentType = { _, _ -> \"AppEntry\" },\n            ) { _, entry ->\n                val pm = LocalContext.current.packageManager\n                val appLabel = remember(entry.packageName) {\n                    try {\n                        pm.getApplicationInfo(entry.packageName, 0).loadLabel(pm).toString()\n                    } catch (_: Exception) {\n                        entry.packageName\n                    }\n                }\n\n                val dateFormat = remember {\n                    SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\", Locale.getDefault())\n                }\n\n                val (actionLabel, actionColor) = when (entry.action) {\n                    \"GRANT\" -> stringResource(R.string.su_audit_action_grant) to MaterialTheme.colorScheme.primary\n                    \"REVOKE\" -> stringResource(R.string.su_audit_action_revoke) to MaterialTheme.colorScheme.error\n                    else -> stringResource(R.string.su_audit_action_exclude) to MaterialTheme.colorScheme.tertiary\n                }\n\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(horizontal = 16.dp, vertical = 12.dp),\n                    verticalAlignment = Alignment.CenterVertically,\n                ) {\n                    // Icon\n                    val appInfo = remember(entry.packageName) {\n                        try {\n                            pm.getPackageInfo(entry.packageName, 0)\n                        } catch (_: Exception) {\n                            null\n                        }\n                    }\n                    if (appInfo != null) {\n                        AsyncImage(\n                            model = ImageRequest.Builder(LocalContext.current).data(appInfo)\n                                .crossfade(true).build(),\n                            contentDescription = null,\n                            modifier = Modifier.size(40.dp).clip(CircleShape),\n                        )\n                    } else {\n                        Box(\n                            modifier = Modifier\n                                .size(40.dp)\n                                .clip(CircleShape)\n                                .background(MaterialTheme.colorScheme.surfaceContainerHigh),\n                            contentAlignment = Alignment.Center,\n                        ) {\n                            Text(\n                                text = entry.packageName.take(1).uppercase(),\n                                style = MaterialTheme.typography.labelMedium,\n                                color = MaterialTheme.colorScheme.onSurfaceVariant,\n                            )\n                        }\n                    }\n\n                    Spacer(modifier = Modifier.width(16.dp))\n\n                    Column(modifier = Modifier.weight(1f)) {\n                        Text(\n                            buildAnnotatedString {\n                                append(appLabel)\n                                append(\"  \")\n                                withStyle(SpanStyle(color = actionColor, fontWeight = FontWeight.Medium)) {\n                                    append(actionLabel)\n                                }\n                            },\n                            style = MaterialTheme.typography.bodyMedium,\n                            fontWeight = FontWeight.Medium,\n                        )\n                        Text(\n                            dateFormat.format(Date(entry.timestamp)),\n                            style = MaterialTheme.typography.bodySmall,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant,\n                        )\n                    }\n                }\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun EmptyAuditView() {\n    Box(\n        modifier = Modifier.fillMaxSize(),\n        contentAlignment = Alignment.Center,\n    ) {\n        Text(\n            text = stringResource(R.string.su_audit_log_empty),\n            style = MaterialTheme.typography.bodyLarge,\n            color = MaterialTheme.colorScheme.onSurfaceVariant,\n        )\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun SuAuditClearDialog(\n    onDismiss: () -> Unit,\n    onConfirm: () -> Unit,\n) {\n    BasicAlertDialog(\n        onDismissRequest = onDismiss,\n        properties = DialogProperties(\n            decorFitsSystemWindows = true,\n            usePlatformDefaultWidth = false,\n        ),\n    ) {\n        Surface(\n            modifier = Modifier.width(320.dp),\n            shape = RoundedCornerShape(20.dp),\n            tonalElevation = androidx.compose.material3.AlertDialogDefaults.TonalElevation,\n            color = androidx.compose.material3.AlertDialogDefaults.containerColor,\n        ) {\n            Column(modifier = Modifier.padding(24.dp)) {\n                Text(\n                    text = stringResource(R.string.su_audit_log_clear),\n                    style = MaterialTheme.typography.headlineSmall,\n                )\n                Spacer(Modifier.height(16.dp))\n                Text(\n                    text = stringResource(R.string.su_audit_log_clear_confirm),\n                    style = MaterialTheme.typography.bodyMedium,\n                )\n                Spacer(Modifier.height(24.dp))\n                Row(\n                    modifier = Modifier.fillMaxWidth(),\n                    horizontalArrangement = Arrangement.End,\n                ) {\n                    TextButton(onClick = onDismiss) {\n                        Text(text = android.R.string.cancel.let { stringResource(it) })\n                    }\n                    TextButton(onClick = onConfirm) {\n                        Text(text = stringResource(R.string.su_audit_log_clear))\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/SuperUser.kt",
    "content": "package me.bmax.apatch.ui.screen\n\nimport androidx.activity.compose.rememberLauncherForActivityResult\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.compose.animation.Crossfade\nimport androidx.compose.animation.core.animateDpAsState\nimport androidx.compose.animation.core.tween\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.combinedClickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.ExperimentalLayoutApi\nimport androidx.compose.foundation.layout.FlowRow\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.offset\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.layout.wrapContentHeight\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material.ExperimentalMaterialApi\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight\nimport androidx.compose.material.icons.automirrored.filled.PlaylistAddCheck\nimport androidx.compose.material.icons.filled.Close\nimport androidx.compose.material.icons.filled.History\nimport androidx.compose.material.icons.filled.MoreVert\nimport androidx.compose.material.icons.filled.Security\nimport androidx.compose.material.icons.filled.Terminal\nimport androidx.compose.material3.AlertDialogDefaults\nimport androidx.compose.material3.BasicAlertDialog\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.ExperimentalMaterial3ExpressiveApi\nimport androidx.compose.material3.FloatingActionButton\nimport androidx.compose.material3.FloatingActionButtonMenu\nimport androidx.compose.material3.FloatingActionButtonMenuItem\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.ListItem\nimport androidx.compose.material3.ListItemDefaults\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.ModalBottomSheet\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Surface\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.material3.contentColorFor\nimport androidx.compose.material3.pulltorefresh.PullToRefreshBox\nimport androidx.compose.material3.pulltorefresh.PullToRefreshDefaults\nimport androidx.compose.material3.pulltorefresh.rememberPullToRefreshState\nimport androidx.compose.material3.rememberModalBottomSheetState\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.platform.LocalConfiguration\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalView\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.TextStyle\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.compose.ui.window.DialogProperties\nimport androidx.compose.ui.window.DialogWindowProvider\nimport androidx.compose.ui.window.SecureFlagPolicy\nimport androidx.lifecycle.viewmodel.compose.viewModel\nimport coil.compose.AsyncImage\nimport coil.request.ImageRequest\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport com.ramcosta.composedestinations.generated.destinations.AppProfileScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.ScriptLibraryScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.SuAuditLogScreenDestination\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport kotlinx.coroutines.launch\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.Natives\nimport me.bmax.apatch.R\nimport me.bmax.apatch.apApp\nimport me.bmax.apatch.ui.LocalBottomBarVisible\nimport me.bmax.apatch.ui.LocalIsFloatingNavMode\nimport me.bmax.apatch.ui.component.ExpressiveSwitch\nimport me.bmax.apatch.ui.component.SearchAppBar\nimport me.bmax.apatch.ui.component.SwitchItem\nimport me.bmax.apatch.ui.component.WallpaperAwareDropdownMenu\nimport me.bmax.apatch.ui.component.WallpaperAwareDropdownMenuItem\nimport me.bmax.apatch.ui.component.splicedLazyColumnGroup\nimport me.bmax.apatch.ui.viewmodel.SuperUserViewModel\nimport me.bmax.apatch.util.PkgConfig\nimport me.bmax.apatch.util.SuAuditLog\nimport me.bmax.apatch.util.ui.APDialogBlurBehindUtils.Companion.setupWindowBlurListener\nimport me.bmax.apatch.util.ui.showToast\n\n\n@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)\n@Destination<RootGraph>\n@Composable\nfun SuperUserScreen(navigator: DestinationsNavigator) {\n    val prefs = APApplication.sharedPreferences\n    val useLegacySuPage = prefs.getBoolean(\"use_legacy_su_page\", false)\n\n    SuperUserScreenModern(navigator, useLegacySuPage)\n}\n\n@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)\n@Composable\nprivate fun SuperUserScreenModern(navigator: DestinationsNavigator, useLegacySuPage: Boolean) {\n    val viewModel = viewModel<SuperUserViewModel>()\n    val scope = rememberCoroutineScope()\n    val context = LocalContext.current\n\n    val backupLauncher = rememberLauncherForActivityResult(\n        contract = ActivityResultContracts.CreateDocument(\"application/json\")\n    ) { uri ->\n        uri?.let { viewModel.backupAppList(context, it) }\n    }\n\n    val restoreLauncher = rememberLauncherForActivityResult(\n        contract = ActivityResultContracts.OpenDocument()\n    ) { uri ->\n        uri?.let { viewModel.restoreAppList(context, it) }\n    }\n\n    var showBatchExcludeDialog by remember { mutableStateOf(false) }\n    var showAppActionDialog by remember { mutableStateOf(false) }\n    var selectedApp by remember { mutableStateOf<SuperUserViewModel.AppInfo?>(null) }\n    var showOptionsSheet by remember { mutableStateOf(false) }\n\n    if (showBatchExcludeDialog) {\n        BatchExcludeDialog(\n            onDismiss = { showBatchExcludeDialog = false },\n            onExclude = {\n                viewModel.excludeAll()\n                showBatchExcludeDialog = false\n            },\n            onReverseExclude = {\n                viewModel.reverseExcludeAll()\n                showBatchExcludeDialog = false\n            }\n        )\n    }\n\n    if (showAppActionDialog && selectedApp != null) {\n        AppActionDialog(\n            app = selectedApp!!,\n            onDismiss = { showAppActionDialog = false },\n            onLaunch = {\n                val success = viewModel.launchApp(context, selectedApp!!.packageName)\n                if (success) {\n                    scope.launch {\n                        showToast(context, context.getString(R.string.su_app_action_launch_success, selectedApp!!.label))\n                    }\n                } else {\n                    scope.launch {\n                        showToast(context, context.getString(R.string.su_app_action_failed, selectedApp!!.label))\n                    }\n                }\n                showAppActionDialog = false\n            },\n            onForceStop = {\n                val success = viewModel.forceStopApp(selectedApp!!.packageName)\n                if (success) {\n                    scope.launch {\n                        showToast(context, context.getString(R.string.su_app_action_force_stop_success, selectedApp!!.label))\n                    }\n                } else {\n                    scope.launch {\n                        showToast(context, context.getString(R.string.su_app_action_failed, selectedApp!!.label))\n                    }\n                }\n                showAppActionDialog = false\n            }\n        )\n    }\n\n    if (showOptionsSheet) {\n        SuperUserOptionsSheet(\n            onDismiss = { showOptionsSheet = false },\n            onRefresh = {\n                scope.launch { viewModel.fetchAppList() }\n                showOptionsSheet = false\n            },\n            onToggleSystemApps = {\n                viewModel.showSystemApps = !viewModel.showSystemApps\n                showOptionsSheet = false\n            },\n            showSystemApps = viewModel.showSystemApps,\n            onBackup = {\n                backupLauncher.launch(\"FolkPatch_list_backup.json\")\n                showOptionsSheet = false\n            },\n            onRestore = {\n                restoreLauncher.launch(arrayOf(\"application/json\", \"*/*\"))\n                showOptionsSheet = false\n            },\n        )\n    }\n\n    LaunchedEffect(Unit) {\n        if (viewModel.appList.isEmpty()) {\n            viewModel.fetchAppList()\n        }\n    }\n\n    Scaffold(\n        topBar = {\n            SearchAppBar(\n                title = { Text(stringResource(R.string.su_title)) },\n                searchText = viewModel.search,\n                onSearchTextChange = { viewModel.search = it },\n                onClearClick = { viewModel.search = \"\" },\n                leadingActions = {\n                    IconButton(onClick = {\n                        showBatchExcludeDialog = true\n                    }) {\n                        Icon(Icons.AutoMirrored.Filled.PlaylistAddCheck, contentDescription = stringResource(R.string.su_batch_exclude_title))\n                    }\n                },\n                dropdownContent = {\n                    IconButton(onClick = { showOptionsSheet = true }) {\n                        Icon(\n                            imageVector = Icons.Filled.MoreVert,\n                            contentDescription = stringResource(id = R.string.settings)\n                        )\n                    }\n                },\n            )\n        },\n        floatingActionButton = run {\n            {\n                var fabExpanded by remember { mutableStateOf(false) }\n                val isFloatingMode = LocalIsFloatingNavMode.current\n\n                val fabContent: @Composable () -> Unit = {\n                    FloatingActionButtonMenu(\n                        expanded = fabExpanded,\n                        button = {\n                            FloatingActionButton(\n                                onClick = { fabExpanded = !fabExpanded },\n                                shape = CircleShape,\n                                contentColor = MaterialTheme.colorScheme.onPrimary.copy(alpha = 1f),\n                                containerColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                            ) {\n                                Crossfade(\n                                    targetState = fabExpanded,\n                                    animationSpec = tween(durationMillis = 200),\n                                    label = \"fabIconCrossfade\"\n                                ) { isExpanded ->\n                                    if (isExpanded) {\n                                        Icon(Icons.Filled.Close, contentDescription = null)\n                                    } else {\n                                        Icon(Icons.Filled.History, contentDescription = null)\n                                    }\n                                }\n                            }\n                        },\n                    ) {\n                        FloatingActionButtonMenuItem(\n                            onClick = {\n                                fabExpanded = false\n                                navigator.navigate(ScriptLibraryScreenDestination)\n                            },\n                            icon = {\n                                Icon(\n                                    Icons.Filled.Terminal,\n                                    contentDescription = null,\n                                    modifier = Modifier.size(18.dp)\n                                )\n                            },\n                            text = { Text(stringResource(R.string.script_library), style = MaterialTheme.typography.bodyMedium) },\n                        )\n                        FloatingActionButtonMenuItem(\n                            onClick = {\n                                fabExpanded = false\n                                navigator.navigate(SuAuditLogScreenDestination)\n                            },\n                            icon = {\n                                Icon(\n                                    Icons.Filled.History,\n                                    contentDescription = null,\n                                    modifier = Modifier.size(18.dp)\n                                )\n                            },\n                            text = { Text(stringResource(R.string.su_audit_log_title), style = MaterialTheme.typography.bodyMedium) },\n                        )\n                    }\n                }\n\n                val bottomBarVisible = LocalBottomBarVisible.current.value\n                val configuration = LocalConfiguration.current\n                val isLandscape = configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE\n                val animatedOffset by animateDpAsState(\n                    targetValue = if (isFloatingMode && bottomBarVisible && !isLandscape) (-88).dp else 0.dp,\n                    animationSpec = tween(durationMillis = 300),\n                    label = \"fabOffset\"\n                )\n                if (isFloatingMode) {\n                    Box(modifier = Modifier.offset(y = animatedOffset)) {\n                        fabContent()\n                    }\n                } else {\n                    fabContent()\n                }\n            }\n        }\n    ) { innerPadding ->\n        val pullToRefreshState = rememberPullToRefreshState()\n        PullToRefreshBox(\n            modifier = Modifier.padding(innerPadding),\n            onRefresh = { scope.launch { viewModel.fetchAppList() } },\n            isRefreshing = viewModel.isRefreshing,\n            state = pullToRefreshState,\n            indicator = { PullToRefreshDefaults.LoadingIndicator(state = pullToRefreshState, isRefreshing = viewModel.isRefreshing, modifier = Modifier.align(Alignment.TopCenter)) }\n        ) {\n            val filteredApps = viewModel.appList.filter { it.packageName != apApp.packageName }\n\n            LazyColumn(Modifier.fillMaxSize()) {\n                if (useLegacySuPage) {\n                    items(\n                        filteredApps,\n                        key = { it.packageName + it.uid }\n                    ) { app ->\n                        AppItemLegacy(app)\n                    }\n                } else {\n                    item { Spacer(Modifier.height(8.dp)) }\n                    splicedLazyColumnGroup(\n                        items = filteredApps,\n                        key = { _, app -> app.packageName + app.uid },\n                        contentType = { _, _ -> \"AppItem\" },\n                    ) { _, app ->\n                        AppItemM3E(\n                            app = app,\n                            onClick = {\n                                navigator.navigate(AppProfileScreenDestination(app.packageName, app.uid))\n                            },\n                            onLongClick = {\n                                selectedApp = app\n                                showAppActionDialog = true\n                            }\n                        )\n                    }\n                    item { Spacer(Modifier.height(if (LocalIsFloatingNavMode.current) 88.dp else 8.dp)) }\n                }\n            }\n        }\n    }\n}\n\n// ── M3E App Item ──────────────────────────────────────────────────────────\n\n@OptIn(ExperimentalLayoutApi::class)\n@Composable\nprivate fun AppItemM3E(\n    app: SuperUserViewModel.AppInfo,\n    onClick: () -> Unit,\n    onLongClick: () -> Unit,\n) {\n    val config = app.config\n    val rootGranted = config.allow != 0\n    val excludeApp = config.exclude == 1\n\n    Row(\n        modifier = Modifier\n            .fillMaxWidth()\n            .combinedClickable(\n                onClick = onClick,\n                onLongClick = onLongClick,\n            )\n            .padding(horizontal = 16.dp, vertical = 12.dp),\n        verticalAlignment = Alignment.CenterVertically,\n    ) {\n        AsyncImage(\n            model = ImageRequest.Builder(LocalContext.current)\n                .data(app.packageInfo)\n                .crossfade(true)\n                .build(),\n            contentDescription = app.label,\n            modifier = Modifier\n                .size(48.dp)\n                .clip(CircleShape),\n        )\n\n        Column(\n            modifier = Modifier\n                .weight(1f)\n                .padding(horizontal = 16.dp),\n        ) {\n            Text(\n                text = app.label,\n                style = MaterialTheme.typography.titleMedium,\n                fontWeight = FontWeight.SemiBold,\n                color = MaterialTheme.colorScheme.onSurface,\n            )\n            Text(\n                text = app.packageName,\n                style = MaterialTheme.typography.bodySmall,\n                color = MaterialTheme.colorScheme.onSurfaceVariant,\n            )\n            FlowRow(modifier = Modifier.padding(top = 4.dp)) {\n                if (rootGranted) {\n                    LabelText(label = \"ROOT\", containerColor = MaterialTheme.colorScheme.primaryContainer)\n                }\n                if (excludeApp) {\n                    LabelText(\n                        label = stringResource(id = R.string.su_pkg_excluded_label),\n                        containerColor = MaterialTheme.colorScheme.primaryContainer,\n                    )\n                }\n            }\n        }\n\n        Icon(\n            imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,\n            contentDescription = null,\n            tint = MaterialTheme.colorScheme.onSurfaceVariant,\n            modifier = Modifier.size(20.dp),\n        )\n    }\n}\n\n// ── Legacy App Item ───────────────────────────────────────────────────────\n\n@OptIn(ExperimentalLayoutApi::class)\n@Composable\nprivate fun AppItemLegacy(\n    app: SuperUserViewModel.AppInfo,\n) {\n    val config = app.config\n    var showEditProfile by remember { mutableStateOf(false) }\n    var rootGranted by remember { mutableStateOf(config.allow != 0) }\n    var excludeApp by remember { mutableIntStateOf(config.exclude) }\n\n    ListItem(\n        modifier = Modifier.clickable(onClick = {\n            if (!rootGranted) {\n                showEditProfile = !showEditProfile\n            } else {\n                rootGranted = false\n                config.allow = 0\n                Natives.revokeSu(app.uid)\n                PkgConfig.changeConfig(config)\n                SuAuditLog.logRevoke(app.packageName, app.uid)\n            }\n        }),\n        colors = ListItemDefaults.colors(containerColor = Color.Transparent),\n        headlineContent = { Text(app.label) },\n        leadingContent = {\n            AsyncImage(\n                model = ImageRequest.Builder(LocalContext.current).data(app.packageInfo)\n                    .crossfade(true).build(),\n                contentDescription = app.label,\n                modifier = Modifier\n                    .padding(4.dp)\n                    .width(48.dp)\n                    .height(48.dp)\n            )\n        },\n        supportingContent = {\n            Column {\n                Text(app.packageName)\n                FlowRow {\n                    if (excludeApp == 1) {\n                        LabelText(\n                            label = stringResource(id = R.string.su_pkg_excluded_label),\n                            containerColor = MaterialTheme.colorScheme.tertiaryContainer,\n                        )\n                    }\n                    if (rootGranted) {\n                        LabelText(label = config.profile.uid.toString())\n                        LabelText(label = config.profile.toUid.toString())\n                        LabelText(\n                            label = when {\n                                config.profile.scontext.isNotEmpty() -> config.profile.scontext\n                                else -> stringResource(id = R.string.su_selinux_via_hook)\n                            }\n                        )\n                    }\n                }\n            }\n        },\n        trailingContent = {\n            ExpressiveSwitch(checked = rootGranted, onCheckedChange = {\n                rootGranted = !rootGranted\n                if (rootGranted) {\n                    excludeApp = 0\n                    config.allow = 1\n                    config.exclude = 0\n                    config.profile.scontext = APApplication.MAGISK_SCONTEXT\n                } else {\n                    config.allow = 0\n                }\n                config.profile.uid = app.uid\n                PkgConfig.changeConfig(config)\n                if (config.allow == 1) {\n                    Natives.grantSu(app.uid, 0, config.profile.scontext)\n                    Natives.setUidExclude(app.uid, 0)\n                    SuAuditLog.logGrant(app.packageName, app.uid)\n                } else {\n                    Natives.revokeSu(app.uid)\n                    SuAuditLog.logRevoke(app.packageName, app.uid)\n                }\n            })\n        },\n    )\n\n    AnimatedVisibility(\n        visible = showEditProfile && !rootGranted,\n        modifier = Modifier.fillMaxWidth()\n    ) {\n        SwitchItem(\n            icon = Icons.Filled.Security,\n            title = stringResource(id = R.string.su_pkg_excluded_setting_title),\n            summary = stringResource(id = R.string.su_pkg_excluded_setting_summary),\n            checked = excludeApp == 1,\n            onCheckedChange = {\n                if (it) {\n                    excludeApp = 1\n                    config.allow = 0\n                    config.profile.scontext = APApplication.DEFAULT_SCONTEXT\n                    Natives.revokeSu(app.uid)\n                    SuAuditLog.logExclude(app.packageName, app.uid)\n                } else {\n                    excludeApp = 0\n                    SuAuditLog.logRevoke(app.packageName, app.uid)\n                }\n                config.exclude = excludeApp\n                config.profile.uid = app.uid\n                PkgConfig.changeConfig(config)\n                Natives.setUidExclude(app.uid, excludeApp)\n            },\n        )\n    }\n}\n\n// ── Label Text Badge ──────────────────────────────────────────────────────\n\n@Composable\nfun LabelText(\n    label: String,\n    containerColor: Color = MaterialTheme.colorScheme.surfaceVariant,\n) {\n    Surface(\n        modifier = Modifier.padding(end = 4.dp),\n        shape = RoundedCornerShape(4.dp),\n        color = containerColor,\n    ) {\n        Text(\n            text = label,\n            modifier = Modifier.padding(horizontal = 6.dp, vertical = 1.dp),\n            style = MaterialTheme.typography.labelSmall.copy(fontSize = 10.sp),\n            color = contentColorFor(containerColor),\n            fontWeight = FontWeight.Medium,\n        )\n    }\n}\n\n// ── Options Bottom Sheet ──────────────────────────────────────────────────\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun SuperUserOptionsSheet(\n    onDismiss: () -> Unit,\n    onRefresh: () -> Unit,\n    onToggleSystemApps: () -> Unit,\n    showSystemApps: Boolean,\n    onBackup: () -> Unit,\n    onRestore: () -> Unit,\n) {\n    val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)\n\n    ModalBottomSheet(\n        onDismissRequest = onDismiss,\n        sheetState = sheetState,\n        containerColor = MaterialTheme.colorScheme.surfaceContainerLow,\n    ) {\n        Column(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(horizontal = 16.dp)\n                .padding(bottom = 32.dp),\n        ) {\n            Text(\n                text = stringResource(R.string.su_title),\n                style = MaterialTheme.typography.titleMedium,\n                fontWeight = FontWeight.SemiBold,\n                modifier = Modifier.padding(bottom = 16.dp),\n            )\n\n            // Refresh\n            Surface(\n                onClick = onRefresh,\n                shape = RoundedCornerShape(12.dp),\n                color = Color.Transparent,\n            ) {\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(12.dp),\n                    verticalAlignment = Alignment.CenterVertically,\n                ) {\n                    Text(\n                        text = stringResource(R.string.su_refresh),\n                        style = MaterialTheme.typography.bodyLarge,\n                    )\n                }\n            }\n\n            // Show/Hide System Apps\n            Surface(\n                onClick = onToggleSystemApps,\n                shape = RoundedCornerShape(12.dp),\n                color = Color.Transparent,\n            ) {\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(12.dp),\n                    verticalAlignment = Alignment.CenterVertically,\n                ) {\n                    Text(\n                        text = if (showSystemApps) {\n                            stringResource(R.string.su_hide_system_apps)\n                        } else {\n                            stringResource(R.string.su_show_system_apps)\n                        },\n                        style = MaterialTheme.typography.bodyLarge,\n                    )\n                }\n            }\n\n            // Backup\n            Surface(\n                onClick = onBackup,\n                shape = RoundedCornerShape(12.dp),\n                color = Color.Transparent,\n            ) {\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(12.dp),\n                    verticalAlignment = Alignment.CenterVertically,\n                ) {\n                    Text(\n                        text = stringResource(R.string.su_backup_list),\n                        style = MaterialTheme.typography.bodyLarge,\n                    )\n                }\n            }\n\n            // Restore\n            Surface(\n                onClick = onRestore,\n                shape = RoundedCornerShape(12.dp),\n                color = Color.Transparent,\n            ) {\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(12.dp),\n                    verticalAlignment = Alignment.CenterVertically,\n                ) {\n                    Text(\n                        text = stringResource(R.string.su_restore_list),\n                        style = MaterialTheme.typography.bodyLarge,\n                    )\n                }\n            }\n        }\n    }\n}\n\n// ── Dialogs ───────────────────────────────────────────────────────────────\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun BatchExcludeDialog(\n    onDismiss: () -> Unit,\n    onExclude: () -> Unit,\n    onReverseExclude: () -> Unit\n) {\n    val title = stringResource(R.string.su_batch_exclude_title)\n    val content = stringResource(R.string.su_batch_exclude_content)\n    val excludeText = stringResource(R.string.su_exclude_btn)\n    val reverseText = stringResource(R.string.su_exclude_reverse_btn)\n    val cancelText = stringResource(android.R.string.cancel)\n\n    BasicAlertDialog(\n        onDismissRequest = onDismiss,\n        properties = DialogProperties(\n            decorFitsSystemWindows = true,\n            usePlatformDefaultWidth = false,\n            securePolicy = SecureFlagPolicy.SecureOff,\n            dismissOnClickOutside = false\n        )\n    ) {\n        Surface(\n            modifier = Modifier\n                .width(320.dp)\n                .wrapContentHeight(),\n            shape = RoundedCornerShape(20.dp),\n            tonalElevation = AlertDialogDefaults.TonalElevation,\n            color = AlertDialogDefaults.containerColor,\n        ) {\n            Column(modifier = Modifier.padding(PaddingValues(all = 24.dp))) {\n                Box(\n                    Modifier\n                        .padding(PaddingValues(bottom = 16.dp))\n                        .align(Alignment.Start)\n                ) {\n                    Text(text = title, style = MaterialTheme.typography.headlineSmall)\n                }\n                Box(\n                    Modifier\n                        .weight(weight = 1f, fill = false)\n                        .padding(PaddingValues(bottom = 24.dp))\n                        .align(Alignment.Start)\n                ) {\n                    Text(text = content, style = MaterialTheme.typography.bodyMedium)\n                }\n                Row(\n                    modifier = Modifier.fillMaxWidth(),\n                    horizontalArrangement = Arrangement.End\n                ) {\n                    TextButton(onClick = onDismiss) {\n                        Text(text = cancelText)\n                    }\n                    TextButton(onClick = onExclude) {\n                        Text(text = excludeText)\n                    }\n                    TextButton(onClick = onReverseExclude) {\n                        Text(text = reverseText)\n                    }\n                }\n            }\n            val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n            setupWindowBlurListener(dialogWindowProvider.window)\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun AppActionDialog(\n    app: SuperUserViewModel.AppInfo,\n    onDismiss: () -> Unit,\n    onLaunch: () -> Unit,\n    onForceStop: () -> Unit\n) {\n    val title = stringResource(R.string.su_app_action_title)\n    val content = stringResource(R.string.su_app_action_content)\n    val launchText = stringResource(R.string.su_app_action_launch)\n    val forceStopText = stringResource(R.string.su_app_action_force_stop)\n    val cancelText = stringResource(android.R.string.cancel)\n\n    BasicAlertDialog(\n        onDismissRequest = onDismiss,\n        properties = DialogProperties(\n            decorFitsSystemWindows = true,\n            usePlatformDefaultWidth = false,\n            securePolicy = SecureFlagPolicy.SecureOff,\n            dismissOnClickOutside = true\n        )\n    ) {\n        Surface(\n            modifier = Modifier\n                .width(320.dp)\n                .wrapContentHeight(),\n            shape = RoundedCornerShape(20.dp),\n            tonalElevation = AlertDialogDefaults.TonalElevation,\n            color = AlertDialogDefaults.containerColor,\n        ) {\n            Column(modifier = Modifier.padding(PaddingValues(all = 24.dp))) {\n                Box(\n                    Modifier\n                        .padding(PaddingValues(bottom = 16.dp))\n                        .align(Alignment.Start)\n                ) {\n                    Text(text = title, style = MaterialTheme.typography.headlineSmall)\n                }\n                Box(\n                    Modifier\n                        .weight(weight = 1f, fill = false)\n                        .padding(PaddingValues(bottom = 24.dp))\n                        .align(Alignment.Start)\n                ) {\n                    Text(\n                        text = content,\n                        style = MaterialTheme.typography.bodyMedium,\n                        color = MaterialTheme.colorScheme.onSurfaceVariant\n                    )\n                }\n                Row(\n                    modifier = Modifier.fillMaxWidth(),\n                    horizontalArrangement = Arrangement.End\n                ) {\n                    TextButton(onClick = onDismiss) {\n                        Text(text = cancelText)\n                    }\n                    TextButton(onClick = onLaunch) {\n                        Text(text = launchText)\n                    }\n                    TextButton(onClick = onForceStop) {\n                        Text(text = forceStopText)\n                    }\n                }\n            }\n            val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n            setupWindowBlurListener(dialogWindowProvider.window)\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/ThemeStore.kt",
    "content": "package me.bmax.apatch.ui.screen\n\nimport android.content.Intent\nimport android.net.Uri\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.horizontalScroll\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid\nimport androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells\nimport androidx.compose.foundation.lazy.staggeredgrid.items\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.filled.*\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.window.DialogProperties\nimport androidx.compose.animation.core.animateFloatAsState\nimport androidx.compose.material3.*\nimport androidx.compose.runtime.*\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.layout.ContentScale\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.unit.dp\nimport androidx.lifecycle.viewmodel.compose.viewModel\nimport coil.compose.AsyncImage\nimport coil.request.CachePolicy\nimport coil.request.ImageRequest\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport com.ramcosta.composedestinations.generated.destinations.MyThemesScreenDestination\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.launch\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.component.AppLoadingIndicator\nimport me.bmax.apatch.ui.viewmodel.ThemeStoreViewModel\nimport java.io.File\nimport me.bmax.apatch.util.DownloadProgress\nimport me.bmax.apatch.util.DownloadStatus\nimport me.bmax.apatch.util.ThemeDownloader\n\n@Destination<RootGraph>\n@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)\n@Composable\nfun ThemeStoreScreen(\n    navigator: DestinationsNavigator\n) {\n    val viewModel = viewModel<ThemeStoreViewModel>(\n        factory = ThemeStoreViewModel.Factory(LocalContext.current)\n    )\n    val context = LocalContext.current\n    val scope = rememberCoroutineScope()\n    val snackbarHostState = remember { SnackbarHostState() }\n    \n    var selectedTheme by remember { mutableStateOf<ThemeStoreViewModel.RemoteTheme?>(null) }\n    var downloadingTheme by remember { mutableStateOf<ThemeStoreViewModel.RemoteTheme?>(null) }\n    var downloadCompletedTheme by remember { mutableStateOf<ThemeStoreViewModel.RemoteTheme?>(null) }\n    var isSearchActive by remember { mutableStateOf(false) }\n    var showFilterSheet by remember { mutableStateOf(false) }\n\n    // 监听下载进度\n    val downloadProgressFlow = remember { viewModel.getDownloadProgressFlow() }\n    var downloadProgress by remember { mutableStateOf<DownloadProgress?>(null) }\n\n    LaunchedEffect(downloadProgressFlow) {\n        downloadProgressFlow.collect { progressMap ->\n            downloadingTheme?.let { theme ->\n                downloadProgress = progressMap[theme.id]\n                \n                // 检查下载是否完成\n                if (downloadProgress?.status == DownloadStatus.COMPLETED) {\n                    downloadCompletedTheme = downloadingTheme\n                    downloadingTheme = null\n                    downloadProgress = null\n                } else if (downloadProgress?.status == DownloadStatus.FAILED) {\n                    // 下载失败\n                    scope.launch {\n                        snackbarHostState.showSnackbar(\n                            context.getString(R.string.theme_download_failed) + \n                            \": ${downloadProgress?.errorMessage}\"\n                        )\n                    }\n                    downloadingTheme = null\n                    downloadProgress = null\n                }\n            }\n        }\n    }\n\n    LaunchedEffect(Unit) {\n        if (viewModel.themes.isEmpty()) {\n            viewModel.fetchThemes()\n        }\n    }\n\n    // 下载对话框\n    if (downloadingTheme != null && downloadProgress != null) {\n        ThemeDownloadDialog(\n            theme = downloadingTheme!!,\n            progress = downloadProgress!!,\n            onCancel = {\n                viewModel.cancelDownload(downloadingTheme!!.id)\n                downloadingTheme = null\n                downloadProgress = null\n            },\n            onPause = {\n                // TODO: 实现暂停功能\n            }\n        )\n    }\n\n    // 下载完成对话框\n    if (downloadCompletedTheme != null) {\n        val completedTheme = downloadCompletedTheme!!\n        ThemeDownloadCompleteDialog(\n            theme = completedTheme,\n            onApply = {\n                scope.launch {\n                    // 重新加载本地主题列表以确保最新\n                    viewModel.loadLocalThemes()\n                    // 等待一小段时间让列表更新\n                    kotlinx.coroutines.delay(100)\n                    val localTheme = viewModel.localThemes.find { it.id == completedTheme.id }\n                    if (localTheme != null) {\n                                    val success = viewModel.applyTheme(localTheme)\n                                    if (success) {\n                                        snackbarHostState.showSnackbar(context.getString(R.string.my_themes_applied))\n                                    } else {\n                                        snackbarHostState.showSnackbar(context.getString(R.string.my_themes_apply_failed))\n                                    }\n                    } else {\n                        snackbarHostState.showSnackbar(\"Theme not found in local list\")\n                    }\n                }\n                downloadCompletedTheme = null\n            },\n            onGoToMyThemes = {\n                navigator.navigate(MyThemesScreenDestination)\n                downloadCompletedTheme = null\n            },\n            onDismiss = {\n                downloadCompletedTheme = null\n            }\n        )\n    }\n\n    // 主题详情对话框\n    if (selectedTheme != null) {\n        val theme = selectedTheme!!\n        val typeString = if (theme.type == \"tablet\") stringResource(R.string.theme_type_tablet) else stringResource(R.string.theme_type_phone)\n        val sourceString = if (theme.source == \"official\") stringResource(R.string.theme_source_official) else stringResource(R.string.theme_source_third_party)\n        val isDownloaded = viewModel.isThemeDownloaded(theme.id)\n        val isDownloading = viewModel.isThemeDownloading(theme.id)\n\n        AlertDialog(\n            onDismissRequest = { selectedTheme = null },\n            title = { Text(text = theme.name) },\n            text = {\n                Column {\n                    Text(\n                        text = stringResource(R.string.theme_store_author, theme.author),\n                        style = MaterialTheme.typography.bodyMedium\n                    )\n                    Text(\n                        text = stringResource(R.string.theme_store_version, theme.version),\n                        style = MaterialTheme.typography.bodyMedium\n                    )\n                    Text(\n                        text = \"${stringResource(R.string.theme_type)}: $typeString\",\n                        style = MaterialTheme.typography.bodyMedium\n                    )\n                    Text(\n                        text = \"${stringResource(R.string.theme_source)}: $sourceString\",\n                        style = MaterialTheme.typography.bodyMedium\n                    )\n                    Spacer(modifier = Modifier.height(8.dp))\n                    Text(\n                        text = theme.description,\n                        style = MaterialTheme.typography.bodySmall\n                    )\n                    if (isDownloaded) {\n                        Spacer(modifier = Modifier.height(8.dp))\n                        Text(\n                            text = \"✓ Already downloaded\",\n                            color = MaterialTheme.colorScheme.primary,\n                            style = MaterialTheme.typography.bodySmall\n                        )\n                    }\n                }\n            },\n            confirmButton = {\n                if (isDownloaded) {\n                    Button(\n                        onClick = {\n                            scope.launch {\n                                val localTheme = viewModel.localThemes.find { it.id == theme.id }\n                                if (localTheme != null) {\n                                    val success = viewModel.applyTheme(localTheme)\n                                    if (success) {\n                                        snackbarHostState.showSnackbar(context.getString(R.string.my_themes_applied))\n                                    }\n                                }\n                            }\n                            selectedTheme = null\n                        }\n                    ) {\n                        Text(stringResource(R.string.my_themes_apply))\n                    }\n                } else if (isDownloading) {\n                    OutlinedButton(\n                        onClick = { selectedTheme = null },\n                        enabled = false\n                    ) {\n                        CircularProgressIndicator(modifier = Modifier.size(16.dp))\n                        Spacer(modifier = Modifier.width(8.dp))\n                        Text(\"Downloading...\")\n                    }\n                } else {\n                    Button(\n                        onClick = {\n                            downloadingTheme = theme\n                            viewModel.startDownload(theme)\n                            selectedTheme = null\n                        }\n                    ) {\n                        Icon(Icons.Filled.Download, contentDescription = null)\n                        Spacer(modifier = Modifier.width(8.dp))\n                        Text(stringResource(R.string.theme_store_download))\n                    }\n                }\n            },\n            dismissButton = {\n                TextButton(onClick = { selectedTheme = null }) {\n                    Text(stringResource(android.R.string.cancel))\n                }\n            }\n        )\n    }\n\n    // 过滤器对话框\n    if (showFilterSheet) {\n        BasicAlertDialog(\n            onDismissRequest = { showFilterSheet = false },\n            properties = DialogProperties(\n                usePlatformDefaultWidth = false\n            )\n        ) {\n            Surface(\n                modifier = Modifier\n                    .width(320.dp)\n                    .wrapContentHeight(),\n                shape = RoundedCornerShape(28.dp),\n                color = AlertDialogDefaults.containerColor,\n                tonalElevation = AlertDialogDefaults.TonalElevation\n            ) {\n                ThemeFilterSheetContent(\n                    currentAuthor = viewModel.filterAuthor,\n                    currentSource = viewModel.filterSource,\n                    currentTypePhone = viewModel.filterTypePhone,\n                    currentTypeTablet = viewModel.filterTypeTablet,\n                    onApply = { author, source, phone, tablet ->\n                        viewModel.updateFilters(author, source, phone, tablet)\n                        showFilterSheet = false\n                    },\n                    onReset = {\n                        viewModel.updateFilters(\"\", \"all\", phone = true, tablet = true)\n                    }\n                )\n            }\n        }\n    }\n\n    Scaffold(\n        topBar = {\n            TopAppBar(\n                title = {\n                    if (isSearchActive) {\n                        TextField(\n                            value = viewModel.searchQuery,\n                            onValueChange = { viewModel.onSearchQueryChange(it) },\n                            placeholder = { Text(stringResource(R.string.theme_store_search_hint)) },\n                            singleLine = true,\n                            colors = TextFieldDefaults.colors(\n                                focusedContainerColor =  Color.Transparent,\n                                unfocusedContainerColor = Color.Transparent,\n                                disabledContainerColor = Color.Transparent,\n                                focusedIndicatorColor = Color.Transparent,\n                                unfocusedIndicatorColor = Color.Transparent,\n                            ),\n                            modifier = Modifier.fillMaxWidth()\n                        )\n                    } else {\n                        Text(stringResource(R.string.theme_store_title))\n                    }\n                },\n                navigationIcon = {\n                    IconButton(onClick = {\n                        if (isSearchActive) {\n                            isSearchActive = false\n                            viewModel.onSearchQueryChange(\"\")\n                        } else {\n                            navigator.popBackStack()\n                        }\n                    }) {\n                        Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = \"Back\")\n                    }\n                },\n                actions = {\n                    // \"我的主题\"按钮\n                    IconButton(onClick = { navigator.navigate(MyThemesScreenDestination) }) {\n                        Icon(Icons.Filled.ColorLens, contentDescription = \"My Themes\")\n                    }\n                    if (isSearchActive) {\n                        if (viewModel.searchQuery.isNotEmpty()) {\n                            IconButton(onClick = { viewModel.onSearchQueryChange(\"\") }) {\n                                Icon(Icons.Filled.Close, contentDescription = \"Clear\")\n                            }\n                        }\n                    } else {\n                        IconButton(onClick = { isSearchActive = true }) {\n                            Icon(Icons.Filled.Search, contentDescription = \"Search\")\n                        }\n                    }\n                    IconButton(onClick = { showFilterSheet = true }) {\n                        Icon(Icons.Filled.FilterList, contentDescription = \"Filter\")\n                    }\n                }\n            )\n        },\n        snackbarHost = { SnackbarHost(snackbarHostState) }\n    ) { paddingValues ->\n        if (viewModel.isRefreshing) {\n            Box(\n                modifier = Modifier.fillMaxSize().padding(paddingValues),\n                contentAlignment = Alignment.Center,\n            ) {\n                AppLoadingIndicator(\n                    text = stringResource(R.string.loading_themes),\n                )\n            }\n        } else if (viewModel.errorMessage != null) {\n            Column(\n                modifier = Modifier.fillMaxSize().padding(paddingValues),\n                verticalArrangement = Arrangement.Center,\n                horizontalAlignment = Alignment.CenterHorizontally\n            ) {\n                Text(\n                    text = viewModel.errorMessage ?: \"Unknown error\",\n                    color = MaterialTheme.colorScheme.error,\n                    modifier = Modifier.padding(16.dp)\n                )\n                Button(onClick = { viewModel.fetchThemes() }) {\n                    Text(stringResource(R.string.retry))\n                }\n            }\n        } else {\n            LazyVerticalStaggeredGrid(\n                columns = StaggeredGridCells.Adaptive(minSize = 128.dp),\n                modifier = Modifier.fillMaxSize().padding(paddingValues),\n                contentPadding = PaddingValues(16.dp),\n                verticalItemSpacing = 16.dp,\n                horizontalArrangement = Arrangement.spacedBy(16.dp)\n            ) {\n                items(\n                    items = viewModel.themes.distinctBy { it.id },\n                    key = { it.id }\n                ) { theme ->\n                    val localTheme = viewModel.localThemes.find { it.id == theme.id }\n                    ThemeGridItem(\n                        theme = theme,\n                        localPreviewPath = localTheme?.previewImagePath,\n                        onClick = { selectedTheme = theme }\n                    )\n                }\n            }\n        }\n    }\n}\n\n/**\n * 主题下载对话框\n */\n@OptIn(ExperimentalMaterial3ExpressiveApi::class)\n@Composable\nfun ThemeDownloadDialog(\n    theme: ThemeStoreViewModel.RemoteTheme,\n    progress: DownloadProgress,\n    onCancel: () -> Unit,\n    onPause: () -> Unit\n) {\n    AlertDialog(\n        onDismissRequest = { },\n        title = { Text(stringResource(R.string.theme_download_title)) },\n        text = {\n            Column {\n                // 主题信息\n                Row(\n                    modifier = Modifier.fillMaxWidth(),\n                    verticalAlignment = Alignment.CenterVertically\n                ) {\n                    // 预览图（正方形加圆角）\n                    AsyncImage(\n                        model = ImageRequest.Builder(LocalContext.current)\n                            .data(theme.previewUrl)\n                            .crossfade(true)\n                            .diskCachePolicy(CachePolicy.ENABLED)\n                            .memoryCachePolicy(CachePolicy.ENABLED)\n                            .build(),\n                        contentDescription = theme.name,\n                        modifier = Modifier\n                            .size(64.dp)\n                            .padding(end = 12.dp)\n                            .clip(RoundedCornerShape(8.dp)),\n                        contentScale = ContentScale.Crop\n                    )\n                    \n                    Column {\n                        Text(\n                            text = theme.name,\n                            style = MaterialTheme.typography.titleMedium\n                        )\n                        Text(\n                            text = theme.author,\n                            style = MaterialTheme.typography.bodySmall,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant\n                        )\n                    }\n                }\n                \n                Spacer(modifier = Modifier.height(16.dp))\n                \n                // 总进度条\n                Text(\n                    text = \"${stringResource(R.string.theme_download_progress)}: ${(progress.overallProgress * 100).toInt()}%\",\n                    style = MaterialTheme.typography.bodyMedium\n                )\n                Spacer(modifier = Modifier.height(8.dp))\n                val animatedProgress by animateFloatAsState(\n                    targetValue = progress.overallProgress,\n                    animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec,\n                    label = \"DownloadProgress\"\n                )\n                LinearWavyProgressIndicator(\n                    progress = { animatedProgress },\n                    modifier = Modifier.fillMaxWidth()\n                )\n                \n                Spacer(modifier = Modifier.height(16.dp))\n                \n                // 文件进度\n                Row(\n                    modifier = Modifier.fillMaxWidth(),\n                    horizontalArrangement = Arrangement.SpaceBetween\n                ) {\n                    Text(\n                        text = \"${stringResource(R.string.theme_download_file)}: ${(progress.fileProgress * 100).toInt()}%\",\n                        style = MaterialTheme.typography.bodySmall\n                    )\n                    Text(\n                        text = \"${stringResource(R.string.theme_download_image)}: ${(progress.imageProgress * 100).toInt()}%\",\n                        style = MaterialTheme.typography.bodySmall\n                    )\n                }\n                \n                // 错误信息\n                if (progress.errorMessage != null) {\n                    Spacer(modifier = Modifier.height(8.dp))\n                    Text(\n                        text = progress.errorMessage!!,\n                        color = MaterialTheme.colorScheme.error,\n                        style = MaterialTheme.typography.bodySmall\n                    )\n                }\n            }\n        },\n        confirmButton = {\n            Button(onClick = onCancel) {\n                Text(stringResource(R.string.theme_download_cancel))\n            }\n        },\n        dismissButton = {\n            OutlinedButton(\n                onClick = onPause,\n                enabled = false // TODO: 实现暂停功能\n            ) {\n                Text(stringResource(R.string.theme_download_pause))\n            }\n        }\n    )\n}\n\n/**\n * 下载完成对话框\n */\n@Composable\nfun ThemeDownloadCompleteDialog(\n    theme: ThemeStoreViewModel.RemoteTheme,\n    onApply: () -> Unit,\n    onGoToMyThemes: () -> Unit,\n    onDismiss: () -> Unit\n) {\n    AlertDialog(\n        onDismissRequest = onDismiss,\n        title = { \n            Row(verticalAlignment = Alignment.CenterVertically) {\n                Text(stringResource(R.string.theme_download_completed))\n            }\n        },\n        text = {\n            Column {\n                Text(\n                    text = \"${theme.name} ${stringResource(R.string.theme_download_finalizing)}\",\n                    style = MaterialTheme.typography.bodyMedium\n                )\n            }\n        },\n        confirmButton = {\n            Button(onClick = onApply) {\n                Text(stringResource(R.string.theme_download_apply))\n            }\n        },\n        dismissButton = {\n            TextButton(onClick = onGoToMyThemes) {\n                Text(stringResource(R.string.theme_download_go_to_my_themes))\n            }\n        }\n    )\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun ThemeFilterSheetContent(\n    currentAuthor: String,\n    currentSource: String,\n    currentTypePhone: Boolean,\n    currentTypeTablet: Boolean,\n    onApply: (String, String, Boolean, Boolean) -> Unit,\n    onReset: () -> Unit\n) {\n    var author by remember { mutableStateOf(currentAuthor) }\n    var source by remember { mutableStateOf(currentSource) }\n    var typePhone by remember { mutableStateOf(currentTypePhone) }\n    var typeTablet by remember { mutableStateOf(currentTypeTablet) }\n\n    Column(\n        modifier = Modifier\n            .fillMaxWidth()\n            .verticalScroll(rememberScrollState())\n            .padding(24.dp)\n    ) {\n        Text(\n            text = stringResource(R.string.theme_store_filter_title),\n            style = MaterialTheme.typography.titleLarge,\n            modifier = Modifier.padding(bottom = 16.dp)\n        )\n\n        OutlinedTextField(\n            value = author,\n            onValueChange = { author = it },\n            label = { Text(stringResource(R.string.theme_store_filter_author)) },\n            placeholder = { Text(stringResource(R.string.theme_store_filter_author_hint)) },\n            modifier = Modifier.fillMaxWidth(),\n            singleLine = true\n        )\n        \n        Spacer(modifier = Modifier.height(16.dp))\n\n        Text(\n            text = stringResource(R.string.theme_store_filter_source),\n            style = MaterialTheme.typography.titleMedium\n        )\n        Spacer(modifier = Modifier.height(8.dp))\n        Row(\n            modifier = Modifier\n                .horizontalScroll(rememberScrollState())\n                .fillMaxWidth(),\n            horizontalArrangement = Arrangement.spacedBy(8.dp)\n        ) {\n            val chipColors = FilterChipDefaults.filterChipColors(\n                selectedContainerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 1f)\n            )\n            FilterChip(\n                selected = source == \"all\",\n                onClick = { source = \"all\" },\n                label = { Text(stringResource(R.string.theme_store_filter_source_all)) },\n                colors = chipColors\n            )\n            FilterChip(\n                selected = source == \"official\",\n                onClick = { source = \"official\" },\n                label = { Text(stringResource(R.string.theme_source_official)) },\n                colors = chipColors\n            )\n            FilterChip(\n                selected = source == \"third_party\",\n                onClick = { source = \"third_party\" },\n                label = { Text(stringResource(R.string.theme_source_third_party)) },\n                colors = chipColors\n            )\n        }\n\n        Spacer(modifier = Modifier.height(16.dp))\n\n        Text(\n            text = stringResource(R.string.theme_store_filter_type),\n            style = MaterialTheme.typography.titleMedium\n        )\n        Spacer(modifier = Modifier.height(8.dp))\n        Row(\n            modifier = Modifier\n                .horizontalScroll(rememberScrollState())\n                .fillMaxWidth(),\n            horizontalArrangement = Arrangement.spacedBy(8.dp)\n        ) {\n            val chipColors = FilterChipDefaults.filterChipColors(\n                selectedContainerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 1f)\n            )\n            FilterChip(\n                selected = typePhone,\n                onClick = { typePhone = !typePhone },\n                label = { Text(stringResource(R.string.theme_type_phone)) },\n                colors = chipColors\n            )\n            FilterChip(\n                selected = typeTablet,\n                onClick = { typeTablet = !typeTablet },\n                label = { Text(stringResource(R.string.theme_type_tablet)) },\n                colors = chipColors\n            )\n        }\n\n        Spacer(modifier = Modifier.height(24.dp))\n\n        Row(\n            modifier = Modifier.fillMaxWidth(),\n            horizontalArrangement = Arrangement.End,\n            verticalAlignment = Alignment.CenterVertically\n        ) {\n            TextButton(\n                onClick = {\n                    onReset()\n                    author = \"\"\n                    source = \"all\"\n                    typePhone = true\n                    typeTablet = true\n                }\n            ) {\n                Text(stringResource(R.string.theme_store_filter_reset))\n            }\n            Spacer(modifier = Modifier.width(8.dp))\n            Button(\n                onClick = { onApply(author, source, typePhone, typeTablet) }\n            ) {\n                Text(stringResource(R.string.theme_store_filter_apply))\n            }\n        }\n        Spacer(modifier = Modifier.height(16.dp))\n    }\n}\n\n@Composable\nfun ThemeGridItem(\n    theme: ThemeStoreViewModel.RemoteTheme,\n    localPreviewPath: String? = null,\n    onClick: () -> Unit\n) {\n    val context = LocalContext.current\n    val previewFile = localPreviewPath?.let { File(it) }\n    val imageModel = if (previewFile != null && previewFile.exists()) {\n        Uri.fromFile(previewFile)\n    } else {\n        theme.previewUrl\n    }\n\n    Card(\n        modifier = Modifier\n            .fillMaxWidth()\n            .clickable(onClick = onClick),\n        elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)\n    ) {\n        AsyncImage(\n            model = ImageRequest.Builder(context)\n                .data(imageModel)\n                .crossfade(true)\n                .diskCachePolicy(CachePolicy.ENABLED)\n                .memoryCachePolicy(CachePolicy.ENABLED)\n                .build(),\n            contentDescription = theme.name,\n            modifier = Modifier\n                .fillMaxWidth()\n                .wrapContentHeight(),\n            contentScale = ContentScale.FillWidth\n        )\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/AppearanceSettings.kt",
    "content": "package me.bmax.apatch.ui.screen.settings\n\nimport android.app.Activity\nimport android.content.ActivityNotFoundException\nimport android.net.Uri\nimport android.os.Build\nimport me.bmax.apatch.util.ui.showToast\nimport androidx.activity.compose.rememberLauncherForActivityResult\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.annotation.StringRes\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.animation.core.animateFloatAsState\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.interaction.MutableInteractionSource\nimport androidx.compose.foundation.isSystemInDarkTheme\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.selection.toggleable\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.*\nimport androidx.compose.material.icons.automirrored.filled.ViewQuilt\nimport androidx.compose.material.icons.automirrored.filled.VolumeUp\nimport androidx.compose.material3.*\nimport androidx.compose.runtime.*\nimport androidx.compose.runtime.livedata.observeAsState\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalView\nimport androidx.compose.ui.res.painterResource\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.semantics.Role\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.window.DialogProperties\nimport androidx.compose.ui.window.DialogWindowProvider\nimport androidx.core.content.edit\nimport kotlinx.coroutines.launch\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.component.ExpressiveCard\nimport me.bmax.apatch.ui.component.ExpressiveSwitch\nimport me.bmax.apatch.ui.component.SwitchIconState\nimport me.bmax.apatch.ui.component.FilePickerDialog\n\nimport me.bmax.apatch.ui.component.SplicedColumnGroup\nimport me.bmax.apatch.ui.component.ToggleSettingCard\nimport me.bmax.apatch.ui.component.ThemeColorPicker\nimport me.bmax.apatch.ui.component.ThemeMode\nimport me.bmax.apatch.ui.component.ThemeModeSelector\nimport me.bmax.apatch.ui.component.rememberConfirmDialog\nimport me.bmax.apatch.ui.component.rememberLoadingDialog\nimport me.bmax.apatch.ui.theme.BackgroundConfig\nimport me.bmax.apatch.ui.theme.BackgroundManager\nimport me.bmax.apatch.ui.theme.FontConfig\nimport me.bmax.apatch.ui.theme.ThemeManager\nimport me.bmax.apatch.ui.theme.refreshTheme\nimport me.bmax.apatch.util.PermissionUtils\nimport me.bmax.apatch.util.ui.APDialogBlurBehindUtils\nimport me.bmax.apatch.util.ui.NavigationBarsSpacer\nimport androidx.compose.ui.draw.rotate\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun AppearanceSettingsContent(\n    snackBarHost: SnackbarHostState,\n    kPatchReady: Boolean,\n    onNavigateToThemeStore: () -> Unit,\n    onNavigateToApiMarketplace: () -> Unit,\n    flat: Boolean = false,\n) {\n    val prefs = APApplication.sharedPreferences\n    val context = LocalContext.current\n    val scope = rememberCoroutineScope()\n    val loadingDialog = rememberLoadingDialog()\n\n    var pickingType by remember { mutableStateOf<String?>(null) }\n\n    val pickImageLauncher = rememberLauncherForActivityResult(\n        ActivityResultContracts.GetContent()\n    ) { uri: Uri? ->\n        uri?.let {\n            scope.launch {\n                loadingDialog.show()\n                val success = when (pickingType) {\n                    \"home\" -> BackgroundManager.saveAndApplyHomeBackground(context, it)\n                    \"kernel\" -> BackgroundManager.saveAndApplyKernelBackground(context, it)\n                    \"superuser\" -> BackgroundManager.saveAndApplySuperuserBackground(context, it)\n                    \"system\" -> BackgroundManager.saveAndApplySystemModuleBackground(context, it)\n                    \"settings\" -> BackgroundManager.saveAndApplySettingsBackground(context, it)\n                    else -> BackgroundManager.saveAndApplyCustomBackground(context, it)\n                }\n                loadingDialog.hide()\n                if (success) {\n                    snackBarHost.showSnackbar(message = context.getString(R.string.settings_custom_background_saved))\n                    refreshTheme.value = true\n                } else {\n                    snackBarHost.showSnackbar(message = context.getString(R.string.settings_custom_background_error))\n                }\n                pickingType = null\n            }\n        }\n    }\n\n    val pickVideoLauncher = rememberLauncherForActivityResult(\n        ActivityResultContracts.GetContent()\n    ) { uri: Uri? ->\n        uri?.let {\n            scope.launch {\n                loadingDialog.show()\n                val success = BackgroundManager.saveAndApplyVideoBackground(context, it)\n                loadingDialog.hide()\n                if (success) {\n                    snackBarHost.showSnackbar(message = context.getString(R.string.settings_video_selected))\n                    refreshTheme.value = true\n                } else {\n                    snackBarHost.showSnackbar(message = context.getString(R.string.settings_custom_background_error))\n                }\n            }\n        }\n    }\n\n    val pickGridImageLauncher = rememberLauncherForActivityResult(\n        ActivityResultContracts.GetContent()\n    ) { uri: Uri? ->\n        uri?.let {\n            scope.launch {\n                loadingDialog.show()\n                val success = BackgroundManager.saveAndApplyGridWorkingCardBackground(context, it)\n                loadingDialog.hide()\n                if (success) {\n                    snackBarHost.showSnackbar(message = context.getString(R.string.settings_grid_working_card_background_saved))\n                } else {\n                    snackBarHost.showSnackbar(message = context.getString(R.string.settings_grid_working_card_background_error))\n                }\n            }\n        }\n    }\n\n    val pickFontLauncher = rememberLauncherForActivityResult(\n        ActivityResultContracts.GetContent()\n    ) { uri: Uri? ->\n        uri?.let {\n            scope.launch {\n                loadingDialog.show()\n                val success = FontConfig.saveFontFile(context, it)\n                loadingDialog.hide()\n                if (success) {\n                    snackBarHost.showSnackbar(message = context.getString(R.string.settings_custom_font_saved))\n                    refreshTheme.value = true\n                } else {\n                    snackBarHost.showSnackbar(message = context.getString(R.string.settings_custom_font_error))\n                }\n            }\n        }\n    }\n\n    val pickTitleImageLauncher = rememberLauncherForActivityResult(\n        ActivityResultContracts.GetContent()\n    ) { uri: Uri? ->\n        uri?.let {\n            scope.launch {\n                loadingDialog.show()\n                val success = BackgroundManager.saveAndApplyTitleImage(context, it)\n                loadingDialog.hide()\n                if (success) {\n                    snackBarHost.showSnackbar(message = context.getString(R.string.settings_title_image_saved))\n                    refreshTheme.value = true\n                } else {\n                    snackBarHost.showSnackbar(message = context.getString(R.string.settings_title_image_error))\n                }\n            }\n        }\n    }\n\n    var pendingExportMetadata by remember { mutableStateOf<ThemeManager.ThemeMetadata?>(null) }\n    val showExportDialog = remember { mutableStateOf(false) }\n    var pendingImportUri by remember { mutableStateOf<Uri?>(null) }\n    var pendingImportMetadata by remember { mutableStateOf<ThemeManager.ThemeMetadata?>(null) }\n    val showImportDialog = remember { mutableStateOf(false) }\n    val showFilePicker = remember { mutableStateOf(false) }\n\n    val importThemeLauncher = rememberLauncherForActivityResult(\n        ActivityResultContracts.OpenDocument()\n    ) { uri: Uri? ->\n        if (uri != null) {\n            scope.launch {\n                loadingDialog.show()\n                val metadata = ThemeManager.readThemeMetadata(context, uri)\n                loadingDialog.hide()\n                if (metadata != null) {\n                    pendingImportUri = uri\n                    pendingImportMetadata = metadata\n                    showImportDialog.value = true\n                } else {\n                    loadingDialog.show()\n                    val success = ThemeManager.importTheme(context, uri)\n                    loadingDialog.hide()\n                    snackBarHost.showSnackbar(\n                        message = if (success) context.getString(R.string.settings_theme_imported) else context.getString(R.string.settings_theme_import_failed)\n                    )\n                }\n            }\n        }\n    }\n\n    val isNightModeSupported = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q\n    var nightModeFollowSys by remember { mutableStateOf(prefs.getBoolean(\"night_mode_follow_sys\", true)) }\n    var nightModeEnabled by remember { mutableStateOf(prefs.getBoolean(\"night_mode_enabled\", true)) }\n    val isDynamicColorSupport = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S\n    var useSystemDynamicColor by remember { mutableStateOf(prefs.getBoolean(\"use_system_color_theme\", false)) }\n    var customFontEnabled by remember { mutableStateOf(FontConfig.isCustomFontEnabled) }\n\n    val refreshThemeObserver by refreshTheme.observeAsState(false)\n\n    var customColorScheme by remember { mutableStateOf(prefs.getString(\"custom_color\", \"indigo\")) }\n    var amoledTheme by remember { mutableStateOf(prefs.getBoolean(\"amoled_theme\", false)) }\n\n    var currentStyle by remember { mutableStateOf(prefs.getString(\"home_layout_style\", \"circle\")) }\n\n    if (refreshThemeObserver) {\n        nightModeFollowSys = prefs.getBoolean(\"night_mode_follow_sys\", false)\n        nightModeEnabled = prefs.getBoolean(\"night_mode_enabled\", true)\n        useSystemDynamicColor = prefs.getBoolean(\"use_system_color_theme\", true)\n        customFontEnabled = FontConfig.isCustomFontEnabled\n        customColorScheme = prefs.getString(\"custom_color\", \"indigo\")\n        amoledTheme = prefs.getBoolean(\"amoled_theme\", false)\n        currentStyle = prefs.getString(\"home_layout_style\", \"circle\")\n    }\n\n    val isDarkTheme = if (nightModeFollowSys) isSystemInDarkTheme() else nightModeEnabled\n    val themeMode = if (nightModeFollowSys) ThemeMode.SYSTEM else if (nightModeEnabled) ThemeMode.DARK else ThemeMode.LIGHT\n    val isStatsLayout = currentStyle == \"stats\"\n    var statsTopLayout by remember { mutableStateOf(prefs.getString(\"stats_top_layout\", \"list\") ?: \"list\") }\n    val statsTopLayoutListLabel = stringResource(id = R.string.settings_stats_top_layout_list)\n    val statsTopLayoutGridLabel = stringResource(id = R.string.settings_stats_top_layout_grid)\n    val statsTopLayoutValue = if (statsTopLayout == \"grid\") statsTopLayoutGridLabel else statsTopLayoutListLabel\n    var showStatsTopLayoutDialog by remember { mutableStateOf(false) }\n\n    var showNavApm by remember { mutableStateOf(prefs.getBoolean(\"show_nav_apm\", true)) }\n    var showNavKpm by remember { mutableStateOf(prefs.getBoolean(\"show_nav_kpm\", true)) }\n    var showNavSuperUser by remember { mutableStateOf(prefs.getBoolean(\"show_nav_superuser\", true)) }\n\n    var currentNavMode by remember { mutableStateOf(prefs.getString(\"nav_mode\", \"floating\") ?: \"floating\") }\n    val navSchemeLabel = when (currentNavMode) {\n        \"rail\" -> stringResource(R.string.settings_nav_mode_rail)\n        \"bottom\" -> stringResource(R.string.settings_nav_mode_bottom)\n        \"floating\" -> stringResource(R.string.settings_nav_mode_floating)\n        else -> stringResource(R.string.settings_nav_mode_auto)\n    }\n    var showNavSchemeDialog by remember { mutableStateOf(false) }\n\n    val isFloatingNav = currentNavMode == \"floating\"\n    var floatingAutoHide by remember { mutableStateOf(prefs.getBoolean(\"floating_auto_hide\", true)) }\n    var floatingSwipeHide by remember { mutableStateOf(prefs.getBoolean(\"floating_swipe_hide\", true)) }\n\n    val isKernelSuStyle = currentStyle == \"kernelsu\"\n    val showGridCardSettings = isKernelSuStyle || (isStatsLayout && statsTopLayout == \"grid\")\n    val isListStyle = currentStyle != \"kernelsu\" && currentStyle != \"focus\" && !(isStatsLayout && statsTopLayout == \"grid\")\n\n    val badgeTextModes = listOf(\n        stringResource(R.string.settings_custom_badge_text_full_half),\n        stringResource(R.string.settings_custom_badge_text_lkm),\n        stringResource(R.string.settings_custom_badge_text_gki),\n        stringResource(R.string.settings_custom_badge_text_n_gki),\n        stringResource(R.string.settings_custom_badge_text_oki),\n        stringResource(R.string.settings_custom_badge_text_built_in)\n    )\n    val currentBadgeTextModeIndex = BackgroundConfig.customBadgeTextMode\n    val currentBadgeTextMode = badgeTextModes.getOrElse(currentBadgeTextModeIndex) { badgeTextModes[0] }\n    val showCustomBadgeTextDialog = remember { mutableStateOf(false) }\n\n    val showHomeLayoutChooseDialog = remember { mutableStateOf(false) }\n\n    Column(modifier = Modifier.fillMaxWidth()) {\n\n        SplicedColumnGroup(title = stringResource(R.string.settings_appearance_night_mode), flat = flat) {\n            if (isNightModeSupported) {\n                item {\n                    ThemeModeSelector(\n                        selectedMode = themeMode,\n                        onModeSelected = { mode ->\n                            when (mode) {\n                                ThemeMode.LIGHT -> {\n                                    nightModeFollowSys = false\n                                    nightModeEnabled = false\n                                    prefs.edit().putBoolean(\"night_mode_follow_sys\", false).putBoolean(\"night_mode_enabled\", false).apply()\n                                }\n                                ThemeMode.DARK -> {\n                                    nightModeFollowSys = false\n                                    nightModeEnabled = true\n                                    prefs.edit().putBoolean(\"night_mode_follow_sys\", false).putBoolean(\"night_mode_enabled\", true).apply()\n                                }\n                                ThemeMode.SYSTEM -> {\n                                    nightModeFollowSys = true\n                                    prefs.edit().putBoolean(\"night_mode_follow_sys\", true).apply()\n                                }\n                            }\n                            refreshTheme.value = true\n                        },\n                        flat = flat,\n                        bare = true,\n                    )\n                }\n            }\n\n            item {\n                ThemeColorPicker(\n                    selectedColorKey = customColorScheme ?: \"indigo\",\n                    onColorSelected = { key ->\n                        prefs.edit().putString(\"custom_color\", key).putBoolean(\"use_system_color_theme\", false).apply()\n                        customColorScheme = key\n                        useSystemDynamicColor = false\n                        refreshTheme.value = true\n                    },\n                    isDarkTheme = isDarkTheme,\n                    flat = flat,\n                    isDynamicColorSupported = isDynamicColorSupport,\n                    isDynamicColorEnabled = useSystemDynamicColor,\n                    onDynamicColorSelected = {\n                        prefs.edit().putBoolean(\"use_system_color_theme\", true).apply()\n                        useSystemDynamicColor = true\n                        refreshTheme.value = true\n                    },\n                    bare = true,\n                )\n            }\n\n            if (isDarkTheme) {\n                item {\n                    val isWallpaperEnabled = BackgroundConfig.isCustomBackgroundEnabled\n                    Row(\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .toggleable(\n                                value = amoledTheme,\n                                onValueChange = {\n                                    if (!isWallpaperEnabled) {\n                                        amoledTheme = it\n                                        prefs.edit().putBoolean(\"amoled_theme\", it).apply()\n                                        refreshTheme.value = true\n                                    }\n                                },\n                                role = Role.Switch,\n                                enabled = !isWallpaperEnabled,\n                                interactionSource = remember { MutableInteractionSource() },\n                                indication = ripple(),\n                            )\n                            .padding(horizontal = 16.dp, vertical = 12.dp),\n                        horizontalArrangement = Arrangement.SpaceBetween,\n                        verticalAlignment = Alignment.CenterVertically,\n                    ) {\n                        Icon(imageVector = Icons.Filled.DarkMode, contentDescription = null, tint = if (!isWallpaperEnabled) MaterialTheme.colorScheme.onSurfaceVariant else MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.38f), modifier = Modifier.size(24.dp))\n                        Spacer(Modifier.width(16.dp))\n                        Column(modifier = Modifier.weight(1f).padding(end = 16.dp)) {\n                            Text(\n                                text = stringResource(R.string.settings_amoled_theme),\n                                style = MaterialTheme.typography.titleMedium,\n                                color = if (!isWallpaperEnabled) MaterialTheme.colorScheme.onSurface\n                                else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f),\n                                fontWeight = FontWeight.SemiBold,\n                            )\n                            Spacer(Modifier.height(4.dp))\n                            Text(\n                                text = stringResource(R.string.settings_amoled_theme_desc),\n                                style = MaterialTheme.typography.bodyMedium,\n                                color = if (!isWallpaperEnabled) MaterialTheme.colorScheme.onSurfaceVariant\n                                else MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.38f),\n                            )\n                        }\n                        ExpressiveSwitch(\n                            checked = amoledTheme,\n                            onCheckedChange = null,\n                            enabled = !isWallpaperEnabled,\n                        )\n                    }\n                }\n            }\n\n            item {\n                var showSwitchIcon by remember { mutableStateOf(SwitchIconState.showIcon) }\n                ToggleSettingCard(\n                    icon = Icons.Filled.ToggleOn,\n                    flat = flat,\n                    title = stringResource(R.string.settings_switch_icon),\n                    description = stringResource(R.string.settings_switch_icon_desc),\n                    checked = showSwitchIcon,\n                    onCheckedChange = {\n                        showSwitchIcon = it\n                        SwitchIconState.showIcon = it\n                        prefs.edit().putBoolean(\"show_switch_icon\", it).apply()\n                    },\n                )\n            }\n        }\n\n        SplicedColumnGroup(title = stringResource(R.string.settings_appearance_layout), flat = flat) {\n            item {\n                ExpressiveCard(flat = flat, onClick = { showHomeLayoutChooseDialog.value = true }) {\n                    Row(\n                        modifier = Modifier.fillMaxWidth().padding(16.dp),\n                        horizontalArrangement = Arrangement.SpaceBetween,\n                        verticalAlignment = Alignment.CenterVertically,\n                    ) {\n                        Icon(imageVector = Icons.Filled.Dashboard, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                        Spacer(Modifier.width(16.dp))\n                        Column(modifier = Modifier.weight(1f)) {\n                            Text(\n                                text = stringResource(id = R.string.settings_home_layout_style),\n                                style = MaterialTheme.typography.titleMedium,\n                                color = MaterialTheme.colorScheme.onSurface,\n                            )\n                            Text(\n                                text = stringResource(homeLayoutStyleToString(currentStyle.toString())),\n                                style = MaterialTheme.typography.bodyMedium,\n                                color = MaterialTheme.colorScheme.outline,\n                            )\n                        }\n                    }\n                }\n            }\n\n            item(visible = isStatsLayout) {\n                ExpressiveCard(flat = flat, onClick = { showStatsTopLayoutDialog = true }) {\n                    Row(\n                        modifier = Modifier.fillMaxWidth().padding(16.dp),\n                        horizontalArrangement = Arrangement.SpaceBetween,\n                        verticalAlignment = Alignment.CenterVertically,\n                    ) {\n                        Icon(imageVector = Icons.Filled.GridView, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                        Spacer(Modifier.width(16.dp))\n                        Column(modifier = Modifier.weight(1f)) {\n                            Text(\n                                text = stringResource(id = R.string.settings_stats_top_layout),\n                                style = MaterialTheme.typography.titleMedium,\n                                color = MaterialTheme.colorScheme.onSurface,\n                            )\n                            Text(\n                                text = statsTopLayoutValue,\n                                style = MaterialTheme.typography.bodyMedium,\n                                color = MaterialTheme.colorScheme.outline,\n                            )\n                        }\n                    }\n                }\n            }\n\n            if (kPatchReady) {\n                item {\n                    var expanded by remember { mutableStateOf(false) }\n                    val rotationState by animateFloatAsState(\n                        targetValue = if (expanded) 180f else 0f,\n                        label = \"ArrowRotation\",\n                    )\n                    ExpressiveCard(flat = flat, onClick = { expanded = !expanded }) {\n                        Row(\n                            modifier = Modifier.fillMaxWidth().padding(16.dp),\n                            horizontalArrangement = Arrangement.SpaceBetween,\n                            verticalAlignment = Alignment.CenterVertically,\n                        ) {\n                            Icon(imageVector = Icons.Filled.Navigation, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                            Spacer(Modifier.width(16.dp))\n                            Column(modifier = Modifier.weight(1f)) {\n                                Text(\n                                    text = stringResource(id = R.string.settings_nav_layout_title),\n                                    style = MaterialTheme.typography.titleMedium,\n                                    color = MaterialTheme.colorScheme.onSurface,\n                                )\n                                Text(\n                                    text = stringResource(id = R.string.settings_nav_layout_summary),\n                                    style = MaterialTheme.typography.bodyMedium,\n                                    color = MaterialTheme.colorScheme.outline,\n                                )\n                            }\n                            Icon(\n                                imageVector = Icons.Filled.KeyboardArrowDown,\n                                contentDescription = null,\n                                tint = MaterialTheme.colorScheme.onSurfaceVariant,\n                                modifier = Modifier.rotate(rotationState),\n                            )\n                        }\n                    }\n                    AnimatedVisibility(visible = expanded) {\n                        Column(modifier = Modifier.padding(start = 16.dp, top = 8.dp)) {\n                            me.bmax.apatch.ui.component.CheckboxItem(\n                                icon = null,\n                                title = stringResource(id = R.string.settings_show_apm),\n                                summary = null,\n                                checked = showNavApm,\n                                onCheckedChange = {\n                                    showNavApm = it\n                                    prefs.edit().putBoolean(\"show_nav_apm\", it).apply()\n                                },\n                            )\n                            me.bmax.apatch.ui.component.CheckboxItem(\n                                icon = null,\n                                title = stringResource(id = R.string.settings_show_kpm),\n                                summary = null,\n                                checked = showNavKpm,\n                                onCheckedChange = {\n                                    showNavKpm = it\n                                    prefs.edit().putBoolean(\"show_nav_kpm\", it).apply()\n                                },\n                            )\n                            me.bmax.apatch.ui.component.CheckboxItem(\n                                icon = null,\n                                title = stringResource(id = R.string.settings_show_superuser),\n                                summary = null,\n                                checked = showNavSuperUser,\n                                onCheckedChange = {\n                                    showNavSuperUser = it\n                                    prefs.edit().putBoolean(\"show_nav_superuser\", it).apply()\n                                },\n                            )\n                        }\n                    }\n                }\n            }\n\n            item {\n                ExpressiveCard(flat = flat, onClick = { showNavSchemeDialog = true }) {\n                    Row(\n                        modifier = Modifier.fillMaxWidth().padding(16.dp),\n                        horizontalArrangement = Arrangement.SpaceBetween,\n                        verticalAlignment = Alignment.CenterVertically,\n                    ) {\n                        Icon(imageVector = Icons.Filled.Menu, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                        Spacer(Modifier.width(16.dp))\n                        Column(modifier = Modifier.weight(1f)) {\n                            Text(\n                                text = stringResource(id = R.string.settings_nav_scheme),\n                                style = MaterialTheme.typography.titleMedium,\n                                color = MaterialTheme.colorScheme.onSurface,\n                            )\n                            Text(\n                                text = navSchemeLabel,\n                                style = MaterialTheme.typography.bodyMedium,\n                                color = MaterialTheme.colorScheme.outline,\n                            )\n                        }\n                    }\n                }\n            }\n\n            if (isFloatingNav) {\n                item {\n                    ToggleSettingCard(\n                        flat = flat,\n                        icon = Icons.Filled.AutoAwesome,\n                        title = stringResource(id = R.string.settings_navbar_glass_effect),\n                        description = stringResource(id = R.string.settings_navbar_glass_effect_summary),\n                        checked = BackgroundConfig.isNavBarGlassEnabled,\n                        onCheckedChange = {\n                            BackgroundConfig.setNavBarGlassEnabledState(it)\n                            BackgroundConfig.save(context)\n                        },\n                    )\n                }\n\n                if (BackgroundConfig.isNavBarGlassEnabled) {\n                    item {\n                        ExpressiveCard(flat = flat) {\n                            Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {\n                                Text(\n                                    text = stringResource(id = R.string.settings_navbar_glass_blur_strength),\n                                    style = MaterialTheme.typography.titleMedium,\n                                    color = MaterialTheme.colorScheme.onSurface,\n                                )\n                                Slider(\n                                    value = BackgroundConfig.navBarGlassBlurStrength,\n                                    onValueChange = { BackgroundConfig.setNavBarGlassBlurStrengthValue(it) },\n                                    onValueChangeFinished = { BackgroundConfig.save(context) },\n                                    valueRange = 0f..1f,\n                                    colors = SliderDefaults.colors(\n                                        thumbColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                        activeTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                    ),\n                                )\n                            }\n                        }\n                    }\n\n                    item {\n                        ExpressiveCard(flat = flat) {\n                            Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {\n                                Text(\n                                    text = stringResource(id = R.string.settings_navbar_glass_transparency),\n                                    style = MaterialTheme.typography.titleMedium,\n                                    color = MaterialTheme.colorScheme.onSurface,\n                                )\n                                Slider(\n                                    value = BackgroundConfig.navBarGlassTransparency,\n                                    onValueChange = { BackgroundConfig.setNavBarGlassTransparencyValue(it) },\n                                    onValueChangeFinished = { BackgroundConfig.save(context) },\n                                    valueRange = 0f..1f,\n                                    colors = SliderDefaults.colors(\n                                        thumbColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                        activeTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                    ),\n                                )\n                            }\n                        }\n                    }\n\n                    item {\n                        ExpressiveCard(flat = flat) {\n                            Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {\n                                Text(\n                                    text = stringResource(id = R.string.settings_navbar_glass_highlight_strength),\n                                    style = MaterialTheme.typography.titleMedium,\n                                    color = MaterialTheme.colorScheme.onSurface,\n                                )\n                                Slider(\n                                    value = BackgroundConfig.navBarGlassHighlightStrength,\n                                    onValueChange = { BackgroundConfig.setNavBarGlassHighlightStrengthValue(it) },\n                                    onValueChangeFinished = { BackgroundConfig.save(context) },\n                                    valueRange = 0f..1f,\n                                    colors = SliderDefaults.colors(\n                                        thumbColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                        activeTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                    ),\n                                )\n                            }\n                        }\n                    }\n\n                    item {\n                        ToggleSettingCard(\n                            flat = flat,\n                            icon = Icons.Filled.LensBlur,\n                            title = stringResource(id = R.string.settings_navbar_glass_specular),\n                            description = stringResource(id = R.string.settings_navbar_glass_specular_summary),\n                            checked = BackgroundConfig.isNavBarGlassSpecularEnabled,\n                            onCheckedChange = {\n                                BackgroundConfig.setNavBarGlassSpecularEnabledState(it)\n                                BackgroundConfig.save(context)\n                            },\n                        )\n                    }\n\n                    item {\n                        ToggleSettingCard(\n                            flat = flat,\n                            icon = Icons.Filled.Grain,\n                            title = stringResource(id = R.string.settings_navbar_glass_inner_glow),\n                            description = stringResource(id = R.string.settings_navbar_glass_inner_glow_summary),\n                            checked = BackgroundConfig.isNavBarGlassInnerGlowEnabled,\n                            onCheckedChange = {\n                                BackgroundConfig.setNavBarGlassInnerGlowEnabledState(it)\n                                BackgroundConfig.save(context)\n                            },\n                        )\n                    }\n\n                    item {\n                        ToggleSettingCard(\n                            flat = flat,\n                            icon = Icons.Filled.BorderStyle,\n                            title = stringResource(id = R.string.settings_navbar_glass_border),\n                            description = stringResource(id = R.string.settings_navbar_glass_border_summary),\n                            checked = BackgroundConfig.isNavBarGlassBorderEnabled,\n                            onCheckedChange = {\n                                BackgroundConfig.setNavBarGlassBorderEnabledState(it)\n                                BackgroundConfig.save(context)\n                            },\n                        )\n                    }\n                }\n\n                item {\n                    ToggleSettingCard(\n                        flat = flat,\n                        icon = Icons.Filled.VisibilityOff,\n                        title = stringResource(id = R.string.settings_floating_auto_hide),\n                        description = stringResource(id = R.string.settings_floating_auto_hide_summary),\n                        checked = floatingAutoHide,\n                        onCheckedChange = {\n                            floatingAutoHide = it\n                            prefs.edit().putBoolean(\"floating_auto_hide\", it).apply()\n                        },\n                    )\n                }\n\n                item {\n                    ToggleSettingCard(\n                        flat = flat,\n                        icon = Icons.Filled.Swipe,\n                        title = stringResource(id = R.string.settings_floating_swipe_hide),\n                        description = stringResource(id = R.string.settings_floating_swipe_hide_summary),\n                        checked = floatingSwipeHide,\n                        onCheckedChange = {\n                            floatingSwipeHide = it\n                            prefs.edit().putBoolean(\"floating_swipe_hide\", it).apply()\n                        },\n                    )\n                }\n            }\n\n            item(visible = isListStyle) {\n                ToggleSettingCard(\n                    flat = flat,\n                    icon = Icons.Filled.LabelOff,\n                    title = stringResource(id = R.string.settings_list_card_hide_status_badge),\n                    description = stringResource(id = R.string.settings_list_card_hide_status_badge_summary),\n                    checked = BackgroundConfig.isListWorkingCardModeHidden,\n                    onCheckedChange = {\n                        BackgroundConfig.setListWorkingCardModeHiddenState(it)\n                        BackgroundConfig.save(context)\n                    },\n                )\n            }\n\n            item(visible = isListStyle && !BackgroundConfig.isListWorkingCardModeHidden) {\n                ExpressiveCard(flat = flat, onClick = { showCustomBadgeTextDialog.value = true }) {\n                    Row(\n                        modifier = Modifier.fillMaxWidth().padding(16.dp),\n                        horizontalArrangement = Arrangement.SpaceBetween,\n                        verticalAlignment = Alignment.CenterVertically,\n                    ) {\n                        Icon(imageVector = Icons.Filled.Badge, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                        Spacer(Modifier.width(16.dp))\n                        Column(modifier = Modifier.weight(1f)) {\n                            Text(\n                                text = stringResource(id = R.string.settings_custom_badge_text),\n                                style = MaterialTheme.typography.titleMedium,\n                                color = MaterialTheme.colorScheme.onSurface,\n                            )\n                            Text(\n                                text = currentBadgeTextMode,\n                                style = MaterialTheme.typography.bodyMedium,\n                                color = MaterialTheme.colorScheme.outline,\n                            )\n                        }\n                    }\n                }\n            }\n\n            item {\n                ToggleSettingCard(\n                    flat = flat,\n                    icon = Icons.Filled.Title,\n                    title = stringResource(id = R.string.settings_advanced_title_style),\n                    description = if (BackgroundConfig.isAdvancedTitleStyleEnabled) stringResource(id = R.string.settings_advanced_title_style_enabled) else stringResource(id = R.string.settings_advanced_title_style_summary),\n                    checked = BackgroundConfig.isAdvancedTitleStyleEnabled,\n                    onCheckedChange = {\n                        BackgroundConfig.setAdvancedTitleStyleEnabledState(it)\n                        BackgroundConfig.save(context)\n                        refreshTheme.value = true\n                    },\n                )\n            }\n\n            if (BackgroundConfig.isAdvancedTitleStyleEnabled) {\n                item {\n                    ExpressiveCard(flat = flat) {\n                        Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {\n                            Text(text = stringResource(id = R.string.settings_title_image_day_opacity), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)\n                            Slider(\n                                value = BackgroundConfig.titleImageDayOpacity,\n                                onValueChange = { BackgroundConfig.setTitleImageDayOpacityValue(it) },\n                                onValueChangeFinished = { BackgroundConfig.save(context) },\n                                valueRange = 0f..1f,\n                                colors = SliderDefaults.colors(\n                                    thumbColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                    activeTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                ),\n                            )\n                        }\n                    }\n                }\n\n                item {\n                    ExpressiveCard(flat = flat) {\n                        Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {\n                            Text(text = stringResource(id = R.string.settings_title_image_night_opacity), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)\n                            Slider(\n                                value = BackgroundConfig.titleImageNightOpacity,\n                                onValueChange = { BackgroundConfig.setTitleImageNightOpacityValue(it) },\n                                onValueChangeFinished = { BackgroundConfig.save(context) },\n                                valueRange = 0f..1f,\n                                colors = SliderDefaults.colors(\n                                    thumbColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                    activeTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                ),\n                            )\n                        }\n                    }\n                }\n\n                item {\n                    ExpressiveCard(flat = flat) {\n                        Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {\n                            Text(text = stringResource(id = R.string.settings_title_image_dim), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)\n                            Slider(\n                                value = BackgroundConfig.titleImageDim,\n                                onValueChange = { BackgroundConfig.setTitleImageDimValue(it) },\n                                onValueChangeFinished = { BackgroundConfig.save(context) },\n                                valueRange = 0f..1f,\n                                colors = SliderDefaults.colors(\n                                    thumbColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                    activeTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                ),\n                            )\n                        }\n                    }\n                }\n\n                item {\n                    ExpressiveCard(flat = flat) {\n                        Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {\n                            Text(text = stringResource(id = R.string.settings_title_image_offset_x), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)\n                            Slider(\n                                value = BackgroundConfig.titleImageOffsetX,\n                                onValueChange = { BackgroundConfig.setTitleImageOffsetXValue(it) },\n                                onValueChangeFinished = { BackgroundConfig.save(context) },\n                                valueRange = -1f..1f,\n                                colors = SliderDefaults.colors(\n                                    thumbColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                    activeTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                ),\n                            )\n                        }\n                    }\n                }\n\n                item {\n                    ExpressiveCard(\n                        flat = flat,\n                        onClick = {\n                            if (PermissionUtils.hasExternalStoragePermission(context)) {\n                                try {\n                                    pickTitleImageLauncher.launch(\"image/*\")\n                                } catch (e: ActivityNotFoundException) {\n                                    showToast(context, e.message ?: \"\")\n                                }\n                            } else {\n                                showToast(context, \"请先授予存储权限才能选择标题图片\")\n                            }\n                        }\n                    ) {\n                        Row(\n                            modifier = Modifier.fillMaxWidth().padding(16.dp),\n                            horizontalArrangement = Arrangement.SpaceBetween,\n                            verticalAlignment = Alignment.CenterVertically,\n                        ) {\n                            Icon(imageVector = Icons.Filled.Image, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                            Spacer(Modifier.width(16.dp))\n                            Column(modifier = Modifier.weight(1f)) {\n                                Text(text = stringResource(id = R.string.settings_select_title_image), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)\n                                if (!BackgroundConfig.titleImageUri.isNullOrEmpty()) {\n                                    Text(text = stringResource(id = R.string.settings_title_image_selected), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.outline)\n                                }\n                            }\n                        }\n                    }\n                }\n\n                if (!BackgroundConfig.titleImageUri.isNullOrEmpty()) {\n                    item {\n                        val clearTitleImageDialog = rememberConfirmDialog(\n                            onConfirm = {\n                                scope.launch {\n                                    loadingDialog.show()\n                                    BackgroundManager.clearTitleImage(context)\n                                    loadingDialog.hide()\n                                    snackBarHost.showSnackbar(message = context.getString(R.string.settings_title_image_cleared))\n                                    refreshTheme.value = true\n                                }\n                            }\n                        )\n                        ExpressiveCard(\n                            flat = flat,\n                            onClick = {\n                                clearTitleImageDialog.showConfirm(\n                                    title = context.getString(R.string.settings_clear_title_image),\n                                    content = context.getString(R.string.settings_clear_title_image_confirm),\n                                    markdown = false,\n                                )\n                            }\n                        ) {\n                            Row(\n                                modifier = Modifier.fillMaxWidth().padding(16.dp),\n                                verticalAlignment = Alignment.CenterVertically,\n                            ) {\n                                Icon(imageVector = Icons.Filled.Delete, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                                Spacer(Modifier.width(16.dp))\n                                Text(text = stringResource(id = R.string.settings_clear_title_image), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        SplicedColumnGroup(title = stringResource(R.string.settings_appearance_background), flat = flat) {\n            item {\n                ToggleSettingCard(\n                    flat = flat,\n                    icon = Icons.Filled.Wallpaper,\n                    title = stringResource(id = R.string.settings_custom_background),\n                    description = if (BackgroundConfig.isCustomBackgroundEnabled) stringResource(id = R.string.settings_custom_background_enabled) else stringResource(id = R.string.settings_custom_background_summary),\n                    checked = BackgroundConfig.isCustomBackgroundEnabled,\n                    onCheckedChange = {\n                        BackgroundConfig.setCustomBackgroundEnabledState(it)\n                        BackgroundConfig.save(context)\n                        refreshTheme.value = true\n                    },\n                )\n            }\n\n            if (BackgroundConfig.isCustomBackgroundEnabled) {\n                if (!BackgroundConfig.isVideoBackgroundEnabled) {\n                    item {\n                        ToggleSettingCard(\n                            flat = flat,\n                            icon = Icons.Filled.Contrast,\n                            title = stringResource(id = R.string.settings_custom_background_dual_dim),\n                            description = stringResource(id = R.string.settings_custom_background_dual_dim_desc),\n                            checked = BackgroundConfig.isDualBackgroundDimEnabled,\n                            onCheckedChange = {\n                                BackgroundConfig.setDualBackgroundDimEnabledState(it)\n                                BackgroundConfig.save(context)\n                                refreshTheme.value = true\n                            },\n                        )\n                    }\n\n                    item {\n                        ExpressiveCard(flat = flat) {\n                            Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {\n                                Text(text = stringResource(id = R.string.settings_custom_background_opacity), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)\n                                Slider(\n                                    value = BackgroundConfig.customBackgroundOpacity,\n                                    onValueChange = { BackgroundConfig.setCustomBackgroundOpacityValue(it) },\n                                    onValueChangeFinished = { BackgroundConfig.save(context) },\n                                    valueRange = 0f..1f,\n                                    colors = SliderDefaults.colors(\n                                        thumbColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                        activeTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                    ),\n                                )\n                            }\n                        }\n                    }\n\n                    item {\n                        ExpressiveCard(flat = flat) {\n                            Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {\n                                Text(text = stringResource(id = R.string.settings_custom_background_blur), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)\n                                Slider(\n                                    value = BackgroundConfig.customBackgroundBlur,\n                                    onValueChange = { BackgroundConfig.setCustomBackgroundBlurValue(it) },\n                                    onValueChangeFinished = { BackgroundConfig.save(context) },\n                                    valueRange = 0f..50f,\n                                    colors = SliderDefaults.colors(\n                                        thumbColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                        activeTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                    ),\n                                )\n                            }\n                        }\n                    }\n\n                    if (!BackgroundConfig.isDualBackgroundDimEnabled) {\n                        item {\n                            ExpressiveCard(flat = flat) {\n                                Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {\n                                    Text(text = stringResource(id = R.string.settings_custom_background_dim), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)\n                                    Slider(\n                                        value = BackgroundConfig.customBackgroundDim,\n                                        onValueChange = { BackgroundConfig.setCustomBackgroundDimValue(it) },\n                                        onValueChangeFinished = { BackgroundConfig.save(context) },\n                                        valueRange = 0f..1f,\n                                        colors = SliderDefaults.colors(\n                                            thumbColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                            activeTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                        ),\n                                    )\n                                }\n                            }\n                        }\n                    } else {\n                        item {\n                            ExpressiveCard(flat = flat) {\n                                Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {\n                                    Text(text = stringResource(id = R.string.settings_custom_background_day_dim), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)\n                                    Slider(\n                                        value = BackgroundConfig.customBackgroundDayDim,\n                                        onValueChange = { BackgroundConfig.setCustomBackgroundDayDimValue(it) },\n                                        onValueChangeFinished = { BackgroundConfig.save(context) },\n                                        valueRange = 0f..1f,\n                                        colors = SliderDefaults.colors(\n                                            thumbColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                            activeTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                        ),\n                                    )\n                                }\n                            }\n                        }\n\n                        item {\n                            ExpressiveCard(flat = flat) {\n                                Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {\n                                    Text(text = stringResource(id = R.string.settings_custom_background_night_dim), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)\n                                    Slider(\n                                        value = BackgroundConfig.customBackgroundNightDim,\n                                        onValueChange = { BackgroundConfig.setCustomBackgroundNightDimValue(it) },\n                                        onValueChangeFinished = { BackgroundConfig.save(context) },\n                                        valueRange = 0f..1f,\n                                        colors = SliderDefaults.colors(\n                                            thumbColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                            activeTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                        ),\n                                    )\n                                }\n                            }\n                        }\n                    }\n                }\n\n                item {\n                    ToggleSettingCard(\n                        flat = flat,\n                        icon = Icons.Filled.VideoFile,\n                        title = stringResource(id = R.string.settings_video_background),\n                        description = stringResource(id = R.string.settings_video_background_summary),\n                        checked = BackgroundConfig.isVideoBackgroundEnabled,\n                        onCheckedChange = {\n                            BackgroundConfig.setVideoBackgroundEnabledState(it)\n                            BackgroundConfig.save(context)\n                            refreshTheme.value = true\n                        },\n                    )\n                }\n\n                if (BackgroundConfig.isVideoBackgroundEnabled) {\n                    item {\n                        ExpressiveCard(\n                            flat = flat,\n                            onClick = {\n                                try {\n                                    pickVideoLauncher.launch(\"video/*\")\n                                } catch (e: ActivityNotFoundException) {\n                                    showToast(context, e.message ?: \"\")\n                                }\n                            }\n                        ) {\n                            Row(\n                                modifier = Modifier.fillMaxWidth().padding(16.dp),\n                                horizontalArrangement = Arrangement.SpaceBetween,\n                                verticalAlignment = Alignment.CenterVertically,\n                            ) {\n                                Icon(imageVector = Icons.Filled.VideoFile, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                                Spacer(Modifier.width(16.dp))\n                                Column(modifier = Modifier.weight(1f)) {\n                                    Text(text = stringResource(id = R.string.settings_select_video), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)\n                                    if (!BackgroundConfig.videoBackgroundUri.isNullOrEmpty()) {\n                                        Text(text = stringResource(id = R.string.settings_video_selected), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.outline)\n                                    }\n                                }\n                            }\n                        }\n                    }\n\n                    if (!BackgroundConfig.videoBackgroundUri.isNullOrEmpty()) {\n                        item {\n                            val clearVideoDialog = rememberConfirmDialog(\n                                onConfirm = {\n                                    scope.launch {\n                                        loadingDialog.show()\n                                        BackgroundManager.clearVideoBackground(context)\n                                        loadingDialog.hide()\n                                        snackBarHost.showSnackbar(message = context.getString(R.string.settings_background_image_cleared))\n                                        refreshTheme.value = true\n                                    }\n                                }\n                            )\n                            val clearVideoTitle = stringResource(id = R.string.settings_clear_video_background)\n                            val clearVideoConfirm = context.getString(R.string.settings_clear_video_background_confirm)\n                            ExpressiveCard(\n                                flat = flat,\n                                onClick = {\n                                    clearVideoDialog.showConfirm(\n                                        title = clearVideoTitle,\n                                        content = clearVideoConfirm,\n                                        markdown = false,\n                                    )\n                                }\n                            ) {\n                                Row(\n                                    modifier = Modifier.fillMaxWidth().padding(16.dp),\n                                    verticalAlignment = Alignment.CenterVertically,\n                                ) {\n                                    Icon(imageVector = Icons.Filled.Delete, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                                    Spacer(Modifier.width(16.dp))\n                                    Text(text = clearVideoTitle, style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)\n                                }\n                            }\n                        }\n                    }\n\n                    item {\n                        ExpressiveCard(flat = flat) {\n                            Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {\n                                Text(text = stringResource(id = R.string.settings_video_volume), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)\n                                Slider(\n                                    value = BackgroundConfig.videoVolume,\n                                    onValueChange = { BackgroundConfig.setVideoVolumeValue(it) },\n                                    onValueChangeFinished = { BackgroundConfig.save(context) },\n                                    valueRange = 0f..1f,\n                                    colors = SliderDefaults.colors(\n                                        thumbColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                        activeTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                    ),\n                                )\n                            }\n                        }\n                    }\n                } else {\n                    item {\n                        ToggleSettingCard(\n                            flat = flat,\n                            icon = Icons.Filled.GridView,\n                            title = stringResource(id = R.string.settings_multi_background_mode),\n                            description = stringResource(id = R.string.settings_multi_background_mode_summary),\n                            checked = BackgroundConfig.isMultiBackgroundEnabled,\n                            onCheckedChange = {\n                                BackgroundConfig.setMultiBackgroundEnabledState(it)\n                                BackgroundConfig.save(context)\n                                refreshTheme.value = true\n                            },\n                        )\n                    }\n\n                    if (BackgroundConfig.isMultiBackgroundEnabled) {\n                        item {\n                            val multiItems = listOf(\n                                Triple(R.string.settings_select_home_background, \"home\", BackgroundConfig.homeBackgroundUri),\n                                Triple(R.string.settings_select_kernel_background, \"kernel\", BackgroundConfig.kernelBackgroundUri),\n                                Triple(R.string.settings_select_superuser_background, \"superuser\", BackgroundConfig.superuserBackgroundUri),\n                                Triple(R.string.settings_select_system_module_background, \"system\", BackgroundConfig.systemModuleBackgroundUri),\n                                Triple(R.string.settings_select_settings_background, \"settings\", BackgroundConfig.settingsBackgroundUri)\n                            )\n                            Column {\n                                multiItems.forEach { (titleRes, type, uri) ->\n                                    ExpressiveCard(\n                                        flat = flat,\n                                        onClick = {\n                                            if (PermissionUtils.hasExternalStoragePermission(context) &&\n                                                PermissionUtils.hasWriteExternalStoragePermission(context)) {\n                                                pickingType = type\n                                                try {\n                                                    pickImageLauncher.launch(\"image/*\")\n                                                } catch (e: ActivityNotFoundException) {\n                                                    showToast(context, e.message ?: \"\")\n                                                }\n                                            } else {\n                                                showToast(context, \"请先授予存储权限才能选择背景图片\")\n                                            }\n                                        }\n                                    ) {\n                                        Row(\n                                            modifier = Modifier.fillMaxWidth().padding(16.dp),\n                                            horizontalArrangement = Arrangement.SpaceBetween,\n                                            verticalAlignment = Alignment.CenterVertically,\n                                        ) {\n                                            Icon(imageVector = Icons.Filled.Image, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                                            Spacer(Modifier.width(16.dp))\n                                            Column(modifier = Modifier.weight(1f)) {\n                                                Text(text = stringResource(id = titleRes), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)\n                                                if (!uri.isNullOrEmpty()) {\n                                                    Text(text = stringResource(id = R.string.settings_background_selected), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.outline)\n                                                }\n                                            }\n                                        }\n                                    }\n                                }\n                            }\n                        }\n                    } else {\n                        item {\n                            ExpressiveCard(\n                                flat = flat,\n                                onClick = {\n                                    if (PermissionUtils.hasExternalStoragePermission(context) &&\n                                        PermissionUtils.hasWriteExternalStoragePermission(context)) {\n                                        pickingType = \"default\"\n                                        try {\n                                            pickImageLauncher.launch(\"image/*\")\n                                        } catch (e: ActivityNotFoundException) {\n                                            showToast(context, e.message ?: \"\")\n                                        }\n                                    } else {\n                                        showToast(context, \"请先授予存储权限才能选择背景图片\")\n                                    }\n                                }\n                            ) {\n                                Row(\n                                    modifier = Modifier.fillMaxWidth().padding(16.dp),\n                                    horizontalArrangement = Arrangement.SpaceBetween,\n                                    verticalAlignment = Alignment.CenterVertically,\n                                ) {\n                                    Icon(imageVector = Icons.Filled.Image, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                                    Spacer(Modifier.width(16.dp))\n                                    Column(modifier = Modifier.weight(1f)) {\n                                        Text(text = stringResource(id = R.string.settings_select_background_image), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)\n                                        if (!BackgroundConfig.customBackgroundUri.isNullOrEmpty()) {\n                                            Text(text = stringResource(id = R.string.settings_background_selected), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.outline)\n                                        }\n                                    }\n                                }\n                            }\n                        }\n\n                        if (!BackgroundConfig.customBackgroundUri.isNullOrEmpty()) {\n                            item {\n                                val clearBackgroundDialog = rememberConfirmDialog(\n                                    onConfirm = {\n                                        scope.launch {\n                                            loadingDialog.show()\n                                            BackgroundManager.clearCustomBackground(context)\n                                            loadingDialog.hide()\n                                            snackBarHost.showSnackbar(message = context.getString(R.string.settings_background_image_cleared))\n                                            refreshTheme.value = true\n                                        }\n                                    }\n                                )\n                                val clearBgTitle = stringResource(id = R.string.settings_clear_background)\n                                val clearBgConfirm = context.getString(R.string.settings_clear_background_confirm)\n                                ExpressiveCard(\n                                    flat = flat,\n                                    onClick = {\n                                        clearBackgroundDialog.showConfirm(\n                                            title = clearBgTitle,\n                                            content = clearBgConfirm,\n                                            markdown = false,\n                                        )\n                                    }\n                                ) {\n                                    Row(\n                                        modifier = Modifier.fillMaxWidth().padding(16.dp),\n                                        verticalAlignment = Alignment.CenterVertically,\n                                    ) {\n                                        Icon(imageVector = Icons.Filled.Delete, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                                        Spacer(Modifier.width(16.dp))\n                                        Text(text = clearBgTitle, style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n\n            if (showGridCardSettings) {\n                item {\n                    ToggleSettingCard(\n                        flat = flat,\n                        icon = Icons.Filled.GridView,\n                        title = stringResource(id = R.string.settings_grid_working_card_background),\n                        description = if (BackgroundConfig.isGridWorkingCardBackgroundEnabled) stringResource(id = R.string.settings_grid_working_card_background_enabled) else stringResource(id = R.string.settings_grid_working_card_background_summary),\n                        checked = BackgroundConfig.isGridWorkingCardBackgroundEnabled,\n                        onCheckedChange = {\n                            BackgroundConfig.setGridWorkingCardBackgroundEnabledState(it)\n                            BackgroundConfig.save(context)\n                        },\n                    )\n                }\n\n                if (BackgroundConfig.isGridWorkingCardBackgroundEnabled) {\n                    item {\n                        ToggleSettingCard(\n                            flat = flat,\n                            icon = Icons.Filled.Contrast,\n                            title = stringResource(id = R.string.settings_grid_working_card_dual_opacity),\n                            description = stringResource(id = R.string.settings_grid_working_card_dual_opacity_desc),\n                            checked = BackgroundConfig.isGridDualOpacityEnabled,\n                            onCheckedChange = {\n                                BackgroundConfig.setGridDualOpacityEnabledState(it)\n                                BackgroundConfig.save(context)\n                            },\n                        )\n                    }\n\n                    if (!BackgroundConfig.isGridDualOpacityEnabled) {\n                        item {\n                            ExpressiveCard(flat = flat) {\n                                Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {\n                                    Text(text = stringResource(id = R.string.settings_custom_background_opacity), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)\n                                    Slider(\n                                        value = BackgroundConfig.gridWorkingCardBackgroundOpacity,\n                                        onValueChange = { BackgroundConfig.setGridWorkingCardBackgroundOpacityValue(it) },\n                                        onValueChangeFinished = { BackgroundConfig.save(context) },\n                                        valueRange = 0f..1f,\n                                        colors = SliderDefaults.colors(\n                                            thumbColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                            activeTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                        ),\n                                    )\n                                }\n                            }\n                        }\n                    } else {\n                        item {\n                            ExpressiveCard(flat = flat) {\n                                Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {\n                                    Text(text = stringResource(id = R.string.settings_grid_working_card_day_opacity), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)\n                                    Slider(\n                                        value = BackgroundConfig.gridWorkingCardBackgroundDayOpacity,\n                                        onValueChange = { BackgroundConfig.setGridWorkingCardBackgroundDayOpacityValue(it) },\n                                        onValueChangeFinished = { BackgroundConfig.save(context) },\n                                        valueRange = 0f..1f,\n                                        colors = SliderDefaults.colors(\n                                            thumbColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                            activeTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                        ),\n                                    )\n                                }\n                            }\n                        }\n\n                        item {\n                            ExpressiveCard(flat = flat) {\n                                Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {\n                                    Text(text = stringResource(id = R.string.settings_grid_working_card_night_opacity), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)\n                                    Slider(\n                                        value = BackgroundConfig.gridWorkingCardBackgroundNightOpacity,\n                                        onValueChange = { BackgroundConfig.setGridWorkingCardBackgroundNightOpacityValue(it) },\n                                        onValueChangeFinished = { BackgroundConfig.save(context) },\n                                        valueRange = 0f..1f,\n                                        colors = SliderDefaults.colors(\n                                            thumbColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                            activeTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                        ),\n                                    )\n                                }\n                            }\n                        }\n                    }\n\n                    item {\n                        ExpressiveCard(flat = flat) {\n                            Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {\n                                Text(text = stringResource(id = R.string.settings_custom_background_dim), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)\n                                Slider(\n                                    value = BackgroundConfig.gridWorkingCardBackgroundDim,\n                                    onValueChange = { BackgroundConfig.setGridWorkingCardBackgroundDimValue(it) },\n                                    onValueChangeFinished = { BackgroundConfig.save(context) },\n                                    valueRange = 0f..1f,\n                                    colors = SliderDefaults.colors(\n                                        thumbColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                        activeTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                    ),\n                                )\n                            }\n                        }\n                    }\n\n                    item {\n                        ExpressiveCard(\n                            flat = flat,\n                            onClick = {\n                                if (PermissionUtils.hasExternalStoragePermission(context)) {\n                                    try {\n                                        pickGridImageLauncher.launch(\"image/*\")\n                                    } catch (e: ActivityNotFoundException) {\n                                        showToast(context, e.message ?: \"\")\n                                    }\n                                } else {\n                                    showToast(context, \"请先授予存储权限才能选择背景图片\")\n                                }\n                            }\n                        ) {\n                            Row(\n                                modifier = Modifier.fillMaxWidth().padding(16.dp),\n                                horizontalArrangement = Arrangement.SpaceBetween,\n                                verticalAlignment = Alignment.CenterVertically,\n                            ) {\n                                Icon(imageVector = Icons.Filled.Image, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                                Spacer(Modifier.width(16.dp))\n                                Column(modifier = Modifier.weight(1f)) {\n                                    Text(text = stringResource(id = R.string.settings_select_background_image), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)\n                                    if (!BackgroundConfig.gridWorkingCardBackgroundUri.isNullOrEmpty()) {\n                                        Text(text = stringResource(id = R.string.settings_grid_working_card_background_selected), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.outline)\n                                    }\n                                }\n                            }\n                        }\n                    }\n\n                    item {\n                        val clearGridBackgroundDialog = rememberConfirmDialog(\n                            onConfirm = {\n                                scope.launch {\n                                    loadingDialog.show()\n                                    BackgroundManager.clearGridWorkingCardBackground(context)\n                                    loadingDialog.hide()\n                                    snackBarHost.showSnackbar(message = context.getString(R.string.settings_grid_working_card_background_cleared))\n                                }\n                            }\n                        )\n                        ExpressiveCard(\n                            flat = flat,\n                            onClick = {\n                                clearGridBackgroundDialog.showConfirm(\n                                    title = context.getString(R.string.settings_clear_grid_working_card_background),\n                                    content = context.getString(R.string.settings_clear_grid_working_card_background_confirm),\n                                    markdown = false,\n                                )\n                            }\n                        ) {\n                            Row(\n                                modifier = Modifier.fillMaxWidth().padding(16.dp),\n                                verticalAlignment = Alignment.CenterVertically,\n                            ) {\n                                Icon(imageVector = Icons.Filled.Delete, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                                Spacer(Modifier.width(16.dp))\n                                Text(text = stringResource(id = R.string.settings_clear_grid_working_card_background), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)\n                            }\n                        }\n                    }\n                }\n\n                item {\n                    ToggleSettingCard(\n                        flat = flat,\n                        icon = Icons.Filled.CheckCircle,\n                        title = stringResource(id = R.string.settings_grid_working_card_hide_check),\n                        description = stringResource(id = R.string.settings_grid_working_card_hide_check_summary),\n                        checked = BackgroundConfig.isGridWorkingCardCheckHidden,\n                        onCheckedChange = {\n                            BackgroundConfig.setGridWorkingCardCheckHiddenState(it)\n                            BackgroundConfig.save(context)\n                        },\n                    )\n                }\n\n                item {\n                    ToggleSettingCard(\n                        flat = flat,\n                        icon = Icons.Filled.TextFields,\n                        title = stringResource(id = R.string.settings_grid_working_card_hide_text),\n                        description = stringResource(id = R.string.settings_grid_working_card_hide_text_summary),\n                        checked = BackgroundConfig.isGridWorkingCardTextHidden,\n                        onCheckedChange = {\n                            BackgroundConfig.setGridWorkingCardTextHiddenState(it)\n                            BackgroundConfig.save(context)\n                        },\n                    )\n                }\n\n                item {\n                    ToggleSettingCard(\n                        flat = flat,\n                        icon = Icons.Filled.Label,\n                        title = stringResource(id = R.string.settings_grid_working_card_hide_mode),\n                        description = stringResource(id = R.string.settings_grid_working_card_hide_mode_summary),\n                        checked = BackgroundConfig.isGridWorkingCardModeHidden,\n                        onCheckedChange = {\n                            BackgroundConfig.setGridWorkingCardModeHiddenState(it)\n                            BackgroundConfig.save(context)\n                        },\n                    )\n                }\n\n                item(visible = !BackgroundConfig.isGridWorkingCardModeHidden) {\n                    ExpressiveCard(flat = flat, onClick = { showCustomBadgeTextDialog.value = true }) {\n                        Row(\n                            modifier = Modifier.fillMaxWidth().padding(16.dp),\n                            horizontalArrangement = Arrangement.SpaceBetween,\n                            verticalAlignment = Alignment.CenterVertically,\n                        ) {\n                            Icon(imageVector = Icons.Filled.Badge, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                            Spacer(Modifier.width(16.dp))\n                            Column(modifier = Modifier.weight(1f)) {\n                                Text(\n                                    text = stringResource(id = R.string.settings_custom_badge_text),\n                                    style = MaterialTheme.typography.titleMedium,\n                                    color = MaterialTheme.colorScheme.onSurface,\n                                )\n                                Text(\n                                    text = currentBadgeTextMode,\n                                    style = MaterialTheme.typography.bodyMedium,\n                                    color = MaterialTheme.colorScheme.outline,\n                                )\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        if (showCustomBadgeTextDialog.value) {\n            AlertDialog(\n                onDismissRequest = { showCustomBadgeTextDialog.value = false },\n                title = { Text(stringResource(id = R.string.settings_custom_badge_text)) },\n                text = {\n                    Column {\n                        Text(\n                            stringResource(id = R.string.settings_custom_badge_text_summary),\n                            style = MaterialTheme.typography.bodyMedium,\n                            modifier = Modifier.padding(bottom = 16.dp),\n                        )\n                        badgeTextModes.forEachIndexed { index, mode ->\n                            Row(\n                                verticalAlignment = Alignment.CenterVertically,\n                                modifier = Modifier\n                                    .fillMaxWidth()\n                                    .clickable {\n                                        BackgroundConfig.setCustomBadgeTextModeValue(index)\n                                        BackgroundConfig.save(context)\n                                        showCustomBadgeTextDialog.value = false\n                                    }\n                                    .padding(vertical = 12.dp)\n                            ) {\n                                RadioButton(\n                                    selected = index == currentBadgeTextModeIndex,\n                                    onClick = null\n                                )\n                                Spacer(modifier = Modifier.width(8.dp))\n                                Text(text = mode)\n                            }\n                        }\n                    }\n                },\n                confirmButton = {\n                    TextButton(onClick = { showCustomBadgeTextDialog.value = false }) {\n                        Text(stringResource(android.R.string.cancel))\n                    }\n                }\n            )\n        }\n\n        SplicedColumnGroup(title = stringResource(R.string.settings_appearance_banner), flat = flat) {\n            item {\n                ToggleSettingCard(\n                    flat = flat,\n                    icon = Icons.Filled.Campaign,\n                    title = stringResource(id = R.string.apm_enable_module_banner),\n                    description = stringResource(id = R.string.apm_enable_module_banner_summary),\n                    checked = BackgroundConfig.isBannerEnabled,\n                    onCheckedChange = {\n                        BackgroundConfig.setBannerEnabledState(it)\n                        BackgroundConfig.save(context)\n                    },\n                )\n            }\n\n            if (BackgroundConfig.isBannerEnabled) {\n                item {\n                    ToggleSettingCard(\n                        flat = flat,\n                        icon = Icons.Filled.Image,\n                        title = stringResource(id = R.string.apm_enable_folk_banner),\n                        description = stringResource(id = R.string.apm_enable_folk_banner_summary),\n                        checked = BackgroundConfig.isFolkBannerEnabled,\n                        onCheckedChange = {\n                            BackgroundConfig.setFolkBannerEnabledState(it)\n                            BackgroundConfig.save(context)\n                        },\n                    )\n                }\n\n                if (BackgroundConfig.isFolkBannerEnabled) {\n                    item {\n                        ToggleSettingCard(\n                            flat = flat,\n                            icon = Icons.Filled.Api,\n                            title = stringResource(id = R.string.apm_banner_api_mode),\n                            description = stringResource(id = R.string.apm_banner_api_mode_summary),\n                            checked = BackgroundConfig.isBannerApiModeEnabled,\n                            onCheckedChange = {\n                                BackgroundConfig.setBannerApiModeEnabledState(it)\n                                BackgroundConfig.save(context)\n                            },\n                        )\n                    }\n\n                    if (BackgroundConfig.isBannerApiModeEnabled) {\n                        item {\n                            val showBannerApiConfigDialog = remember { mutableStateOf(false) }\n                            val apiSourceSummary = if (BackgroundConfig.bannerApiSource.isNotBlank()) {\n                                if (BackgroundConfig.bannerApiSource.startsWith(\"/\")) {\n                                    context.getString(R.string.apm_banner_local_dir_configured)\n                                } else {\n                                    context.getString(R.string.apm_banner_api_url_configured)\n                                }\n                            } else {\n                                context.getString(R.string.apm_banner_api_source_not_configured)\n                            }\n\n                            Column {\n                                ExpressiveCard(flat = flat, onClick = { showBannerApiConfigDialog.value = true }) {\n                                    Row(\n                                        modifier = Modifier.fillMaxWidth().padding(16.dp),\n                                        horizontalArrangement = Arrangement.SpaceBetween,\n                                        verticalAlignment = Alignment.CenterVertically,\n                                    ) {\n                                        Icon(imageVector = Icons.Filled.Api, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                                        Spacer(Modifier.width(16.dp))\n                                        Column(modifier = Modifier.weight(1f)) {\n                                            Text(\n                                                text = stringResource(id = R.string.apm_banner_api_source),\n                                                style = MaterialTheme.typography.titleMedium,\n                                                color = MaterialTheme.colorScheme.onSurface,\n                                            )\n                                            Text(\n                                                text = apiSourceSummary,\n                                                style = MaterialTheme.typography.bodyMedium,\n                                                color = MaterialTheme.colorScheme.outline,\n                                            )\n                                        }\n                                    }\n                                }\n\n                                if (showBannerApiConfigDialog.value) {\n                                    BannerApiConfigDialog(\n                                        showDialog = showBannerApiConfigDialog,\n                                        currentSource = BackgroundConfig.bannerApiSource,\n                                        onConfirm = { newSource ->\n                                            BackgroundConfig.setBannerApiSourceValue(newSource)\n                                            BackgroundConfig.save(context)\n                                        },\n                                        onClearCache = {\n                                            scope.launch {\n                                                loadingDialog.show()\n                                                me.bmax.apatch.ui.screen.BannerApiService.clearAllCache(context)\n                                                loadingDialog.hide()\n                                                showToast(context, context.getString(R.string.apm_banner_cache_cleared))\n                                            }\n                                        }\n                                    )\n                                }\n\n                                ExpressiveCard(flat = flat, onClick = { onNavigateToApiMarketplace() }) {\n                                    Row(\n                                        modifier = Modifier.fillMaxWidth().padding(16.dp),\n                                        verticalAlignment = Alignment.CenterVertically,\n                                    ) {\n                                        Icon(imageVector = Icons.Filled.Store, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                                        Spacer(Modifier.width(16.dp))\n                                        Text(\n                                            text = stringResource(id = R.string.apm_api_marketplace_title),\n                                            style = MaterialTheme.typography.titleMedium,\n                                            color = MaterialTheme.colorScheme.onSurface,\n                                        )\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n\n                item {\n                    ToggleSettingCard(\n                        flat = flat,\n                        icon = Icons.Filled.Opacity,\n                        title = stringResource(id = R.string.settings_banner_custom_opacity),\n                        description = stringResource(id = R.string.settings_banner_custom_opacity_summary),\n                        checked = BackgroundConfig.isBannerCustomOpacityEnabled,\n                        onCheckedChange = {\n                            BackgroundConfig.setBannerCustomOpacityEnabledState(it)\n                            BackgroundConfig.save(context)\n                        },\n                    )\n                }\n\n                if (BackgroundConfig.isBannerCustomOpacityEnabled) {\n                    item {\n                        ExpressiveCard(flat = flat) {\n                            Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {\n                                Text(text = stringResource(id = R.string.settings_banner_opacity), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)\n                                Slider(\n                                    value = BackgroundConfig.bannerCustomOpacity,\n                                    onValueChange = { BackgroundConfig.setBannerCustomOpacityValue(it) },\n                                    onValueChangeFinished = { BackgroundConfig.save(context) },\n                                    valueRange = 0f..1f,\n                                    colors = SliderDefaults.colors(\n                                        thumbColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                        activeTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                                    ),\n                                )\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        SplicedColumnGroup(title = stringResource(R.string.settings_appearance_font), flat = flat) {\n            item {\n                ToggleSettingCard(\n                    flat = flat,\n                    icon = Icons.Filled.FormatSize,\n                    title = stringResource(id = R.string.settings_custom_font),\n                    description = if (customFontEnabled) {\n                        if (FontConfig.customFontFilename != null) stringResource(id = R.string.settings_font_selected) else stringResource(id = R.string.settings_custom_font_enabled)\n                    } else {\n                        stringResource(id = R.string.settings_custom_font_summary)\n                    },\n                    checked = customFontEnabled,\n                    onCheckedChange = {\n                        customFontEnabled = it\n                        FontConfig.setCustomFontEnabledState(it)\n                        FontConfig.save(context)\n                        refreshTheme.value = true\n                    },\n                )\n            }\n\n            if (FontConfig.isCustomFontEnabled) {\n                item {\n                    ExpressiveCard(\n                        flat = flat,\n                        onClick = {\n                            try {\n                                pickFontLauncher.launch(\"*/*\")\n                            } catch (e: ActivityNotFoundException) {\n                                showToast(context, e.message ?: \"\")\n                            }\n                        }\n                    ) {\n                        Row(\n                            modifier = Modifier.fillMaxWidth().padding(16.dp),\n                            verticalAlignment = Alignment.CenterVertically,\n                        ) {\n                            Icon(imageVector = Icons.Filled.FontDownload, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                            Spacer(Modifier.width(16.dp))\n                            Text(text = stringResource(id = R.string.settings_select_font_file), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)\n                        }\n                    }\n                }\n\n                if (FontConfig.customFontFilename != null) {\n                    item {\n                        val clearFontDialog = rememberConfirmDialog(\n                            onConfirm = {\n                                FontConfig.clearFont(context)\n                                refreshTheme.value = true\n                                scope.launch {\n                                    snackBarHost.showSnackbar(message = context.getString(R.string.settings_font_cleared))\n                                }\n                            }\n                        )\n                        val clearFontTitle = stringResource(id = R.string.settings_clear_font)\n                        val clearFontConfirm = context.getString(R.string.settings_clear_font_confirm)\n                        ExpressiveCard(\n                            flat = flat,\n                            onClick = {\n                                clearFontDialog.showConfirm(\n                                    title = clearFontTitle,\n                                    content = clearFontConfirm,\n                                )\n                            }\n                        ) {\n                            Row(\n                                modifier = Modifier.fillMaxWidth().padding(16.dp),\n                                verticalAlignment = Alignment.CenterVertically,\n                            ) {\n                                Icon(imageVector = Icons.Filled.Delete, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                                Spacer(Modifier.width(16.dp))\n                                Text(text = clearFontTitle, style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        SplicedColumnGroup(title = stringResource(R.string.settings_appearance_theme), flat = flat) {\n            item {\n                ExpressiveCard(flat = flat, onClick = { onNavigateToThemeStore() }) {\n                    Row(\n                        modifier = Modifier.fillMaxWidth().padding(16.dp),\n                        verticalAlignment = Alignment.CenterVertically,\n                    ) {\n                        Icon(imageVector = Icons.Filled.Store, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                        Spacer(Modifier.width(16.dp))\n                        Text(text = stringResource(id = R.string.theme_store_title), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)\n                    }\n                }\n            }\n\n            item {\n                ExpressiveCard(flat = flat, onClick = { showExportDialog.value = true }) {\n                    Row(\n                        modifier = Modifier.fillMaxWidth().padding(16.dp),\n                        verticalAlignment = Alignment.CenterVertically,\n                    ) {\n                        Icon(imageVector = Icons.Filled.FileDownload, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                        Spacer(Modifier.width(16.dp))\n                        Text(text = stringResource(id = R.string.settings_save_theme), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)\n                    }\n                }\n            }\n\n            item {\n                ExpressiveCard(flat = flat, onClick = { showFilePicker.value = true }) {\n                    Row(\n                        modifier = Modifier.fillMaxWidth().padding(16.dp),\n                        verticalAlignment = Alignment.CenterVertically,\n                    ) {\n                        Icon(imageVector = Icons.Filled.FileUpload, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                        Spacer(Modifier.width(16.dp))\n                        Text(text = stringResource(id = R.string.settings_import_theme), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)\n                    }\n                }\n            }\n\n            item {\n                val resetThemeDialog = rememberConfirmDialog(\n                    onConfirm = {\n                        scope.launch {\n                            loadingDialog.show()\n                            val success = ThemeManager.resetTheme(context)\n                            loadingDialog.hide()\n                            snackBarHost.showSnackbar(\n                                message = if (success) context.getString(R.string.settings_theme_reset) else context.getString(R.string.settings_theme_reset_failed)\n                            )\n                        }\n                    }\n                )\n                val resetThemeTitle = stringResource(id = R.string.settings_reset_theme)\n                val resetThemeConfirm = context.getString(R.string.settings_reset_theme_confirm)\n                ExpressiveCard(\n                    flat = flat,\n                    onClick = {\n                        resetThemeDialog.showConfirm(\n                            title = resetThemeTitle,\n                            content = resetThemeConfirm,\n                        )\n                    }\n                ) {\n                    Row(\n                        modifier = Modifier.fillMaxWidth().padding(16.dp),\n                        verticalAlignment = Alignment.CenterVertically,\n                    ) {\n                        Icon(imageVector = Icons.Filled.RestartAlt, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                        Spacer(Modifier.width(16.dp))\n                        Text(text = resetThemeTitle, style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)\n                    }\n                }\n            }\n        }\n        }\n\n    if (showHomeLayoutChooseDialog.value) {\n        HomeLayoutChooseDialog(showHomeLayoutChooseDialog) { selectedLayout ->\n            currentStyle = selectedLayout\n            refreshTheme.value = true\n        }\n    }\n\n    if (showNavSchemeDialog) {\n        NavModeChooseDialog(\n            showDialog = remember { mutableStateOf(true) }.apply { value = showNavSchemeDialog },\n            currentMode = currentNavMode,\n            onModeSelected = { mode ->\n                currentNavMode = mode\n                prefs.edit().putString(\"nav_mode\", mode).apply()\n                showNavSchemeDialog = false\n            },\n            onDismiss = { showNavSchemeDialog = false }\n        )\n    }\n\n    if (showStatsTopLayoutDialog) {\n        StatsTopLayoutChooseDialog(\n            showDialog = remember { mutableStateOf(true) }.apply { value = showStatsTopLayoutDialog },\n            currentMode = statsTopLayout,\n            onModeSelected = { mode ->\n                statsTopLayout = mode\n                prefs.edit().putString(\"stats_top_layout\", mode).apply()\n                showStatsTopLayoutDialog = false\n            },\n            onDismiss = { showStatsTopLayoutDialog = false }\n        )\n    }\n\n    if (showExportDialog.value) {\n        ThemeExportDialog(\n            showDialog = showExportDialog,\n            onConfirm = { metadata ->\n                pendingExportMetadata = metadata\n                scope.launch {\n                    loadingDialog.show()\n                    try {\n                        val exportDir = java.io.File(\"/storage/emulated/0/Download/FolkPatch/Themes/\")\n                        if (!exportDir.exists()) {\n                            exportDir.mkdirs()\n                        }\n                        val safeName = metadata.name.replace(\"[\\\\\\\\/:*?\\\"<>|]\".toRegex(), \"_\")\n                        val fileName = \"$safeName.fpt\"\n                        val file = java.io.File(exportDir, fileName)\n                        val uri = Uri.fromFile(file)\n                        val success = ThemeManager.exportTheme(context, uri, metadata)\n                        loadingDialog.hide()\n                        snackBarHost.showSnackbar(\n                            message = if (success) context.getString(R.string.settings_theme_saved) + \": ${file.absolutePath}\" else context.getString(R.string.settings_theme_save_failed)\n                        )\n                    } catch (e: Exception) {\n                        loadingDialog.hide()\n                        snackBarHost.showSnackbar(message = context.getString(R.string.settings_theme_save_failed) + \": ${e.message}\")\n                    }\n                    pendingExportMetadata = null\n                }\n            }\n        )\n    }\n\n    if (showImportDialog.value && pendingImportMetadata != null) {\n        ThemeImportDialog(\n            showDialog = showImportDialog,\n            metadata = pendingImportMetadata!!,\n            onConfirm = {\n                pendingImportUri?.let { uri ->\n                    scope.launch {\n                        loadingDialog.show()\n                        val success = ThemeManager.importTheme(context, uri)\n                        loadingDialog.hide()\n                        snackBarHost.showSnackbar(\n                            message = if (success) context.getString(R.string.settings_theme_imported) else context.getString(R.string.settings_theme_import_failed)\n                        )\n                        pendingImportUri = null\n                        pendingImportMetadata = null\n                    }\n                }\n            }\n        )\n    }\n\n    if (showFilePicker.value) {\n        FilePickerDialog(\n            onDismissRequest = { showFilePicker.value = false },\n            onFileSelected = { file ->\n                showFilePicker.value = false\n                val uri = Uri.fromFile(file)\n                scope.launch {\n                    loadingDialog.show()\n                    val metadata = ThemeManager.readThemeMetadata(context, uri)\n                    loadingDialog.hide()\n                    if (metadata != null) {\n                        pendingImportUri = uri\n                        pendingImportMetadata = metadata\n                        showImportDialog.value = true\n                    } else {\n                        loadingDialog.show()\n                        val success = ThemeManager.importTheme(context, uri)\n                        loadingDialog.hide()\n                        snackBarHost.showSnackbar(\n                            message = if (success) context.getString(R.string.settings_theme_imported) else context.getString(R.string.settings_theme_import_failed)\n                        )\n                    }\n                }\n            }\n        )\n    }\n}\n\n@Composable\nprivate fun colorNameToString(colorName: String): Int {\n    return colorsList().find { it.name == colorName }?.nameId ?: R.string.blue_theme\n}\n\nprivate data class APColor(\n    val name: String, @param:StringRes val nameId: Int\n)\n\nprivate fun colorsList(): List<APColor> {\n    return listOf(\n        APColor(\"amber\", R.string.amber_theme),\n        APColor(\"blue_grey\", R.string.blue_grey_theme),\n        APColor(\"blue\", R.string.blue_theme),\n        APColor(\"brown\", R.string.brown_theme),\n        APColor(\"cyan\", R.string.cyan_theme),\n        APColor(\"deep_orange\", R.string.deep_orange_theme),\n        APColor(\"deep_purple\", R.string.deep_purple_theme),\n        APColor(\"green\", R.string.green_theme),\n        APColor(\"indigo\", R.string.indigo_theme),\n        APColor(\"light_blue\", R.string.light_blue_theme),\n        APColor(\"light_green\", R.string.light_green_theme),\n        APColor(\"lime\", R.string.lime_theme),\n        APColor(\"orange\", R.string.orange_theme),\n        APColor(\"pink\", R.string.pink_theme),\n        APColor(\"purple\", R.string.purple_theme),\n        APColor(\"red\", R.string.red_theme),\n        APColor(\"sakura\", R.string.sakura_theme),\n        APColor(\"teal\", R.string.teal_theme),\n        APColor(\"yellow\", R.string.yellow_theme),\n        APColor(\"ink_wash\", R.string.ink_wash_theme),\n    )\n}\n\n@Composable\nprivate fun homeLayoutStyleToString(style: String): Int {\n    return when (style) {\n        \"kernelsu\" -> R.string.settings_home_layout_grid\n        \"focus\" -> R.string.settings_home_layout_focus\n        \"sign\" -> R.string.settings_home_layout_sign\n        \"circle\" -> R.string.settings_home_layout_circle\n        \"dashboard_ui\" -> R.string.settings_home_layout_dashboard_pro\n        \"stats\" -> R.string.settings_home_layout_stats\n        else -> R.string.settings_home_layout_default\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun ThemeChooseDialog(showDialog: MutableState<Boolean>) {\n    val prefs = APApplication.sharedPreferences\n    BasicAlertDialog(\n        onDismissRequest = { showDialog.value = false }, properties = DialogProperties(\n            decorFitsSystemWindows = true,\n            usePlatformDefaultWidth = false,\n        )\n    ) {\n        Surface(\n            modifier = Modifier\n                .width(310.dp)\n                .wrapContentHeight(),\n            shape = RoundedCornerShape(30.dp),\n            tonalElevation = AlertDialogDefaults.TonalElevation,\n            color = AlertDialogDefaults.containerColor,\n        ) {\n            LazyColumn {\n                items(colorsList()) {\n                    ListItem(\n                        headlineContent = { Text(text = stringResource(it.nameId)) },\n                        modifier = Modifier.clickable {\n                            showDialog.value = false\n                            prefs.edit { putString(\"custom_color\", it.name) }\n                            refreshTheme.value = true\n                        })\n                }\n            }\n            val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n            APDialogBlurBehindUtils.setupWindowBlurListener(dialogWindowProvider.window)\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun HomeLayoutChooseDialog(showDialog: MutableState<Boolean>, onLayoutSelected: (String) -> Unit) {\n    val prefs = APApplication.sharedPreferences\n\n    BasicAlertDialog(\n        onDismissRequest = { showDialog.value = false }, properties = DialogProperties(\n            decorFitsSystemWindows = true,\n            usePlatformDefaultWidth = false,\n        )\n    ) {\n        Surface(\n            modifier = Modifier\n                .width(310.dp)\n                .wrapContentHeight(),\n            shape = RoundedCornerShape(30.dp),\n            tonalElevation = AlertDialogDefaults.TonalElevation,\n            color = AlertDialogDefaults.containerColor,\n        ) {\n            Column(modifier = Modifier.padding(24.dp)) {\n                Text(\n                    text = stringResource(R.string.settings_home_layout_style),\n                    style = MaterialTheme.typography.headlineSmall,\n                    modifier = Modifier.padding(bottom = 16.dp)\n                )\n                \n                val currentStyle = prefs.getString(\"home_layout_style\", \"circle\")\n                \n                Surface(\n                    shape = RoundedCornerShape(12.dp),\n                    color = AlertDialogDefaults.containerColor,\n                    tonalElevation = 2.dp\n                ) {\n                    Column {\n                        ListItem(\n                            headlineContent = { Text(stringResource(R.string.settings_home_layout_default)) },\n                            leadingContent = {\n                                RadioButton(\n                                    selected = currentStyle == \"default\",\n                                    onClick = null\n                                )\n                            },\n                            modifier = Modifier.clickable {\n                                prefs.edit().putString(\"home_layout_style\", \"default\").apply()\n                                onLayoutSelected(\"default\")\n                                showDialog.value = false\n                            }\n                        )\n                        \n                        ListItem(\n                            headlineContent = { Text(stringResource(R.string.settings_home_layout_grid)) },\n                            leadingContent = {\n                                RadioButton(\n                                    selected = currentStyle == \"kernelsu\",\n                                    onClick = null\n                                )\n                            },\n                            modifier = Modifier.clickable {\n                                prefs.edit().putString(\"home_layout_style\", \"kernelsu\").apply()\n                                onLayoutSelected(\"kernelsu\")\n                                showDialog.value = false\n                            }\n                        )\n                        \n                        ListItem(\n                            headlineContent = { Text(stringResource(R.string.settings_home_layout_focus)) },\n                            leadingContent = {\n                                RadioButton(\n                                    selected = currentStyle == \"focus\",\n                                    onClick = null\n                                )\n                            },\n                            modifier = Modifier.clickable {\n                                prefs.edit().putString(\"home_layout_style\", \"focus\").apply()\n                                onLayoutSelected(\"focus\")\n                                showDialog.value = false\n                            }\n                        )\n\n                        ListItem(\n                            headlineContent = { Text(stringResource(R.string.settings_home_layout_sign)) },\n                            leadingContent = {\n                                RadioButton(\n                                    selected = currentStyle == \"sign\",\n                                    onClick = null\n                                )\n                            },\n                            modifier = Modifier.clickable {\n                                prefs.edit().putString(\"home_layout_style\", \"sign\").apply()\n                                onLayoutSelected(\"sign\")\n                                showDialog.value = false\n                            }\n                        )\n\n                        ListItem(\n                            headlineContent = { Text(stringResource(R.string.settings_home_layout_circle)) },\n                            leadingContent = {\n                                RadioButton(\n                                    selected = currentStyle == \"circle\",\n                                    onClick = null\n                                )\n                            },\n                            modifier = Modifier.clickable {\n                                prefs.edit().putString(\"home_layout_style\", \"circle\").apply()\n                                onLayoutSelected(\"circle\")\n                                showDialog.value = false\n                            }\n                        )\n\n                        ListItem(\n                            headlineContent = { Text(stringResource(R.string.settings_home_layout_dashboard_pro)) },\n                            leadingContent = {\n                                RadioButton(\n                                    selected = currentStyle == \"dashboard_ui\",\n                                    onClick = null\n                                )\n                            },\n                            modifier = Modifier.clickable {\n                                prefs.edit().putString(\"home_layout_style\", \"dashboard_ui\").apply()\n                                onLayoutSelected(\"dashboard_ui\")\n                                showDialog.value = false\n                            }\n                        )\n\n                        ListItem(\n                            headlineContent = { Text(stringResource(R.string.settings_home_layout_stats)) },\n                            leadingContent = {\n                                RadioButton(\n                                    selected = currentStyle == \"stats\",\n                                    onClick = null\n                                )\n                            },\n                            modifier = Modifier.clickable {\n                                prefs.edit().putString(\"home_layout_style\", \"stats\").apply()\n                                onLayoutSelected(\"stats\")\n                                showDialog.value = false\n                            }\n                        )\n                    }\n                }\n                \n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(top = 24.dp),\n                    horizontalArrangement = Arrangement.End\n                ) {\n                    TextButton(onClick = { showDialog.value = false }) {\n                        Text(stringResource(id = android.R.string.cancel))\n                    }\n                }\n            }\n            val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n            APDialogBlurBehindUtils.setupWindowBlurListener(dialogWindowProvider.window)\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun ThemeExportDialog(\n    showDialog: MutableState<Boolean>,\n    onConfirm: (ThemeManager.ThemeMetadata) -> Unit\n) {\n    var name by remember { mutableStateOf(\"\") }\n    var type by remember { mutableStateOf(\"phone\") }\n    var version by remember { mutableStateOf(\"\") }\n    var author by remember { mutableStateOf(\"\") }\n    var description by remember { mutableStateOf(\"\") }\n\n    BasicAlertDialog(\n        onDismissRequest = { showDialog.value = false },\n        properties = DialogProperties(\n            decorFitsSystemWindows = true,\n            usePlatformDefaultWidth = false,\n        )\n    ) {\n        Surface(\n            modifier = Modifier\n                .width(320.dp)\n                .wrapContentHeight(),\n            shape = RoundedCornerShape(28.dp),\n            tonalElevation = AlertDialogDefaults.TonalElevation,\n            color = AlertDialogDefaults.containerColor,\n        ) {\n            Column(\n                modifier = Modifier\n                    .padding(24.dp)\n                    .verticalScroll(rememberScrollState())\n            ) {\n                Text(\n                    text = stringResource(R.string.theme_export_title),\n                    style = MaterialTheme.typography.headlineSmall,\n                    modifier = Modifier.padding(bottom = 16.dp)\n                )\n\n                OutlinedTextField(\n                    value = name,\n                    onValueChange = { name = it },\n                    label = { Text(stringResource(R.string.theme_name)) },\n                    modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp)\n                )\n\n                Text(\n                    text = stringResource(R.string.theme_type),\n                    style = MaterialTheme.typography.bodyMedium,\n                    modifier = Modifier.padding(bottom = 4.dp, top = 4.dp)\n                )\n                Row(modifier = Modifier.fillMaxWidth()) {\n                    Row(\n                        verticalAlignment = Alignment.CenterVertically,\n                        modifier = Modifier.clickable { type = \"phone\" }\n                    ) {\n                        RadioButton(\n                            selected = type == \"phone\",\n                            onClick = { type = \"phone\" }\n                        )\n                        Text(\n                            text = stringResource(R.string.theme_type_phone),\n                            style = MaterialTheme.typography.bodyMedium,\n                            modifier = Modifier.padding(start = 8.dp)\n                        )\n                    }\n                    Row(\n                        verticalAlignment = Alignment.CenterVertically,\n                        modifier = Modifier\n                            .clickable { type = \"tablet\" }\n                            .padding(start = 16.dp)\n                    ) {\n                        RadioButton(\n                            selected = type == \"tablet\",\n                            onClick = { type = \"tablet\" }\n                        )\n                        Text(\n                            text = stringResource(R.string.theme_type_tablet),\n                            style = MaterialTheme.typography.bodyMedium,\n                            modifier = Modifier.padding(start = 8.dp)\n                        )\n                    }\n                }\n\n                OutlinedTextField(\n                    value = version,\n                    onValueChange = { version = it },\n                    label = { Text(stringResource(R.string.theme_version)) },\n                    modifier = Modifier.fillMaxWidth().padding(top = 8.dp, bottom = 8.dp)\n                )\n\n                OutlinedTextField(\n                    value = author,\n                    onValueChange = { author = it },\n                    label = { Text(stringResource(R.string.theme_author)) },\n                    modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp)\n                )\n\n                OutlinedTextField(\n                    value = description,\n                    onValueChange = { description = it },\n                    label = { Text(stringResource(R.string.theme_description)) },\n                    modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp),\n                    minLines = 3\n                )\n\n                Row(\n                    modifier = Modifier.fillMaxWidth(),\n                    horizontalArrangement = Arrangement.End\n                ) {\n                    TextButton(onClick = { showDialog.value = false }) {\n                        Text(stringResource(android.R.string.cancel))\n                    }\n                    Button(\n                        onClick = {\n                            if (name.isNotEmpty()) {\n                                showDialog.value = false\n                                onConfirm(\n                                    ThemeManager.ThemeMetadata(\n                                        name = name,\n                                        type = type,\n                                        version = version,\n                                        author = author,\n                                        description = description\n                                    )\n                                )\n                            }\n                        },\n                        enabled = name.isNotEmpty()\n                    ) {\n                        Text(stringResource(R.string.theme_export_action))\n                    }\n                }\n            }\n            val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n            APDialogBlurBehindUtils.setupWindowBlurListener(dialogWindowProvider.window)\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun ThemeImportDialog(\n    showDialog: MutableState<Boolean>,\n    metadata: ThemeManager.ThemeMetadata,\n    onConfirm: () -> Unit\n) {\n    BasicAlertDialog(\n        onDismissRequest = { showDialog.value = false },\n        properties = DialogProperties(\n            decorFitsSystemWindows = true,\n            usePlatformDefaultWidth = false,\n        )\n    ) {\n        Surface(\n            modifier = Modifier\n                .width(320.dp)\n                .wrapContentHeight(),\n            shape = RoundedCornerShape(28.dp),\n            tonalElevation = AlertDialogDefaults.TonalElevation,\n            color = AlertDialogDefaults.containerColor,\n        ) {\n            Column(\n                modifier = Modifier\n                    .padding(24.dp)\n                    .verticalScroll(rememberScrollState())\n            ) {\n                Text(\n                    text = stringResource(R.string.theme_import_title),\n                    style = MaterialTheme.typography.headlineSmall,\n                    modifier = Modifier.padding(bottom = 16.dp)\n                )\n\n                Text(\n                    text = stringResource(R.string.theme_import_confirm),\n                    style = MaterialTheme.typography.bodyLarge,\n                    modifier = Modifier.padding(bottom = 16.dp)\n                )\n\n                Surface(\n                    shape = RoundedCornerShape(12.dp),\n                    color = MaterialTheme.colorScheme.surfaceVariant,\n                    modifier = Modifier.fillMaxWidth().padding(bottom = 24.dp)\n                ) {\n                    Column(modifier = Modifier.padding(16.dp)) {\n                        Text(\n                            text = stringResource(R.string.theme_info),\n                            style = MaterialTheme.typography.titleMedium,\n                            color = MaterialTheme.colorScheme.primary,\n                            modifier = Modifier.padding(bottom = 8.dp)\n                        )\n                        \n                        Text(text = \"${stringResource(R.string.theme_name)}: ${metadata.name}\")\n                        Text(text = \"${stringResource(R.string.theme_type)}: ${if (metadata.type == \"tablet\") stringResource(R.string.theme_type_tablet) else stringResource(R.string.theme_type_phone)}\")\n                        if (metadata.version.isNotEmpty()) {\n                            Text(text = \"${stringResource(R.string.theme_version)}: ${metadata.version}\")\n                        }\n                        if (metadata.author.isNotEmpty()) {\n                            Text(text = \"${stringResource(R.string.theme_author)}: ${metadata.author}\")\n                        }\n                        if (metadata.description.isNotEmpty()) {\n                            Text(\n                                text = \"${stringResource(R.string.theme_description)}: ${metadata.description}\",\n                                modifier = Modifier.padding(top = 4.dp)\n                            )\n                        }\n                    }\n                }\n\n                Row(\n                    modifier = Modifier.fillMaxWidth(),\n                    horizontalArrangement = Arrangement.End\n                ) {\n                    TextButton(onClick = { showDialog.value = false }) {\n                        Text(stringResource(android.R.string.cancel))\n                    }\n                    Button(onClick = {\n                        showDialog.value = false\n                        onConfirm()\n                    }) {\n                        Text(stringResource(R.string.theme_import_action))\n                    }\n                }\n            }\n            val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n            APDialogBlurBehindUtils.setupWindowBlurListener(dialogWindowProvider.window)\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun NavModeChooseDialog(\n    showDialog: MutableState<Boolean>,\n    currentMode: String,\n    onModeSelected: (String) -> Unit,\n    onDismiss: () -> Unit\n) {\n    val prefs = APApplication.sharedPreferences\n\n    BasicAlertDialog(\n        onDismissRequest = onDismiss,\n        properties = DialogProperties(\n            decorFitsSystemWindows = true,\n            usePlatformDefaultWidth = false,\n        )\n    ) {\n        Surface(\n            modifier = Modifier\n                .width(310.dp)\n                .wrapContentHeight(),\n            shape = RoundedCornerShape(30.dp),\n            tonalElevation = AlertDialogDefaults.TonalElevation,\n            color = AlertDialogDefaults.containerColor,\n        ) {\n            Column(modifier = Modifier.padding(24.dp)) {\n                Text(\n                    text = stringResource(R.string.settings_nav_scheme),\n                    style = MaterialTheme.typography.headlineSmall,\n                    modifier = Modifier.padding(bottom = 16.dp)\n                )\n\n                Surface(\n                    shape = RoundedCornerShape(12.dp),\n                    color = AlertDialogDefaults.containerColor,\n                    tonalElevation = 2.dp\n                ) {\n                    Column {\n                        ListItem(\n                            headlineContent = { Text(stringResource(R.string.settings_nav_mode_floating)) },\n                            leadingContent = {\n                                RadioButton(\n                                    selected = currentMode == \"floating\",\n                                    onClick = null\n                                )\n                            },\n                            modifier = Modifier.clickable {\n                                onModeSelected(\"floating\")\n                            }\n                        )\n                        ListItem(\n                            headlineContent = { Text(stringResource(R.string.settings_nav_mode_auto)) },\n                            leadingContent = {\n                                RadioButton(\n                                    selected = currentMode == \"auto\",\n                                    onClick = null\n                                )\n                            },\n                            modifier = Modifier.clickable {\n                                onModeSelected(\"auto\")\n                            }\n                        )\n                        ListItem(\n                            headlineContent = { Text(stringResource(R.string.settings_nav_mode_bottom)) },\n                            leadingContent = {\n                                RadioButton(\n                                    selected = currentMode == \"bottom\",\n                                    onClick = null\n                                )\n                            },\n                            modifier = Modifier.clickable {\n                                onModeSelected(\"bottom\")\n                            }\n                        )\n                        ListItem(\n                            headlineContent = { Text(stringResource(R.string.settings_nav_mode_rail)) },\n                            leadingContent = {\n                                RadioButton(\n                                    selected = currentMode == \"rail\",\n                                    onClick = null\n                                )\n                            },\n                            modifier = Modifier.clickable {\n                                onModeSelected(\"rail\")\n                            }\n                        )\n                    }\n                }\n            }\n\n            val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n            APDialogBlurBehindUtils.setupWindowBlurListener(dialogWindowProvider.window)\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun StatsTopLayoutChooseDialog(\n    showDialog: MutableState<Boolean>,\n    currentMode: String,\n    onModeSelected: (String) -> Unit,\n    onDismiss: () -> Unit\n) {\n    val prefs = APApplication.sharedPreferences\n\n    BasicAlertDialog(\n        onDismissRequest = onDismiss,\n        properties = DialogProperties(\n            decorFitsSystemWindows = true,\n            usePlatformDefaultWidth = false,\n        )\n    ) {\n        Surface(\n            modifier = Modifier\n                .width(310.dp)\n                .wrapContentHeight(),\n            shape = RoundedCornerShape(30.dp),\n            tonalElevation = AlertDialogDefaults.TonalElevation,\n            color = AlertDialogDefaults.containerColor,\n        ) {\n            Column(modifier = Modifier.padding(24.dp)) {\n                Text(\n                    text = stringResource(R.string.settings_stats_top_layout),\n                    style = MaterialTheme.typography.headlineSmall,\n                    modifier = Modifier.padding(bottom = 16.dp)\n                )\n\n                Surface(\n                    shape = RoundedCornerShape(12.dp),\n                    color = AlertDialogDefaults.containerColor,\n                    tonalElevation = 2.dp\n                ) {\n                    Column {\n                        ListItem(\n                            headlineContent = { Text(stringResource(R.string.settings_stats_top_layout_list)) },\n                            leadingContent = {\n                                RadioButton(\n                                    selected = currentMode == \"list\",\n                                    onClick = null\n                                )\n                            },\n                            modifier = Modifier.clickable {\n                                onModeSelected(\"list\")\n                            }\n                        )\n                        ListItem(\n                            headlineContent = { Text(stringResource(R.string.settings_stats_top_layout_grid)) },\n                            leadingContent = {\n                                RadioButton(\n                                    selected = currentMode == \"grid\",\n                                    onClick = null\n                                )\n                            },\n                            modifier = Modifier.clickable {\n                                onModeSelected(\"grid\")\n                            }\n                        )\n                    }\n                }\n            }\n\n            val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n            APDialogBlurBehindUtils.setupWindowBlurListener(dialogWindowProvider.window)\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun BannerApiConfigDialog(\n    showDialog: MutableState<Boolean>,\n    currentSource: String,\n    onConfirm: (String) -> Unit,\n    onClearCache: () -> Unit\n) {\n    val context = LocalContext.current\n    var sourceText by remember { mutableStateOf(currentSource) }\n\n    BasicAlertDialog(\n        onDismissRequest = { showDialog.value = false },\n        properties = DialogProperties(\n            decorFitsSystemWindows = true,\n            usePlatformDefaultWidth = false,\n        )\n    ) {\n        Surface(\n            modifier = Modifier\n                .width(340.dp)\n                .wrapContentHeight(),\n            shape = RoundedCornerShape(28.dp),\n            tonalElevation = AlertDialogDefaults.TonalElevation,\n            color = AlertDialogDefaults.containerColor,\n        ) {\n            Column(\n                modifier = Modifier\n                    .padding(24.dp)\n                    .verticalScroll(rememberScrollState())\n            ) {\n                Text(\n                    text = stringResource(R.string.apm_banner_api_config_title),\n                    style = MaterialTheme.typography.headlineSmall,\n                    modifier = Modifier.padding(bottom = 16.dp)\n                )\n\n                Text(\n                    text = stringResource(R.string.apm_banner_api_config_desc),\n                    style = MaterialTheme.typography.bodyMedium,\n                    color = MaterialTheme.colorScheme.onSurfaceVariant,\n                    modifier = Modifier.padding(bottom = 16.dp)\n                )\n\n                OutlinedTextField(\n                    value = sourceText,\n                    onValueChange = { sourceText = it },\n                    label = { Text(stringResource(R.string.apm_banner_api_source)) },\n                    placeholder = { Text(stringResource(R.string.apm_banner_api_source_hint), style = MaterialTheme.typography.bodySmall) },\n                    modifier = Modifier.fillMaxWidth(),\n                    singleLine = true,\n                    trailingIcon = {\n                        if (sourceText.isNotEmpty()) {\n                            IconButton(onClick = { sourceText = \"\" }) {\n                                Icon(Icons.Filled.Clear, contentDescription = \"Clear\")\n                            }\n                        }\n                    }\n                )\n\n                Spacer(modifier = Modifier.height(12.dp))\n\n                Surface(\n                    shape = RoundedCornerShape(12.dp),\n                    color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)\n                ) {\n                    Column(modifier = Modifier.padding(12.dp)) {\n                        Text(\n                            text = stringResource(R.string.apm_banner_api_examples_title),\n                            style = MaterialTheme.typography.labelMedium,\n                            color = MaterialTheme.colorScheme.primary\n                        )\n                        Spacer(modifier = Modifier.height(8.dp))\n                        Text(\n                            text = stringResource(R.string.apm_banner_api_examples),\n                            style = MaterialTheme.typography.bodySmall,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant\n                        )\n                    }\n                }\n\n                Spacer(modifier = Modifier.height(16.dp))\n\n                Row(\n                    modifier = Modifier.fillMaxWidth(),\n                    horizontalArrangement = Arrangement.spacedBy(8.dp)\n                ) {\n                    OutlinedButton(\n                        onClick = {\n                            onClearCache()\n                        },\n                        modifier = Modifier.weight(1f)\n                    ) {\n                        Text(stringResource(R.string.apm_banner_clear_cache))\n                    }\n                    Button(\n                        onClick = {\n                            onConfirm(sourceText)\n                            showDialog.value = false\n                            showToast(context, context.getString(R.string.apm_banner_api_source_saved))\n                        },\n                        enabled = sourceText.isNotBlank(),\n                        modifier = Modifier.weight(1f)\n                    ) {\n                        Text(stringResource(android.R.string.ok))\n                    }\n                }\n\n                Spacer(modifier = Modifier.height(8.dp))\n\n                Row(\n                    modifier = Modifier.fillMaxWidth(),\n                    horizontalArrangement = Arrangement.End\n                ) {\n                    TextButton(onClick = { showDialog.value = false }) {\n                        Text(stringResource(android.R.string.cancel))\n                    }\n                }\n            }\n            val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n            APDialogBlurBehindUtils.setupWindowBlurListener(dialogWindowProvider.window)\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/AppearanceSettingsScreen.kt",
    "content": "package me.bmax.apatch.ui.screen.settings\n\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.outlined.ArrowBack\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.SnackbarHost\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.livedata.observeAsState\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.input.nestedscroll.nestedScroll\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport com.ramcosta.composedestinations.generated.destinations.ApiMarketplaceScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.ThemeStoreScreenDestination\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.theme.BackgroundConfig\nimport me.bmax.apatch.util.ui.LocalSnackbarHost\nimport me.bmax.apatch.util.ui.NavigationBarsSpacer\n\n@Destination<RootGraph>\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun AppearanceSettingsScreen(navigator: DestinationsNavigator) {\n    val state by APApplication.apStateLiveData.observeAsState(APApplication.State.UNKNOWN_STATE)\n    val kPatchReady = state != APApplication.State.UNKNOWN_STATE\n\n    val snackBarHost = LocalSnackbarHost.current\n    val flat = BackgroundConfig.isCustomBackgroundEnabled || BackgroundConfig.settingsBackgroundUri != null\n    val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()\n\n    Scaffold(\n        topBar = {\n            TopAppBar(\n                title = { Text(stringResource(R.string.settings_category_appearance), style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.SemiBold) },\n                navigationIcon = {\n                    IconButton(onClick = { navigator.popBackStack() }) {\n                        Icon(Icons.AutoMirrored.Outlined.ArrowBack, contentDescription = null)\n                    }\n                },\n                scrollBehavior = scrollBehavior,\n            )\n        },\n        containerColor = Color.Transparent,\n        snackbarHost = { SnackbarHost(snackBarHost) },\n    ) { paddingValues ->\n        LazyColumn(\n            modifier = Modifier.padding(paddingValues).nestedScroll(scrollBehavior.nestedScrollConnection),\n            verticalArrangement = Arrangement.spacedBy(8.dp),\n        ) {\n            item { Spacer(Modifier.height(8.dp)) }\n            item {\n                AppearanceSettingsContent(\n                    snackBarHost = snackBarHost,\n                    kPatchReady = kPatchReady,\n                    onNavigateToThemeStore = { navigator.navigate(ThemeStoreScreenDestination) },\n                    onNavigateToApiMarketplace = { navigator.navigate(ApiMarketplaceScreenDestination) },\n                    flat = flat,\n                )\n            }\n            item { Spacer(Modifier.height(8.dp)) }\n            item { NavigationBarsSpacer() }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/BackupSettings.kt",
    "content": "package me.bmax.apatch.ui.screen.settings\n\nimport android.content.Intent\nimport me.bmax.apatch.util.ui.showToast\nimport androidx.core.content.FileProvider\nimport me.bmax.apatch.BuildConfig\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.Cloud\nimport androidx.compose.material.icons.filled.FolderOpen\nimport androidx.compose.material.icons.filled.Settings\nimport androidx.compose.material.icons.filled.Save\nimport androidx.compose.material.icons.filled.RestartAlt\nimport androidx.compose.material3.*\nimport androidx.compose.runtime.*\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.input.PasswordVisualTransformation\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.window.DialogProperties\nimport kotlinx.coroutines.launch\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.component.ExpressiveCard\nimport me.bmax.apatch.ui.component.SplicedColumnGroup\nimport me.bmax.apatch.ui.component.ToggleSettingCard\nimport me.bmax.apatch.ui.theme.BackupConfig\nimport me.bmax.apatch.util.BackupLogManager\nimport me.bmax.apatch.util.WebDavUtils\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.material3.Icon\nimport androidx.compose.ui.graphics.vector.ImageVector\n\n@Composable\nfun BackupSettingsContent(\n    autoBackupModule: Boolean,\n    onAutoBackupModuleChange: (Boolean) -> Unit,\n    flat: Boolean = false,\n) {\n    val prefs = APApplication.sharedPreferences\n    val context = LocalContext.current\n\n    val showWebDavDialog = remember { mutableStateOf(false) }\n\n    SplicedColumnGroup(flat = flat) {\n        item {\n            ToggleSettingCard(\n                flat = flat,\n                icon = Icons.Filled.Save,\n                title = stringResource(id = R.string.settings_enable_local_backup),\n                description = stringResource(id = R.string.settings_enable_local_backup_summary),\n                checked = autoBackupModule,\n                onCheckedChange = {\n                    onAutoBackupModuleChange(it)\n                    prefs.edit().putBoolean(\"auto_backup_module\", it).apply()\n                }\n            )\n        }\n\n        item {\n            var autoBackupBoot by remember { mutableStateOf(prefs.getBoolean(\"auto_backup_boot\", false)) }\n            ToggleSettingCard(\n                flat = flat,\n                icon = Icons.Filled.RestartAlt,\n                title = stringResource(id = R.string.settings_auto_backup_boot),\n                description = stringResource(id = R.string.settings_auto_backup_boot_summary),\n                checked = autoBackupBoot,\n                onCheckedChange = {\n                    autoBackupBoot = it\n                    prefs.edit().putBoolean(\"auto_backup_boot\", it).apply()\n                }\n            )\n        }\n\n        item(visible = autoBackupModule) {\n            val openBackupDirTitle = stringResource(id = R.string.settings_open_backup_dir)\n            ExpressiveCard(\n                flat = flat,\n                onClick = {\n                    val backupDir = java.io.File(me.bmax.apatch.util.getSafeDownloadsDir(context), \"FolkPatch/ModuleBackups\")\n                    if (!backupDir.exists()) backupDir.mkdirs()\n\n                    try {\n                        val intent = Intent(android.app.DownloadManager.ACTION_VIEW_DOWNLOADS)\n                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)\n                        context.startActivity(intent)\n                    } catch (e: Exception) {\n                        try {\n                            val uri = FileProvider.getUriForFile(context, \"${BuildConfig.APPLICATION_ID}.fileprovider\", backupDir)\n                            val intent = Intent(Intent.ACTION_VIEW)\n                            intent.setDataAndType(uri, \"resource/folder\")\n                            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)\n                            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)\n                            try {\n                                context.startActivity(intent)\n                            } catch (e2: Exception) {\n                                val intent2 = Intent(Intent.ACTION_VIEW)\n                                intent2.setDataAndType(uri, \"*/*\")\n                                intent2.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)\n                                intent2.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)\n                                context.startActivity(Intent.createChooser(intent2, context.getString(R.string.settings_open_backup_dir)))\n                            }\n                        } catch (e3: Exception) {\n                            showToast(context, R.string.backup_dir_open_failed)\n                        }\n                    }\n                }\n            ) {\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(16.dp),\n                    verticalAlignment = Alignment.CenterVertically,\n                ) {\n                    Icon(\n                        imageVector = Icons.Filled.FolderOpen,\n                        contentDescription = null,\n                        tint = MaterialTheme.colorScheme.onSurfaceVariant,\n                        modifier = Modifier.size(24.dp),\n                    )\n                    Spacer(Modifier.width(16.dp))\n                    Column {\n                        Text(\n                            text = openBackupDirTitle,\n                            style = MaterialTheme.typography.titleMedium,\n                            color = MaterialTheme.colorScheme.onSurface,\n                            fontWeight = FontWeight.SemiBold,\n                        )\n                    }\n                }\n            }\n        }\n\n        item {\n            ToggleSettingCard(\n                flat = flat,\n                icon = Icons.Filled.Cloud,\n                title = stringResource(id = R.string.settings_enable_cloud_backup),\n                description = stringResource(id = R.string.settings_enable_cloud_backup_summary),\n                checked = BackupConfig.isBackupEnabled,\n                onCheckedChange = {\n                    BackupConfig.isBackupEnabled = it\n                    BackupConfig.save(context)\n                }\n            )\n        }\n\n        item(visible = BackupConfig.isBackupEnabled) {\n            val configureWebDavTitle = stringResource(id = R.string.settings_configure_webdav)\n            ExpressiveCard(\n                flat = flat,\n                onClick = {\n                    showWebDavDialog.value = true\n                }\n            ) {\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(16.dp),\n                    verticalAlignment = Alignment.CenterVertically,\n                ) {\n                    Icon(\n                        imageVector = Icons.Filled.Settings,\n                        contentDescription = null,\n                        tint = MaterialTheme.colorScheme.onSurfaceVariant,\n                        modifier = Modifier.size(24.dp),\n                    )\n                    Spacer(Modifier.width(16.dp))\n                    Column {\n                        Text(\n                            text = configureWebDavTitle,\n                            style = MaterialTheme.typography.titleMedium,\n                            color = MaterialTheme.colorScheme.onSurface,\n                            fontWeight = FontWeight.SemiBold,\n                        )\n                    }\n                }\n            }\n        }\n    }\n\n    if (showWebDavDialog.value) {\n        WebDavConfigDialog(showWebDavDialog)\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun WebDavConfigDialog(showDialog: MutableState<Boolean>) {\n    val context = LocalContext.current\n    var url by remember { mutableStateOf(BackupConfig.webdavUrl) }\n    var username by remember { mutableStateOf(BackupConfig.webdavUsername) }\n    var password by remember { mutableStateOf(BackupConfig.webdavPassword) }\n    var path by remember { mutableStateOf(BackupConfig.webdavPath) }\n    var isTesting by remember { mutableStateOf(false) }\n    var showLogDialog by remember { mutableStateOf(false) }\n\n    val scope = rememberCoroutineScope()\n\n    BasicAlertDialog(\n        onDismissRequest = { showDialog.value = false },\n        properties = DialogProperties(\n            decorFitsSystemWindows = true,\n            usePlatformDefaultWidth = false,\n        )\n    ) {\n        Surface(\n            modifier = Modifier\n                .width(400.dp)\n                .wrapContentHeight(),\n            shape = MaterialTheme.shapes.large,\n            tonalElevation = AlertDialogDefaults.TonalElevation,\n            color = MaterialTheme.colorScheme.surface.copy(alpha = 1f)\n        ) {\n            Column(modifier = Modifier.padding(24.dp)) {\n                Text(\n                    text = stringResource(R.string.webdav_config_title),\n                    style = MaterialTheme.typography.headlineSmall,\n                    modifier = Modifier.padding(bottom = 16.dp)\n                )\n\n                OutlinedTextField(\n                    value = url,\n                    onValueChange = { url = it },\n                    label = { Text(stringResource(R.string.webdav_url)) },\n                    modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),\n                    singleLine = true\n                )\n\n                OutlinedTextField(\n                    value = username,\n                    onValueChange = { username = it },\n                    label = { Text(stringResource(R.string.webdav_username)) },\n                    modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),\n                    singleLine = true\n                )\n\n                OutlinedTextField(\n                    value = password,\n                    onValueChange = { password = it },\n                    label = { Text(stringResource(R.string.webdav_password)) },\n                    modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),\n                    singleLine = true,\n                    visualTransformation = PasswordVisualTransformation()\n                )\n\n                OutlinedTextField(\n                    value = path,\n                    onValueChange = { path = it },\n                    label = { Text(stringResource(R.string.webdav_path_label)) },\n                    modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp),\n                    singleLine = true\n                )\n\n                Row(\n                    modifier = Modifier.fillMaxWidth(),\n                    horizontalArrangement = Arrangement.End\n                ) {\n                    TextButton(onClick = { showDialog.value = false }) {\n                        Text(stringResource(android.R.string.cancel))\n                    }\n\n                    TextButton(onClick = { showLogDialog = true }) {\n                        Text(stringResource(R.string.webdav_view_logs))\n                    }\n\n                    TextButton(\n                        onClick = {\n                            scope.launch {\n                                isTesting = true\n                                val result = WebDavUtils.testConnection(url, username, password)\n                                isTesting = false\n                                if (result.isSuccess) {\n                                    showToast(context, context.getString(R.string.webdav_test_success))\n                                } else {\n                                    showToast(context, context.getString(R.string.webdav_test_failed, result.exceptionOrNull()?.message))\n                                }\n                            }\n                        },\n                        enabled = !isTesting\n                    ) {\n                        Text(stringResource(R.string.test))\n                    }\n\n                    Button(onClick = {\n                        BackupConfig.webdavUrl = url\n                        BackupConfig.webdavUsername = username\n                        BackupConfig.webdavPassword = password\n                        BackupConfig.webdavPath = path\n                        BackupConfig.save(context)\n                        showDialog.value = false\n                    }) {\n                        Text(stringResource(R.string.save))\n                    }\n                }\n            }\n        }\n    }\n\n    if (showLogDialog) {\n        BackupLogDialog(showDialog = remember { mutableStateOf(true) }, onDismiss = { showLogDialog = false })\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun BackupLogDialog(showDialog: MutableState<Boolean>, onDismiss: () -> Unit) {\n    var logs by remember { mutableStateOf(\"\") }\n    val scope = rememberCoroutineScope()\n\n    LaunchedEffect(Unit) {\n        logs = BackupLogManager.readLogs()\n    }\n\n    BasicAlertDialog(\n        onDismissRequest = onDismiss,\n        properties = DialogProperties(\n            decorFitsSystemWindows = true,\n            usePlatformDefaultWidth = false,\n        )\n    ) {\n        Surface(\n            modifier = Modifier\n                .width(350.dp)\n                .height(500.dp),\n            shape = MaterialTheme.shapes.large,\n            tonalElevation = AlertDialogDefaults.TonalElevation,\n            color = MaterialTheme.colorScheme.surface.copy(alpha = 1f)\n        ) {\n            Column(modifier = Modifier.padding(24.dp)) {\n                Text(\n                    text = stringResource(R.string.webdav_backup_logs_title),\n                    style = MaterialTheme.typography.headlineSmall,\n                    modifier = Modifier.padding(bottom = 16.dp)\n                )\n\n                Surface(\n                    modifier = Modifier.weight(1f).fillMaxWidth(),\n                    shape = RoundedCornerShape(8.dp),\n                    color = MaterialTheme.colorScheme.surfaceVariant\n                ) {\n                    val scrollState = rememberScrollState()\n                    Text(\n                        text = logs.ifEmpty { stringResource(R.string.webdav_no_logs) },\n                        modifier = Modifier\n                            .padding(8.dp)\n                            .verticalScroll(scrollState),\n                        style = MaterialTheme.typography.bodySmall,\n                        fontFamily = androidx.compose.ui.text.font.FontFamily.Monospace\n                    )\n                }\n\n                Row(\n                    modifier = Modifier.fillMaxWidth().padding(top = 16.dp),\n                    horizontalArrangement = Arrangement.End\n                ) {\n                    TextButton(onClick = {\n                        scope.launch {\n                            BackupLogManager.clearLogs()\n                            logs = \"\"\n                        }\n                    }) {\n                        Text(stringResource(R.string.webdav_clear_logs))\n                    }\n                    Button(onClick = onDismiss) {\n                        Text(stringResource(R.string.close))\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/BackupSettingsScreen.kt",
    "content": "package me.bmax.apatch.ui.screen.settings\n\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.outlined.ArrowBack\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.SnackbarHost\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.input.nestedscroll.nestedScroll\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.theme.BackgroundConfig\nimport me.bmax.apatch.util.ui.LocalSnackbarHost\nimport me.bmax.apatch.util.ui.NavigationBarsSpacer\n\n@Destination<RootGraph>\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun BackupSettingsScreen(navigator: DestinationsNavigator) {\n    val prefs = APApplication.sharedPreferences\n    var autoBackupModule by rememberSaveable { mutableStateOf(prefs.getBoolean(\"auto_backup_module\", false)) }\n\n    val snackBarHost = LocalSnackbarHost.current\n    val flat = BackgroundConfig.isCustomBackgroundEnabled || BackgroundConfig.settingsBackgroundUri != null\n    val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()\n\n    Scaffold(\n        topBar = {\n            TopAppBar(\n                title = { Text(stringResource(R.string.settings_category_backup), style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.SemiBold) },\n                navigationIcon = {\n                    IconButton(onClick = { navigator.popBackStack() }) {\n                        Icon(Icons.AutoMirrored.Outlined.ArrowBack, contentDescription = null)\n                    }\n                },\n                scrollBehavior = scrollBehavior,\n            )\n        },\n        containerColor = Color.Transparent,\n        snackbarHost = { SnackbarHost(snackBarHost) },\n    ) { paddingValues ->\n        LazyColumn(\n            modifier = Modifier.padding(paddingValues).nestedScroll(scrollBehavior.nestedScrollConnection),\n            verticalArrangement = Arrangement.spacedBy(8.dp),\n        ) {\n            item { Spacer(Modifier.height(8.dp)) }\n            item {\n                BackupSettingsContent(\n                    autoBackupModule = autoBackupModule,\n                    onAutoBackupModuleChange = { autoBackupModule = it },\n                    flat = flat,\n                )\n            }\n            item { Spacer(Modifier.height(8.dp)) }\n            item { NavigationBarsSpacer() }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/BehaviorSettings.kt",
    "content": "package me.bmax.apatch.ui.screen.settings\n\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.animation.core.animateFloatAsState\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.Badge\nimport androidx.compose.material.icons.filled.BugReport\nimport androidx.compose.material.icons.filled.Fingerprint\nimport androidx.compose.material.icons.filled.History\nimport androidx.compose.material.icons.filled.KeyboardArrowDown\nimport androidx.compose.material.icons.filled.OpenInNew\nimport androidx.compose.material.icons.filled.Verified\nimport androidx.compose.material.icons.filled.VisibilityOff\nimport androidx.compose.material.icons.filled.AddToHomeScreen\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.DisposableEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.rotate\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.unit.dp\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.component.CheckboxItem\nimport me.bmax.apatch.ui.component.ExpressiveCard\nimport me.bmax.apatch.ui.component.SplicedColumnGroup\nimport me.bmax.apatch.ui.component.ToggleSettingCard\n\n@Composable\nfun BehaviorSettingsContent(\n    kPatchReady: Boolean,\n    aPatchReady: Boolean,\n    flat: Boolean = false,\n) {\n    val prefs = APApplication.sharedPreferences\n\n    var currentStyle by remember { mutableStateOf(prefs.getString(\"home_layout_style\", \"circle\")) }\n    DisposableEffect(Unit) {\n        val listener = android.content.SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->\n            if (key == \"home_layout_style\") {\n                currentStyle = sharedPreferences.getString(\"home_layout_style\", \"circle\")\n            }\n        }\n        prefs.registerOnSharedPreferenceChangeListener(listener)\n        onDispose {\n            prefs.unregisterOnSharedPreferenceChangeListener(listener)\n        }\n    }\n\n    SplicedColumnGroup(flat = flat) {\n\n    item {\n    var enableWebDebugging by remember { mutableStateOf(prefs.getBoolean(\"enable_web_debugging\", false)) }\n    ToggleSettingCard(\n            flat = flat,\n            icon = Icons.Filled.BugReport,\n        title = stringResource(id = R.string.enable_web_debugging),\n        description = stringResource(id = R.string.enable_web_debugging_summary),\n        checked = enableWebDebugging,\n        onCheckedChange = {\n            enableWebDebugging = it\n            prefs.edit().putBoolean(\"enable_web_debugging\", it).apply()\n        }\n    )\n    }\n\n    item(visible = aPatchReady) {\n        var installConfirm by remember { mutableStateOf(prefs.getBoolean(\"apm_install_confirm_enabled\", true)) }\n        ToggleSettingCard(\n            flat = flat,\n            icon = Icons.Filled.Verified,\n            title = stringResource(id = R.string.settings_apm_install_confirm),\n            description = stringResource(id = R.string.settings_apm_install_confirm_summary),\n            checked = installConfirm,\n            onCheckedChange = {\n                installConfirm = it\n                prefs.edit().putBoolean(\"apm_install_confirm_enabled\", it).apply()\n            }\n        )\n    }\n\n    item(visible = aPatchReady) {\n        var enableModuleShortcutAdd by remember { mutableStateOf(prefs.getBoolean(\"enable_module_shortcut_add\", true)) }\n        ToggleSettingCard(\n            flat = flat,\n            icon = Icons.Filled.AddToHomeScreen,\n            title = stringResource(id = R.string.settings_enable_module_shortcut_add),\n            description = stringResource(id = R.string.settings_enable_module_shortcut_add_summary),\n            checked = enableModuleShortcutAdd,\n            onCheckedChange = {\n                enableModuleShortcutAdd = it\n                prefs.edit().putBoolean(\"enable_module_shortcut_add\", it).apply()\n            }\n        )\n    }\n\n    item(visible = aPatchReady) {\n        var stayOnPage by remember { mutableStateOf(prefs.getBoolean(\"apm_action_stay_on_page\", true)) }\n        ToggleSettingCard(\n            flat = flat,\n            icon = Icons.Filled.OpenInNew,\n            title = stringResource(id = R.string.settings_apm_stay_on_page),\n            description = stringResource(id = R.string.settings_apm_stay_on_page_summary),\n            checked = stayOnPage,\n            onCheckedChange = {\n                stayOnPage = it\n                prefs.edit().putBoolean(\"apm_action_stay_on_page\", it).apply()\n            }\n        )\n    }\n\n    item(visible = currentStyle != \"focus\") {\n        var hideApatchCard by remember { mutableStateOf(prefs.getBoolean(\"hide_apatch_card\", false)) }\n        ToggleSettingCard(\n            flat = flat,\n            icon = Icons.Filled.VisibilityOff,\n            title = stringResource(id = R.string.settings_hide_apatch_card),\n            description = stringResource(id = R.string.settings_hide_apatch_card_summary),\n            checked = hideApatchCard,\n            onCheckedChange = {\n                hideApatchCard = it\n                prefs.edit().putBoolean(\"hide_apatch_card\", it).apply()\n            }\n        )\n    }\n\n    item(visible = kPatchReady) {\n        var hideSuPath by remember { mutableStateOf(prefs.getBoolean(\"hide_su_path\", false)) }\n        ToggleSettingCard(\n            flat = flat,\n            icon = Icons.Filled.VisibilityOff,\n            title = stringResource(id = R.string.home_hide_su_path),\n            description = stringResource(id = R.string.home_hide_su_path_summary),\n            checked = hideSuPath,\n            onCheckedChange = {\n                hideSuPath = it\n                prefs.edit().putBoolean(\"hide_su_path\", it).apply()\n            }\n        )\n    }\n\n    item(visible = kPatchReady) {\n        var hideKpatchVersion by remember { mutableStateOf(prefs.getBoolean(\"hide_kpatch_version\", false)) }\n        ToggleSettingCard(\n            flat = flat,\n            icon = Icons.Filled.VisibilityOff,\n            title = stringResource(id = R.string.home_hide_kpatch_version),\n            description = stringResource(id = R.string.home_hide_kpatch_version_summary),\n            checked = hideKpatchVersion,\n            onCheckedChange = {\n                hideKpatchVersion = it\n                prefs.edit().putBoolean(\"hide_kpatch_version\", it).apply()\n            }\n        )\n    }\n\n    item(visible = kPatchReady) {\n        var hideFingerprint by remember { mutableStateOf(prefs.getBoolean(\"hide_fingerprint\", false)) }\n        ToggleSettingCard(\n            flat = flat,\n            icon = Icons.Filled.Fingerprint,\n            title = stringResource(id = R.string.home_hide_fingerprint),\n            description = stringResource(id = R.string.home_hide_fingerprint_summary),\n            checked = hideFingerprint,\n            onCheckedChange = {\n                hideFingerprint = it\n                prefs.edit().putBoolean(\"hide_fingerprint\", it).apply()\n            }\n        )\n    }\n\n    item(visible = kPatchReady) {\n        var hideZygisk by remember { mutableStateOf(prefs.getBoolean(\"hide_zygisk\", false)) }\n        ToggleSettingCard(\n            flat = flat,\n            icon = Icons.Filled.VisibilityOff,\n            title = stringResource(id = R.string.home_hide_zygisk),\n            description = stringResource(id = R.string.home_hide_zygisk_summary),\n            checked = hideZygisk,\n            onCheckedChange = {\n                hideZygisk = it\n                prefs.edit().putBoolean(\"hide_zygisk\", it).apply()\n            }\n        )\n    }\n\n    item(visible = kPatchReady) {\n        var hideMount by remember { mutableStateOf(prefs.getBoolean(\"hide_mount\", false)) }\n        ToggleSettingCard(\n            flat = flat,\n            icon = Icons.Filled.VisibilityOff,\n            title = stringResource(id = R.string.home_hide_mount),\n            description = stringResource(id = R.string.home_hide_mount_summary),\n            checked = hideMount,\n            onCheckedChange = {\n                hideMount = it\n                prefs.edit().putBoolean(\"hide_mount\", it).apply()\n            }\n        )\n    }\n\n    item(visible = kPatchReady) {\n        var useLegacySuPage by remember { mutableStateOf(prefs.getBoolean(\"use_legacy_su_page\", false)) }\n        ToggleSettingCard(\n            flat = flat,\n            icon = Icons.Filled.History,\n            title = stringResource(id = R.string.settings_use_legacy_su_page),\n            description = stringResource(id = R.string.settings_use_legacy_su_page_summary),\n            checked = useLegacySuPage,\n            onCheckedChange = {\n                useLegacySuPage = it\n                prefs.edit().putBoolean(\"use_legacy_su_page\", it).apply()\n            }\n        )\n    }\n\n    item(visible = kPatchReady) {\n        var enableSuperUserBadge by remember { mutableStateOf(prefs.getBoolean(\"badge_superuser\", true)) }\n        var enableApmBadge by remember { mutableStateOf(prefs.getBoolean(\"badge_apm\", true)) }\n        var enableKernelBadge by remember { mutableStateOf(prefs.getBoolean(\"badge_kernel\", true)) }\n        var expanded by remember { mutableStateOf(false) }\n        val rotationState by animateFloatAsState(\n            targetValue = if (expanded) 180f else 0f,\n            label = \"ArrowRotation\"\n        )\n        val badgeCountTitle = stringResource(id = R.string.enable_badge_count)\n        val badgeCountSummary = stringResource(id = R.string.enable_badge_count_summary)\n\n        ExpressiveCard(\n            flat = flat,\n            onClick = { expanded = !expanded }\n        ) {\n            Row(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .padding(16.dp),\n                horizontalArrangement = Arrangement.SpaceBetween,\n                verticalAlignment = Alignment.CenterVertically,\n            ) {\n                Icon(\n                    imageVector = Icons.Filled.Badge,\n                    contentDescription = null,\n                    tint = MaterialTheme.colorScheme.onSurfaceVariant,\n                    modifier = Modifier.size(24.dp),\n                )\n                Spacer(Modifier.width(16.dp))\n                Column(modifier = Modifier.weight(1f)) {\n                    Text(\n                        text = badgeCountTitle,\n                        style = MaterialTheme.typography.titleMedium,\n                        color = MaterialTheme.colorScheme.onSurface,\n                    )\n                    Spacer(Modifier.height(4.dp))\n                    Text(\n                        text = badgeCountSummary,\n                        style = MaterialTheme.typography.bodyMedium,\n                        color = MaterialTheme.colorScheme.onSurfaceVariant,\n                    )\n                }\n                Icon(\n                    imageVector = Icons.Filled.KeyboardArrowDown,\n                    contentDescription = null,\n                    tint = MaterialTheme.colorScheme.onSurfaceVariant,\n                    modifier = Modifier.rotate(rotationState)\n                )\n            }\n        }\n\n        AnimatedVisibility(visible = expanded) {\n            Column(modifier = Modifier.padding(start = 16.dp)) {\n                CheckboxItem(\n                    icon = null,\n                    title = stringResource(id = R.string.badge_superuser),\n                    summary = null,\n                    checked = enableSuperUserBadge,\n                    onCheckedChange = {\n                        enableSuperUserBadge = it\n                        prefs.edit().putBoolean(\"badge_superuser\", it).apply()\n                    }\n                )\n\n                CheckboxItem(\n                    icon = null,\n                    title = stringResource(id = R.string.badge_apm),\n                    summary = null,\n                    checked = enableApmBadge,\n                    onCheckedChange = {\n                        enableApmBadge = it\n                        prefs.edit().putBoolean(\"badge_apm\", it).apply()\n                    }\n                )\n\n                CheckboxItem(\n                    icon = null,\n                    title = stringResource(id = R.string.badge_kernel),\n                    summary = null,\n                    checked = enableKernelBadge,\n                    onCheckedChange = {\n                        enableKernelBadge = it\n                        prefs.edit().putBoolean(\"badge_kernel\", it).apply()\n                    }\n                )\n            }\n        }\n    }\n\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/BehaviorSettingsScreen.kt",
    "content": "package me.bmax.apatch.ui.screen.settings\n\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.outlined.ArrowBack\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.SnackbarHost\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.livedata.observeAsState\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.input.nestedscroll.nestedScroll\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.theme.BackgroundConfig\nimport me.bmax.apatch.util.ui.LocalSnackbarHost\nimport me.bmax.apatch.util.ui.NavigationBarsSpacer\n\n@Destination<RootGraph>\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun BehaviorSettingsScreen(navigator: DestinationsNavigator) {\n    val state by APApplication.apStateLiveData.observeAsState(APApplication.State.UNKNOWN_STATE)\n    val kPatchReady = state != APApplication.State.UNKNOWN_STATE\n    val aPatchReady = (state == APApplication.State.ANDROIDPATCH_INSTALLING || state == APApplication.State.ANDROIDPATCH_INSTALLED || state == APApplication.State.ANDROIDPATCH_NEED_UPDATE)\n\n    val snackBarHost = LocalSnackbarHost.current\n    val flat = BackgroundConfig.isCustomBackgroundEnabled || BackgroundConfig.settingsBackgroundUri != null\n    val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()\n\n    Scaffold(\n        topBar = {\n            TopAppBar(\n                title = { Text(stringResource(R.string.settings_category_behavior), style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.SemiBold) },\n                navigationIcon = {\n                    IconButton(onClick = { navigator.popBackStack() }) {\n                        Icon(Icons.AutoMirrored.Outlined.ArrowBack, contentDescription = null)\n                    }\n                },\n                scrollBehavior = scrollBehavior,\n            )\n        },\n        containerColor = Color.Transparent,\n        snackbarHost = { SnackbarHost(snackBarHost) },\n    ) { paddingValues ->\n        LazyColumn(\n            modifier = Modifier.padding(paddingValues).nestedScroll(scrollBehavior.nestedScrollConnection),\n            verticalArrangement = Arrangement.spacedBy(8.dp),\n        ) {\n            item { Spacer(Modifier.height(8.dp)) }\n            item {\n                BehaviorSettingsContent(\n                    kPatchReady = kPatchReady,\n                    aPatchReady = aPatchReady,\n                    flat = flat,\n                )\n            }\n            item { Spacer(Modifier.height(8.dp)) }\n            item { NavigationBarsSpacer() }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/FunctionSettings.kt",
    "content": "package me.bmax.apatch.ui.screen.settings\n\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxHeight\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.layout.wrapContentHeight\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.Add\nimport androidx.compose.material.icons.filled.Close\nimport androidx.compose.material.icons.filled.FolderOff\nimport androidx.compose.material.icons.filled.Memory\nimport androidx.compose.material.icons.filled.Search\nimport androidx.compose.material.icons.filled.VisibilityOff\nimport androidx.compose.material.icons.filled.HideSource\nimport androidx.compose.material.icons.filled.PersonPin\nimport androidx.compose.material.icons.filled.WifiOff\nimport androidx.compose.material3.AlertDialogDefaults\nimport androidx.compose.material3.BasicAlertDialog\nimport androidx.compose.material3.Button\nimport androidx.compose.material3.Checkbox\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.ModalBottomSheet\nimport androidx.compose.material3.OutlinedButton\nimport androidx.compose.material3.OutlinedTextField\nimport androidx.compose.material3.SnackbarHostState\nimport androidx.compose.material3.Surface\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.material3.TextFieldDefaults\nimport androidx.compose.material3.rememberModalBottomSheetState\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.MutableState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.draw.drawBehind\nimport androidx.compose.ui.geometry.Offset\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalView\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontFamily\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.input.KeyboardType\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.window.DialogProperties\nimport androidx.compose.ui.window.DialogWindowProvider\nimport androidx.compose.foundation.text.KeyboardOptions\nimport me.bmax.apatch.util.ui.APDialogBlurBehindUtils\nimport android.content.pm.ApplicationInfo\nimport android.content.pm.PackageInfo\nimport coil.compose.AsyncImage\nimport coil.request.ImageRequest\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.component.ExpressiveCard\nimport me.bmax.apatch.ui.component.ExpressiveSwitch\nimport me.bmax.apatch.ui.component.SplicedColumnGroup\nimport me.bmax.apatch.ui.component.ToggleSettingCard\nimport me.bmax.apatch.util.setHideServiceEnabled\n\n@Composable\nfun FunctionSettingsContent(\n    kPatchReady: Boolean,\n    aPatchReady: Boolean,\n    isHideServiceEnabled: Boolean,\n    onHideServiceChange: (Boolean) -> Unit,\n    isKernelSpoofEnabled: Boolean,\n    onKernelSpoofChange: (Boolean) -> Unit,\n    kernelSpoofVersion: String,\n    onKernelSpoofVersionChange: (String) -> Unit,\n    kernelSpoofBuildTime: String,\n    onKernelSpoofBuildTimeChange: (String) -> Unit,\n    onKernelSpoofSave: () -> Unit,\n    onKernelSpoofRestore: () -> Unit,\n    snackBarHost: SnackbarHostState,\n    isPathHideEnabled: Boolean,\n    onPathHideChange: (Boolean) -> Unit,\n    pathHidePaths: String,\n    onPathHidePathsChange: (String) -> Unit,\n    onPathHideSave: () -> Unit,\n    isPathHideUidMode: Boolean,\n    onPathHideUidModeChange: (Boolean) -> Unit,\n    isPathHideFilterSystem: Boolean,\n    onPathHideFilterSystemChange: (Boolean) -> Unit,\n    selectedUids: Set<Int>,\n    onUidToggle: (Int) -> Unit,\n    onUidRemoveStale: () -> Unit,\n    isUmountEnabled: Boolean,\n    onUmountEnabledChange: (Boolean) -> Unit,\n    umountPaths: String,\n    onUmountPathsChange: (String) -> Unit,\n    onUmountSave: () -> Unit,\n    isNetIsolateEnabled: Boolean,\n    onNetIsolateChange: (Boolean) -> Unit,\n    niSelectedUids: Set<Int>,\n    onNiUidToggle: (Int) -> Unit,\n    flat: Boolean = false,\n) {\n    val context = LocalContext.current\n    val hideServiceTitle = stringResource(id = R.string.settings_hide_service)\n    val hideServiceSummary = stringResource(id = R.string.settings_hide_service_summary)\n    val umountServiceTitle = stringResource(id = R.string.settings_umount_service)\n    val umountServiceSummary = stringResource(id = R.string.settings_umount_service_summary)\n\n    SplicedColumnGroup(flat = flat) {\n        item(visible = kPatchReady && aPatchReady) {\n            ToggleSettingCard(\n                flat = flat,\n                icon = Icons.Filled.VisibilityOff,\n                title = hideServiceTitle,\n                description = hideServiceSummary,\n                checked = isHideServiceEnabled,\n                onCheckedChange = {\n                    setHideServiceEnabled(it)\n                    onHideServiceChange(it)\n                }\n            )\n        }\n\n        item(visible = kPatchReady && aPatchReady) {\n            val umountPathsLabel = stringResource(id = R.string.umount_config_paths_label)\n            val umountPathsPlaceholder = stringResource(id = R.string.umount_config_paths_placeholder)\n            val umountPathsHelper = stringResource(id = R.string.umount_config_paths_helper)\n\n            ExpressiveCard(flat = flat) {\n                Column(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(16.dp),\n                ) {\n                    Row(\n                        modifier = Modifier.fillMaxWidth(),\n                        verticalAlignment = Alignment.CenterVertically,\n                        horizontalArrangement = Arrangement.SpaceBetween,\n                    ) {\n                        Row(\n                            verticalAlignment = Alignment.CenterVertically,\n                            modifier = Modifier.weight(1f),\n                        ) {\n                            Icon(\n                                imageVector = Icons.Filled.FolderOff,\n                                contentDescription = null,\n                                tint = MaterialTheme.colorScheme.onSurfaceVariant,\n                                modifier = Modifier.size(24.dp),\n                            )\n                            Spacer(Modifier.width(16.dp))\n                            Column {\n                                Text(\n                                    text = umountServiceTitle,\n                                    style = MaterialTheme.typography.titleMedium,\n                                    color = MaterialTheme.colorScheme.onSurface,\n                                    fontWeight = FontWeight.SemiBold,\n                                )\n                                Spacer(modifier = Modifier.height(4.dp))\n                                Text(\n                                    text = umountServiceSummary,\n                                    style = MaterialTheme.typography.bodyMedium,\n                                    color = MaterialTheme.colorScheme.onSurfaceVariant,\n                                )\n                            }\n                        }\n                        ExpressiveSwitch(\n                            checked = isUmountEnabled,\n                            onCheckedChange = onUmountEnabledChange,\n                        )\n                    }\n\n                    AnimatedVisibility(visible = isUmountEnabled) {\n                        Column(modifier = Modifier.padding(top = 12.dp)) {\n                            OutlinedTextField(\n                                value = umountPaths,\n                                onValueChange = onUmountPathsChange,\n                                modifier = Modifier.fillMaxWidth().height(160.dp),\n                                label = { Text(umountPathsLabel) },\n                                placeholder = { Text(umountPathsPlaceholder) },\n                                supportingText = { Text(umountPathsHelper) },\n                                minLines = 4,\n                                maxLines = Int.MAX_VALUE,\n                                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text),\n                            )\n\n                            Spacer(modifier = Modifier.height(12.dp))\n\n                            Button(\n                                onClick = onUmountSave,\n                                modifier = Modifier.fillMaxWidth(),\n                            ) {\n                                Text(stringResource(R.string.umount_config_save))\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        item(visible = kPatchReady && aPatchReady) {\n            val kernelSpoofTitle = stringResource(id = R.string.settings_kernel_spoof)\n            val kernelSpoofSummary = stringResource(id = R.string.settings_kernel_spoof_summary)\n            val versionLabel = stringResource(id = R.string.settings_kernel_spoof_version)\n            val buildTimeLabel = stringResource(id = R.string.settings_kernel_spoof_build_time)\n\n            ExpressiveCard(flat = flat) {\n                Column(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(16.dp),\n                ) {\n                    Row(\n                        modifier = Modifier.fillMaxWidth(),\n                        verticalAlignment = Alignment.CenterVertically,\n                        horizontalArrangement = Arrangement.SpaceBetween,\n                    ) {\n                        Row(\n                            verticalAlignment = Alignment.CenterVertically,\n                            modifier = Modifier.weight(1f),\n                        ) {\n                            Icon(\n                                imageVector = Icons.Filled.Memory,\n                                contentDescription = null,\n                                tint = MaterialTheme.colorScheme.onSurfaceVariant,\n                                modifier = Modifier.size(24.dp),\n                            )\n                            Spacer(Modifier.width(16.dp))\n                            Column {\n                                Text(\n                                    text = kernelSpoofTitle,\n                                    style = MaterialTheme.typography.titleMedium,\n                                    color = MaterialTheme.colorScheme.onSurface,\n                                    fontWeight = FontWeight.SemiBold,\n                                )\n                                Spacer(modifier = Modifier.height(4.dp))\n                                Text(\n                                    text = kernelSpoofSummary,\n                                    style = MaterialTheme.typography.bodyMedium,\n                                    color = MaterialTheme.colorScheme.onSurfaceVariant,\n                                )\n                            }\n                        }\n                        ExpressiveSwitch(\n                            checked = isKernelSpoofEnabled,\n                            onCheckedChange = onKernelSpoofChange,\n                        )\n                    }\n\n                    AnimatedVisibility(visible = isKernelSpoofEnabled) {\n                        Column(modifier = Modifier.padding(top = 12.dp)) {\n                            OutlinedTextField(\n                                value = kernelSpoofVersion,\n                                onValueChange = onKernelSpoofVersionChange,\n                                modifier = Modifier.fillMaxWidth(),\n                                label = { Text(versionLabel) },\n                                singleLine = true,\n                            )\n\n                            Spacer(modifier = Modifier.height(8.dp))\n\n                            OutlinedTextField(\n                                value = kernelSpoofBuildTime,\n                                onValueChange = onKernelSpoofBuildTimeChange,\n                                modifier = Modifier.fillMaxWidth(),\n                                label = { Text(buildTimeLabel) },\n                                singleLine = true,\n                            )\n\n                            Spacer(modifier = Modifier.height(12.dp))\n\n                            Row(\n                                modifier = Modifier.fillMaxWidth(),\n                                horizontalArrangement = Arrangement.spacedBy(8.dp),\n                            ) {\n                                Button(\n                                    onClick = onKernelSpoofSave,\n                                    modifier = Modifier.weight(1f),\n                                ) {\n                                    Text(stringResource(R.string.save))\n                                }\n                                OutlinedButton(\n                                    onClick = onKernelSpoofRestore,\n                                    modifier = Modifier.weight(1f),\n                                ) {\n                                    Text(stringResource(R.string.settings_kernel_spoof_restore))\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        item(visible = kPatchReady && aPatchReady) {\n            val pathHideTitle = stringResource(id = R.string.settings_path_hide)\n            val pathHideSummary = stringResource(id = R.string.settings_path_hide_summary)\n            val pathsLabel = stringResource(id = R.string.path_hide_paths_label)\n            val pathsPlaceholder = stringResource(id = R.string.path_hide_paths_placeholder)\n            val pathsHelper = stringResource(id = R.string.path_hide_paths_helper)\n\n            ExpressiveCard(flat = flat) {\n                Column(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(16.dp),\n                ) {\n                    Row(\n                        modifier = Modifier.fillMaxWidth(),\n                        verticalAlignment = Alignment.CenterVertically,\n                        horizontalArrangement = Arrangement.SpaceBetween,\n                    ) {\n                        Row(\n                            verticalAlignment = Alignment.CenterVertically,\n                            modifier = Modifier.weight(1f),\n                        ) {\n                            Icon(\n                                imageVector = Icons.Filled.HideSource,\n                                contentDescription = null,\n                                tint = MaterialTheme.colorScheme.onSurfaceVariant,\n                                modifier = Modifier.size(24.dp),\n                            )\n                            Spacer(Modifier.width(16.dp))\n                            Column {\n                                Text(\n                                    text = pathHideTitle,\n                                    style = MaterialTheme.typography.titleMedium,\n                                    color = MaterialTheme.colorScheme.onSurface,\n                                    fontWeight = FontWeight.SemiBold,\n                                )\n                                Spacer(modifier = Modifier.height(4.dp))\n                                Text(\n                                    text = pathHideSummary,\n                                    style = MaterialTheme.typography.bodyMedium,\n                                    color = MaterialTheme.colorScheme.onSurfaceVariant,\n                                )\n                            }\n                        }\n                        ExpressiveSwitch(\n                            checked = isPathHideEnabled,\n                            onCheckedChange = onPathHideChange,\n                        )\n                    }\n\n                    AnimatedVisibility(visible = isPathHideEnabled) {\n                        Column(modifier = Modifier.padding(top = 12.dp)) {\n                            OutlinedTextField(\n                                value = pathHidePaths,\n                                onValueChange = onPathHidePathsChange,\n                                modifier = Modifier.fillMaxWidth().height(160.dp),\n                                label = { Text(pathsLabel) },\n                                placeholder = { Text(pathsPlaceholder) },\n                                supportingText = { Text(pathsHelper) },\n                                minLines = 4,\n                                maxLines = Int.MAX_VALUE,\n                                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text),\n                            )\n\n                            Spacer(modifier = Modifier.height(12.dp))\n\n                            Button(\n                                onClick = onPathHideSave,\n                                modifier = Modifier.fillMaxWidth(),\n                            ) {\n                                Text(stringResource(R.string.path_hide_save))\n                            }\n\n                            Spacer(modifier = Modifier.height(12.dp))\n\n                            val pathCount = pathHidePaths.lines().count { it.isNotBlank() }\n                            val appCount = selectedUids.size\n                            if (pathCount > 0 || appCount > 0) {\n                                Row(\n                                    modifier = Modifier.fillMaxWidth(),\n                                    verticalAlignment = Alignment.CenterVertically,\n                                ) {\n                                    if (pathCount > 0) {\n                                        Icon(\n                                            Icons.Filled.FolderOff,\n                                            contentDescription = null,\n                                            modifier = Modifier.size(16.dp),\n                                            tint = MaterialTheme.colorScheme.onSurfaceVariant,\n                                        )\n                                        Spacer(Modifier.width(4.dp))\n                                        Text(\n                                            \"$pathCount paths\",\n                                            style = MaterialTheme.typography.labelSmall,\n                                            color = MaterialTheme.colorScheme.onSurfaceVariant,\n                                        )\n                                    }\n                                    if (pathCount > 0 && appCount > 0) {\n                                        Spacer(Modifier.width(12.dp))\n                                    }\n                                    if (appCount > 0) {\n                                        Icon(\n                                            Icons.Filled.PersonPin,\n                                            contentDescription = null,\n                                            modifier = Modifier.size(16.dp),\n                                            tint = MaterialTheme.colorScheme.onSurfaceVariant,\n                                        )\n                                        Spacer(Modifier.width(4.dp))\n                                        Text(\n                                            \"$appCount apps\",\n                                            style = MaterialTheme.typography.labelSmall,\n                                            color = MaterialTheme.colorScheme.onSurfaceVariant,\n                                        )\n                                    }\n                                }\n                                Spacer(modifier = Modifier.height(12.dp))\n                            }\n\n                            // UID Execution Mode\n                            val uidModeTitle = stringResource(id = R.string.path_hide_uid_mode)\n                            val uidModeSummary = stringResource(id = R.string.path_hide_uid_mode_summary)\n\n                            Row(\n                                modifier = Modifier.fillMaxWidth(),\n                                verticalAlignment = Alignment.CenterVertically,\n                                horizontalArrangement = Arrangement.SpaceBetween,\n                            ) {\n                                Row(\n                                    verticalAlignment = Alignment.CenterVertically,\n                                    modifier = Modifier.weight(1f),\n                                ) {\n                                    Icon(\n                                        imageVector = Icons.Filled.PersonPin,\n                                        contentDescription = null,\n                                        tint = MaterialTheme.colorScheme.onSurfaceVariant,\n                                        modifier = Modifier.size(20.dp),\n                                    )\n                                    Spacer(Modifier.width(12.dp))\n                                    Column {\n                                        Text(\n                                            text = uidModeTitle,\n                                            style = MaterialTheme.typography.titleSmall,\n                                            color = MaterialTheme.colorScheme.onSurface,\n                                            fontWeight = FontWeight.SemiBold,\n                                        )\n                                        Text(\n                                            text = uidModeSummary,\n                                            style = MaterialTheme.typography.bodySmall,\n                                            color = MaterialTheme.colorScheme.onSurfaceVariant,\n                                        )\n                                    }\n                                }\n                                ExpressiveSwitch(\n                                    checked = isPathHideUidMode,\n                                    onCheckedChange = onPathHideUidModeChange,\n                                )\n                            }\n\n                            // Filter system/root UID toggle\n                            val filterSystemTitle = stringResource(id = R.string.path_hide_filter_system)\n                            val filterSystemSummary = stringResource(id = R.string.path_hide_filter_system_summary)\n\n                            Row(\n                                modifier = Modifier.fillMaxWidth(),\n                                verticalAlignment = Alignment.CenterVertically,\n                                horizontalArrangement = Arrangement.SpaceBetween,\n                            ) {\n                                Row(\n                                    verticalAlignment = Alignment.CenterVertically,\n                                    modifier = Modifier.weight(1f),\n                                ) {\n                                    Icon(\n                                        imageVector = Icons.Filled.VisibilityOff,\n                                        contentDescription = null,\n                                        tint = MaterialTheme.colorScheme.onSurfaceVariant,\n                                        modifier = Modifier.size(20.dp),\n                                    )\n                                    Spacer(Modifier.width(12.dp))\n                                    Column {\n                                        Text(\n                                            text = filterSystemTitle,\n                                            style = MaterialTheme.typography.titleSmall,\n                                            color = MaterialTheme.colorScheme.onSurface,\n                                            fontWeight = FontWeight.SemiBold,\n                                        )\n                                        Text(\n                                            text = filterSystemSummary,\n                                            style = MaterialTheme.typography.bodySmall,\n                                            color = MaterialTheme.colorScheme.onSurfaceVariant,\n                                        )\n                                    }\n                                }\n                                ExpressiveSwitch(\n                                    checked = isPathHideFilterSystem,\n                                    onCheckedChange = onPathHideFilterSystemChange,\n                                )\n                            }\n\n                            AnimatedVisibility(visible = isPathHideUidMode) {\n                                Column(modifier = Modifier.padding(top = 8.dp)) {\n                                    val pm = context.packageManager\n                                    val noAppsText = stringResource(R.string.path_hide_no_apps_selected)\n\n                                    if (selectedUids.isNotEmpty()) {\n                                        Column(\n                                            verticalArrangement = Arrangement.spacedBy(8.dp),\n                                        ) {\n                                            selectedUids.forEach { uid ->\n                                                val pkgs = pm.getPackagesForUid(uid)\n                                                val pkgName = pkgs?.firstOrNull()\n                                                val pkgInfo = remember(pkgName) {\n                                                    pkgName?.let {\n                                                        try { pm.getPackageInfo(it, 0) } catch (_: Exception) { null }\n                                                    }\n                                                }\n                                                val label = pkgName?.let {\n                                                    try { pm.getApplicationInfo(it, 0).loadLabel(pm).toString() }\n                                                    catch (_: Exception) { it }\n                                                } ?: \"UID $uid\"\n                                                val isSystemApp = remember(pkgName) {\n                                                    pkgName?.let {\n                                                        try {\n                                                            val appInfo = pm.getApplicationInfo(it, 0)\n                                                            (appInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0 ||\n                                                                (appInfo.flags and ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0\n                                                        } catch (_: Exception) {\n                                                            false\n                                                        }\n                                                    } ?: false\n                                                }\n\n                                                SelectedPathHideAppItem(\n                                                    label = label,\n                                                    packageName = pkgName ?: \"UID $uid\",\n                                                    uid = uid,\n                                                    packageInfo = pkgInfo,\n                                                    isSystemApp = isSystemApp,\n                                                    onRemove = { onUidToggle(uid) },\n                                                )\n                                            }\n                                        }\n                                    } else {\n                                        Text(\n                                            noAppsText,\n                                            style = MaterialTheme.typography.bodySmall,\n                                            color = MaterialTheme.colorScheme.onSurfaceVariant,\n                                        )\n                                    }\n\n                                    Spacer(modifier = Modifier.height(8.dp))\n\n                                    AppPickerButton(\n                                        selectedUids = selectedUids,\n                                        onUidToggle = onUidToggle,\n                                    )\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        item(visible = kPatchReady && aPatchReady) {\n            val niTitle = stringResource(id = R.string.netisolate_title)\n            val niSummary = stringResource(id = R.string.netisolate_enable_summary)\n            val noAppsText = stringResource(R.string.netisolate_no_uids)\n\n            ExpressiveCard(flat = flat) {\n                Column(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(16.dp),\n                ) {\n                    Row(\n                        modifier = Modifier.fillMaxWidth(),\n                        verticalAlignment = Alignment.CenterVertically,\n                        horizontalArrangement = Arrangement.SpaceBetween,\n                    ) {\n                        Row(\n                            verticalAlignment = Alignment.CenterVertically,\n                            modifier = Modifier.weight(1f),\n                        ) {\n                            Icon(\n                                imageVector = Icons.Filled.WifiOff,\n                                contentDescription = null,\n                                tint = MaterialTheme.colorScheme.onSurfaceVariant,\n                                modifier = Modifier.size(24.dp),\n                            )\n                            Spacer(Modifier.width(16.dp))\n                            Column {\n                                Text(\n                                    text = niTitle,\n                                    style = MaterialTheme.typography.titleMedium,\n                                    color = MaterialTheme.colorScheme.onSurface,\n                                    fontWeight = FontWeight.SemiBold,\n                                )\n                                Spacer(modifier = Modifier.height(4.dp))\n                                Text(\n                                    text = niSummary,\n                                    style = MaterialTheme.typography.bodyMedium,\n                                    color = MaterialTheme.colorScheme.onSurfaceVariant,\n                                )\n                            }\n                        }\n                        ExpressiveSwitch(\n                            checked = isNetIsolateEnabled,\n                            onCheckedChange = onNetIsolateChange,\n                        )\n                    }\n\n                    AnimatedVisibility(visible = isNetIsolateEnabled) {\n                        Column(modifier = Modifier.padding(top = 12.dp)) {\n                            val pm = context.packageManager\n\n                            if (niSelectedUids.isNotEmpty()) {\n                                Column(\n                                    verticalArrangement = Arrangement.spacedBy(8.dp),\n                                ) {\n                                    niSelectedUids.forEach { uid ->\n                                        val pkgs = pm.getPackagesForUid(uid)\n                                        val pkgName = pkgs?.firstOrNull()\n                                        val pkgInfo = remember(pkgName) {\n                                            pkgName?.let {\n                                                try { pm.getPackageInfo(it, 0) } catch (_: Exception) { null }\n                                            }\n                                        }\n                                        val label = pkgName?.let {\n                                            try { pm.getApplicationInfo(it, 0).loadLabel(pm).toString() }\n                                            catch (_: Exception) { it }\n                                        } ?: \"UID $uid\"\n                                        val isSystemApp = remember(pkgName) {\n                                            pkgName?.let {\n                                                try {\n                                                    val appInfo = pm.getApplicationInfo(it, 0)\n                                                    (appInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0 ||\n                                                        (appInfo.flags and ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0\n                                                } catch (_: Exception) {\n                                                    false\n                                                }\n                                            } ?: false\n                                        }\n\n                                        SelectedPathHideAppItem(\n                                            label = label,\n                                            packageName = pkgName ?: \"UID $uid\",\n                                            uid = uid,\n                                            packageInfo = pkgInfo,\n                                            isSystemApp = isSystemApp,\n                                            onRemove = { onNiUidToggle(uid) },\n                                        )\n                                    }\n                                }\n                            } else {\n                                Text(\n                                    noAppsText,\n                                    style = MaterialTheme.typography.bodySmall,\n                                    color = MaterialTheme.colorScheme.onSurfaceVariant,\n                                )\n                            }\n\n                            Spacer(modifier = Modifier.height(8.dp))\n\n                            AppPickerButton(\n                                selectedUids = niSelectedUids,\n                                onUidToggle = onNiUidToggle,\n                            )\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun SelectedPathHideAppItem(\n    label: String,\n    packageName: String,\n    uid: Int,\n    packageInfo: PackageInfo?,\n    isSystemApp: Boolean,\n    onRemove: () -> Unit,\n) {\n    val context = LocalContext.current\n\n    Surface(\n        modifier = Modifier.fillMaxWidth(),\n        shape = RoundedCornerShape(18.dp),\n        color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.45f),\n        tonalElevation = 0.dp,\n        onClick = onRemove,\n    ) {\n        Row(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(horizontal = 14.dp, vertical = 12.dp),\n            verticalAlignment = Alignment.CenterVertically,\n        ) {\n            AsyncImage(\n                model = ImageRequest.Builder(context)\n                    .data(packageInfo)\n                    .crossfade(true)\n                    .build(),\n                contentDescription = label,\n                modifier = Modifier\n                    .size(42.dp)\n                    .clip(CircleShape),\n            )\n\n            Column(\n                modifier = Modifier\n                    .weight(1f)\n                    .padding(horizontal = 12.dp),\n                verticalArrangement = Arrangement.spacedBy(2.dp),\n            ) {\n                Text(\n                    text = label,\n                    style = MaterialTheme.typography.bodyLarge,\n                    fontWeight = FontWeight.SemiBold,\n                    color = MaterialTheme.colorScheme.onSurface,\n                    maxLines = 1,\n                    overflow = TextOverflow.Ellipsis,\n                )\n                Text(\n                    text = packageName,\n                    style = MaterialTheme.typography.bodySmall,\n                    color = MaterialTheme.colorScheme.onSurfaceVariant,\n                    fontFamily = FontFamily.Monospace,\n                    maxLines = 1,\n                    overflow = TextOverflow.Ellipsis,\n                )\n                Row(\n                    horizontalArrangement = Arrangement.spacedBy(8.dp),\n                    verticalAlignment = Alignment.CenterVertically,\n                ) {\n                    Text(\n                        text = \"UID $uid\",\n                        style = MaterialTheme.typography.labelSmall,\n                        color = MaterialTheme.colorScheme.onSurfaceVariant,\n                    )\n                    if (isSystemApp) {\n                        MiniBadge(text = stringResource(R.string.path_hide_show_system))\n                    }\n                }\n            }\n\n            Box(\n                modifier = Modifier\n                    .clip(CircleShape)\n                    .background(MaterialTheme.colorScheme.surfaceContainerHigh)\n                    .padding(8.dp),\n                contentAlignment = Alignment.Center,\n            ) {\n                Icon(\n                    Icons.Filled.Close,\n                    contentDescription = null,\n                    modifier = Modifier.size(18.dp),\n                    tint = MaterialTheme.colorScheme.onSurfaceVariant,\n                )\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun MiniBadge(\n    text: String,\n    containerColor: Color = MaterialTheme.colorScheme.secondaryContainer,\n    contentColor: Color = MaterialTheme.colorScheme.onSecondaryContainer,\n) {\n    Box(\n        modifier = Modifier\n            .clip(CircleShape)\n            .background(containerColor)\n            .padding(horizontal = 8.dp, vertical = 3.dp),\n        contentAlignment = Alignment.Center,\n    ) {\n        Text(\n            text = text,\n            style = MaterialTheme.typography.labelSmall,\n            color = contentColor,\n        )\n    }\n}\n\ndata class AppListEntry(\n    val uid: Int,\n    val packageName: String,\n    val label: String,\n    val packageInfo: PackageInfo?,\n    val isSystemApp: Boolean,\n)\n\n@Composable\nprivate fun AppPickerButton(\n    selectedUids: Set<Int>,\n    onUidToggle: (Int) -> Unit,\n) {\n    val context = LocalContext.current\n    var showPicker by remember { mutableStateOf(false) }\n    val selectText = stringResource(R.string.path_hide_select_apps)\n\n    OutlinedButton(\n        onClick = { showPicker = true },\n        modifier = Modifier.fillMaxWidth(),\n    ) {\n        Icon(Icons.Filled.Add, contentDescription = null, modifier = Modifier.size(18.dp))\n        Spacer(Modifier.width(8.dp))\n        Text(selectText)\n    }\n\n    if (showPicker) {\n        AppPickerSheet(\n            selectedUids = selectedUids,\n            onUidToggle = onUidToggle,\n            onDismiss = { showPicker = false },\n        )\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun AppPickerSheet(\n    selectedUids: Set<Int>,\n    onUidToggle: (Int) -> Unit,\n    onDismiss: () -> Unit,\n) {\n    val context = LocalContext.current\n    val pm = context.packageManager\n    var searchQuery by remember { mutableStateOf(\"\") }\n    var showSystem by remember { mutableStateOf(false) }\n\n    val allApps = remember {\n        pm.getInstalledApplications(0)\n            .map { appInfo ->\n                AppListEntry(\n                    uid = appInfo.uid,\n                    packageName = appInfo.packageName,\n                    label = appInfo.loadLabel(pm).toString(),\n                    packageInfo = try { pm.getPackageInfo(appInfo.packageName, 0) } catch (_: Exception) { null },\n                    isSystemApp = (appInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0 ||\n                        (appInfo.flags and ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0,\n                )\n            }\n            .distinctBy { it.uid }\n            .sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.label })\n    }\n\n    val filteredApps = remember(searchQuery, showSystem) {\n        allApps\n            .filter { showSystem || !it.isSystemApp }\n            .let { apps ->\n                if (searchQuery.isBlank()) apps\n                else apps.filter {\n                    it.packageName.contains(searchQuery, true) ||\n                        it.label.contains(searchQuery, true)\n                }\n            }\n    }\n\n    val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)\n    val searchHint = stringResource(R.string.path_hide_search_apps)\n    val systemLabel = stringResource(R.string.path_hide_show_system)\n\n    ModalBottomSheet(\n        onDismissRequest = onDismiss,\n        sheetState = sheetState,\n    ) {\n        Column(modifier = Modifier.fillMaxWidth().fillMaxHeight(0.7f)) {\n            Row(\n                modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),\n                verticalAlignment = Alignment.CenterVertically,\n                horizontalArrangement = Arrangement.SpaceBetween,\n            ) {\n                Text(\n                    stringResource(R.string.path_hide_select_apps),\n                    style = MaterialTheme.typography.titleMedium,\n                    fontWeight = FontWeight.SemiBold,\n                )\n                TextButton(onClick = onDismiss) {\n                    Text(stringResource(android.R.string.ok))\n                }\n            }\n\n            Spacer(Modifier.height(8.dp))\n\n            Row(\n                modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),\n                verticalAlignment = Alignment.CenterVertically,\n            ) {\n                OutlinedTextField(\n                    value = searchQuery,\n                    onValueChange = { searchQuery = it },\n                    modifier = Modifier.weight(1f),\n                    placeholder = { Text(searchHint) },\n                    leadingIcon = { Icon(Icons.Filled.Search, contentDescription = null) },\n                    singleLine = true,\n                    colors = TextFieldDefaults.colors(\n                        focusedContainerColor = MaterialTheme.colorScheme.surfaceVariant,\n                        unfocusedContainerColor = MaterialTheme.colorScheme.surfaceVariant,\n                    ),\n                )\n            }\n\n            Row(\n                modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 4.dp),\n                verticalAlignment = Alignment.CenterVertically,\n            ) {\n                Checkbox(checked = showSystem, onCheckedChange = { showSystem = it })\n                Spacer(Modifier.width(4.dp))\n                Text(systemLabel, style = MaterialTheme.typography.bodySmall)\n            }\n\n            val dividerColor = MaterialTheme.colorScheme.outlineVariant\n            LazyColumn(modifier = Modifier.fillMaxWidth().weight(1f)) {\n                items(filteredApps, key = { it.uid }) { app ->\n                    val isSelected = app.uid in selectedUids\n                    Row(\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .clickable { onUidToggle(app.uid) }\n                            .padding(horizontal = 16.dp, vertical = 8.dp)\n                            .drawBehind {\n                                drawLine(\n                                    dividerColor,\n                                    Offset(0f, size.height),\n                                    Offset(size.width, size.height),\n                                    strokeWidth = 0.5.dp.toPx(),\n                                )\n                            },\n                        verticalAlignment = Alignment.CenterVertically,\n                    ) {\n                        Checkbox(\n                            checked = isSelected,\n                            onCheckedChange = { onUidToggle(app.uid) },\n                        )\n                        Spacer(Modifier.width(8.dp))\n                        AsyncImage(\n                            model = ImageRequest.Builder(context)\n                                .data(app.packageInfo)\n                                .crossfade(true)\n                                .build(),\n                            contentDescription = null,\n                            modifier = Modifier.size(36.dp).clip(CircleShape),\n                        )\n                        Spacer(Modifier.width(12.dp))\n                        Column(modifier = Modifier.weight(1f)) {\n                            Text(\n                                app.label,\n                                style = MaterialTheme.typography.bodyMedium,\n                                maxLines = 1,\n                                overflow = TextOverflow.Ellipsis,\n                            )\n                            Text(\n                                app.packageName,\n                                style = MaterialTheme.typography.bodySmall,\n                                color = MaterialTheme.colorScheme.onSurfaceVariant,\n                                maxLines = 1,\n                                overflow = TextOverflow.Ellipsis,\n                                fontFamily = FontFamily.Monospace,\n                            )\n                        }\n                        Text(\n                            \"UID ${app.uid}\",\n                            style = MaterialTheme.typography.labelSmall,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant,\n                        )\n                    }\n                }\n            }\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun PathHideFilterSystemWarningDialog(\n    showDialog: MutableState<Boolean>,\n    onConfirm: () -> Unit,\n) {\n    BasicAlertDialog(\n        onDismissRequest = { showDialog.value = false },\n        properties = DialogProperties(\n            decorFitsSystemWindows = true,\n            usePlatformDefaultWidth = false,\n        )\n    ) {\n        Surface(\n            modifier = Modifier\n                .width(310.dp)\n                .wrapContentHeight(),\n            shape = RoundedCornerShape(30.dp),\n            tonalElevation = AlertDialogDefaults.TonalElevation,\n            color = AlertDialogDefaults.containerColor,\n        ) {\n            Column(modifier = Modifier.padding(PaddingValues(all = 24.dp))) {\n                Text(\n                    text = stringResource(R.string.path_hide_filter_system_warning_title),\n                    style = MaterialTheme.typography.headlineSmall,\n                    modifier = Modifier.padding(bottom = 16.dp)\n                )\n                Text(\n                    text = stringResource(R.string.path_hide_filter_system_warning_message),\n                    style = MaterialTheme.typography.bodyMedium,\n                    color = MaterialTheme.colorScheme.onSurfaceVariant,\n                    modifier = Modifier.padding(bottom = 24.dp)\n                )\n                Row(\n                    modifier = Modifier.fillMaxWidth(),\n                    horizontalArrangement = Arrangement.End\n                ) {\n                    TextButton(onClick = { showDialog.value = false }) {\n                        Text(stringResource(android.R.string.cancel))\n                    }\n                    Spacer(Modifier.width(8.dp))\n                    Button(onClick = {\n                        showDialog.value = false\n                        onConfirm()\n                    }) {\n                        Text(stringResource(android.R.string.ok))\n                    }\n                }\n            }\n            val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n            APDialogBlurBehindUtils.setupWindowBlurListener(dialogWindowProvider.window)\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/FunctionSettingsScreen.kt",
    "content": "package me.bmax.apatch.ui.screen.settings\n\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.outlined.ArrowBack\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.SnackbarHost\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.livedata.observeAsState\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.setValue\nimport androidx.compose.runtime.snapshotFlow\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.input.nestedscroll.nestedScroll\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport android.system.Os\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.debounce\nimport kotlinx.coroutines.flow.drop\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.Natives\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.component.UmountConfig\nimport me.bmax.apatch.ui.component.UmountConfigManager\nimport me.bmax.apatch.ui.theme.BackgroundConfig\nimport me.bmax.apatch.util.isHideServiceEnabled as checkHideServiceEnabled\nimport me.bmax.apatch.util.isUtsSpoofEnabled as checkUtsSpoofEnabled\nimport me.bmax.apatch.util.setUtsSpoofEnabled\nimport me.bmax.apatch.util.writeUtsSpoofConfig\nimport me.bmax.apatch.util.removeUtsSpoofConfig\nimport me.bmax.apatch.util.isPathHideEnabled as checkPathHideEnabled\nimport me.bmax.apatch.util.isPathHideUidModeEnabled as checkPathHideUidMode\nimport me.bmax.apatch.util.isPathHideFilterSystemEnabled as checkPathHideFilterSystem\nimport me.bmax.apatch.util.setPathHideEnabled\nimport me.bmax.apatch.util.writePathHidePaths\nimport me.bmax.apatch.util.readPathHidePaths\nimport me.bmax.apatch.util.isNetIsolateEnabled as checkNetIsolateEnabled\nimport me.bmax.apatch.util.readNetIsolateUids\nimport me.bmax.apatch.util.setNetIsolateEnabled\nimport me.bmax.apatch.util.writeNetIsolateUids\nimport me.bmax.apatch.util.writePathHideUids\nimport me.bmax.apatch.util.setPathHideUidMode\nimport me.bmax.apatch.util.setPathHideFilterSystem\nimport me.bmax.apatch.util.ui.LocalSnackbarHost\nimport me.bmax.apatch.util.ui.NavigationBarsSpacer\nimport androidx.compose.ui.platform.LocalContext\nimport me.bmax.apatch.util.ui.showToast\nimport kotlinx.coroutines.FlowPreview\n\n@OptIn(FlowPreview::class, ExperimentalMaterial3Api::class)\n@Destination<RootGraph>\n@Composable\nfun FunctionSettingsScreen(navigator: DestinationsNavigator) {\n    val state by APApplication.apStateLiveData.observeAsState(APApplication.State.UNKNOWN_STATE)\n    val kPatchReady = state != APApplication.State.UNKNOWN_STATE\n    val aPatchReady = (state == APApplication.State.ANDROIDPATCH_INSTALLING || state == APApplication.State.ANDROIDPATCH_INSTALLED || state == APApplication.State.ANDROIDPATCH_NEED_UPDATE)\n\n    var isHideServiceEnabled by rememberSaveable { mutableStateOf(false) }\n    var isKernelSpoofEnabled by rememberSaveable { mutableStateOf(false) }\n    var kernelSpoofVersion by rememberSaveable { mutableStateOf(\"\") }\n    var kernelSpoofBuildTime by rememberSaveable { mutableStateOf(\"\") }\n    var isUmountEnabled by rememberSaveable { mutableStateOf(false) }\n    var umountPaths by rememberSaveable { mutableStateOf(\"\") }\n    var isNetIsolateEnabled by rememberSaveable { mutableStateOf(false) }\n    var niSelectedUids by rememberSaveable { mutableStateOf(emptySet<Int>()) }\n    var isPathHideEnabled by rememberSaveable { mutableStateOf(false) }\n    var pathHidePaths by rememberSaveable { mutableStateOf(\"\") }\n    var isPathHideUidMode by rememberSaveable { mutableStateOf(false) }\n    var isPathHideFilterSystem by rememberSaveable { mutableStateOf(false) }\n    val showFilterSystemWarningDialog = rememberSaveable { mutableStateOf(false) }\n    var selectedUids by rememberSaveable { mutableStateOf(emptySet<Int>()) }\n\n    val scope = rememberCoroutineScope()\n    val context = LocalContext.current\n\n    LaunchedEffect(kPatchReady, aPatchReady) {\n        if (kPatchReady && aPatchReady) {\n            withContext(Dispatchers.IO) {\n                isHideServiceEnabled = checkHideServiceEnabled()\n                val prefs = APApplication.sharedPreferences\n                isKernelSpoofEnabled = prefs.getBoolean(APApplication.PREF_UTS_SPOOF_ENABLED, false)\n                    && checkUtsSpoofEnabled()\n                kernelSpoofVersion = prefs.getString(APApplication.PREF_UTS_SPOOF_RELEASE, \"\") ?: \"\"\n                kernelSpoofBuildTime = prefs.getString(APApplication.PREF_UTS_SPOOF_VERSION, \"\") ?: \"\"\n                val umountConfig = UmountConfigManager.loadConfig(context)\n                isUmountEnabled = umountConfig.enabled\n                umountPaths = umountConfig.paths\n                // Load netisolate state\n                isNetIsolateEnabled = checkNetIsolateEnabled()\n                val niUidSource = Natives.netIsolateUidList().ifBlank {\n                    readNetIsolateUids()\n                }\n                niSelectedUids = niUidSource.lines()\n                    .map { it.trim() }\n                    .filter { it.isNotBlank() }\n                    .mapNotNull { it.toIntOrNull() }\n                    .toSet()\n                // Load pathhide state from kernel + config file\n                isPathHideEnabled = checkPathHideEnabled()\n                // Try to get paths from kernel first, fall back to config file\n                val kernelPaths = Natives.pathHideList()\n                if (kernelPaths.isNotBlank()) {\n                    pathHidePaths = kernelPaths.trimEnd('\\n')\n                } else {\n                    pathHidePaths = readPathHidePaths()\n                }\n                // Load UID mode state\n                isPathHideUidMode = checkPathHideUidMode()\n                isPathHideFilterSystem = checkPathHideFilterSystem()\n                val pm = context.packageManager\n                val uidSource = Natives.pathHideUidList().ifBlank {\n                    me.bmax.apatch.util.readPathHideUids()\n                }\n                if (uidSource.isNotBlank()) {\n                    val loaded = uidSource.lines()\n                        .map { it.trim() }\n                        .filter { it.isNotBlank() }\n                        .mapNotNull { it.toIntOrNull() }\n                        .toMutableSet()\n                    // Auto-cleanup: remove UIDs for uninstalled apps\n                    val stale = loaded.filter { uid ->\n                        uid > 0 && pm.getPackagesForUid(uid) == null\n                    }\n                    if (stale.isNotEmpty()) {\n                        stale.forEach { loaded.remove(it) }\n                        // Sync cleanup to kernel + file\n                        Natives.pathHideUidClear()\n                        loaded.forEach { Natives.pathHideUidAdd(it) }\n                        writePathHideUids(loaded.joinToString(\"\\n\"))\n                    }\n                    selectedUids = loaded.toSet()\n                }\n            }\n        }\n\n        launch {\n            snapshotFlow { kernelSpoofVersion to kernelSpoofBuildTime }\n                .drop(1)\n                .debounce(1000L)\n                .collect { (version, buildTime) ->\n                    val prefs = APApplication.sharedPreferences\n                    prefs.edit()\n                        .putString(APApplication.PREF_UTS_SPOOF_RELEASE, version)\n                        .putString(APApplication.PREF_UTS_SPOOF_VERSION, buildTime)\n                        .apply()\n                    if (isKernelSpoofEnabled) {\n                        setUtsSpoofEnabled(true)\n                        writeUtsSpoofConfig(version, buildTime)\n                        Natives.utsSet(version.ifBlank { null }, buildTime.ifBlank { null })\n                    }\n                }\n        }\n\n        launch {\n            snapshotFlow { pathHidePaths }\n                .drop(1)\n                .debounce(1000L)\n                .collect { paths ->\n                    writePathHidePaths(paths)\n                    Natives.pathHideClear()\n                    if (paths.isNotBlank()) {\n                        paths.lines()\n                            .map { it.trim() }\n                            .filter { it.isNotBlank() }\n                            .forEach { path -> Natives.pathHideAdd(path) }\n                    }\n                }\n        }\n\n        launch {\n            snapshotFlow { umountPaths }\n                .drop(1)\n                .debounce(1000L)\n                .collect { paths ->\n                    UmountConfigManager.saveConfig(context, UmountConfig(enabled = isUmountEnabled, paths = paths))\n                }\n        }\n    }\n\n    val snackBarHost = LocalSnackbarHost.current\n    val flat = BackgroundConfig.isCustomBackgroundEnabled || BackgroundConfig.settingsBackgroundUri != null\n    val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()\n\n    Scaffold(\n        topBar = {\n            TopAppBar(\n                title = { Text(stringResource(R.string.settings_category_function), style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.SemiBold) },\n                navigationIcon = {\n                    IconButton(onClick = { navigator.popBackStack() }) {\n                        Icon(Icons.AutoMirrored.Outlined.ArrowBack, contentDescription = null)\n                    }\n                },\n                scrollBehavior = scrollBehavior,\n            )\n        },\n        containerColor = Color.Transparent,\n        snackbarHost = { SnackbarHost(snackBarHost) },\n    ) { paddingValues ->\n        LazyColumn(\n            modifier = Modifier.padding(paddingValues).nestedScroll(scrollBehavior.nestedScrollConnection),\n            verticalArrangement = Arrangement.spacedBy(8.dp),\n        ) {\n            item { Spacer(Modifier.height(8.dp)) }\n            item {\n                FunctionSettingsContent(\n                    kPatchReady = kPatchReady,\n                    aPatchReady = aPatchReady,\n                    isHideServiceEnabled = isHideServiceEnabled,\n                    onHideServiceChange = { isHideServiceEnabled = it },\n                    isKernelSpoofEnabled = isKernelSpoofEnabled,\n                    onKernelSpoofChange = { enabled ->\n                        isKernelSpoofEnabled = enabled\n                        scope.launch(Dispatchers.IO) {\n                            val prefs = APApplication.sharedPreferences\n                            prefs.edit().putBoolean(APApplication.PREF_UTS_SPOOF_ENABLED, enabled).apply()\n                            if (enabled) {\n                                setUtsSpoofEnabled(true)\n                                val savedRelease = prefs.getString(APApplication.PREF_UTS_SPOOF_RELEASE, \"\") ?: \"\"\n                                val savedVersion = prefs.getString(APApplication.PREF_UTS_SPOOF_VERSION, \"\") ?: \"\"\n                                writeUtsSpoofConfig(savedRelease, savedVersion)\n                                Natives.utsSet(savedRelease.ifBlank { null }, savedVersion.ifBlank { null })\n                                withContext(Dispatchers.Main) {\n                                    snackBarHost.showSnackbar(context.getString(R.string.kernel_spoof_enabled))\n                                }\n                            } else {\n                                Natives.utsReset()\n                                setUtsSpoofEnabled(false)\n                                removeUtsSpoofConfig()\n                                withContext(Dispatchers.Main) {\n                                    snackBarHost.showSnackbar(context.getString(R.string.kernel_spoof_disabled_restored))\n                                }\n                            }\n                        }\n                    },\n                    kernelSpoofVersion = kernelSpoofVersion,\n                    onKernelSpoofVersionChange = { kernelSpoofVersion = it },\n                    kernelSpoofBuildTime = kernelSpoofBuildTime,\n                    onKernelSpoofBuildTimeChange = { kernelSpoofBuildTime = it },\n                    onKernelSpoofSave = {\n                        val currentEnabled = isKernelSpoofEnabled\n                        val currentVersion = kernelSpoofVersion\n                        val currentBuildTime = kernelSpoofBuildTime\n                        scope.launch(Dispatchers.IO) {\n                            val prefs = APApplication.sharedPreferences\n                            prefs.edit()\n                                .putBoolean(APApplication.PREF_UTS_SPOOF_ENABLED, currentEnabled)\n                                .putString(APApplication.PREF_UTS_SPOOF_RELEASE, currentVersion)\n                                .putString(APApplication.PREF_UTS_SPOOF_VERSION, currentBuildTime)\n                                .apply()\n\n                            if (currentEnabled) {\n                                setUtsSpoofEnabled(true)\n                                writeUtsSpoofConfig(currentVersion, currentBuildTime)\n                                val rc = Natives.utsSet(\n                                    currentVersion.ifBlank { null },\n                                    currentBuildTime.ifBlank { null }\n                                )\n                                withContext(Dispatchers.Main) {\n                                    if (rc < 0) {\n                                        snackBarHost.showSnackbar(context.getString(R.string.kernel_spoof_failed, rc))\n                                    } else {\n                                        snackBarHost.showSnackbar(context.getString(R.string.kernel_spoof_applied))\n                                    }\n                                }\n                            } else {\n                                Natives.utsReset()\n                                setUtsSpoofEnabled(false)\n                                removeUtsSpoofConfig()\n                                withContext(Dispatchers.Main) {\n                                    snackBarHost.showSnackbar(context.getString(R.string.kernel_spoof_disabled_restored))\n                                }\n                            }\n                        }\n                    },\n                    onKernelSpoofRestore = {\n                        scope.launch(Dispatchers.IO) {\n                            Natives.utsReset()\n                            val uname = Os.uname()\n                            val realRelease = uname.release\n                            val realVersion = uname.version\n                            withContext(Dispatchers.Main) {\n                                kernelSpoofVersion = realRelease\n                                kernelSpoofBuildTime = realVersion\n                            }\n                            if (isKernelSpoofEnabled) {\n                                val prefs = APApplication.sharedPreferences\n                                val savedRelease = prefs.getString(APApplication.PREF_UTS_SPOOF_RELEASE, \"\") ?: \"\"\n                                val savedVersion = prefs.getString(APApplication.PREF_UTS_SPOOF_VERSION, \"\") ?: \"\"\n                                if (savedRelease.isNotBlank() || savedVersion.isNotBlank()) {\n                                    Natives.utsSet(\n                                        savedRelease.ifBlank { null },\n                                        savedVersion.ifBlank { null }\n                                    )\n                                }\n                            }\n                        }\n                    },\n                    snackBarHost = snackBarHost,\n                    isPathHideEnabled = isPathHideEnabled,\n                    onPathHideChange = { enabled ->\n                        isPathHideEnabled = enabled\n                        scope.launch(Dispatchers.IO) {\n                            setPathHideEnabled(enabled)\n                            val rc = Natives.pathHideEnable(enabled)\n                            withContext(Dispatchers.Main) {\n                                if (rc < 0) {\n                                    snackBarHost.showSnackbar(context.getString(R.string.path_hide_failed, rc.toInt()))\n                                } else {\n                                    snackBarHost.showSnackbar(\n                                        context.getString(if (enabled) R.string.path_hide_enabled else R.string.path_hide_disabled)\n                                    )\n                                }\n                            }\n                        }\n                    },\n                    pathHidePaths = pathHidePaths,\n                    onPathHidePathsChange = { pathHidePaths = it },\n                    onPathHideSave = {\n                        val currentPaths = pathHidePaths\n                        scope.launch(Dispatchers.IO) {\n                            // Save to config file for persistence\n                            writePathHidePaths(currentPaths)\n                            // Clear existing kernel paths and re-add\n                            Natives.pathHideClear()\n                            if (currentPaths.isNotBlank()) {\n                                currentPaths.lines()\n                                    .map { it.trim() }\n                                    .filter { it.isNotBlank() }\n                                    .forEach { path ->\n                                        Natives.pathHideAdd(path)\n                                    }\n                            }\n                            withContext(Dispatchers.Main) {\n                                snackBarHost.showSnackbar(context.getString(R.string.path_hide_applied))\n                            }\n                        }\n                    },\n                    isPathHideUidMode = isPathHideUidMode,\n                    onPathHideUidModeChange = { enabled ->\n                        isPathHideUidMode = enabled\n                        scope.launch(Dispatchers.IO) {\n                            setPathHideUidMode(enabled)\n                            Natives.pathHideUidMode(enabled)\n                            withContext(Dispatchers.Main) {\n                                snackBarHost.showSnackbar(\n                                    context.getString(if (enabled) R.string.path_hide_uid_mode_enabled else R.string.path_hide_uid_mode_disabled)\n                                )\n                            }\n                        }\n                    },\n                    isPathHideFilterSystem = isPathHideFilterSystem,\n                    onPathHideFilterSystemChange = { enabled ->\n                        if (enabled) {\n                            showFilterSystemWarningDialog.value = true\n                        } else {\n                            isPathHideFilterSystem = false\n                            scope.launch(Dispatchers.IO) {\n                                setPathHideFilterSystem(false)\n                                Natives.pathHideFilterSystem(false)\n                                withContext(Dispatchers.Main) {\n                                    snackBarHost.showSnackbar(\n                                        context.getString(R.string.path_hide_filter_system_disabled)\n                                    )\n                                }\n                            }\n                        }\n                    },\n                    selectedUids = selectedUids,\n                    onUidToggle = { uid ->\n                        scope.launch(Dispatchers.IO) {\n                            val newSet = if (uid in selectedUids) {\n                                Natives.pathHideUidRemove(uid)\n                                selectedUids - uid\n                            } else {\n                                Natives.pathHideUidAdd(uid)\n                                selectedUids + uid\n                            }\n                            writePathHideUids(newSet.joinToString(\"\\n\"))\n                            withContext(Dispatchers.Main) {\n                                selectedUids = newSet\n                            }\n                        }\n                    },\n                    onUidRemoveStale = {},\n                    isUmountEnabled = isUmountEnabled,\n                    onUmountEnabledChange = { enabled ->\n                        isUmountEnabled = enabled\n                        scope.launch(Dispatchers.IO) {\n                            val config = UmountConfig(enabled = enabled, paths = umountPaths)\n                            val success = UmountConfigManager.saveConfig(context, config)\n                            withContext(Dispatchers.Main) {\n                                if (success) {\n                                    showToast(context, context.getString(R.string.umount_config_save_success))\n                                } else {\n                                    showToast(context, context.getString(R.string.umount_config_save_failed))\n                                }\n                            }\n                        }\n                    },\n                    umountPaths = umountPaths,\n                    onUmountPathsChange = { umountPaths = it },\n                    onUmountSave = {\n                        val currentEnabled = isUmountEnabled\n                        val currentPaths = umountPaths\n                        scope.launch(Dispatchers.IO) {\n                            val config = UmountConfig(enabled = currentEnabled, paths = currentPaths)\n                            val success = UmountConfigManager.saveConfig(context, config)\n                            withContext(Dispatchers.Main) {\n                                if (success) {\n                                    showToast(context, context.getString(R.string.umount_config_save_success))\n                                } else {\n                                    showToast(context, context.getString(R.string.umount_config_save_failed))\n                                }\n                            }\n                        }\n                    },\n                    flat = flat,\n                    isNetIsolateEnabled = isNetIsolateEnabled,\n                    onNetIsolateChange = { enabled ->\n                        isNetIsolateEnabled = enabled\n                        scope.launch(Dispatchers.IO) {\n                            setNetIsolateEnabled(enabled)\n                            Natives.netIsolateEnable(enabled)\n                            withContext(Dispatchers.Main) {\n                                snackBarHost.showSnackbar(\n                                    context.getString(if (enabled) R.string.netisolate_enable else R.string.netisolate_disable)\n                                )\n                            }\n                        }\n                    },\n                    niSelectedUids = niSelectedUids,\n                    onNiUidToggle = { uid ->\n                        scope.launch(Dispatchers.IO) {\n                            val newSet = if (uid in niSelectedUids) {\n                                Natives.netIsolateUidRemove(uid)\n                                niSelectedUids - uid\n                            } else {\n                                Natives.netIsolateUidAdd(uid)\n                                niSelectedUids + uid\n                            }\n                            writeNetIsolateUids(newSet.joinToString(\"\\n\"))\n                            withContext(Dispatchers.Main) {\n                                niSelectedUids = newSet\n                            }\n                        }\n                    },\n                )\n            }\n            item { Spacer(Modifier.height(8.dp)) }\n            item { NavigationBarsSpacer() }\n        }\n    }\n\n    if (showFilterSystemWarningDialog.value) {\n        PathHideFilterSystemWarningDialog(\n            showDialog = showFilterSystemWarningDialog,\n            onConfirm = {\n                isPathHideFilterSystem = true\n                scope.launch(Dispatchers.IO) {\n                    setPathHideFilterSystem(true)\n                    Natives.pathHideFilterSystem(true)\n                    withContext(Dispatchers.Main) {\n                        snackBarHost.showSnackbar(\n                            context.getString(R.string.path_hide_filter_system_enabled)\n                        )\n                    }\n                }\n            },\n        )\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/GeneralSettings.kt",
    "content": "package me.bmax.apatch.ui.screen.settings\n\nimport android.app.Activity\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Build\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.animation.core.animateFloatAsState\nimport me.bmax.apatch.util.ui.showToast\nimport androidx.appcompat.app.AppCompatDelegate\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.lazy.itemsIndexed\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.*\nimport androidx.compose.material3.*\nimport androidx.compose.runtime.*\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalView\nimport androidx.compose.ui.res.stringArrayResource\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.window.DialogProperties\nimport androidx.compose.ui.window.DialogWindowProvider\nimport androidx.core.content.FileProvider\nimport androidx.core.content.edit\nimport androidx.core.os.LocaleListCompat\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport com.ramcosta.composedestinations.generated.destinations.LanguagePickerScreenDestination\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.BuildConfig\nimport me.bmax.apatch.Natives\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.component.ExpressiveCard\nimport me.bmax.apatch.ui.component.SplicedColumnGroup\nimport me.bmax.apatch.ui.component.ToggleSettingCard\nimport me.bmax.apatch.ui.component.UpdateDialog\nimport me.bmax.apatch.ui.component.rememberLoadingDialog\nimport me.bmax.apatch.util.*\nimport me.bmax.apatch.util.ui.APDialogBlurBehindUtils\nimport java.util.Locale\n\n@Composable\nfun GeneralSettingsContent(\n    kPatchReady: Boolean,\n    aPatchReady: Boolean,\n    currentSELinuxMode: String,\n    onSELinuxModeChange: (String) -> Unit,\n    isGlobalNamespaceEnabled: Boolean,\n    onGlobalNamespaceChange: (Boolean) -> Unit,\n    isMagicMountEnabled: Boolean,\n    onMagicMountChange: (Boolean) -> Unit,\n    snackBarHost: SnackbarHostState,\n    flat: Boolean = false,\n    navigator: DestinationsNavigator,\n) {\n    val context = LocalContext.current\n    val prefs = APApplication.sharedPreferences\n    val scope = rememberCoroutineScope()\n    val loadingDialog = rememberLoadingDialog()\n\n    val languageTitle = stringResource(id = R.string.settings_app_language)\n    val languageValue = AppCompatDelegate.getApplicationLocales()[0]?.displayLanguage?.replaceFirstChar {\n        if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString()\n    } ?: stringResource(id = R.string.system_default)\n\n    val updateTitle = stringResource(id = R.string.settings_check_update)\n\n    val autoUpdateTitle = stringResource(id = R.string.settings_auto_update_check)\n    val autoUpdateSummary = stringResource(id = R.string.settings_auto_update_check_summary)\n\n    val globalNamespaceTitle = stringResource(id = R.string.settings_global_namespace_mode)\n    val globalNamespaceSummary = stringResource(id = R.string.settings_global_namespace_mode_summary)\n\n    val magicMountTitle = stringResource(id = R.string.settings_magic_mount)\n    val magicMountSummary = stringResource(id = R.string.settings_magic_mount_summary)\n\n    val selinuxModeTitle = stringResource(id = R.string.settings_selinux_mode)\n    val selinuxModeSummary = stringResource(id = R.string.settings_selinux_mode_summary)\n    val selinuxModeValue = when (currentSELinuxMode) {\n        \"Enforcing\" -> stringResource(R.string.settings_selinux_mode_enforcing)\n        \"Permissive\" -> stringResource(R.string.settings_selinux_mode_permissive)\n        else -> stringResource(R.string.home_selinux_status_unknown)\n    }\n\n    val resetSuPathTitle = stringResource(id = R.string.setting_reset_su_path)\n\n    val launcherIconTitle = stringResource(id = R.string.settings_alt_icon)\n    val launcherIconSummary = stringResource(id = R.string.alt_icon_summary)\n\n    val appTitleTitle = stringResource(id = R.string.settings_app_title)\n    var currentAppTitle by remember { mutableStateOf(prefs.getString(\"app_title\", \"folkpatch\") ?: \"folkpatch\") }\n    val appTitleLabel = when (currentAppTitle) {\n        \"custom\" -> remember { prefs.getString(\"custom_app_title\", \"FolkPatch\") } ?: stringResource(R.string.app_title_custom)\n        \"fpatch\" -> stringResource(R.string.app_title_fpatch)\n        \"apatch_folk\" -> stringResource(R.string.app_title_apatch_folk)\n        \"apatchx\" -> stringResource(R.string.app_title_apatchx)\n        \"apatch\" -> stringResource(R.string.app_title_apatch)\n        \"kernelpatch\" -> stringResource(R.string.app_title_kernelpatch)\n        \"kernelsu\" -> stringResource(R.string.app_title_kernelsu)\n        \"supersu\" -> stringResource(R.string.app_title_supersu)\n        \"folksu\" -> stringResource(R.string.app_title_fpatch)\n        \"superuser\" -> stringResource(R.string.app_title_superuser)\n        \"superpatch\" -> stringResource(R.string.app_title_superpatch)\n        \"magicpatch\" -> stringResource(R.string.app_title_magicpatch)\n        else -> stringResource(R.string.app_title_folkpatch)\n    }\n\n    val customAppTitleTitle = stringResource(id = R.string.settings_custom_app_title)\n    val currentCustomAppTitle = remember { prefs.getString(\"custom_app_title\", \"FolkPatch\") }\n\n    val desktopAppNameTitle = stringResource(id = R.string.desktop_app_name)\n    val currentDesktopAppName = remember { prefs.getString(\"desktop_app_name\", \"FolkPatch\") }\n\n    val dpiTitle = stringResource(id = R.string.settings_app_dpi)\n    val currentDpiVal = DPIUtils.currentDpi\n    val dpiValue = if (currentDpiVal == DPIUtils.DEFAULT_DPI) stringResource(id = R.string.system_default) else \"${DPIUtils.getDpiFriendlyName(currentDpiVal)} ($currentDpiVal DPI)\"\n\n    val logTitle = stringResource(id = R.string.send_log)\n\n    val folkXEngineTitle = stringResource(id = R.string.settings_folkx_engine_title)\n    val folkXEngineSummary = stringResource(id = R.string.settings_folkx_engine_summary)\n\n    val predictiveBackTitle = stringResource(id = R.string.settings_predictive_back)\n    val predictiveBackSummary = stringResource(id = R.string.settings_predictive_back_summary)\n\n    val appListLoadingSchemeTitle = stringResource(id = R.string.settings_app_list_loading_scheme)\n    val currentScheme = remember { prefs.getString(\"app_list_loading_scheme\", \"root_service\") }\n    val currentSchemeLabel = if (currentScheme == \"root_service\") stringResource(R.string.app_list_loading_scheme_root_service) else stringResource(R.string.app_list_loading_scheme_package_manager)\n    val newAppProfileTitle = stringResource(id = R.string.settings_new_app_profile_mode)\n\n    val blockUpdateTitle = stringResource(id = R.string.settings_block_kernelpatch_update)\n    val blockUpdateSummary = stringResource(id = R.string.settings_block_kernelpatch_update_summary)\n\n    val blockApUpdateTitle = stringResource(id = R.string.settings_block_androidpatch_update)\n    val blockApUpdateSummary = stringResource(id = R.string.settings_block_androidpatch_update_summary)\n\n    val showUpdateDialog = remember { mutableStateOf(false) }\n    val showResetSuPathDialog = remember { mutableStateOf(false) }\n    val showAppTitleDialog = remember { mutableStateOf(false) }\n    val showCustomAppTitleDialog = remember { mutableStateOf(false) }\n    val showDesktopAppNameDialog = remember { mutableStateOf(false) }\n    val showDpiDialog = remember { mutableStateOf(false) }\n    val showFolkXAnimationTypeDialog = remember { mutableStateOf(false) }\n    val showFolkXAnimationSpeedDialog = remember { mutableStateOf(false) }\n    val showAppListLoadingSchemeDialog = remember { mutableStateOf(false) }\n    val showNewAppProfileModeDialog = remember { mutableStateOf(false) }\n    val showSELinuxModeDialog = remember { mutableStateOf(false) }\n\n    val useAltIcon = remember { mutableStateOf(prefs.getBoolean(\"use_alt_icon\", false)) }\n    var autoUpdateCheck by remember { mutableStateOf(prefs.getBoolean(\"auto_update_check\", true)) }\n    var blockUpdateChecked by remember { mutableStateOf(prefs.getBoolean(APApplication.PREF_BLOCK_KERNELPATCH_UPDATE, false)) }\n    var blockApUpdateChecked by remember { mutableStateOf(prefs.getBoolean(APApplication.PREF_BLOCK_ANDROIDPATCH_UPDATE, false)) }\n    var folkXEngineEnabled by remember { mutableStateOf(prefs.getBoolean(\"folkx_engine_enabled\", true)) }\n    val currentType = remember { prefs.getString(\"folkx_animation_type\", \"linear\") }\n    val currentSpeed = remember { prefs.getFloat(\"folkx_animation_speed\", 1.0f) }\n    var predictiveBackEnabled by remember { mutableStateOf(prefs.getBoolean(\"predictive_back_enabled\", true)) }\n    var newAppProfileMode by remember {\n        mutableIntStateOf(\n            prefs.getInt(APApplication.PREF_AUTO_EXCLUDE_NEW_APPS, Natives.getNewAppProfileMode())\n        )\n    }\n    val currentNewAppProfileLabel = when (newAppProfileMode) {\n        1 -> stringResource(R.string.settings_new_app_profile_root)\n        2 -> stringResource(R.string.settings_new_app_profile_exclude)\n        else -> stringResource(R.string.settings_new_app_profile_normal)\n    }\n\n    SplicedColumnGroup(flat = flat) {\n\n        item {\n            ExpressiveCard(flat = flat, onClick = { navigator.navigate(LanguagePickerScreenDestination) }) {\n                Row(\n                    modifier = Modifier.fillMaxWidth().padding(16.dp),\n                    verticalAlignment = Alignment.CenterVertically\n                ) {\n                    Icon(imageVector = Icons.Filled.Translate, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                    Spacer(Modifier.width(16.dp))\n                    Column {\n                        Text(\n                            text = languageTitle,\n                            style = MaterialTheme.typography.titleMedium,\n                            color = MaterialTheme.colorScheme.onSurface,\n                            fontWeight = FontWeight.SemiBold\n                        )\n                        Spacer(Modifier.height(4.dp))\n                        Text(\n                            text = languageValue,\n                            style = MaterialTheme.typography.bodyMedium,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant\n                        )\n                    }\n                }\n            }\n        }\n\n        item {\n            ExpressiveCard(flat = flat, onClick = {\n                scope.launch {\n                    loadingDialog.show()\n                    val hasUpdate = UpdateChecker.checkUpdate()\n                    loadingDialog.hide()\n                    if (hasUpdate) {\n                        showUpdateDialog.value = true\n                    } else {\n                        showToast(context, R.string.update_latest)\n                    }\n                }\n            }) {\n                Row(\n                    modifier = Modifier.fillMaxWidth().padding(16.dp),\n                    verticalAlignment = Alignment.CenterVertically\n                ) {\n                    Icon(imageVector = Icons.Filled.Update, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                    Spacer(Modifier.width(16.dp))\n                    Text(\n                        text = updateTitle,\n                        style = MaterialTheme.typography.titleMedium,\n                        fontWeight = FontWeight.SemiBold,\n                        color = MaterialTheme.colorScheme.onSurface\n                    )\n                }\n            }\n        }\n\n        item {\n            ToggleSettingCard(\n            flat = flat,\n            icon = Icons.Filled.Autorenew,\n            title = autoUpdateTitle,\n            description = autoUpdateSummary,\n            checked = autoUpdateCheck,\n            onCheckedChange = {\n                autoUpdateCheck = it\n                prefs.edit { putBoolean(\"auto_update_check\", it) }\n            }\n        )\n        }\n\n        item {\n            ToggleSettingCard(\n            flat = flat,\n            icon = Icons.Filled.Block,\n            title = blockUpdateTitle,\n            description = blockUpdateSummary,\n            checked = blockUpdateChecked,\n            onCheckedChange = {\n                blockUpdateChecked = it\n                prefs.edit { putBoolean(APApplication.PREF_BLOCK_KERNELPATCH_UPDATE, it) }\n            }\n        )\n        }\n\n        item {\n            ToggleSettingCard(\n            flat = flat,\n            icon = Icons.Filled.Block,\n            title = blockApUpdateTitle,\n            description = blockApUpdateSummary,\n            checked = blockApUpdateChecked,\n            onCheckedChange = {\n                blockApUpdateChecked = it\n                prefs.edit { putBoolean(APApplication.PREF_BLOCK_ANDROIDPATCH_UPDATE, it) }\n            }\n        )\n        }\n\n        item {\n            ToggleSettingCard(\n            flat = flat,\n            icon = Icons.Filled.AutoAwesome,\n            title = folkXEngineTitle,\n            description = folkXEngineSummary,\n            checked = folkXEngineEnabled,\n            onCheckedChange = {\n                folkXEngineEnabled = it\n                prefs.edit().putBoolean(\"folkx_engine_enabled\", it).apply()\n            }\n        )\n        }\n\n        item(visible = folkXEngineEnabled) {\n            val animationTypeLabel = when (currentType) {\n                \"linear\" -> R.string.settings_folkx_animation_linear\n                \"spatial\" -> R.string.settings_folkx_animation_spatial\n                \"fade\" -> R.string.settings_folkx_animation_fade\n                \"vertical\" -> R.string.settings_folkx_animation_vertical\n                \"diagonal\" -> R.string.settings_folkx_animation_diagonal\n                else -> R.string.settings_folkx_animation_linear\n            }\n\n            ExpressiveCard(flat = flat, onClick = { showFolkXAnimationTypeDialog.value = true }) {\n                Row(\n                    modifier = Modifier.fillMaxWidth().padding(16.dp),\n                    verticalAlignment = Alignment.CenterVertically\n                ) {\n                    Icon(imageVector = Icons.Filled.Animation, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                    Spacer(Modifier.width(16.dp))\n                    Column {\n                        Text(\n                            text = stringResource(R.string.settings_folkx_animation_type),\n                            style = MaterialTheme.typography.titleMedium,\n                            color = MaterialTheme.colorScheme.onSurface,\n                            fontWeight = FontWeight.SemiBold\n                        )\n                        Spacer(Modifier.height(4.dp))\n                        Text(\n                            text = stringResource(animationTypeLabel),\n                            style = MaterialTheme.typography.bodyMedium,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant\n                        )\n                    }\n                }\n            }\n        }\n\n        item(visible = folkXEngineEnabled) {\n            ExpressiveCard(flat = flat, onClick = { showFolkXAnimationSpeedDialog.value = true }) {\n                Row(\n                    modifier = Modifier.fillMaxWidth().padding(16.dp),\n                    verticalAlignment = Alignment.CenterVertically\n                ) {\n                    Icon(imageVector = Icons.Filled.Speed, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                    Spacer(Modifier.width(16.dp))\n                    Column {\n                        Text(\n                            text = stringResource(R.string.settings_folkx_animation_speed),\n                            style = MaterialTheme.typography.titleMedium,\n                            color = MaterialTheme.colorScheme.onSurface,\n                            fontWeight = FontWeight.SemiBold\n                        )\n                        Spacer(Modifier.height(4.dp))\n                        Text(\n                            text = \"${currentSpeed}x\",\n                            style = MaterialTheme.typography.bodyMedium,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant\n                        )\n                    }\n                }\n            }\n        }\n\n        item(visible = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {\n            ToggleSettingCard(\n            flat = flat,\n            icon = Icons.Filled.ArrowBack,\n                title = predictiveBackTitle,\n                description = predictiveBackSummary,\n                checked = predictiveBackEnabled,\n                onCheckedChange = {\n                    predictiveBackEnabled = it\n                    prefs.edit { putBoolean(\"predictive_back_enabled\", it) }\n                    (context as? Activity)?.recreate()\n                }\n            )\n        }\n\n        item(visible = kPatchReady) {\n            ExpressiveCard(flat = flat, onClick = { showNewAppProfileModeDialog.value = true }) {\n                Row(\n                    modifier = Modifier.fillMaxWidth().padding(16.dp),\n                    verticalAlignment = Alignment.CenterVertically\n                ) {\n                    Icon(imageVector = Icons.Filled.Block, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                    Spacer(Modifier.width(16.dp))\n                    Column {\n                        Text(\n                            text = newAppProfileTitle,\n                            style = MaterialTheme.typography.titleMedium,\n                            color = MaterialTheme.colorScheme.onSurface,\n                            fontWeight = FontWeight.SemiBold\n                        )\n                        Spacer(Modifier.height(4.dp))\n                        Text(\n                            text = currentNewAppProfileLabel,\n                            style = MaterialTheme.typography.bodyMedium,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant\n                        )\n                    }\n                }\n            }\n        }\n\n        item(visible = kPatchReady) {\n            ExpressiveCard(flat = flat, onClick = { showAppListLoadingSchemeDialog.value = true }) {\n                Row(\n                    modifier = Modifier.fillMaxWidth().padding(16.dp),\n                    verticalAlignment = Alignment.CenterVertically\n                ) {\n                    Icon(imageVector = Icons.Filled.List, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                    Spacer(Modifier.width(16.dp))\n                    Column {\n                        Text(\n                            text = appListLoadingSchemeTitle,\n                            style = MaterialTheme.typography.titleMedium,\n                            color = MaterialTheme.colorScheme.onSurface,\n                            fontWeight = FontWeight.SemiBold\n                        )\n                        Spacer(Modifier.height(4.dp))\n                        Text(\n                            text = currentSchemeLabel,\n                            style = MaterialTheme.typography.bodyMedium,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant\n                        )\n                    }\n                }\n            }\n        }\n\n        item(visible = kPatchReady && aPatchReady) {\n            ExpressiveCard(flat = flat, onClick = { showSELinuxModeDialog.value = true }) {\n                Row(\n                    modifier = Modifier.fillMaxWidth().padding(16.dp),\n                    verticalAlignment = Alignment.CenterVertically\n                ) {\n                    Icon(imageVector = Icons.Filled.Security, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                    Spacer(Modifier.width(16.dp))\n                    Column {\n                        Text(\n                            text = selinuxModeTitle,\n                            style = MaterialTheme.typography.titleMedium,\n                            color = MaterialTheme.colorScheme.onSurface,\n                            fontWeight = FontWeight.SemiBold\n                        )\n                        Spacer(Modifier.height(4.dp))\n                        Text(\n                            text = stringResource(R.string.settings_selinux_current_mode, selinuxModeValue),\n                            style = MaterialTheme.typography.bodyMedium,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant\n                        )\n                    }\n                }\n            }\n        }\n\n        item(visible = kPatchReady && aPatchReady) {\n            ToggleSettingCard(\n            flat = flat,\n            icon = Icons.Filled.Public,\n                title = globalNamespaceTitle,\n                description = globalNamespaceSummary,\n                checked = isGlobalNamespaceEnabled,\n                onCheckedChange = {\n                    setGlobalNamespaceEnabled(if (isGlobalNamespaceEnabled) \"0\" else \"1\")\n                    onGlobalNamespaceChange(it)\n                }\n            )\n        }\n\n        item(visible = kPatchReady && aPatchReady) {\n            ToggleSettingCard(\n            flat = flat,\n            icon = Icons.Filled.FolderSpecial,\n                title = magicMountTitle,\n                description = magicMountSummary,\n                checked = isMagicMountEnabled,\n                onCheckedChange = {\n                    setMagicMountEnabled(it)\n                    onMagicMountChange(it)\n                }\n            )\n        }\n\n        item {\n            ToggleSettingCard(\n            flat = flat,\n            icon = Icons.Filled.Android,\n            title = launcherIconTitle,\n            description = launcherIconSummary,\n            checked = useAltIcon.value,\n            onCheckedChange = {\n                prefs.edit { putBoolean(\"use_alt_icon\", it) }\n                LauncherIconUtils.updateLauncherState(context)\n                useAltIcon.value = it\n            }\n        )\n        }\n\n        item(visible = kPatchReady) {\n            ExpressiveCard(flat = flat, onClick = { showResetSuPathDialog.value = true }) {\n                Row(\n                    modifier = Modifier.fillMaxWidth().padding(16.dp),\n                    verticalAlignment = Alignment.CenterVertically\n                ) {\n                    Icon(imageVector = Icons.Filled.LinkOff, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                    Spacer(Modifier.width(16.dp))\n                    Text(\n                        text = resetSuPathTitle,\n                        style = MaterialTheme.typography.titleMedium,\n                        fontWeight = FontWeight.SemiBold,\n                        color = MaterialTheme.colorScheme.onSurface\n                    )\n                }\n            }\n        }\n\n        item {\n            ExpressiveCard(flat = flat, onClick = { showAppTitleDialog.value = true }) {\n                Row(\n                    modifier = Modifier.fillMaxWidth().padding(16.dp),\n                    verticalAlignment = Alignment.CenterVertically\n                ) {\n                    Icon(imageVector = Icons.Filled.Label, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                    Spacer(Modifier.width(16.dp))\n                    Column {\n                        Text(\n                            text = appTitleTitle,\n                            style = MaterialTheme.typography.titleMedium,\n                            color = MaterialTheme.colorScheme.onSurface,\n                            fontWeight = FontWeight.SemiBold\n                        )\n                        Spacer(Modifier.height(4.dp))\n                        Text(\n                            text = appTitleLabel,\n                            style = MaterialTheme.typography.bodyMedium,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant\n                        )\n                    }\n                }\n            }\n        }\n\n        item(visible = currentAppTitle == \"custom\") {\n            ExpressiveCard(flat = flat, onClick = { showCustomAppTitleDialog.value = true }) {\n                Row(\n                    modifier = Modifier.fillMaxWidth().padding(16.dp),\n                    verticalAlignment = Alignment.CenterVertically\n                ) {\n                    Icon(imageVector = Icons.Filled.Edit, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                    Spacer(Modifier.width(16.dp))\n                    Column {\n                        Text(\n                            text = customAppTitleTitle,\n                            style = MaterialTheme.typography.titleMedium,\n                            color = MaterialTheme.colorScheme.onSurface,\n                            fontWeight = FontWeight.SemiBold\n                        )\n                        Spacer(Modifier.height(4.dp))\n                        Text(\n                            text = currentCustomAppTitle ?: \"\",\n                            style = MaterialTheme.typography.bodyMedium,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant\n                        )\n                    }\n                }\n            }\n        }\n\n        item {\n            ExpressiveCard(flat = flat, onClick = { showDesktopAppNameDialog.value = true }) {\n                Row(\n                    modifier = Modifier.fillMaxWidth().padding(16.dp),\n                    verticalAlignment = Alignment.CenterVertically\n                ) {\n                    Icon(imageVector = Icons.Filled.PhoneAndroid, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                    Spacer(Modifier.width(16.dp))\n                    Column {\n                        Text(\n                            text = desktopAppNameTitle,\n                            style = MaterialTheme.typography.titleMedium,\n                            color = MaterialTheme.colorScheme.onSurface,\n                            fontWeight = FontWeight.SemiBold\n                        )\n                        Spacer(Modifier.height(4.dp))\n                        Text(\n                            text = currentDesktopAppName.toString(),\n                            style = MaterialTheme.typography.bodyMedium,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant\n                        )\n                    }\n                }\n            }\n        }\n\n        item {\n            ExpressiveCard(flat = flat, onClick = { showDpiDialog.value = true }) {\n                Row(\n                    modifier = Modifier.fillMaxWidth().padding(16.dp),\n                    verticalAlignment = Alignment.CenterVertically\n                ) {\n                    Icon(imageVector = Icons.Filled.FormatSize, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                    Spacer(Modifier.width(16.dp))\n                    Column {\n                        Text(\n                            text = dpiTitle,\n                            style = MaterialTheme.typography.titleMedium,\n                            color = MaterialTheme.colorScheme.onSurface,\n                            fontWeight = FontWeight.SemiBold\n                        )\n                        Spacer(Modifier.height(4.dp))\n                        Text(\n                            text = dpiValue,\n                            style = MaterialTheme.typography.bodyMedium,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant\n                        )\n                    }\n                }\n            }\n        }\n\n        item {\n            ExpressiveCard(flat = flat, onClick = {\n                scope.launch {\n                    val bugreport = loadingDialog.withLoading {\n                        withContext(Dispatchers.IO) {\n                            getBugreportFile(context)\n                        }\n                    }\n\n                    val uri: Uri = FileProvider.getUriForFile(\n                        context,\n                        \"${BuildConfig.APPLICATION_ID}.fileprovider\",\n                        bugreport\n                    )\n\n                    val shareIntent = Intent(Intent.ACTION_SEND).apply {\n                        putExtra(Intent.EXTRA_STREAM, uri)\n                        type = \"application/gzip\"\n                        clipData = android.content.ClipData.newRawUri(null, uri)\n                        addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)\n                    }\n\n                    context.startActivity(\n                        Intent.createChooser(\n                            shareIntent,\n                            context.getString(R.string.send_log)\n                        )\n                    )\n                }\n            }) {\n                Row(\n                    modifier = Modifier.fillMaxWidth().padding(16.dp),\n                    verticalAlignment = Alignment.CenterVertically\n                ) {\n                    Icon(imageVector = Icons.Filled.BugReport, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                    Spacer(Modifier.width(16.dp))\n                    Text(\n                        text = logTitle,\n                        style = MaterialTheme.typography.titleMedium,\n                        fontWeight = FontWeight.SemiBold,\n                        color = MaterialTheme.colorScheme.onSurface\n                    )\n                }\n            }\n        }\n    }\n\n    if (showUpdateDialog.value) {\n        UpdateDialog(\n            onDismiss = { showUpdateDialog.value = false },\n            onUpdate = {\n                showUpdateDialog.value = false\n                UpdateChecker.openUpdateUrl(context)\n            }\n        )\n    }\n\n    if (showResetSuPathDialog.value) {\n        ResetSUPathDialog(showResetSuPathDialog)\n    }\n\n    if (showSELinuxModeDialog.value) {\n        SELinuxModeDialog(\n            showDialog = showSELinuxModeDialog,\n            currentMode = currentSELinuxMode,\n            onModeChanged = onSELinuxModeChange\n        )\n    }\n\n    if (showAppTitleDialog.value) {\n        AppTitleChooseDialog(showAppTitleDialog) { newTitle ->\n            currentAppTitle = newTitle\n        }\n    }\n\n    if (showCustomAppTitleDialog.value) {\n        CustomAppTitleDialog(showCustomAppTitleDialog, snackBarHost)\n    }\n\n    if (showDesktopAppNameDialog.value) {\n        DesktopAppNameChooseDialog(showDesktopAppNameDialog)\n    }\n\n    if (showDpiDialog.value) {\n        DpiChooseDialog(showDpiDialog)\n    }\n\n    if (showFolkXAnimationTypeDialog.value) {\n        FolkXAnimationTypeDialog(showFolkXAnimationTypeDialog)\n    }\n\n    if (showFolkXAnimationSpeedDialog.value) {\n        FolkXAnimationSpeedDialog(showFolkXAnimationSpeedDialog)\n    }\n\n    if (showAppListLoadingSchemeDialog.value) {\n        AppListLoadingSchemeDialog(showAppListLoadingSchemeDialog)\n    }\n\n    if (showNewAppProfileModeDialog.value) {\n        NewAppProfileModeDialog(showNewAppProfileModeDialog) { mode ->\n            newAppProfileMode = mode\n            prefs.edit { putInt(APApplication.PREF_AUTO_EXCLUDE_NEW_APPS, mode) }\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun NewAppProfileModeDialog(\n    showDialog: MutableState<Boolean>,\n    onModeChanged: (Int) -> Unit,\n) {\n    val context = LocalContext.current\n    val currentMode = remember { mutableIntStateOf(Natives.getNewAppProfileMode()) }\n    val options = listOf(\n        0 to R.string.settings_new_app_profile_normal,\n        1 to R.string.settings_new_app_profile_root,\n        2 to R.string.settings_new_app_profile_exclude,\n    )\n\n    BasicAlertDialog(\n        onDismissRequest = { showDialog.value = false },\n        properties = DialogProperties(\n            decorFitsSystemWindows = true,\n            usePlatformDefaultWidth = false,\n        )\n    ) {\n        Surface(\n            modifier = Modifier\n                .width(310.dp)\n                .wrapContentHeight(),\n            shape = RoundedCornerShape(30.dp),\n            tonalElevation = AlertDialogDefaults.TonalElevation,\n            color = AlertDialogDefaults.containerColor,\n        ) {\n            Column(modifier = Modifier.padding(24.dp)) {\n                Text(\n                    text = stringResource(R.string.settings_new_app_profile_mode),\n                    style = MaterialTheme.typography.headlineSmall,\n                    modifier = Modifier.padding(bottom = 16.dp)\n                )\n\n                Surface(\n                    shape = RoundedCornerShape(12.dp),\n                    color = AlertDialogDefaults.containerColor,\n                    tonalElevation = 2.dp\n                ) {\n                    Column {\n                        options.forEach { (mode, labelId) ->\n                            ListItem(\n                                headlineContent = { Text(stringResource(labelId)) },\n                                leadingContent = {\n                                    RadioButton(\n                                        selected = currentMode.intValue == mode,\n                                        onClick = null\n                                    )\n                                },\n                                modifier = Modifier.clickable {\n                                    val result = Natives.setNewAppProfileMode(mode)\n                                    if (result == 0L) {\n                                        currentMode.intValue = mode\n                                        onModeChanged(mode)\n                                        showDialog.value = false\n                                    } else {\n                                        showToast(context, \"New app profile update failed: $result\")\n                                    }\n                                }\n                            )\n                        }\n                    }\n                }\n\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(top = 24.dp),\n                    horizontalArrangement = Arrangement.End\n                ) {\n                    TextButton(onClick = { showDialog.value = false }) {\n                        Text(stringResource(id = android.R.string.cancel))\n                    }\n                }\n            }\n            val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n            APDialogBlurBehindUtils.setupWindowBlurListener(dialogWindowProvider.window)\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun DpiChooseDialog(showDialog: MutableState<Boolean>) {\n    val context = LocalContext.current\n    val activity = context as? Activity\n\n    val savedDpi = DPIUtils.currentDpi\n    var tempDpi by remember { mutableIntStateOf(if (savedDpi == DPIUtils.DEFAULT_DPI) DPIUtils.systemDpi else savedDpi) }\n    var isSystemDefault by remember { mutableStateOf(savedDpi == DPIUtils.DEFAULT_DPI) }\n\n    BasicAlertDialog(\n        onDismissRequest = { showDialog.value = false },\n        properties = DialogProperties(\n            decorFitsSystemWindows = true,\n            usePlatformDefaultWidth = false,\n        )\n    ) {\n        Surface(\n            modifier = Modifier\n                .width(340.dp)\n                .wrapContentHeight(),\n            shape = RoundedCornerShape(30.dp),\n            tonalElevation = AlertDialogDefaults.TonalElevation,\n            color = AlertDialogDefaults.containerColor,\n        ) {\n            Column(modifier = Modifier.padding(24.dp)) {\n                Text(\n                    text = stringResource(id = R.string.settings_app_dpi),\n                    style = MaterialTheme.typography.headlineSmall,\n                    fontWeight = FontWeight.SemiBold,\n                )\n\n                Spacer(Modifier.height(16.dp))\n\n                // System default toggle\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .clip(RoundedCornerShape(12.dp))\n                        .background(\n                            if (isSystemDefault) MaterialTheme.colorScheme.primaryContainer\n                            else MaterialTheme.colorScheme.surfaceVariant\n                        )\n                        .clickable {\n                            isSystemDefault = !isSystemDefault\n                            if (isSystemDefault) {\n                                tempDpi = DPIUtils.systemDpi\n                            }\n                        }\n                        .padding(horizontal = 16.dp, vertical = 12.dp),\n                    verticalAlignment = Alignment.CenterVertically,\n                ) {\n                    Text(\n                        text = stringResource(id = R.string.system_default),\n                        style = MaterialTheme.typography.bodyMedium,\n                        fontWeight = FontWeight.SemiBold,\n                        color = if (isSystemDefault)\n                            MaterialTheme.colorScheme.onPrimaryContainer\n                        else\n                            MaterialTheme.colorScheme.onSurfaceVariant,\n                        modifier = Modifier.weight(1f),\n                    )\n                    if (isSystemDefault) {\n                        Icon(\n                            Icons.Filled.Check,\n                            contentDescription = null,\n                            tint = MaterialTheme.colorScheme.primary,\n                            modifier = Modifier.size(20.dp),\n                        )\n                    }\n                }\n\n                AnimatedVisibility(visible = !isSystemDefault) {\n                    Column {\n                        Spacer(Modifier.height(16.dp))\n\n                        // Slider\n                        val sliderValue by animateFloatAsState(\n                            targetValue = tempDpi.toFloat(),\n                            label = \"DpiSlider\",\n                        )\n                        Slider(\n                            value = sliderValue,\n                            onValueChange = { newValue ->\n                                tempDpi = newValue.toInt()\n                            },\n                            valueRange = DPIUtils.DPI_MIN.toFloat()..DPIUtils.DPI_MAX.toFloat(),\n                            steps = 10,\n                            colors = SliderDefaults.colors(\n                                thumbColor = MaterialTheme.colorScheme.primary,\n                                activeTrackColor = MaterialTheme.colorScheme.primary,\n                            ),\n                        )\n\n                        // Current value display\n                        Text(\n                            text = \"${DPIUtils.getDpiFriendlyName(tempDpi)} ($tempDpi DPI)\",\n                            style = MaterialTheme.typography.bodyMedium,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant,\n                            modifier = Modifier.padding(bottom = 12.dp),\n                        )\n\n                        // Preset pills\n                        Row(\n                            modifier = Modifier\n                                .fillMaxWidth()\n                                .padding(bottom = 8.dp),\n                            horizontalArrangement = Arrangement.spacedBy(6.dp),\n                        ) {\n                            DPIUtils.presets.forEach { preset ->\n                                val isSelected = tempDpi == preset.value\n                                Box(\n                                    modifier = Modifier\n                                        .weight(1f)\n                                        .clip(RoundedCornerShape(8.dp))\n                                        .background(\n                                            if (isSelected) MaterialTheme.colorScheme.primaryContainer\n                                            else MaterialTheme.colorScheme.surfaceVariant\n                                        )\n                                        .clickable { tempDpi = preset.value }\n                                        .padding(vertical = 8.dp, horizontal = 4.dp),\n                                    contentAlignment = Alignment.Center,\n                                ) {\n                                    Text(\n                                        text = preset.name,\n                                        style = MaterialTheme.typography.labelMedium,\n                                        color = if (isSelected)\n                                            MaterialTheme.colorScheme.onPrimaryContainer\n                                        else MaterialTheme.colorScheme.onSurfaceVariant,\n                                        maxLines = 1,\n                                        overflow = TextOverflow.Ellipsis,\n                                    )\n                                }\n                            }\n                        }\n                    }\n                }\n\n                Spacer(Modifier.height(16.dp))\n\n                // Apply button\n                Row(\n                    modifier = Modifier.fillMaxWidth(),\n                    horizontalArrangement = Arrangement.End,\n                ) {\n                    TextButton(onClick = { showDialog.value = false }) {\n                        Text(stringResource(android.R.string.cancel))\n                    }\n                    Spacer(Modifier.width(8.dp))\n                    Button(\n                        onClick = {\n                            val finalDpi = if (isSystemDefault) DPIUtils.DEFAULT_DPI else tempDpi\n                            showDialog.value = false\n                            DPIUtils.setDpi(context, finalDpi)\n                            activity?.recreate()\n                        },\n                    ) {\n                        Icon(\n                            Icons.Filled.Check,\n                            contentDescription = null,\n                            modifier = Modifier.size(18.dp),\n                        )\n                        Spacer(Modifier.width(8.dp))\n                        Text(stringResource(R.string.dpi_apply_settings))\n                    }\n                }\n            }\n\n            val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n            APDialogBlurBehindUtils.setupWindowBlurListener(dialogWindowProvider.window)\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun SELinuxModeDialog(\n    showDialog: MutableState<Boolean>,\n    currentMode: String,\n    onModeChanged: (String) -> Unit\n) {\n    val context = LocalContext.current\n    var selectedMode by remember { mutableStateOf(currentMode) }\n\n    BasicAlertDialog(\n        onDismissRequest = { showDialog.value = false },\n        properties = DialogProperties(\n            decorFitsSystemWindows = true,\n            usePlatformDefaultWidth = false,\n        )\n    ) {\n        Surface(\n            modifier = Modifier\n                .width(310.dp)\n                .wrapContentHeight(),\n            shape = RoundedCornerShape(30.dp),\n            tonalElevation = AlertDialogDefaults.TonalElevation,\n            color = AlertDialogDefaults.containerColor,\n        ) {\n            Column(modifier = Modifier.padding(24.dp)) {\n                Text(\n                    text = stringResource(R.string.settings_selinux_mode),\n                    style = MaterialTheme.typography.headlineSmall,\n                    modifier = Modifier.padding(bottom = 16.dp)\n                )\n\n                Surface(\n                    shape = RoundedCornerShape(12.dp),\n                    color = AlertDialogDefaults.containerColor,\n                    tonalElevation = 2.dp\n                ) {\n                    Column {\n                        ListItem(\n                            headlineContent = { Text(stringResource(R.string.settings_selinux_mode_enforcing)) },\n                            supportingContent = {\n                                Text(\n                                    text = stringResource(R.string.settings_selinux_mode_enforcing_summary),\n                                    style = MaterialTheme.typography.bodyMedium,\n                                    color = MaterialTheme.colorScheme.outline\n                                )\n                            },\n                            leadingContent = {\n                                RadioButton(\n                                    selected = selectedMode == \"Enforcing\",\n                                    onClick = { selectedMode = \"Enforcing\" }\n                                )\n                            },\n                            modifier = Modifier.clickable { selectedMode = \"Enforcing\" }\n                        )\n\n                        ListItem(\n                            headlineContent = { Text(stringResource(R.string.settings_selinux_mode_permissive)) },\n                            supportingContent = {\n                                Text(\n                                    text = stringResource(R.string.settings_selinux_mode_permissive_summary),\n                                    style = MaterialTheme.typography.bodyMedium,\n                                    color = MaterialTheme.colorScheme.outline\n                                )\n                            },\n                            leadingContent = {\n                                RadioButton(\n                                    selected = selectedMode == \"Permissive\",\n                                    onClick = { selectedMode = \"Permissive\" }\n                                )\n                            },\n                            modifier = Modifier.clickable { selectedMode = \"Permissive\" }\n                        )\n                    }\n                }\n\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(top = 24.dp),\n                    horizontalArrangement = Arrangement.End\n                ) {\n                    TextButton(onClick = { showDialog.value = false }) {\n                        Text(stringResource(id = android.R.string.cancel))\n                    }\n\n                    Button(\n                        onClick = {\n                            val success = setSELinuxMode(selectedMode == \"Enforcing\")\n                            if (success) {\n                                onModeChanged(selectedMode)\n                            }\n                            showDialog.value = false\n                        },\n                        enabled = selectedMode != currentMode\n                    ) {\n                        Text(stringResource(id = android.R.string.ok))\n                    }\n                }\n            }\n            val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n            APDialogBlurBehindUtils.setupWindowBlurListener(dialogWindowProvider.window)\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun AppTitleChooseDialog(showDialog: MutableState<Boolean>, onTitleChanged: (String) -> Unit = {}) {\n    val prefs = APApplication.sharedPreferences\n    val currentTitle = remember { prefs.getString(\"app_title\", \"folkpatch\") }\n    val titles = listOf(\n        \"custom\" to stringResource(R.string.app_title_custom),\n        \"fpatch\" to stringResource(R.string.app_title_fpatch),\n        \"apatch_folk\" to stringResource(R.string.app_title_apatch_folk),\n        \"apatchx\" to stringResource(R.string.app_title_apatchx),\n        \"apatch\" to stringResource(R.string.app_title_apatch),\n        \"folkpatch\" to stringResource(R.string.app_title_folkpatch),\n        \"kernelpatch\" to stringResource(R.string.app_title_kernelpatch),\n        \"kernelsu\" to stringResource(R.string.app_title_kernelsu),\n        \"supersu\" to stringResource(R.string.app_title_supersu),\n        \"folksu\" to stringResource(R.string.app_title_fpatch),\n        \"superuser\" to stringResource(R.string.app_title_superuser),\n        \"superpatch\" to stringResource(R.string.app_title_superpatch),\n        \"magicpatch\" to stringResource(R.string.app_title_magicpatch)\n    )\n\n    BasicAlertDialog(\n        onDismissRequest = { showDialog.value = false }, properties = DialogProperties(\n            decorFitsSystemWindows = true,\n            usePlatformDefaultWidth = false,\n        )\n    ) {\n        Surface(\n            modifier = Modifier\n                .width(310.dp)\n                .wrapContentHeight(),\n            shape = RoundedCornerShape(30.dp),\n            tonalElevation = AlertDialogDefaults.TonalElevation,\n            color = AlertDialogDefaults.containerColor,\n        ) {\n            LazyColumn {\n                items(titles.size) { index ->\n                    val (key, displayName) = titles[index]\n                    ListItem(\n                        headlineContent = { Text(text = displayName) },\n                        modifier = Modifier.clickable {\n                            showDialog.value = false\n                            prefs.edit { putString(\"app_title\", key) }\n                            onTitleChanged(key)\n                        },\n                        trailingContent = {\n                            if (currentTitle == key) {\n                                Icon(Icons.Filled.Check, contentDescription = null)\n                            }\n                        }\n                    )\n                }\n            }\n\n            val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n            APDialogBlurBehindUtils.setupWindowBlurListener(dialogWindowProvider.window)\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun CustomAppTitleDialog(showDialog: MutableState<Boolean>, snackBarHost: SnackbarHostState) {\n    val prefs = APApplication.sharedPreferences\n    var customTitle by remember {\n        mutableStateOf(prefs.getString(\"custom_app_title\", \"FolkPatch\") ?: \"FolkPatch\")\n    }\n\n    BasicAlertDialog(\n        onDismissRequest = { showDialog.value = false }, properties = DialogProperties(\n            decorFitsSystemWindows = true,\n            usePlatformDefaultWidth = false,\n        )\n    ) {\n        Surface(\n            modifier = Modifier\n                .width(310.dp)\n                .wrapContentHeight()\n                .padding(24.dp),\n            shape = RoundedCornerShape(30.dp),\n            tonalElevation = AlertDialogDefaults.TonalElevation,\n            color = AlertDialogDefaults.containerColor,\n        ) {\n            Column(\n                modifier = Modifier.padding(16.dp),\n                horizontalAlignment = Alignment.CenterHorizontally,\n                verticalArrangement = Arrangement.spacedBy(16.dp)\n            ) {\n                Text(\n                    text = stringResource(R.string.custom_app_title_dialog_title),\n                    style = MaterialTheme.typography.titleMedium\n                )\n                OutlinedTextField(\n                    value = customTitle,\n                    onValueChange = { customTitle = it },\n                    placeholder = { Text(stringResource(R.string.custom_app_title_dialog_hint)) },\n                    singleLine = true,\n                    modifier = Modifier.fillMaxWidth()\n                )\n                Row(\n                    modifier = Modifier.fillMaxWidth(),\n                    horizontalArrangement = Arrangement.End\n                ) {\n                    TextButton(onClick = { showDialog.value = false }) {\n                        Text(stringResource(R.string.cancel))\n                    }\n                    TextButton(onClick = {\n                        val trimmed = customTitle.trim()\n                        if (trimmed.isEmpty()) {\n                            showDialog.value = false\n                            return@TextButton\n                        }\n                        prefs.edit { putString(\"custom_app_title\", trimmed) }\n                        showDialog.value = false\n                    }) {\n                        Text(stringResource(R.string.custom_app_title_dialog_confirm))\n                    }\n                }\n            }\n\n            val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n            APDialogBlurBehindUtils.setupWindowBlurListener(dialogWindowProvider.window)\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun DesktopAppNameChooseDialog(showDialog: MutableState<Boolean>) {\n    val prefs = APApplication.sharedPreferences\n    val context = LocalContext.current\n    val currentName = remember { prefs.getString(\"desktop_app_name\", \"FolkPatch\") }\n    BasicAlertDialog(\n        onDismissRequest = { showDialog.value = false }, properties = DialogProperties(\n            decorFitsSystemWindows = true,\n            usePlatformDefaultWidth = false,\n        )\n    ) {\n        Surface(\n            modifier = Modifier\n                .width(310.dp)\n                .wrapContentHeight(),\n            shape = RoundedCornerShape(30.dp),\n            tonalElevation = AlertDialogDefaults.TonalElevation,\n            color = AlertDialogDefaults.containerColor,\n        ) {\n            LazyColumn {\n                item {\n                    ListItem(\n                        headlineContent = { Text(text = \"FolkPatch\") },\n                        modifier = Modifier.clickable {\n                            showDialog.value = false\n                            prefs.edit {\n                                putString(\"desktop_app_name\", \"FolkPatch\")\n                            }\n                            LauncherIconUtils.applySaved(context)\n                        },\n                        trailingContent = {\n                            if (currentName == \"FolkPatch\" || currentName == null) {\n                                Icon(Icons.Filled.Check, contentDescription = null)\n                            }\n                        }\n                    )\n                }\n                item {\n                    ListItem(\n                        headlineContent = { Text(text = \"FPatch\") },\n                        modifier = Modifier.clickable {\n                            showDialog.value = false\n                            prefs.edit {\n                                putString(\"desktop_app_name\", \"FPatch\")\n                            }\n                            LauncherIconUtils.applySaved(context)\n                        },\n                        trailingContent = {\n                            if (currentName == \"FPatch\") {\n                                Icon(Icons.Filled.Check, contentDescription = null)\n                            }\n                        }\n                    )\n                }\n            }\n\n            val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n            APDialogBlurBehindUtils.setupWindowBlurListener(dialogWindowProvider.window)\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun FolkXAnimationTypeDialog(showDialog: MutableState<Boolean>) {\n    val prefs = APApplication.sharedPreferences\n\n    BasicAlertDialog(\n        onDismissRequest = { showDialog.value = false }, properties = DialogProperties(\n            decorFitsSystemWindows = true,\n            usePlatformDefaultWidth = false,\n        )\n    ) {\n        Surface(\n            modifier = Modifier\n                .width(310.dp)\n                .wrapContentHeight(),\n            shape = RoundedCornerShape(30.dp),\n            tonalElevation = AlertDialogDefaults.TonalElevation,\n            color = AlertDialogDefaults.containerColor,\n        ) {\n            Column(modifier = Modifier.padding(24.dp)) {\n                Text(\n                    text = stringResource(R.string.settings_folkx_animation_type),\n                    style = MaterialTheme.typography.headlineSmall,\n                    modifier = Modifier.padding(bottom = 16.dp)\n                )\n\n                val currentType = remember { prefs.getString(\"folkx_animation_type\", \"linear\") }\n\n                Surface(\n                    shape = RoundedCornerShape(12.dp),\n                    color = AlertDialogDefaults.containerColor,\n                    tonalElevation = 2.dp\n                ) {\n                    Column {\n                        listOf(\"linear\", \"spatial\", \"fade\", \"vertical\", \"diagonal\").forEach { type ->\n                            val labelId = when (type) {\n                                \"linear\" -> R.string.settings_folkx_animation_linear\n                                \"spatial\" -> R.string.settings_folkx_animation_spatial\n                                \"fade\" -> R.string.settings_folkx_animation_fade\n                                \"vertical\" -> R.string.settings_folkx_animation_vertical\n                                \"diagonal\" -> R.string.settings_folkx_animation_diagonal\n                                else -> R.string.settings_folkx_animation_linear\n                            }\n                            ListItem(\n                                headlineContent = { Text(stringResource(labelId)) },\n                                leadingContent = {\n                                    RadioButton(\n                                        selected = currentType == type,\n                                        onClick = null\n                                    )\n                                },\n                                modifier = Modifier.clickable {\n                                    prefs.edit().putString(\"folkx_animation_type\", type).apply()\n                                    showDialog.value = false\n                                }\n                            )\n                        }\n                    }\n                }\n\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(top = 24.dp),\n                    horizontalArrangement = Arrangement.End\n                ) {\n                    TextButton(onClick = { showDialog.value = false }) {\n                        Text(stringResource(id = android.R.string.cancel))\n                    }\n                }\n            }\n            val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n            APDialogBlurBehindUtils.setupWindowBlurListener(dialogWindowProvider.window)\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun AppListLoadingSchemeDialog(showDialog: MutableState<Boolean>) {\n    val prefs = APApplication.sharedPreferences\n\n    BasicAlertDialog(\n        onDismissRequest = { showDialog.value = false }, properties = DialogProperties(\n            decorFitsSystemWindows = true,\n            usePlatformDefaultWidth = false,\n        )\n    ) {\n        Surface(\n            modifier = Modifier\n                .width(310.dp)\n                .wrapContentHeight(),\n            shape = RoundedCornerShape(30.dp),\n            tonalElevation = AlertDialogDefaults.TonalElevation,\n            color = AlertDialogDefaults.containerColor,\n        ) {\n            Column(modifier = Modifier.padding(24.dp)) {\n                Text(\n                    text = stringResource(R.string.settings_app_list_loading_scheme),\n                    style = MaterialTheme.typography.headlineSmall,\n                    modifier = Modifier.padding(bottom = 16.dp)\n                )\n\n                val currentScheme = remember { prefs.getString(\"app_list_loading_scheme\", \"root_service\") }\n\n                Surface(\n                    shape = RoundedCornerShape(12.dp),\n                    color = AlertDialogDefaults.containerColor,\n                    tonalElevation = 2.dp\n                ) {\n                    Column {\n                        val schemes = listOf(\n                            \"root_service\" to R.string.app_list_loading_scheme_root_service,\n                            \"package_manager\" to R.string.app_list_loading_scheme_package_manager\n                        )\n\n                        schemes.forEach { (scheme, labelId) ->\n                            ListItem(\n                                headlineContent = { Text(stringResource(labelId)) },\n                                leadingContent = {\n                                    RadioButton(\n                                        selected = currentScheme == scheme,\n                                        onClick = null\n                                    )\n                                },\n                                modifier = Modifier.clickable {\n                                    prefs.edit { putString(\"app_list_loading_scheme\", scheme) }\n                                    showDialog.value = false\n                                }\n                            )\n                        }\n                    }\n                }\n\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(top = 24.dp),\n                    horizontalArrangement = Arrangement.End\n                ) {\n                    TextButton(onClick = { showDialog.value = false }) {\n                        Text(stringResource(id = android.R.string.cancel))\n                    }\n                }\n            }\n            val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n            APDialogBlurBehindUtils.setupWindowBlurListener(dialogWindowProvider.window)\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun ResetSUPathDialog(showDialog: MutableState<Boolean>) {\n    val context = LocalContext.current\n    var suPath by remember { mutableStateOf(me.bmax.apatch.Natives.suPath()) }\n    BasicAlertDialog(\n        onDismissRequest = { showDialog.value = false }, properties = DialogProperties(\n            decorFitsSystemWindows = true,\n            usePlatformDefaultWidth = false,\n        )\n    ) {\n        Surface(\n            modifier = Modifier\n                .width(310.dp)\n                .wrapContentHeight(),\n            shape = RoundedCornerShape(30.dp),\n            tonalElevation = AlertDialogDefaults.TonalElevation,\n            color = AlertDialogDefaults.containerColor,\n        ) {\n            Column(modifier = Modifier.padding(PaddingValues(all = 24.dp))) {\n                Box(\n                    Modifier\n                        .padding(PaddingValues(bottom = 16.dp))\n                        .align(Alignment.Start)\n                ) {\n                    Text(\n                        text = stringResource(id = R.string.setting_reset_su_path),\n                        style = MaterialTheme.typography.headlineSmall\n                    )\n                }\n                Box(\n                    Modifier\n                        .weight(weight = 1f, fill = false)\n                        .padding(PaddingValues(bottom = 12.dp))\n                        .align(Alignment.Start)\n                ) {\n                    OutlinedTextField(\n                        value = suPath,\n                        onValueChange = {\n                            suPath = it\n                        },\n                        label = { Text(stringResource(id = R.string.setting_reset_su_new_path)) },\n                        visualTransformation = androidx.compose.ui.text.input.VisualTransformation.None,\n                    )\n                }\n\n                Row(\n                    modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End\n                ) {\n                    TextButton(onClick = { showDialog.value = false }) {\n\n                        Text(stringResource(id = android.R.string.cancel))\n                    }\n\n                    Button(enabled = suPath.startsWith(\"/\") && suPath.trim().length > 1, onClick = {\n                        showDialog.value = false\n                        val success = me.bmax.apatch.Natives.resetSuPath(suPath)\n                        showToast(context, if (success) R.string.success else R.string.failure)\n                        rootShellForResult(\"echo $suPath > ${APApplication.SU_PATH_FILE}\")\n                    }) {\n                        Text(stringResource(id = android.R.string.ok))\n                    }\n                }\n            }\n            val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n            APDialogBlurBehindUtils.setupWindowBlurListener(dialogWindowProvider.window)\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun FolkXAnimationSpeedDialog(showDialog: MutableState<Boolean>) {\n    val prefs = APApplication.sharedPreferences\n\n    BasicAlertDialog(\n        onDismissRequest = { showDialog.value = false }, properties = DialogProperties(\n            decorFitsSystemWindows = true,\n            usePlatformDefaultWidth = false,\n        )\n    ) {\n        Surface(\n            modifier = Modifier\n                .width(310.dp)\n                .wrapContentHeight(),\n            shape = RoundedCornerShape(30.dp),\n            tonalElevation = AlertDialogDefaults.TonalElevation,\n            color = AlertDialogDefaults.containerColor,\n        ) {\n            Column(modifier = Modifier.padding(24.dp)) {\n                Text(\n                    text = stringResource(R.string.settings_folkx_animation_speed),\n                    style = MaterialTheme.typography.headlineSmall,\n                    modifier = Modifier.padding(bottom = 16.dp)\n                )\n\n                val currentSpeed = remember { prefs.getFloat(\"folkx_animation_speed\", 1.0f) }\n\n                Surface(\n                    shape = RoundedCornerShape(12.dp),\n                    color = AlertDialogDefaults.containerColor,\n                    tonalElevation = 2.dp\n                ) {\n                    Column {\n                        val speeds = listOf(\n                            0.5f to \"0.5x\",\n                            0.75f to \"0.75x\",\n                            1.0f to \"1.0x\",\n                            1.25f to \"1.25x\",\n                            1.5f to \"1.5x\",\n                            2.0f to \"2.0x\"\n                        )\n\n                        speeds.forEach { (speed, label) ->\n                            ListItem(\n                                headlineContent = { Text(label) },\n                                leadingContent = {\n                                    RadioButton(\n                                        selected = currentSpeed == speed,\n                                        onClick = null\n                                    )\n                                },\n                                modifier = Modifier.clickable {\n                                    prefs.edit().putFloat(\"folkx_animation_speed\", speed).apply()\n                                    showDialog.value = false\n                                }\n                            )\n                        }\n                    }\n                }\n\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(top = 24.dp),\n                    horizontalArrangement = Arrangement.End\n                ) {\n                    TextButton(onClick = { showDialog.value = false }) {\n                        Text(stringResource(id = android.R.string.cancel))\n                    }\n                }\n            }\n            val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n            APDialogBlurBehindUtils.setupWindowBlurListener(dialogWindowProvider.window)\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/GeneralSettingsScreen.kt",
    "content": "package me.bmax.apatch.ui.screen.settings\n\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.outlined.ArrowBack\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.SnackbarHost\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.livedata.observeAsState\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.input.nestedscroll.nestedScroll\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.theme.BackgroundConfig\nimport me.bmax.apatch.util.getSELinuxMode\nimport me.bmax.apatch.util.isGlobalNamespaceEnabled as checkGlobalNamespaceEnabled\nimport me.bmax.apatch.util.isMagicMountEnabled as checkMagicMountEnabled\nimport me.bmax.apatch.util.ui.LocalSnackbarHost\nimport me.bmax.apatch.util.ui.NavigationBarsSpacer\n\n@Destination<RootGraph>\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun GeneralSettingsScreen(navigator: DestinationsNavigator) {\n    val state by APApplication.apStateLiveData.observeAsState(APApplication.State.UNKNOWN_STATE)\n    val kPatchReady = state != APApplication.State.UNKNOWN_STATE\n    val aPatchReady = (state == APApplication.State.ANDROIDPATCH_INSTALLING || state == APApplication.State.ANDROIDPATCH_INSTALLED || state == APApplication.State.ANDROIDPATCH_NEED_UPDATE)\n\n    var isGlobalNamespaceEnabled by rememberSaveable { mutableStateOf(false) }\n    var isMagicMountEnabled by rememberSaveable { mutableStateOf(false) }\n    var currentSELinuxMode by rememberSaveable { mutableStateOf(\"Unknown\") }\n\n    LaunchedEffect(kPatchReady, aPatchReady) {\n        if (kPatchReady && aPatchReady) {\n            withContext(Dispatchers.IO) {\n                isGlobalNamespaceEnabled = checkGlobalNamespaceEnabled()\n                isMagicMountEnabled = checkMagicMountEnabled()\n                currentSELinuxMode = getSELinuxMode()\n            }\n        }\n    }\n\n    val snackBarHost = LocalSnackbarHost.current\n    val flat = BackgroundConfig.isCustomBackgroundEnabled || BackgroundConfig.settingsBackgroundUri != null\n    val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()\n\n    Scaffold(\n        topBar = {\n            TopAppBar(\n                title = { Text(stringResource(R.string.settings_category_general), style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.SemiBold) },\n                navigationIcon = {\n                    IconButton(onClick = { navigator.popBackStack() }) {\n                        Icon(Icons.AutoMirrored.Outlined.ArrowBack, contentDescription = null)\n                    }\n                },\n                scrollBehavior = scrollBehavior,\n            )\n        },\n        containerColor = Color.Transparent,\n        snackbarHost = { SnackbarHost(snackBarHost) },\n    ) { paddingValues ->\n        LazyColumn(\n            modifier = Modifier.padding(paddingValues).nestedScroll(scrollBehavior.nestedScrollConnection),\n            verticalArrangement = Arrangement.spacedBy(8.dp),\n        ) {\n            item { Spacer(Modifier.height(8.dp)) }\n            item {\n                GeneralSettingsContent(\n                    kPatchReady = kPatchReady,\n                    aPatchReady = aPatchReady,\n                    currentSELinuxMode = currentSELinuxMode,\n                    onSELinuxModeChange = { currentSELinuxMode = it },\n                    isGlobalNamespaceEnabled = isGlobalNamespaceEnabled,\n                    onGlobalNamespaceChange = { isGlobalNamespaceEnabled = it },\n                    isMagicMountEnabled = isMagicMountEnabled,\n                    onMagicMountChange = { isMagicMountEnabled = it },\n                    snackBarHost = snackBarHost,\n                    flat = flat,\n                    navigator = navigator,\n                )\n            }\n            item { Spacer(Modifier.height(8.dp)) }\n            item { NavigationBarsSpacer() }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/LanguagePickerScreen.kt",
    "content": "package me.bmax.apatch.ui.screen.settings\n\nimport androidx.appcompat.app.AppCompatDelegate\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.outlined.ArrowBack\nimport androidx.compose.material.icons.filled.Check\nimport androidx.compose.material.icons.filled.Translate\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.input.nestedscroll.nestedScroll\nimport androidx.compose.ui.res.stringArrayResource\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport androidx.core.os.LocaleListCompat\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.component.splicedLazyColumnGroup\nimport me.bmax.apatch.ui.theme.BackgroundConfig\nimport me.bmax.apatch.util.ui.NavigationBarsSpacer\nimport java.util.Locale\n\n@Destination<RootGraph>\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun LanguagePickerScreen(navigator: DestinationsNavigator) {\n    val languages = stringArrayResource(id = R.array.languages)\n    val languagesValues = stringArrayResource(id = R.array.languages_values)\n    val currentLocale = AppCompatDelegate.getApplicationLocales()[0]?.toLanguageTag()\n    val flat = BackgroundConfig.isCustomBackgroundEnabled || BackgroundConfig.settingsBackgroundUri != null\n    val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()\n    val languageList = remember { languages.toList() }\n\n    Scaffold(\n        topBar = {\n            TopAppBar(\n                title = {\n                    Text(\n                        stringResource(R.string.settings_app_language),\n                        style = MaterialTheme.typography.titleLarge,\n                        fontWeight = FontWeight.SemiBold,\n                    )\n                },\n                navigationIcon = {\n                    IconButton(onClick = { navigator.popBackStack() }) {\n                        Icon(Icons.AutoMirrored.Outlined.ArrowBack, contentDescription = null)\n                    }\n                },\n                scrollBehavior = scrollBehavior,\n            )\n        },\n        containerColor = Color.Transparent,\n    ) { paddingValues ->\n        LazyColumn(\n            modifier = Modifier\n                .padding(paddingValues)\n                .nestedScroll(scrollBehavior.nestedScrollConnection),\n            verticalArrangement = Arrangement.spacedBy(0.dp),\n        ) {\n            item { Spacer(Modifier.height(12.dp)) }\n\n            splicedLazyColumnGroup(\n                items = languageList,\n                key = { index, _ -> languagesValues[index] },\n            ) { index, item ->\n                val isSelected = if (index == 0) {\n                    currentLocale == null || currentLocale.isEmpty()\n                } else {\n                    currentLocale == languagesValues[index]\n                }\n\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .clickable {\n                            if (index == 0) {\n                                AppCompatDelegate.setApplicationLocales(\n                                    LocaleListCompat.getEmptyLocaleList()\n                                )\n                            } else {\n                                AppCompatDelegate.setApplicationLocales(\n                                    LocaleListCompat.forLanguageTags(languagesValues[index])\n                                )\n                            }\n                        }\n                        .padding(horizontal = 16.dp, vertical = 14.dp),\n                    verticalAlignment = Alignment.CenterVertically,\n                ) {\n                    if (index == 0) {\n                        Icon(\n                            imageVector = Icons.Filled.Translate,\n                            contentDescription = null,\n                            tint = MaterialTheme.colorScheme.onSurfaceVariant,\n                            modifier = Modifier.size(24.dp),\n                        )\n                    } else {\n                        Box(\n                            modifier = Modifier\n                                .size(32.dp)\n                                .background(\n                                    color = if (isSelected) MaterialTheme.colorScheme.primary\n                                    else MaterialTheme.colorScheme.surfaceContainerHigh,\n                                    shape = CircleShape,\n                                ),\n                            contentAlignment = Alignment.Center,\n                        ) {\n                            Text(\n                                text = getLanguageCode(languagesValues[index]),\n                                style = MaterialTheme.typography.labelSmall,\n                                fontWeight = FontWeight.Bold,\n                                color = if (isSelected) MaterialTheme.colorScheme.onPrimary\n                                else MaterialTheme.colorScheme.onSurfaceVariant,\n                            )\n                        }\n                    }\n                    Spacer(Modifier.width(16.dp))\n                    Text(\n                        text = item,\n                        style = MaterialTheme.typography.bodyLarge,\n                        fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal,\n                        color = if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface,\n                        modifier = Modifier.weight(1f),\n                    )\n                    if (isSelected) {\n                        Icon(\n                            imageVector = Icons.Filled.Check,\n                            contentDescription = null,\n                            tint = MaterialTheme.colorScheme.primary,\n                        )\n                    }\n                }\n            }\n\n            item { Spacer(Modifier.height(16.dp)) }\n            item { NavigationBarsSpacer() }\n        }\n    }\n}\n\nprivate fun getLanguageCode(tag: String): String = when {\n    tag.contains(\"-\") -> tag.substringBefore(\"-\").uppercase()\n    else -> tag.uppercase()\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/ModuleSettings.kt",
    "content": "package me.bmax.apatch.ui.screen.settings\n\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.Block\nimport androidx.compose.material.icons.filled.Dock\nimport androidx.compose.material.icons.filled.Download\nimport androidx.compose.material.icons.filled.Folder\nimport androidx.compose.material.icons.filled.Info\nimport androidx.compose.material.icons.filled.Sort\nimport androidx.compose.material.icons.filled.Update\nimport androidx.compose.material.icons.filled.ViewAgenda\nimport androidx.compose.runtime.*\nimport androidx.compose.ui.res.stringResource\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.component.SplicedColumnGroup\nimport me.bmax.apatch.ui.component.ToggleSettingCard\n\n@Composable\nfun ModuleSettingsContent(\n    aPatchReady: Boolean,\n    flat: Boolean = false,\n) {\n    val prefs = APApplication.sharedPreferences\n\n    val disableModuleUpdateCheckTitle = stringResource(id = R.string.settings_disable_module_update_check)\n    val disableModuleUpdateCheckSummary = stringResource(id = R.string.settings_disable_module_update_check_summary)\n\n    val moreInfoTitle = stringResource(id = R.string.settings_show_more_module_info)\n    val moreInfoSummary = stringResource(id = R.string.settings_show_more_module_info_summary)\n\n    val moduleSortOptimizationTitle = stringResource(id = R.string.settings_module_sort_optimization)\n    val moduleSortOptimizationSummary = stringResource(id = R.string.settings_module_sort_optimization_summary)\n\n    val foldSystemModuleTitle = stringResource(id = R.string.settings_fold_system_module)\n    val foldSystemModuleSummary = stringResource(id = R.string.settings_fold_system_module_summary)\n\n    val apmBatchInstallFullProcessTitle = stringResource(id = R.string.apm_batch_install_full_process)\n    val apmBatchInstallFullProcessSummary = stringResource(id = R.string.apm_batch_install_full_process_summary)\n\n    val simpleListBottomBarTitle = stringResource(id = R.string.settings_simple_list_bottom_bar)\n    val simpleListBottomBarSummary = stringResource(id = R.string.settings_simple_list_bottom_bar_summary)\n\n    val splicedCardGroupTitle = stringResource(id = R.string.settings_spliced_card_group)\n    val splicedCardGroupSummary = stringResource(id = R.string.settings_spliced_card_group_summary)\n\n    var disableModuleUpdateCheck by remember { mutableStateOf(prefs.getBoolean(\"disable_module_update_check\", false)) }\n    var showMoreModuleInfo by remember { mutableStateOf(prefs.getBoolean(\"show_more_module_info\", true)) }\n    var moduleSortOptimization by remember { mutableStateOf(prefs.getBoolean(\"module_sort_optimization\", true)) }\n    var foldSystemModule by remember { mutableStateOf(prefs.getBoolean(\"fold_system_module\", true)) }\n    var apmBatchInstallFullProcess by remember { mutableStateOf(prefs.getBoolean(\"apm_batch_install_full_process\", false)) }\n    var simpleListBottomBar by remember { mutableStateOf(prefs.getBoolean(\"simple_list_bottom_bar\", false)) }\n    var splicedCardGroup by remember { mutableStateOf(prefs.getBoolean(\"spliced_card_group\", true)) }\n\n    SplicedColumnGroup(flat = flat) {\n        item {\n            ToggleSettingCard(\n                icon = Icons.Filled.Update,\n                flat = flat,\n                title = disableModuleUpdateCheckTitle,\n                description = disableModuleUpdateCheckSummary,\n                checked = disableModuleUpdateCheck,\n                onCheckedChange = {\n                    disableModuleUpdateCheck = it\n                    prefs.edit().putBoolean(\"disable_module_update_check\", it).apply()\n                }\n            )\n        }\n\n        item {\n            ToggleSettingCard(\n                icon = Icons.Filled.Info,\n                flat = flat,\n                title = moreInfoTitle,\n                description = moreInfoSummary,\n                checked = showMoreModuleInfo,\n                onCheckedChange = {\n                    showMoreModuleInfo = it\n                    prefs.edit().putBoolean(\"show_more_module_info\", it).apply()\n                }\n            )\n        }\n\n        item {\n            ToggleSettingCard(\n                icon = Icons.Filled.Sort,\n                flat = flat,\n                title = moduleSortOptimizationTitle,\n                description = moduleSortOptimizationSummary,\n                checked = moduleSortOptimization,\n                onCheckedChange = {\n                    moduleSortOptimization = it\n                    prefs.edit().putBoolean(\"module_sort_optimization\", it).apply()\n                }\n            )\n        }\n\n        item {\n            ToggleSettingCard(\n                icon = Icons.Filled.Folder,\n                flat = flat,\n                title = foldSystemModuleTitle,\n                description = foldSystemModuleSummary,\n                checked = foldSystemModule,\n                onCheckedChange = {\n                    foldSystemModule = it\n                    prefs.edit().putBoolean(\"fold_system_module\", it).apply()\n                }\n            )\n        }\n\n        item {\n            ToggleSettingCard(\n                icon = Icons.Filled.Download,\n                flat = flat,\n                title = apmBatchInstallFullProcessTitle,\n                description = apmBatchInstallFullProcessSummary,\n                checked = apmBatchInstallFullProcess,\n                onCheckedChange = {\n                    apmBatchInstallFullProcess = it\n                    prefs.edit().putBoolean(\"apm_batch_install_full_process\", it).apply()\n                }\n            )\n        }\n\n        item {\n            ToggleSettingCard(\n                icon = Icons.Filled.Dock,\n                flat = flat,\n                title = simpleListBottomBarTitle,\n                description = simpleListBottomBarSummary,\n                checked = simpleListBottomBar,\n                onCheckedChange = {\n                    simpleListBottomBar = it\n                    prefs.edit().putBoolean(\"simple_list_bottom_bar\", it).apply()\n                }\n            )\n        }\n\n        item {\n            ToggleSettingCard(\n                icon = Icons.Filled.ViewAgenda,\n                flat = flat,\n                title = splicedCardGroupTitle,\n                description = splicedCardGroupSummary,\n                checked = splicedCardGroup,\n                onCheckedChange = {\n                    splicedCardGroup = it\n                    prefs.edit().putBoolean(\"spliced_card_group\", it).apply()\n                }\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/ModuleSettingsScreen.kt",
    "content": "package me.bmax.apatch.ui.screen.settings\n\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.outlined.ArrowBack\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.SnackbarHost\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.livedata.observeAsState\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.input.nestedscroll.nestedScroll\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.theme.BackgroundConfig\nimport me.bmax.apatch.util.ui.LocalSnackbarHost\nimport me.bmax.apatch.util.ui.NavigationBarsSpacer\n\n@Destination<RootGraph>\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun ModuleSettingsScreen(navigator: DestinationsNavigator) {\n    val state by APApplication.apStateLiveData.observeAsState(APApplication.State.UNKNOWN_STATE)\n    val aPatchReady = (state == APApplication.State.ANDROIDPATCH_INSTALLING || state == APApplication.State.ANDROIDPATCH_INSTALLED || state == APApplication.State.ANDROIDPATCH_NEED_UPDATE)\n\n    val snackBarHost = LocalSnackbarHost.current\n    val flat = BackgroundConfig.isCustomBackgroundEnabled || BackgroundConfig.settingsBackgroundUri != null\n    val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()\n\n    Scaffold(\n        topBar = {\n            TopAppBar(\n                title = { Text(stringResource(R.string.settings_category_module), style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.SemiBold) },\n                navigationIcon = {\n                    IconButton(onClick = { navigator.popBackStack() }) {\n                        Icon(Icons.AutoMirrored.Outlined.ArrowBack, contentDescription = null)\n                    }\n                },\n                scrollBehavior = scrollBehavior,\n            )\n        },\n        containerColor = Color.Transparent,\n        snackbarHost = { SnackbarHost(snackBarHost) },\n    ) { paddingValues ->\n        LazyColumn(\n            modifier = Modifier.padding(paddingValues).nestedScroll(scrollBehavior.nestedScrollConnection),\n            verticalArrangement = Arrangement.spacedBy(8.dp),\n        ) {\n            item { Spacer(Modifier.height(8.dp)) }\n            item {\n                ModuleSettingsContent(\n                    aPatchReady = aPatchReady,\n                    flat = flat,\n                )\n            }\n            item { Spacer(Modifier.height(8.dp)) }\n            item { NavigationBarsSpacer() }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/MultimediaSettings.kt",
    "content": "package me.bmax.apatch.ui.screen.settings\n\nimport android.content.ActivityNotFoundException\nimport android.net.Uri\nimport me.bmax.apatch.util.ui.showToast\nimport androidx.activity.compose.rememberLauncherForActivityResult\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.List\nimport androidx.compose.material.icons.filled.*\nimport androidx.compose.material3.*\nimport androidx.compose.runtime.*\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalView\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.window.DialogProperties\nimport androidx.compose.ui.window.DialogWindowProvider\nimport kotlinx.coroutines.launch\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.component.ExpressiveCard\nimport me.bmax.apatch.ui.component.SplicedColumnGroup\nimport me.bmax.apatch.ui.component.ToggleSettingCard\nimport me.bmax.apatch.ui.component.rememberConfirmDialog\nimport me.bmax.apatch.ui.component.rememberLoadingDialog\nimport me.bmax.apatch.ui.theme.MusicConfig\nimport me.bmax.apatch.ui.theme.SoundEffectConfig\nimport me.bmax.apatch.ui.theme.VibrationConfig\nimport me.bmax.apatch.util.MusicManager\nimport me.bmax.apatch.util.SoundEffectManager\nimport me.bmax.apatch.util.ui.APDialogBlurBehindUtils\n\n@Composable\nfun formatTime(millis: Int): String {\n    val seconds = (millis / 1000) % 60\n    val minutes = (millis / (1000 * 60)) % 60\n    return String.format(\"%02d:%02d\", minutes, seconds)\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun MultimediaSettingsContent(\n    snackBarHost: SnackbarHostState,\n    flat: Boolean = false,\n) {\n    val context = LocalContext.current\n    val scope = rememberCoroutineScope()\n    val loadingDialog = rememberLoadingDialog()\n\n    val pickMusicLauncher = rememberLauncherForActivityResult(\n        ActivityResultContracts.GetContent()\n    ) { uri: Uri? ->\n        uri?.let {\n            scope.launch {\n                loadingDialog.show()\n                val success = MusicConfig.saveMusicFile(context, it)\n                loadingDialog.hide()\n                if (success) {\n                    snackBarHost.showSnackbar(message = context.getString(R.string.settings_music_saved))\n                    MusicManager.reload()\n                } else {\n                    snackBarHost.showSnackbar(message = context.getString(R.string.settings_music_save_error))\n                }\n            }\n        }\n    }\n\n    val pickSoundEffectLauncher = rememberLauncherForActivityResult(\n        ActivityResultContracts.GetContent()\n    ) { uri: Uri? ->\n        uri?.let {\n            scope.launch {\n                loadingDialog.show()\n                val success = SoundEffectConfig.saveSoundEffectFile(context, it)\n                loadingDialog.hide()\n                if (success) {\n                    snackBarHost.showSnackbar(message = context.getString(R.string.settings_sound_effect_selected))\n                } else {\n                    snackBarHost.showSnackbar(message = \"Failed to save sound effect\")\n                }\n            }\n        }\n    }\n\n    val pickStartupSoundLauncher = rememberLauncherForActivityResult(\n        ActivityResultContracts.GetContent()\n    ) { uri: Uri? ->\n        uri?.let {\n            scope.launch {\n                loadingDialog.show()\n                val success = SoundEffectConfig.saveStartupSoundFile(context, it)\n                loadingDialog.hide()\n                if (success) {\n                    snackBarHost.showSnackbar(message = context.getString(R.string.settings_startup_sound_selected))\n                } else {\n                    snackBarHost.showSnackbar(message = \"Failed to save startup sound\")\n                }\n            }\n        }\n    }\n\n    val musicTitle = stringResource(id = R.string.settings_background_music)\n    val musicSummary = stringResource(id = R.string.settings_background_music_summary)\n    val musicEnabledText = stringResource(id = R.string.settings_background_music_enabled)\n    val musicPlayingText = if (MusicConfig.musicFilename != null) stringResource(id = R.string.settings_background_music_playing, MusicConfig.musicFilename!!) else \"\"\n\n    val selectMusicTitle = stringResource(id = R.string.settings_select_music_file)\n    val musicSelectedText = stringResource(id = R.string.settings_music_selected)\n\n    val autoPlayTitle = stringResource(id = R.string.settings_music_auto_play)\n    val autoPlaySummary = stringResource(id = R.string.settings_music_auto_play_summary)\n\n    val loopingTitle = stringResource(id = R.string.settings_music_looping)\n    val loopingSummary = stringResource(id = R.string.settings_music_looping_summary)\n\n    val musicVolumeTitle = stringResource(id = R.string.settings_music_volume)\n\n    val playbackControlTitle = stringResource(id = R.string.settings_music_playback_control)\n\n    val clearMusicTitle = stringResource(id = R.string.settings_clear_music)\n\n    val soundEffectTitle = stringResource(id = R.string.settings_sound_effect)\n    val soundEffectSummary = stringResource(id = R.string.settings_sound_effect_summary)\n    val soundEffectEnabledText = stringResource(id = R.string.settings_sound_effect_enabled)\n    val soundEffectPlayingText = if (SoundEffectConfig.soundEffectFilename != null) stringResource(id = R.string.settings_sound_effect_playing, SoundEffectConfig.soundEffectFilename!!) else \"\"\n\n    val selectSoundEffectTitle = stringResource(id = R.string.settings_select_sound_effect)\n    val soundEffectSelectedText = stringResource(id = R.string.settings_sound_effect_selected)\n\n    val soundEffectScopeTitle = stringResource(id = R.string.settings_sound_effect_scope)\n\n    val startupSoundTitle = stringResource(id = R.string.settings_startup_sound)\n    val startupSoundSummary = stringResource(id = R.string.settings_startup_sound_summary)\n    val startupSoundEnabledText = stringResource(id = R.string.settings_startup_sound_enabled)\n    val startupSoundPlayingText = if (SoundEffectConfig.startupSoundFilename != null) stringResource(id = R.string.settings_startup_sound_playing, SoundEffectConfig.startupSoundFilename!!) else \"\"\n\n    val selectStartupSoundTitle = stringResource(id = R.string.settings_select_startup_sound)\n    val startupSoundSelectedText = stringResource(id = R.string.settings_startup_sound_selected)\n\n    val vibrationTitle = stringResource(id = R.string.settings_vibration)\n    val vibrationSummary = stringResource(id = R.string.settings_vibration_summary)\n    val vibrationEnabledText = stringResource(id = R.string.settings_vibration_enabled)\n\n    val vibrationIntensityTitle = stringResource(id = R.string.settings_vibration_intensity)\n    val vibrationScopeTitle = stringResource(id = R.string.settings_vibration_scope)\n\n    // --- State variables for dialogs (must be declared before SplicedColumnGroup) ---\n\n    // Music playback state\n    val currentPosition by MusicManager.currentPosition.collectAsState(initial = 0)\n    val duration by MusicManager.duration.collectAsState(initial = 0)\n    val isPlaying by MusicManager.isPlaying.collectAsState(initial = false)\n\n    // Clear music dialog\n    val clearMusicDialog = rememberConfirmDialog(\n        onConfirm = {\n            MusicConfig.clearMusic(context)\n            MusicManager.stop()\n            scope.launch {\n                snackBarHost.showSnackbar(message = context.getString(R.string.settings_music_cleared))\n            }\n        }\n    )\n\n    // Sound effect source dialog\n    val soundEffectSourceTitle = stringResource(id = R.string.settings_sound_effect_source)\n    val soundEffectSourceLocal = stringResource(id = R.string.settings_sound_effect_source_local)\n    val soundEffectSourcePreset = stringResource(id = R.string.settings_sound_effect_source_preset)\n    var showSoundEffectSourceDialog by remember { mutableStateOf(false) }\n\n    // Sound effect preset dialog\n    val soundEffectPresetTitle = stringResource(id = R.string.settings_sound_effect_preset_title)\n    var showSoundEffectPresetDialog by remember { mutableStateOf(false) }\n\n    // Clear sound effect dialog\n    val clearSoundEffectTitle = stringResource(id = R.string.settings_clear_sound_effect)\n    val clearSoundEffectDialog = rememberConfirmDialog(\n        onConfirm = {\n            SoundEffectConfig.clearSoundEffect(context)\n            scope.launch {\n                snackBarHost.showSnackbar(message = context.getString(R.string.settings_sound_effect_cleared))\n            }\n        }\n    )\n\n    // Sound effect scope dialog\n    var showSoundEffectScopeDialog by remember { mutableStateOf(false) }\n\n    // Startup sound source dialog\n    val startupSourceTitle = stringResource(id = R.string.settings_sound_effect_source)\n    val startupSourceLocal = stringResource(id = R.string.settings_sound_effect_source_local)\n    val startupSourcePreset = stringResource(id = R.string.settings_sound_effect_source_preset)\n    var showStartupSourceDialog by remember { mutableStateOf(false) }\n\n    // Startup sound preset dialog\n    val startupPresetTitle = stringResource(id = R.string.settings_sound_effect_preset_title)\n    var showStartupPresetDialog by remember { mutableStateOf(false) }\n\n    // Clear startup sound dialog\n    val clearStartupSoundTitle = stringResource(id = R.string.settings_clear_startup_sound)\n    val clearStartupSoundDialog = rememberConfirmDialog(\n        onConfirm = {\n            SoundEffectConfig.clearStartupSound(context)\n            scope.launch {\n                snackBarHost.showSnackbar(message = context.getString(R.string.settings_startup_sound_cleared))\n            }\n        }\n    )\n\n    // Vibration scope dialog\n    var showVibrationScopeDialog by remember { mutableStateOf(false) }\n\n    SplicedColumnGroup(flat = flat) {\n\n        // --- Background Music Toggle ---\n        item {\n            ToggleSettingCard(\n                flat = flat,\n                icon = Icons.Filled.MusicNote,\n                title = musicTitle,\n                description = if (MusicConfig.isMusicEnabled) {\n                    if (MusicConfig.musicFilename != null) {\n                        musicPlayingText\n                    } else {\n                        musicEnabledText\n                    }\n                } else {\n                    musicSummary\n                },\n                checked = MusicConfig.isMusicEnabled,\n                onCheckedChange = {\n                    MusicConfig.setMusicEnabledState(it)\n                    MusicConfig.save(context)\n                    MusicManager.reload()\n                }\n            )\n        }\n\n        // --- Music: Select Music File ---\n        item(visible = MusicConfig.isMusicEnabled) {\n            ExpressiveCard(\n                flat = flat,\n                onClick = {\n                    try {\n                        pickMusicLauncher.launch(\"audio/*\")\n                    } catch (e: ActivityNotFoundException) {\n                        showToast(context, e.message ?: \"\")\n                    }\n                }\n            ) {\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(16.dp),\n                    verticalAlignment = Alignment.CenterVertically,\n                ) {\n                    Icon(imageVector = Icons.Filled.AudioFile, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                    Spacer(Modifier.width(16.dp))\n                    Column(modifier = Modifier.weight(1f)) {\n                        Text(\n                            text = selectMusicTitle,\n                            style = MaterialTheme.typography.titleMedium,\n                            color = MaterialTheme.colorScheme.onSurface,\n                            fontWeight = FontWeight.SemiBold,\n                        )\n                        if (MusicConfig.musicFilename != null) {\n                            Spacer(Modifier.height(4.dp))\n                            Text(\n                                text = musicSelectedText,\n                                style = MaterialTheme.typography.bodyMedium,\n                                color = MaterialTheme.colorScheme.onSurfaceVariant,\n                            )\n                        }\n                    }\n                }\n            }\n        }\n\n        // --- Music: Auto Play Toggle ---\n        item(visible = MusicConfig.isMusicEnabled) {\n            ToggleSettingCard(\n                flat = flat,\n                icon = Icons.Filled.PlayArrow,\n                title = autoPlayTitle,\n                description = autoPlaySummary,\n                checked = MusicConfig.isAutoPlayEnabled,\n                onCheckedChange = {\n                    MusicConfig.setAutoPlayEnabledState(it)\n                    MusicConfig.save(context)\n                }\n            )\n        }\n\n        // --- Music: Looping Toggle ---\n        item(visible = MusicConfig.isMusicEnabled) {\n            ToggleSettingCard(\n                flat = flat,\n                icon = Icons.Filled.Repeat,\n                title = loopingTitle,\n                description = loopingSummary,\n                checked = MusicConfig.isLoopingEnabled,\n                onCheckedChange = {\n                    MusicConfig.setLoopingEnabledState(it)\n                    MusicConfig.save(context)\n                    MusicManager.updateLooping(it)\n                }\n            )\n        }\n\n        // --- Music: Volume Slider ---\n        item(visible = MusicConfig.isMusicEnabled) {\n            ExpressiveCard(flat = flat) {\n                Column(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(16.dp)\n                ) {\n                    Text(\n                        text = musicVolumeTitle,\n                        style = MaterialTheme.typography.titleMedium,\n                        color = MaterialTheme.colorScheme.onSurface,\n                        fontWeight = FontWeight.SemiBold,\n                    )\n                    Spacer(Modifier.height(8.dp))\n                    Slider(\n                        value = MusicConfig.volume,\n                        onValueChange = {\n                            MusicConfig.setVolumeValue(it)\n                            MusicManager.updateVolume(it)\n                        },\n                        onValueChangeFinished = { MusicConfig.save(context) },\n                        valueRange = 0f..1f,\n                        colors = SliderDefaults.colors(\n                            thumbColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                            activeTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f)\n                        )\n                    )\n                }\n            }\n        }\n\n        // --- Music: Playback Control ---\n        item(visible = MusicConfig.isMusicEnabled && MusicConfig.musicFilename != null) {\n            ExpressiveCard(flat = flat) {\n                Column(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(16.dp)\n                ) {\n                    Text(\n                        text = playbackControlTitle,\n                        style = MaterialTheme.typography.titleMedium,\n                        color = MaterialTheme.colorScheme.onSurface,\n                        fontWeight = FontWeight.SemiBold,\n                    )\n                    Spacer(Modifier.height(8.dp))\n                    Slider(\n                        value = currentPosition.toFloat(),\n                        onValueChange = {\n                            MusicManager.seekTo(it.toInt())\n                        },\n                        valueRange = 0f..duration.toFloat().coerceAtLeast(1f),\n                        colors = SliderDefaults.colors(\n                            thumbColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                            activeTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f)\n                        )\n                    )\n                    Row(\n                        modifier = Modifier.fillMaxWidth(),\n                        horizontalArrangement = Arrangement.SpaceBetween,\n                        verticalAlignment = Alignment.CenterVertically,\n                    ) {\n                        Text(\n                            text = formatTime(currentPosition),\n                            style = MaterialTheme.typography.bodySmall,\n                            color = MaterialTheme.colorScheme.onSurface\n                        )\n                        IconButton(onClick = { MusicManager.toggle() }) {\n                            Icon(\n                                imageVector = if (isPlaying) Icons.Filled.Pause else Icons.Filled.PlayArrow,\n                                contentDescription = null\n                            )\n                        }\n                        Text(\n                            text = formatTime(duration),\n                            style = MaterialTheme.typography.bodySmall,\n                            color = MaterialTheme.colorScheme.onSurface\n                        )\n                    }\n                }\n            }\n        }\n\n        // --- Music: Clear Music ---\n        item(visible = MusicConfig.isMusicEnabled && MusicConfig.musicFilename != null) {\n            ExpressiveCard(\n                flat = flat,\n                onClick = {\n                    clearMusicDialog.showConfirm(\n                        title = context.getString(R.string.settings_clear_music),\n                        content = context.getString(R.string.settings_clear_music_confirm)\n                    )\n                }\n            ) {\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(16.dp),\n                    verticalAlignment = Alignment.CenterVertically,\n                ) {\n                    Icon(imageVector = Icons.Filled.Delete, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                    Spacer(Modifier.width(16.dp))\n                    Text(\n                        text = clearMusicTitle,\n                        style = MaterialTheme.typography.titleMedium,\n                        color = MaterialTheme.colorScheme.onSurface,\n                        fontWeight = FontWeight.SemiBold,\n                    )\n                }\n            }\n        }\n\n        // --- Sound Effect Toggle ---\n        item {\n            ToggleSettingCard(\n                flat = flat,\n                icon = Icons.Filled.SurroundSound,\n                title = soundEffectTitle,\n                description = if (SoundEffectConfig.isSoundEffectEnabled) {\n                    if (SoundEffectConfig.soundEffectFilename != null) {\n                        soundEffectPlayingText\n                    } else {\n                        soundEffectEnabledText\n                    }\n                } else {\n                    soundEffectSummary\n                },\n                checked = SoundEffectConfig.isSoundEffectEnabled,\n                onCheckedChange = {\n                    SoundEffectConfig.setEnabledState(it)\n                    SoundEffectConfig.save(context)\n                }\n            )\n        }\n\n        // --- Sound Effect: Source Selector ---\n        item(visible = SoundEffectConfig.isSoundEffectEnabled) {\n            ExpressiveCard(\n                flat = flat,\n                onClick = { showSoundEffectSourceDialog = true }\n            ) {\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(16.dp),\n                    verticalAlignment = Alignment.CenterVertically,\n                ) {\n                    Icon(imageVector = Icons.Filled.Input, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                    Spacer(Modifier.width(16.dp))\n                    Column(modifier = Modifier.weight(1f)) {\n                        Text(\n                            text = soundEffectSourceTitle,\n                            style = MaterialTheme.typography.titleMedium,\n                            color = MaterialTheme.colorScheme.onSurface,\n                            fontWeight = FontWeight.SemiBold,\n                        )\n                        Spacer(Modifier.height(4.dp))\n                        Text(\n                            text = if (SoundEffectConfig.sourceType == SoundEffectConfig.SOURCE_TYPE_LOCAL) soundEffectSourceLocal else soundEffectSourcePreset,\n                            style = MaterialTheme.typography.bodyMedium,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant,\n                        )\n                    }\n                }\n            }\n        }\n\n        // --- Sound Effect: Select Local File (local source) ---\n        item(visible = SoundEffectConfig.isSoundEffectEnabled && SoundEffectConfig.sourceType == SoundEffectConfig.SOURCE_TYPE_LOCAL) {\n            ExpressiveCard(\n                flat = flat,\n                onClick = {\n                    try {\n                        pickSoundEffectLauncher.launch(\"audio/*\")\n                    } catch (e: ActivityNotFoundException) {\n                        showToast(context, e.message ?: \"\")\n                    }\n                }\n            ) {\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(16.dp),\n                    verticalAlignment = Alignment.CenterVertically,\n                ) {\n                    Icon(imageVector = Icons.Filled.AudioFile, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                    Spacer(Modifier.width(16.dp))\n                    Column(modifier = Modifier.weight(1f)) {\n                        Text(\n                            text = selectSoundEffectTitle,\n                            style = MaterialTheme.typography.titleMedium,\n                            color = MaterialTheme.colorScheme.onSurface,\n                            fontWeight = FontWeight.SemiBold,\n                        )\n                        if (SoundEffectConfig.soundEffectFilename != null) {\n                            Spacer(Modifier.height(4.dp))\n                            Text(\n                                text = soundEffectSelectedText,\n                                style = MaterialTheme.typography.bodyMedium,\n                                color = MaterialTheme.colorScheme.onSurfaceVariant,\n                            )\n                        }\n                    }\n                }\n            }\n        }\n\n        // --- Sound Effect: Clear Sound Effect (local source with file) ---\n        item(visible = SoundEffectConfig.isSoundEffectEnabled && SoundEffectConfig.sourceType == SoundEffectConfig.SOURCE_TYPE_LOCAL && SoundEffectConfig.soundEffectFilename != null) {\n            ExpressiveCard(\n                flat = flat,\n                onClick = {\n                    clearSoundEffectDialog.showConfirm(\n                        title = context.getString(R.string.settings_clear_sound_effect),\n                        content = context.getString(R.string.settings_clear_sound_effect_confirm)\n                    )\n                }\n            ) {\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(16.dp),\n                    verticalAlignment = Alignment.CenterVertically,\n                ) {\n                    Icon(imageVector = Icons.Filled.Delete, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                    Spacer(Modifier.width(16.dp))\n                    Text(\n                        text = clearSoundEffectTitle,\n                        style = MaterialTheme.typography.titleMedium,\n                        color = MaterialTheme.colorScheme.onSurface,\n                        fontWeight = FontWeight.SemiBold,\n                    )\n                }\n            }\n        }\n\n        // --- Sound Effect: Preset Selector (preset source) ---\n        item(visible = SoundEffectConfig.isSoundEffectEnabled && SoundEffectConfig.sourceType == SoundEffectConfig.SOURCE_TYPE_PRESET) {\n            ExpressiveCard(\n                flat = flat,\n                onClick = { showSoundEffectPresetDialog = true }\n            ) {\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(16.dp),\n                    verticalAlignment = Alignment.CenterVertically,\n                ) {\n                    Icon(imageVector = Icons.Filled.MusicNote, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                    Spacer(Modifier.width(16.dp))\n                    Column(modifier = Modifier.weight(1f)) {\n                        Text(\n                            text = soundEffectPresetTitle,\n                            style = MaterialTheme.typography.titleMedium,\n                            color = MaterialTheme.colorScheme.onSurface,\n                            fontWeight = FontWeight.SemiBold,\n                        )\n                        Spacer(Modifier.height(4.dp))\n                        Text(\n                            text = SoundEffectConfig.presetName,\n                            style = MaterialTheme.typography.bodyMedium,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant,\n                        )\n                    }\n                }\n            }\n        }\n\n        // --- Sound Effect: Scope Selector ---\n        item(visible = SoundEffectConfig.isSoundEffectEnabled) {\n            ExpressiveCard(\n                flat = flat,\n                onClick = {\n                    showSoundEffectScopeDialog = true\n                }\n            ) {\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(16.dp),\n                    verticalAlignment = Alignment.CenterVertically,\n                ) {\n                    Icon(imageVector = Icons.Filled.Tune, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                    Spacer(Modifier.width(16.dp))\n                    Column(modifier = Modifier.weight(1f)) {\n                        Text(\n                            text = soundEffectScopeTitle,\n                            style = MaterialTheme.typography.titleMedium,\n                            color = MaterialTheme.colorScheme.onSurface,\n                            fontWeight = FontWeight.SemiBold,\n                        )\n                        Spacer(Modifier.height(4.dp))\n                        Text(\n                            text = if (SoundEffectConfig.scope == SoundEffectConfig.SCOPE_GLOBAL)\n                                stringResource(R.string.settings_sound_effect_scope_global)\n                            else\n                                stringResource(R.string.settings_sound_effect_scope_bottom_bar),\n                            style = MaterialTheme.typography.bodyMedium,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant,\n                        )\n                    }\n                }\n            }\n        }\n\n        // --- Startup Sound Toggle ---\n        item {\n            ToggleSettingCard(\n                flat = flat,\n                icon = Icons.Filled.Alarm,\n                title = startupSoundTitle,\n                description = if (SoundEffectConfig.isStartupSoundEnabled) {\n                    if (SoundEffectConfig.startupSoundFilename != null) {\n                        startupSoundPlayingText\n                    } else {\n                        startupSoundEnabledText\n                    }\n                } else {\n                    startupSoundSummary\n                },\n                checked = SoundEffectConfig.isStartupSoundEnabled,\n                onCheckedChange = {\n                    SoundEffectConfig.setStartupEnabledState(it)\n                    SoundEffectConfig.save(context)\n                }\n            )\n        }\n\n        // --- Startup Sound: Source Selector ---\n        item(visible = SoundEffectConfig.isStartupSoundEnabled) {\n            ExpressiveCard(\n                flat = flat,\n                onClick = { showStartupSourceDialog = true }\n            ) {\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(16.dp),\n                    verticalAlignment = Alignment.CenterVertically,\n                ) {\n                    Icon(imageVector = Icons.Filled.Input, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                    Spacer(Modifier.width(16.dp))\n                    Column(modifier = Modifier.weight(1f)) {\n                        Text(\n                            text = startupSourceTitle,\n                            style = MaterialTheme.typography.titleMedium,\n                            color = MaterialTheme.colorScheme.onSurface,\n                            fontWeight = FontWeight.SemiBold,\n                        )\n                        Spacer(Modifier.height(4.dp))\n                        Text(\n                            text = if (SoundEffectConfig.startupSourceType == SoundEffectConfig.SOURCE_TYPE_LOCAL) startupSourceLocal else startupSourcePreset,\n                            style = MaterialTheme.typography.bodyMedium,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant,\n                        )\n                    }\n                }\n            }\n        }\n\n        // --- Startup Sound: Select Local File (local source) ---\n        item(visible = SoundEffectConfig.isStartupSoundEnabled && SoundEffectConfig.startupSourceType == SoundEffectConfig.SOURCE_TYPE_LOCAL) {\n            ExpressiveCard(\n                flat = flat,\n                onClick = {\n                    try {\n                        pickStartupSoundLauncher.launch(\"audio/*\")\n                    } catch (e: ActivityNotFoundException) {\n                        showToast(context, e.message ?: \"\")\n                    }\n                }\n            ) {\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(16.dp),\n                    verticalAlignment = Alignment.CenterVertically,\n                ) {\n                    Icon(imageVector = Icons.Filled.AudioFile, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                    Spacer(Modifier.width(16.dp))\n                    Column(modifier = Modifier.weight(1f)) {\n                        Text(\n                            text = selectStartupSoundTitle,\n                            style = MaterialTheme.typography.titleMedium,\n                            color = MaterialTheme.colorScheme.onSurface,\n                            fontWeight = FontWeight.SemiBold,\n                        )\n                        if (SoundEffectConfig.startupSoundFilename != null) {\n                            Spacer(Modifier.height(4.dp))\n                            Text(\n                                text = startupSoundSelectedText,\n                                style = MaterialTheme.typography.bodyMedium,\n                                color = MaterialTheme.colorScheme.onSurfaceVariant,\n                            )\n                        }\n                    }\n                }\n            }\n        }\n\n        // --- Startup Sound: Clear Startup Sound (local source with file) ---\n        item(visible = SoundEffectConfig.isStartupSoundEnabled && SoundEffectConfig.startupSourceType == SoundEffectConfig.SOURCE_TYPE_LOCAL && SoundEffectConfig.startupSoundFilename != null) {\n            ExpressiveCard(\n                flat = flat,\n                onClick = {\n                    clearStartupSoundDialog.showConfirm(\n                        title = context.getString(R.string.settings_clear_startup_sound),\n                        content = context.getString(R.string.settings_clear_startup_sound_confirm)\n                    )\n                }\n            ) {\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(16.dp),\n                    verticalAlignment = Alignment.CenterVertically,\n                ) {\n                    Icon(imageVector = Icons.Filled.Delete, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                    Spacer(Modifier.width(16.dp))\n                    Text(\n                        text = clearStartupSoundTitle,\n                        style = MaterialTheme.typography.titleMedium,\n                        color = MaterialTheme.colorScheme.onSurface,\n                        fontWeight = FontWeight.SemiBold,\n                    )\n                }\n            }\n        }\n\n        // --- Startup Sound: Preset Selector (preset source) ---\n        item(visible = SoundEffectConfig.isStartupSoundEnabled && SoundEffectConfig.startupSourceType == SoundEffectConfig.SOURCE_TYPE_PRESET) {\n            ExpressiveCard(\n                flat = flat,\n                onClick = { showStartupPresetDialog = true }\n            ) {\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(16.dp),\n                    verticalAlignment = Alignment.CenterVertically,\n                ) {\n                    Icon(imageVector = Icons.Filled.MusicNote, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                    Spacer(Modifier.width(16.dp))\n                    Column(modifier = Modifier.weight(1f)) {\n                        Text(\n                            text = startupPresetTitle,\n                            style = MaterialTheme.typography.titleMedium,\n                            color = MaterialTheme.colorScheme.onSurface,\n                            fontWeight = FontWeight.SemiBold,\n                        )\n                        Spacer(Modifier.height(4.dp))\n                        Text(\n                            text = SoundEffectConfig.startupPresetName,\n                            style = MaterialTheme.typography.bodyMedium,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant,\n                        )\n                    }\n                }\n            }\n        }\n\n        // --- Vibration Toggle ---\n        item {\n            ToggleSettingCard(\n                flat = flat,\n                icon = Icons.Filled.Vibration,\n                title = vibrationTitle,\n                description = if (VibrationConfig.isVibrationEnabled) vibrationEnabledText else vibrationSummary,\n                checked = VibrationConfig.isVibrationEnabled,\n                onCheckedChange = {\n                    VibrationConfig.setEnabledState(it)\n                    VibrationConfig.save(context)\n                }\n            )\n        }\n\n        // --- Vibration: Scope Selector ---\n        item(visible = VibrationConfig.isVibrationEnabled) {\n            ExpressiveCard(\n                flat = flat,\n                onClick = {\n                    showVibrationScopeDialog = true\n                }\n            ) {\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(16.dp),\n                    verticalAlignment = Alignment.CenterVertically,\n                ) {\n                    Icon(imageVector = Icons.Filled.Tune, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp))\n                    Spacer(Modifier.width(16.dp))\n                    Column(modifier = Modifier.weight(1f)) {\n                        Text(\n                            text = vibrationScopeTitle,\n                            style = MaterialTheme.typography.titleMedium,\n                            color = MaterialTheme.colorScheme.onSurface,\n                            fontWeight = FontWeight.SemiBold,\n                        )\n                        Spacer(Modifier.height(4.dp))\n                        Text(\n                            text = if (VibrationConfig.scope == VibrationConfig.SCOPE_GLOBAL)\n                                stringResource(R.string.settings_vibration_scope_global)\n                            else\n                                stringResource(R.string.settings_vibration_scope_bottom_bar),\n                            style = MaterialTheme.typography.bodyMedium,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant,\n                        )\n                    }\n                }\n            }\n        }\n\n        // --- Vibration: Intensity Slider ---\n        item(visible = VibrationConfig.isVibrationEnabled) {\n            ExpressiveCard(flat = flat) {\n                Column(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(16.dp)\n                ) {\n                    Text(\n                        text = vibrationIntensityTitle,\n                        style = MaterialTheme.typography.titleMedium,\n                        color = MaterialTheme.colorScheme.onSurface,\n                        fontWeight = FontWeight.SemiBold,\n                    )\n                    Spacer(Modifier.height(8.dp))\n                    Slider(\n                        value = VibrationConfig.vibrationIntensity,\n                        onValueChange = {\n                            VibrationConfig.setIntensityValue(it)\n                        },\n                        onValueChangeFinished = { VibrationConfig.save(context) },\n                        valueRange = 0f..1f,\n                        colors = SliderDefaults.colors(\n                            thumbColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f),\n                            activeTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 1f)\n                        )\n                    )\n                }\n            }\n        }\n    }\n\n    // --- Dialogs (outside SplicedColumnGroup) ---\n\n    // Sound Effect Source Dialog\n    if (showSoundEffectSourceDialog) {\n        BasicAlertDialog(\n            onDismissRequest = { showSoundEffectSourceDialog = false },\n            properties = DialogProperties(\n                decorFitsSystemWindows = true,\n                usePlatformDefaultWidth = false,\n            )\n        ) {\n            Surface(\n                modifier = Modifier\n                    .width(310.dp)\n                    .wrapContentHeight(),\n                shape = RoundedCornerShape(30.dp),\n                tonalElevation = AlertDialogDefaults.TonalElevation,\n                color = AlertDialogDefaults.containerColor,\n            ) {\n                Column(modifier = Modifier.padding(24.dp)) {\n                    Text(\n                        text = soundEffectSourceTitle,\n                        style = MaterialTheme.typography.headlineSmall,\n                        modifier = Modifier.padding(bottom = 16.dp)\n                    )\n\n                    Surface(\n                        shape = RoundedCornerShape(12.dp),\n                        color = AlertDialogDefaults.containerColor,\n                        tonalElevation = 2.dp\n                    ) {\n                        Column {\n                            ListItem(\n                                headlineContent = { Text(soundEffectSourceLocal) },\n                                leadingContent = {\n                                    RadioButton(\n                                        selected = SoundEffectConfig.sourceType == SoundEffectConfig.SOURCE_TYPE_LOCAL,\n                                        onClick = null\n                                    )\n                                },\n                                modifier = Modifier.clickable {\n                                    SoundEffectConfig.setSourceTypeValue(SoundEffectConfig.SOURCE_TYPE_LOCAL)\n                                    SoundEffectConfig.save(context)\n                                    showSoundEffectSourceDialog = false\n                                }\n                            )\n\n                            ListItem(\n                                headlineContent = { Text(soundEffectSourcePreset) },\n                                leadingContent = {\n                                    RadioButton(\n                                        selected = SoundEffectConfig.sourceType == SoundEffectConfig.SOURCE_TYPE_PRESET,\n                                        onClick = null\n                                    )\n                                },\n                                modifier = Modifier.clickable {\n                                    SoundEffectConfig.setSourceTypeValue(SoundEffectConfig.SOURCE_TYPE_PRESET)\n                                    SoundEffectConfig.save(context)\n                                    showSoundEffectSourceDialog = false\n                                }\n                            )\n                        }\n                    }\n\n                    Row(\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .padding(top = 24.dp),\n                        horizontalArrangement = Arrangement.End\n                    ) {\n                        TextButton(onClick = { showSoundEffectSourceDialog = false }) {\n                            Text(stringResource(id = android.R.string.cancel))\n                        }\n                    }\n                }\n                val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n                APDialogBlurBehindUtils.setupWindowBlurListener(dialogWindowProvider.window)\n            }\n        }\n    }\n\n    // Sound Effect Preset Dialog\n    if (showSoundEffectPresetDialog) {\n        BasicAlertDialog(\n            onDismissRequest = { showSoundEffectPresetDialog = false },\n            properties = DialogProperties(\n                decorFitsSystemWindows = true,\n                usePlatformDefaultWidth = false,\n            )\n        ) {\n            Surface(\n                modifier = Modifier\n                    .width(310.dp)\n                    .wrapContentHeight(),\n                shape = RoundedCornerShape(30.dp),\n                tonalElevation = AlertDialogDefaults.TonalElevation,\n                color = AlertDialogDefaults.containerColor,\n            ) {\n                Column(modifier = Modifier.padding(24.dp)) {\n                    Text(\n                        text = soundEffectPresetTitle,\n                        style = MaterialTheme.typography.headlineSmall,\n                        modifier = Modifier.padding(bottom = 16.dp)\n                    )\n\n                    Surface(\n                        shape = RoundedCornerShape(12.dp),\n                        color = AlertDialogDefaults.containerColor,\n                        tonalElevation = 2.dp,\n                        modifier = Modifier.heightIn(max = 400.dp)\n                    ) {\n                        androidx.compose.foundation.lazy.LazyColumn {\n                            items(SoundEffectConfig.PRESETS.size) { index ->\n                                val preset = SoundEffectConfig.PRESETS[index]\n                                ListItem(\n                                    headlineContent = { Text(preset) },\n                                    leadingContent = {\n                                        RadioButton(\n                                            selected = SoundEffectConfig.presetName == preset,\n                                            onClick = null\n                                        )\n                                    },\n                                    modifier = Modifier.clickable {\n                                        SoundEffectConfig.setPresetNameValue(preset)\n                                        SoundEffectConfig.save(context)\n                                        showSoundEffectPresetDialog = false\n                                    }\n                                )\n                            }\n                        }\n                    }\n\n                    Row(\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .padding(top = 24.dp),\n                        horizontalArrangement = Arrangement.End\n                    ) {\n                        TextButton(onClick = { showSoundEffectPresetDialog = false }) {\n                            Text(stringResource(id = android.R.string.cancel))\n                        }\n                    }\n                }\n                val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n                APDialogBlurBehindUtils.setupWindowBlurListener(dialogWindowProvider.window)\n            }\n        }\n    }\n\n    // Sound Effect Scope Dialog\n    if (showSoundEffectScopeDialog) {\n        BasicAlertDialog(\n            onDismissRequest = { showSoundEffectScopeDialog = false },\n            properties = DialogProperties(\n                decorFitsSystemWindows = true,\n                usePlatformDefaultWidth = false,\n            )\n        ) {\n            Surface(\n                modifier = Modifier\n                    .width(310.dp)\n                    .wrapContentHeight(),\n                shape = RoundedCornerShape(30.dp),\n                tonalElevation = AlertDialogDefaults.TonalElevation,\n                color = AlertDialogDefaults.containerColor,\n            ) {\n                Column(modifier = Modifier.padding(24.dp)) {\n                    Text(\n                        text = soundEffectScopeTitle,\n                        style = MaterialTheme.typography.headlineSmall,\n                        modifier = Modifier.padding(bottom = 16.dp)\n                    )\n\n                    Surface(\n                        shape = RoundedCornerShape(12.dp),\n                        color = AlertDialogDefaults.containerColor,\n                        tonalElevation = 2.dp\n                    ) {\n                        Column {\n                            ListItem(\n                                headlineContent = { Text(stringResource(R.string.settings_sound_effect_scope_global)) },\n                                leadingContent = {\n                                    RadioButton(\n                                        selected = SoundEffectConfig.scope == SoundEffectConfig.SCOPE_GLOBAL,\n                                        onClick = null\n                                    )\n                                },\n                                modifier = Modifier.clickable {\n                                    SoundEffectConfig.setScopeValue(SoundEffectConfig.SCOPE_GLOBAL)\n                                    SoundEffectConfig.save(context)\n                                    showSoundEffectScopeDialog = false\n                                }\n                            )\n\n                            ListItem(\n                                headlineContent = { Text(stringResource(R.string.settings_sound_effect_scope_bottom_bar)) },\n                                leadingContent = {\n                                    RadioButton(\n                                        selected = SoundEffectConfig.scope == SoundEffectConfig.SCOPE_BOTTOM_BAR,\n                                        onClick = null\n                                    )\n                                },\n                                modifier = Modifier.clickable {\n                                    SoundEffectConfig.setScopeValue(SoundEffectConfig.SCOPE_BOTTOM_BAR)\n                                    SoundEffectConfig.save(context)\n                                    showSoundEffectScopeDialog = false\n                                }\n                            )\n                        }\n                    }\n\n                    Row(\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .padding(top = 24.dp),\n                        horizontalArrangement = Arrangement.End\n                    ) {\n                        TextButton(onClick = { showSoundEffectScopeDialog = false }) {\n                            Text(stringResource(id = android.R.string.cancel))\n                        }\n                    }\n                }\n                val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n                APDialogBlurBehindUtils.setupWindowBlurListener(dialogWindowProvider.window)\n            }\n        }\n    }\n\n    // Startup Sound Source Dialog\n    if (showStartupSourceDialog) {\n        BasicAlertDialog(\n            onDismissRequest = { showStartupSourceDialog = false },\n            properties = DialogProperties(\n                decorFitsSystemWindows = true,\n                usePlatformDefaultWidth = false,\n            )\n        ) {\n            Surface(\n                modifier = Modifier\n                    .width(310.dp)\n                    .wrapContentHeight(),\n                shape = RoundedCornerShape(30.dp),\n                tonalElevation = AlertDialogDefaults.TonalElevation,\n                color = AlertDialogDefaults.containerColor,\n            ) {\n                Column(modifier = Modifier.padding(24.dp)) {\n                    Text(\n                        text = startupSourceTitle,\n                        style = MaterialTheme.typography.headlineSmall,\n                        modifier = Modifier.padding(bottom = 16.dp)\n                    )\n\n                    Surface(\n                        shape = RoundedCornerShape(12.dp),\n                        color = AlertDialogDefaults.containerColor,\n                        tonalElevation = 2.dp\n                    ) {\n                        Column {\n                            ListItem(\n                                headlineContent = { Text(startupSourceLocal) },\n                                leadingContent = {\n                                    RadioButton(\n                                        selected = SoundEffectConfig.startupSourceType == SoundEffectConfig.SOURCE_TYPE_LOCAL,\n                                        onClick = null\n                                    )\n                                },\n                                modifier = Modifier.clickable {\n                                    SoundEffectConfig.setStartupSourceTypeValue(SoundEffectConfig.SOURCE_TYPE_LOCAL)\n                                    SoundEffectConfig.save(context)\n                                    showStartupSourceDialog = false\n                                }\n                            )\n\n                            ListItem(\n                                headlineContent = { Text(startupSourcePreset) },\n                                leadingContent = {\n                                    RadioButton(\n                                        selected = SoundEffectConfig.startupSourceType == SoundEffectConfig.SOURCE_TYPE_PRESET,\n                                        onClick = null\n                                    )\n                                },\n                                modifier = Modifier.clickable {\n                                    SoundEffectConfig.setStartupSourceTypeValue(SoundEffectConfig.SOURCE_TYPE_PRESET)\n                                    SoundEffectConfig.save(context)\n                                    showStartupSourceDialog = false\n                                }\n                            )\n                        }\n                    }\n\n                    Row(\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .padding(top = 24.dp),\n                        horizontalArrangement = Arrangement.End\n                    ) {\n                        TextButton(onClick = { showStartupSourceDialog = false }) {\n                            Text(stringResource(id = android.R.string.cancel))\n                        }\n                    }\n                }\n                val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n                APDialogBlurBehindUtils.setupWindowBlurListener(dialogWindowProvider.window)\n            }\n        }\n    }\n\n    // Startup Sound Preset Dialog\n    if (showStartupPresetDialog) {\n        BasicAlertDialog(\n            onDismissRequest = { showStartupPresetDialog = false },\n            properties = DialogProperties(\n                decorFitsSystemWindows = true,\n                usePlatformDefaultWidth = false,\n            )\n        ) {\n            Surface(\n                modifier = Modifier\n                    .width(310.dp)\n                    .wrapContentHeight(),\n                shape = RoundedCornerShape(30.dp),\n                tonalElevation = AlertDialogDefaults.TonalElevation,\n                color = AlertDialogDefaults.containerColor,\n            ) {\n                Column(modifier = Modifier.padding(24.dp)) {\n                    Text(\n                        text = startupPresetTitle,\n                        style = MaterialTheme.typography.headlineSmall,\n                        modifier = Modifier.padding(bottom = 16.dp)\n                    )\n\n                    Surface(\n                        shape = RoundedCornerShape(12.dp),\n                        color = AlertDialogDefaults.containerColor,\n                        tonalElevation = 2.dp,\n                        modifier = Modifier.heightIn(max = 400.dp)\n                    ) {\n                        androidx.compose.foundation.lazy.LazyColumn {\n                            items(SoundEffectConfig.STARTUP_PRESETS.size) { index ->\n                                val preset = SoundEffectConfig.STARTUP_PRESETS[index]\n                                ListItem(\n                                    headlineContent = { Text(preset) },\n                                    leadingContent = {\n                                        RadioButton(\n                                            selected = SoundEffectConfig.startupPresetName == preset,\n                                            onClick = null\n                                        )\n                                    },\n                                    modifier = Modifier.clickable {\n                                        SoundEffectConfig.setStartupPresetNameValue(preset)\n                                        SoundEffectConfig.save(context)\n                                        showStartupPresetDialog = false\n                                    }\n                                )\n                            }\n                        }\n                    }\n\n                    Row(\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .padding(top = 24.dp),\n                        horizontalArrangement = Arrangement.End\n                    ) {\n                        TextButton(onClick = { showStartupPresetDialog = false }) {\n                            Text(stringResource(id = android.R.string.cancel))\n                        }\n                    }\n                }\n                val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n                APDialogBlurBehindUtils.setupWindowBlurListener(dialogWindowProvider.window)\n            }\n        }\n    }\n\n    // Vibration Scope Dialog\n    if (showVibrationScopeDialog) {\n        BasicAlertDialog(\n            onDismissRequest = { showVibrationScopeDialog = false },\n            properties = DialogProperties(\n                decorFitsSystemWindows = true,\n                usePlatformDefaultWidth = false,\n            )\n        ) {\n            Surface(\n                modifier = Modifier\n                    .width(310.dp)\n                    .wrapContentHeight(),\n                shape = RoundedCornerShape(30.dp),\n                tonalElevation = AlertDialogDefaults.TonalElevation,\n                color = AlertDialogDefaults.containerColor,\n            ) {\n                Column(modifier = Modifier.padding(24.dp)) {\n                    Text(\n                        text = vibrationScopeTitle,\n                        style = MaterialTheme.typography.headlineSmall,\n                        modifier = Modifier.padding(bottom = 16.dp)\n                    )\n\n                    Surface(\n                        shape = RoundedCornerShape(12.dp),\n                        color = AlertDialogDefaults.containerColor,\n                        tonalElevation = 2.dp\n                    ) {\n                        Column {\n                            ListItem(\n                                headlineContent = { Text(stringResource(R.string.settings_vibration_scope_global)) },\n                                leadingContent = {\n                                    RadioButton(\n                                        selected = VibrationConfig.scope == VibrationConfig.SCOPE_GLOBAL,\n                                        onClick = null\n                                    )\n                                },\n                                modifier = Modifier.clickable {\n                                    VibrationConfig.setScopeValue(VibrationConfig.SCOPE_GLOBAL)\n                                    VibrationConfig.save(context)\n                                    showVibrationScopeDialog = false\n                                }\n                            )\n\n                            ListItem(\n                                headlineContent = { Text(stringResource(R.string.settings_vibration_scope_bottom_bar)) },\n                                leadingContent = {\n                                    RadioButton(\n                                        selected = VibrationConfig.scope == VibrationConfig.SCOPE_BOTTOM_BAR,\n                                        onClick = null\n                                    )\n                                },\n                                modifier = Modifier.clickable {\n                                    VibrationConfig.setScopeValue(VibrationConfig.SCOPE_BOTTOM_BAR)\n                                    VibrationConfig.save(context)\n                                    showVibrationScopeDialog = false\n                                }\n                            )\n                        }\n                    }\n\n                    Row(\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .padding(top = 24.dp),\n                        horizontalArrangement = Arrangement.End\n                    ) {\n                        TextButton(onClick = { showVibrationScopeDialog = false }) {\n                            Text(stringResource(id = android.R.string.cancel))\n                        }\n                    }\n                }\n                val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider\n                APDialogBlurBehindUtils.setupWindowBlurListener(dialogWindowProvider.window)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/MultimediaSettingsScreen.kt",
    "content": "package me.bmax.apatch.ui.screen.settings\n\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.outlined.ArrowBack\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.SnackbarHost\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.input.nestedscroll.nestedScroll\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.theme.BackgroundConfig\nimport me.bmax.apatch.util.ui.LocalSnackbarHost\nimport me.bmax.apatch.util.ui.NavigationBarsSpacer\n\n@Destination<RootGraph>\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun MultimediaSettingsScreen(navigator: DestinationsNavigator) {\n    val snackBarHost = LocalSnackbarHost.current\n    val flat = BackgroundConfig.isCustomBackgroundEnabled || BackgroundConfig.settingsBackgroundUri != null\n    val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()\n\n    Scaffold(\n        topBar = {\n            TopAppBar(\n                title = { Text(stringResource(R.string.settings_category_multimedia), style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.SemiBold) },\n                navigationIcon = {\n                    IconButton(onClick = { navigator.popBackStack() }) {\n                        Icon(Icons.AutoMirrored.Outlined.ArrowBack, contentDescription = null)\n                    }\n                },\n                scrollBehavior = scrollBehavior,\n            )\n        },\n        containerColor = Color.Transparent,\n        snackbarHost = { SnackbarHost(snackBarHost) },\n    ) { paddingValues ->\n        LazyColumn(\n            modifier = Modifier.padding(paddingValues).nestedScroll(scrollBehavior.nestedScrollConnection),\n            verticalArrangement = Arrangement.spacedBy(8.dp),\n        ) {\n            item { Spacer(Modifier.height(8.dp)) }\n            item {\n                MultimediaSettingsContent(\n                    snackBarHost = snackBarHost,\n                    flat = flat,\n                )\n            }\n            item { Spacer(Modifier.height(8.dp)) }\n            item { NavigationBarsSpacer() }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/SecuritySettings.kt",
    "content": "package me.bmax.apatch.ui.screen.settings\n\nimport androidx.biometric.BiometricPrompt\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.DeleteForever\nimport androidx.compose.material.icons.filled.Fingerprint\nimport androidx.compose.material.icons.filled.Key\nimport androidx.compose.material.icons.filled.Shield\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.SnackbarHostState\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.*\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.unit.dp\nimport androidx.core.content.ContextCompat\nimport androidx.fragment.app.FragmentActivity\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.component.ExpressiveCard\nimport me.bmax.apatch.ui.component.SplicedColumnGroup\nimport me.bmax.apatch.ui.component.ToggleSettingCard\nimport me.bmax.apatch.ui.component.rememberConfirmDialog\nimport me.bmax.apatch.util.APatchKeyHelper\n\n@Composable\nfun SecuritySettingsContent(\n    snackBarHost: SnackbarHostState,\n    kPatchReady: Boolean,\n    flat: Boolean = false,\n) {\n    val prefs = APApplication.sharedPreferences\n    val context = LocalContext.current\n    val activity = context as? FragmentActivity\n\n    var biometricLogin by remember { mutableStateOf(prefs.getBoolean(\"biometric_login\", false)) }\n\n    val biometricManager = androidx.biometric.BiometricManager.from(context)\n    val canAuthenticate = biometricManager.canAuthenticate(\n        androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG or\n                androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL\n    ) == androidx.biometric.BiometricManager.BIOMETRIC_SUCCESS\n\n    SplicedColumnGroup(flat = flat) {\n        item(visible = canAuthenticate) {\n            ToggleSettingCard(\n                flat = flat,\n                icon = Icons.Filled.Fingerprint,\n                title = stringResource(id = R.string.settings_biometric_login),\n                description = stringResource(id = R.string.settings_biometric_login_summary),\n                checked = biometricLogin,\n                onCheckedChange = { checked ->\n                    if (!checked) {\n                        if (activity != null) {\n                            val executor = ContextCompat.getMainExecutor(context)\n                            val biometricPrompt = BiometricPrompt(activity, executor,\n                                object : BiometricPrompt.AuthenticationCallback() {\n                                    override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {\n                                        super.onAuthenticationSucceeded(result)\n                                        biometricLogin = false\n                                        prefs.edit().putBoolean(\"biometric_login\", false).apply()\n                                    }\n\n                                    override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {\n                                        super.onAuthenticationError(errorCode, errString)\n                                    }\n                                })\n\n                            val promptInfo = BiometricPrompt.PromptInfo.Builder()\n                                .setTitle(context.getString(R.string.action_biometric))\n                                .setSubtitle(context.getString(R.string.msg_biometric))\n                                .setAllowedAuthenticators(androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG or androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL)\n                                .build()\n\n                            biometricPrompt.authenticate(promptInfo)\n                        } else {\n                            biometricLogin = false\n                            prefs.edit().putBoolean(\"biometric_login\", false).apply()\n                        }\n                    } else {\n                        biometricLogin = true\n                        prefs.edit().putBoolean(\"biometric_login\", true).apply()\n                    }\n                }\n            )\n        }\n\n        item(visible = biometricLogin && canAuthenticate) {\n            var strongBiometric by remember { mutableStateOf(prefs.getBoolean(\"strong_biometric\", false)) }\n            ToggleSettingCard(\n                flat = flat,\n                icon = Icons.Filled.Shield,\n                title = stringResource(id = R.string.settings_strong_biometric),\n                description = stringResource(id = R.string.settings_strong_biometric_summary),\n                checked = strongBiometric,\n                onCheckedChange = {\n                    strongBiometric = it\n                    prefs.edit().putBoolean(\"strong_biometric\", it).apply()\n                }\n            )\n        }\n\n        item(visible = kPatchReady) {\n            val clearSuperKeyTitle = stringResource(id = R.string.clear_super_key)\n            val clearSuperKeyDialog = rememberConfirmDialog(\n                onConfirm = {\n                    APatchKeyHelper.clearConfigKey()\n                    APApplication.setSuperKeyAndRefresh(\"\")\n                }\n            )\n            ExpressiveCard(\n                flat = flat,\n                onClick = {\n                    clearSuperKeyDialog.showConfirm(\n                        title = clearSuperKeyTitle,\n                        content = context.getString(R.string.settings_clear_super_key_dialog)\n                    )\n                }\n            ) {\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(16.dp),\n                    verticalAlignment = Alignment.CenterVertically,\n                ) {\n                    Icon(\n                        imageVector = Icons.Filled.DeleteForever,\n                        contentDescription = null,\n                        tint = MaterialTheme.colorScheme.onSurfaceVariant,\n                        modifier = Modifier.size(24.dp),\n                    )\n                    Spacer(Modifier.width(16.dp))\n                    Text(text = clearSuperKeyTitle,\n                        style = MaterialTheme.typography.titleMedium,\n                        color = MaterialTheme.colorScheme.onSurface)\n                }\n            }\n        }\n\n        item(visible = kPatchReady) {\n            var noStoreKey by remember { mutableStateOf(APatchKeyHelper.shouldSkipStoreSuperKey()) }\n            ToggleSettingCard(\n                flat = flat,\n                icon = Icons.Filled.Key,\n                title = stringResource(id = R.string.settings_donot_store_superkey),\n                description = stringResource(id = R.string.settings_donot_store_superkey_summary),\n                checked = noStoreKey,\n                onCheckedChange = {\n                    noStoreKey = it\n                    APatchKeyHelper.setShouldSkipStoreSuperKey(it)\n                }\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/SecuritySettingsScreen.kt",
    "content": "package me.bmax.apatch.ui.screen.settings\n\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.outlined.ArrowBack\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.SnackbarHost\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.livedata.observeAsState\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.input.nestedscroll.nestedScroll\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport com.ramcosta.composedestinations.annotation.Destination\nimport com.ramcosta.composedestinations.annotation.RootGraph\nimport com.ramcosta.composedestinations.navigation.DestinationsNavigator\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.theme.BackgroundConfig\nimport me.bmax.apatch.util.ui.LocalSnackbarHost\nimport me.bmax.apatch.util.ui.NavigationBarsSpacer\n\n@Destination<RootGraph>\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun SecuritySettingsScreen(navigator: DestinationsNavigator) {\n    val state by APApplication.apStateLiveData.observeAsState(APApplication.State.UNKNOWN_STATE)\n    val kPatchReady = state != APApplication.State.UNKNOWN_STATE\n\n    val snackBarHost = LocalSnackbarHost.current\n    val flat = BackgroundConfig.isCustomBackgroundEnabled || BackgroundConfig.settingsBackgroundUri != null\n    val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()\n\n    Scaffold(\n        topBar = {\n            TopAppBar(\n                title = { Text(stringResource(R.string.settings_category_security), style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.SemiBold) },\n                navigationIcon = {\n                    IconButton(onClick = { navigator.popBackStack() }) {\n                        Icon(Icons.AutoMirrored.Outlined.ArrowBack, contentDescription = null)\n                    }\n                },\n                scrollBehavior = scrollBehavior,\n            )\n        },\n        containerColor = Color.Transparent,\n        snackbarHost = { SnackbarHost(snackBarHost) },\n    ) { paddingValues ->\n        LazyColumn(\n            modifier = Modifier.padding(paddingValues).nestedScroll(scrollBehavior.nestedScrollConnection),\n            verticalArrangement = Arrangement.spacedBy(8.dp),\n        ) {\n            item { Spacer(Modifier.height(8.dp)) }\n            item {\n                SecuritySettingsContent(\n                    snackBarHost = snackBarHost,\n                    kPatchReady = kPatchReady,\n                    flat = flat,\n                )\n            }\n            item { Spacer(Modifier.height(8.dp)) }\n            item { NavigationBarsSpacer() }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/SettingsShared.kt",
    "content": "package me.bmax.apatch.ui.screen.settings\n\nfun shouldShow(searchText: String, vararg texts: String?): Boolean {\n    if (searchText.isEmpty()) return true\n    return texts.any { it?.contains(searchText, ignoreCase = true) == true }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/AmberTheme.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lightColorScheme\nimport androidx.compose.ui.graphics.Color\n\nprivate val md_theme_light_primary = Color(0xFF785900)\nprivate val md_theme_light_onPrimary = Color(0xFFFFFFFF)\nprivate val md_theme_light_primaryContainer = Color(0xFFFFDF9E)\nprivate val md_theme_light_onPrimaryContainer = Color(0xFF261A00)\nprivate val md_theme_light_secondary = Color(0xFF6B5D3F)\nprivate val md_theme_light_onSecondary = Color(0xFFFFFFFF)\nprivate val md_theme_light_secondaryContainer = Color(0xFFF5E0BB)\nprivate val md_theme_light_onSecondaryContainer = Color(0xFF241A04)\nprivate val md_theme_light_tertiary = Color(0xFF4A6547)\nprivate val md_theme_light_onTertiary = Color(0xFFFFFFFF)\nprivate val md_theme_light_tertiaryContainer = Color(0xFFCCEBC4)\nprivate val md_theme_light_onTertiaryContainer = Color(0xFF072109)\nprivate val md_theme_light_error = Color(0xFFBA1A1A)\nprivate val md_theme_light_errorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_light_onError = Color(0xFFFFFFFF)\nprivate val md_theme_light_onErrorContainer = Color(0xFF410002)\nprivate val md_theme_light_background = Color(0xFFFFFBFF)\nprivate val md_theme_light_onBackground = Color(0xFF1E1B16)\nprivate val md_theme_light_surface = Color(0xFFFFFBFF)\nprivate val md_theme_light_onSurface = Color(0xFF1E1B16)\nprivate val md_theme_light_surfaceVariant = Color(0xFFEDE1CF)\nprivate val md_theme_light_onSurfaceVariant = Color(0xFF4D4639)\nprivate val md_theme_light_outline = Color(0xFF7F7667)\nprivate val md_theme_light_inverseOnSurface = Color(0xFFF7EFE7)\nprivate val md_theme_light_inverseSurface = Color(0xFF33302A)\nprivate val md_theme_light_inversePrimary = Color(0xFFFABD00)\nprivate val md_theme_light_shadow = Color(0xFF000000)\nprivate val md_theme_light_surfaceTint = Color(0xFF785900)\nprivate val md_theme_light_outlineVariant = Color(0xFFD0C5B4)\nprivate val md_theme_light_scrim = Color(0xFF000000)\nprivate val md_theme_light_surfaceContainerLowest = Color(0xFFFEF9FC)\nprivate val md_theme_light_surfaceContainerLow = Color(0xFFFDF8FA)\nprivate val md_theme_light_surfaceContainer = Color(0xFFFBF6F6)\nprivate val md_theme_light_surfaceContainerHigh = Color(0xFFF9F3F1)\nprivate val md_theme_light_surfaceContainerHighest = Color(0xFFF8F1EC)\n\n\nprivate val md_theme_dark_primary = Color(0xFFFABD00)\nprivate val md_theme_dark_onPrimary = Color(0xFF3F2E00)\nprivate val md_theme_dark_primaryContainer = Color(0xFF5B4300)\nprivate val md_theme_dark_onPrimaryContainer = Color(0xFFFFDF9E)\nprivate val md_theme_dark_secondary = Color(0xFFD8C4A0)\nprivate val md_theme_dark_onSecondary = Color(0xFF3A2F15)\nprivate val md_theme_dark_secondaryContainer = Color(0xFF52452A)\nprivate val md_theme_dark_onSecondaryContainer = Color(0xFFF5E0BB)\nprivate val md_theme_dark_tertiary = Color(0xFFB0CFAA)\nprivate val md_theme_dark_onTertiary = Color(0xFF1D361C)\nprivate val md_theme_dark_tertiaryContainer = Color(0xFF334D31)\nprivate val md_theme_dark_onTertiaryContainer = Color(0xFFCCEBC4)\nprivate val md_theme_dark_error = Color(0xFFFFB4AB)\nprivate val md_theme_dark_errorContainer = Color(0xFF93000A)\nprivate val md_theme_dark_onError = Color(0xFF690005)\nprivate val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_dark_background = Color(0xFF1E1B16)\nprivate val md_theme_dark_onBackground = Color(0xFFE9E1D8)\nprivate val md_theme_dark_surface = Color(0xFF1E1B16)\nprivate val md_theme_dark_onSurface = Color(0xFFE9E1D8)\nprivate val md_theme_dark_surfaceVariant = Color(0xFF4D4639)\nprivate val md_theme_dark_onSurfaceVariant = Color(0xFFD0C5B4)\nprivate val md_theme_dark_outline = Color(0xFF998F80)\nprivate val md_theme_dark_inverseOnSurface = Color(0xFF1E1B16)\nprivate val md_theme_dark_inverseSurface = Color(0xFFE9E1D8)\nprivate val md_theme_dark_inversePrimary = Color(0xFF785900)\nprivate val md_theme_dark_shadow = Color(0xFF000000)\nprivate val md_theme_dark_surfaceTint = Color(0xFFFABD00)\nprivate val md_theme_dark_outlineVariant = Color(0xFF4D4639)\nprivate val md_theme_dark_scrim = Color(0xFF000000)\nprivate val md_theme_dark_surfaceContainerLowest = Color(0xFF201D17)\nprivate val md_theme_dark_surfaceContainerLow = Color(0xFF221F19)\nprivate val md_theme_dark_surfaceContainer = Color(0xFF26221C)\nprivate val md_theme_dark_surfaceContainerHigh = Color(0xFF2B271F)\nprivate val md_theme_dark_surfaceContainerHighest = Color(0xFF2F2B23)\n\n\nval LightAmberTheme = lightColorScheme(\n    primary = md_theme_light_primary,\n    onPrimary = md_theme_light_onPrimary,\n    primaryContainer = md_theme_light_primaryContainer,\n    onPrimaryContainer = md_theme_light_onPrimaryContainer,\n    secondary = md_theme_light_secondary,\n    onSecondary = md_theme_light_onSecondary,\n    secondaryContainer = md_theme_light_secondaryContainer,\n    onSecondaryContainer = md_theme_light_onSecondaryContainer,\n    tertiary = md_theme_light_tertiary,\n    onTertiary = md_theme_light_onTertiary,\n    tertiaryContainer = md_theme_light_tertiaryContainer,\n    onTertiaryContainer = md_theme_light_onTertiaryContainer,\n    error = md_theme_light_error,\n    errorContainer = md_theme_light_errorContainer,\n    onError = md_theme_light_onError,\n    onErrorContainer = md_theme_light_onErrorContainer,\n    background = md_theme_light_background,\n    onBackground = md_theme_light_onBackground,\n    surface = md_theme_light_surface,\n    onSurface = md_theme_light_onSurface,\n    surfaceVariant = md_theme_light_surfaceVariant,\n    onSurfaceVariant = md_theme_light_onSurfaceVariant,\n    outline = md_theme_light_outline,\n    inverseOnSurface = md_theme_light_inverseOnSurface,\n    inverseSurface = md_theme_light_inverseSurface,\n    inversePrimary = md_theme_light_inversePrimary,\n    surfaceTint = md_theme_light_surfaceTint,\n    outlineVariant = md_theme_light_outlineVariant,\n    surfaceContainerLowest = md_theme_light_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_light_surfaceContainerLow,\n    surfaceContainer = md_theme_light_surfaceContainer,\n    surfaceContainerHigh = md_theme_light_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_light_surfaceContainerHighest,\n    scrim = md_theme_light_scrim,\n)\n\nval DarkAmberTheme = darkColorScheme(\n    primary = md_theme_dark_primary,\n    onPrimary = md_theme_dark_onPrimary,\n    primaryContainer = md_theme_dark_primaryContainer,\n    onPrimaryContainer = md_theme_dark_onPrimaryContainer,\n    secondary = md_theme_dark_secondary,\n    onSecondary = md_theme_dark_onSecondary,\n    secondaryContainer = md_theme_dark_secondaryContainer,\n    onSecondaryContainer = md_theme_dark_onSecondaryContainer,\n    tertiary = md_theme_dark_tertiary,\n    onTertiary = md_theme_dark_onTertiary,\n    tertiaryContainer = md_theme_dark_tertiaryContainer,\n    onTertiaryContainer = md_theme_dark_onTertiaryContainer,\n    error = md_theme_dark_error,\n    errorContainer = md_theme_dark_errorContainer,\n    onError = md_theme_dark_onError,\n    onErrorContainer = md_theme_dark_onErrorContainer,\n    background = md_theme_dark_background,\n    onBackground = md_theme_dark_onBackground,\n    surface = md_theme_dark_surface,\n    onSurface = md_theme_dark_onSurface,\n    surfaceVariant = md_theme_dark_surfaceVariant,\n    onSurfaceVariant = md_theme_dark_onSurfaceVariant,\n    outline = md_theme_dark_outline,\n    inverseOnSurface = md_theme_dark_inverseOnSurface,\n    inverseSurface = md_theme_dark_inverseSurface,\n    inversePrimary = md_theme_dark_inversePrimary,\n    surfaceTint = md_theme_dark_surfaceTint,\n    outlineVariant = md_theme_dark_outlineVariant,\n    surfaceContainerLowest = md_theme_dark_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_dark_surfaceContainerLow,\n    surfaceContainer = md_theme_dark_surfaceContainer,\n    surfaceContainerHigh = md_theme_dark_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_dark_surfaceContainerHighest,\n    scrim = md_theme_dark_scrim,\n)"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/BackgroundConfig.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport android.content.Context\nimport android.net.Uri\nimport android.os.Environment\nimport android.util.Log\nimport androidx.compose.runtime.*\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.util.SafeUriResolver\nimport java.io.File\nimport java.io.FileOutputStream\n\n/**\n * 背景配置管理类\n */\nobject BackgroundConfig {\n    // State\n    var customBackgroundUri: String? by mutableStateOf(null)\n        private set\n    var isCustomBackgroundEnabled: Boolean by mutableStateOf(false)\n        private set\n    var customBackgroundOpacity: Float by mutableStateOf(0.5f)\n        private set\n    var customBackgroundBlur: Float by mutableStateOf(0.2f)\n        private set\n    var customBackgroundDim: Float by mutableStateOf(0.0f)\n        private set\n    var isDualBackgroundDimEnabled: Boolean by mutableStateOf(true)\n        private set\n    var customBackgroundDayDim: Float by mutableStateOf(0.0f)\n        private set\n    var customBackgroundNightDim: Float by mutableStateOf(0.5f)\n        private set\n\n    // Video Background\n    var videoBackgroundUri: String? by mutableStateOf(null)\n        private set\n    var isVideoBackgroundEnabled: Boolean by mutableStateOf(false)\n        private set\n    var videoVolume: Float by mutableStateOf(0f)\n        private set\n\n    // Grid Layout Working Card Background\n    var gridWorkingCardBackgroundUri: String? by mutableStateOf(null)\n        private set\n    var isGridWorkingCardBackgroundEnabled: Boolean by mutableStateOf(false)\n        private set\n    var gridWorkingCardBackgroundOpacity: Float by mutableStateOf(1.0f)\n        private set\n    var gridWorkingCardBackgroundDim: Float by mutableStateOf(0.3f)\n        private set\n    var isGridDualOpacityEnabled: Boolean by mutableStateOf(false)\n        private set\n    var gridWorkingCardBackgroundDayOpacity: Float by mutableStateOf(1.0f)\n        private set\n    var gridWorkingCardBackgroundNightOpacity: Float by mutableStateOf(1.0f)\n        private set\n    var isGridWorkingCardCheckHidden: Boolean by mutableStateOf(false)\n        private set\n    var isGridWorkingCardTextHidden: Boolean by mutableStateOf(false)\n        private set\n    var isGridWorkingCardModeHidden: Boolean by mutableStateOf(false)\n        private set\n\n    // List Layout Working Card Config\n    var isListWorkingCardModeHidden: Boolean by mutableStateOf(false)\n        private set\n\n    var customBadgeTextMode: Int by mutableStateOf(0)\n        private set\n\n    // Banner Settings\n    var isBannerEnabled: Boolean by mutableStateOf(true)\n        private set\n    var isFolkBannerEnabled: Boolean by mutableStateOf(true)\n        private set\n    var isBannerCustomOpacityEnabled: Boolean by mutableStateOf(false)\n        private set\n    var bannerCustomOpacity: Float by mutableStateOf(0.5f)\n        private set\n\n    // Banner API Mode Settings\n    var isBannerApiModeEnabled: Boolean by mutableStateOf(false)\n        private set\n    var bannerApiSource: String by mutableStateOf(\"\")\n        private set\n\n    // Multi-Background Mode\n    var isMultiBackgroundEnabled: Boolean by mutableStateOf(false)\n        private set\n    var homeBackgroundUri: String? by mutableStateOf(null)\n        private set\n    var kernelBackgroundUri: String? by mutableStateOf(null)\n        private set\n    var superuserBackgroundUri: String? by mutableStateOf(null)\n        private set\n    var systemModuleBackgroundUri: String? by mutableStateOf(null)\n        private set\n    var settingsBackgroundUri: String? by mutableStateOf(null)\n        private set\n\n    // Advanced Title Style\n    var isAdvancedTitleStyleEnabled: Boolean by mutableStateOf(false)\n        private set\n    var titleImageUri: String? by mutableStateOf(null)\n        private set\n    var titleImageDayOpacity: Float by mutableStateOf(1.0f)\n        private set\n    var titleImageNightOpacity: Float by mutableStateOf(1.0f)\n        private set\n    var titleImageDim: Float by mutableStateOf(0.0f)\n        private set\n    var titleImageOffsetX: Float by mutableStateOf(0f)\n        private set\n\n    // NavBar Glass Effect (Floating mode only)\n    var isNavBarGlassEnabled: Boolean by mutableStateOf(true)\n        private set\n    var navBarGlassBlurStrength: Float by mutableStateOf(0.7f)\n        private set\n    var navBarGlassTransparency: Float by mutableStateOf(0.3f)\n        private set\n    var navBarGlassHighlightStrength: Float by mutableStateOf(0.5f)\n        private set\n    var isNavBarGlassSpecularEnabled: Boolean by mutableStateOf(true)\n        private set\n    var isNavBarGlassInnerGlowEnabled: Boolean by mutableStateOf(true)\n        private set\n    var isNavBarGlassBorderEnabled: Boolean by mutableStateOf(true)\n        private set\n    \n    private const val PREFS_NAME = \"background_settings\"\n    private const val KEY_CUSTOM_BACKGROUND_URI = \"custom_background_uri\"\n    private const val KEY_CUSTOM_BACKGROUND_ENABLED = \"custom_background_enabled\"\n    private const val KEY_CUSTOM_BACKGROUND_OPACITY = \"custom_background_opacity\"\n    private const val KEY_CUSTOM_BACKGROUND_BLUR = \"custom_background_blur\"\n    private const val KEY_CUSTOM_BACKGROUND_DIM = \"custom_background_dim\"\n    private const val KEY_CUSTOM_BACKGROUND_DUAL_DIM_ENABLED = \"custom_background_dual_dim_enabled\"\n    private const val KEY_CUSTOM_BACKGROUND_DAY_DIM = \"custom_background_day_dim\"\n    private const val KEY_CUSTOM_BACKGROUND_NIGHT_DIM = \"custom_background_night_dim\"\n    \n    private const val KEY_VIDEO_BACKGROUND_URI = \"video_background_uri\"\n    private const val KEY_VIDEO_BACKGROUND_ENABLED = \"video_background_enabled\"\n    private const val KEY_VIDEO_VOLUME = \"video_volume\"\n\n    private const val KEY_GRID_WORKING_CARD_BACKGROUND_URI = \"grid_working_card_background_uri\"\n    private const val KEY_GRID_WORKING_CARD_BACKGROUND_ENABLED = \"grid_working_card_background_enabled\"\n    private const val KEY_GRID_WORKING_CARD_BACKGROUND_OPACITY = \"grid_working_card_background_opacity\"\n    private const val KEY_GRID_WORKING_CARD_BACKGROUND_DIM = \"grid_working_card_background_dim\"\n    private const val KEY_GRID_WORKING_CARD_DUAL_OPACITY_ENABLED = \"grid_working_card_dual_opacity_enabled\"\n    private const val KEY_GRID_WORKING_CARD_BACKGROUND_DAY_OPACITY = \"grid_working_card_background_day_opacity\"\n    private const val KEY_GRID_WORKING_CARD_BACKGROUND_NIGHT_OPACITY = \"grid_working_card_background_night_opacity\"\n    private const val KEY_GRID_WORKING_CARD_CHECK_HIDDEN = \"grid_working_card_check_hidden\"\n    private const val KEY_GRID_WORKING_CARD_TEXT_HIDDEN = \"grid_working_card_text_hidden\"\n    private const val KEY_GRID_WORKING_CARD_MODE_HIDDEN = \"grid_working_card_mode_hidden\"\n    \n    private const val KEY_LIST_WORKING_CARD_MODE_HIDDEN = \"list_working_card_mode_hidden\"\n    private const val KEY_CUSTOM_BADGE_TEXT_MODE = \"custom_badge_text_mode\"\n\n    private const val KEY_BANNER_ENABLED = \"banner_enabled\"\n    private const val KEY_FOLK_BANNER_ENABLED = \"folk_banner_enabled\"\n    private const val KEY_BANNER_CUSTOM_OPACITY_ENABLED = \"banner_custom_opacity_enabled\"\n    private const val KEY_BANNER_CUSTOM_OPACITY = \"banner_custom_opacity\"\n\n    private const val KEY_BANNER_API_MODE_ENABLED = \"banner_api_mode_enabled\"\n    private const val KEY_BANNER_API_SOURCE = \"banner_api_source\"\n    // Legacy key for migration\n    private const val KEY_FOLK_BANNER_API_SOURCE = \"folk_banner_api_source\"\n\n    private const val KEY_MULTI_BACKGROUND_ENABLED = \"multi_background_enabled\"\n    private const val KEY_HOME_BACKGROUND_URI = \"home_background_uri\"\n    private const val KEY_KERNEL_BACKGROUND_URI = \"kernel_background_uri\"\n    private const val KEY_SUPERUSER_BACKGROUND_URI = \"superuser_background_uri\"\n    private const val KEY_SYSTEM_MODULE_BACKGROUND_URI = \"system_module_background_uri\"\n    private const val KEY_SETTINGS_BACKGROUND_URI = \"settings_background_uri\"\n\n    private const val KEY_ADVANCED_TITLE_STYLE_ENABLED = \"advanced_title_style_enabled\"\n    private const val KEY_TITLE_IMAGE_URI = \"title_image_uri\"\n    private const val KEY_TITLE_IMAGE_DAY_OPACITY = \"title_image_day_opacity\"\n    private const val KEY_TITLE_IMAGE_NIGHT_OPACITY = \"title_image_night_opacity\"\n    private const val KEY_TITLE_IMAGE_DIM = \"title_image_dim\"\n    private const val KEY_TITLE_IMAGE_OFFSET_X = \"title_image_offset_x\"\n\n    private const val KEY_NAVBAR_GLASS_ENABLED = \"navbar_glass_enabled\"\n    private const val KEY_NAVBAR_GLASS_BLUR_STRENGTH = \"navbar_glass_blur_strength\"\n    private const val KEY_NAVBAR_GLASS_TRANSPARENCY = \"navbar_glass_transparency\"\n    private const val KEY_NAVBAR_GLASS_HIGHLIGHT_STRENGTH = \"navbar_glass_highlight_strength\"\n    private const val KEY_NAVBAR_GLASS_SPECULAR_ENABLED = \"navbar_glass_specular_enabled\"\n    private const val KEY_NAVBAR_GLASS_INNER_GLOW_ENABLED = \"navbar_glass_inner_glow_enabled\"\n    private const val KEY_NAVBAR_GLASS_BORDER_ENABLED = \"navbar_glass_border_enabled\"\n\n    private const val TAG = \"BackgroundConfig\"\n    \n    /**\n     * 更新自定义背景URI\n     */\n    fun updateCustomBackgroundUri(uri: String?) {\n        customBackgroundUri = uri\n        isCustomBackgroundEnabled = uri != null\n    }\n\n    /**\n     * 更新视频背景URI\n     */\n    fun updateVideoBackgroundUri(uri: String?) {\n        videoBackgroundUri = uri\n        isVideoBackgroundEnabled = uri != null\n    }\n\n    /**\n     * 启用/禁用视频背景\n     */\n    fun setVideoBackgroundEnabledState(enabled: Boolean) {\n        isVideoBackgroundEnabled = enabled\n    }\n\n    /**\n     * 设置视频背景音量\n     */\n    fun setVideoVolumeValue(volume: Float) {\n        videoVolume = volume\n    }\n\n    /**\n     * 更新Grid布局工作中卡片背景URI\n     */\n    fun updateGridWorkingCardBackgroundUri(uri: String?) {\n        gridWorkingCardBackgroundUri = uri\n        isGridWorkingCardBackgroundEnabled = uri != null\n    }\n    \n    /**\n     * 启用/禁用自定义背景\n     */\n    fun setCustomBackgroundEnabledState(enabled: Boolean) {\n        isCustomBackgroundEnabled = enabled\n    }\n\n    /**\n     * 启用/禁用Grid布局工作中卡片背景\n     */\n    fun setGridWorkingCardBackgroundEnabledState(enabled: Boolean) {\n        isGridWorkingCardBackgroundEnabled = enabled\n    }\n\n    /**\n     * 设置Grid布局工作中卡片背景不透明度\n     */\n    fun setGridWorkingCardBackgroundOpacityValue(opacity: Float) {\n        gridWorkingCardBackgroundOpacity = opacity\n    }\n\n    fun setGridDualOpacityEnabledState(enabled: Boolean) {\n        isGridDualOpacityEnabled = enabled\n    }\n\n    fun setGridWorkingCardBackgroundDayOpacityValue(opacity: Float) {\n        gridWorkingCardBackgroundDayOpacity = opacity\n    }\n\n    fun setGridWorkingCardBackgroundNightOpacityValue(opacity: Float) {\n        gridWorkingCardBackgroundNightOpacity = opacity\n    }\n\n    fun getEffectiveGridBackgroundOpacity(isDarkTheme: Boolean): Float {\n        return if (isGridDualOpacityEnabled) {\n            if (isDarkTheme) gridWorkingCardBackgroundNightOpacity else gridWorkingCardBackgroundDayOpacity\n        } else {\n            gridWorkingCardBackgroundOpacity\n        }\n    }\n\n    /**\n     * 设置Grid布局工作中卡片背景暗度\n     */\n    fun setGridWorkingCardBackgroundDimValue(dim: Float) {\n        gridWorkingCardBackgroundDim = dim\n    }\n\n    /**\n     * 设置Grid布局工作中卡片Check隐藏状态\n     */\n    fun setGridWorkingCardCheckHiddenState(hidden: Boolean) {\n        isGridWorkingCardCheckHidden = hidden\n    }\n\n    /**\n     * 设置Grid布局工作中卡片文字隐藏状态\n     */\n    fun setGridWorkingCardTextHiddenState(hidden: Boolean) {\n        isGridWorkingCardTextHidden = hidden\n    }\n\n    /**\n     * 设置Grid布局工作中卡片Full/Half隐藏状态\n     */\n    fun setGridWorkingCardModeHiddenState(hidden: Boolean) {\n        isGridWorkingCardModeHidden = hidden\n    }\n\n    /**\n     * 设置List布局工作中卡片Full/Half隐藏状态\n     */\n    fun setListWorkingCardModeHiddenState(hidden: Boolean) {\n        isListWorkingCardModeHidden = hidden\n    }\n\n    fun setCustomBadgeTextModeValue(mode: Int) {\n        customBadgeTextMode = mode\n    }\n\n    fun getCustomBadgeText(): String? {\n        return when (customBadgeTextMode) {\n            1 -> \"LKM\"\n            2 -> \"GKI\"\n            3 -> \"N-GKI\"\n            4 -> \"OKI\"\n            5 -> \"Built-in\"\n            else -> null\n        }\n    }\n\n    /**\n     * 设置自定义背景不透明度\n     */\n    fun setCustomBackgroundOpacityValue(opacity: Float) {\n        customBackgroundOpacity = opacity\n    }\n\n    /**\n     * 启用/禁用横幅\n     */\n    fun setBannerEnabledState(enabled: Boolean) {\n        isBannerEnabled = enabled\n    }\n\n    /**\n     * 启用/禁用FolkBanner\n     */\n    fun setFolkBannerEnabledState(enabled: Boolean) {\n        isFolkBannerEnabled = enabled\n    }\n\n    /**\n     * 启用/禁用横幅自定义透明度\n     */\n    fun setBannerCustomOpacityEnabledState(enabled: Boolean) {\n        isBannerCustomOpacityEnabled = enabled\n    }\n\n    /**\n     * 设置横幅自定义透明度\n     */\n    fun setBannerCustomOpacityValue(opacity: Float) {\n        bannerCustomOpacity = opacity\n    }\n\n    /**\n     * 启用/禁用横幅API模式\n     */\n    fun setBannerApiModeEnabledState(enabled: Boolean) {\n        isBannerApiModeEnabled = enabled\n    }\n\n    /**\n     * 设置横幅API源（URL或本地路径）\n     */\n    fun setBannerApiSourceValue(source: String) {\n        bannerApiSource = source\n    }\n\n    /**\n     * 获取有效的横幅API源\n     */\n    fun getEffectiveBannerApiSource(): String {\n        return bannerApiSource\n    }\n\n    /**\n     * 设置自定义背景模糊度\n     */\n    fun setCustomBackgroundBlurValue(blur: Float) {\n        customBackgroundBlur = blur\n    }\n\n    /**\n     * 设置自定义背景暗度\n     */\n    fun setCustomBackgroundDimValue(dim: Float) {\n        customBackgroundDim = dim\n    }\n\n    fun setDualBackgroundDimEnabledState(enabled: Boolean) {\n        isDualBackgroundDimEnabled = enabled\n    }\n\n    fun setCustomBackgroundDayDimValue(dim: Float) {\n        customBackgroundDayDim = dim\n    }\n\n    fun setCustomBackgroundNightDimValue(dim: Float) {\n        customBackgroundNightDim = dim\n    }\n\n    fun getEffectiveBackgroundDim(isDarkTheme: Boolean): Float {\n        return if (isDualBackgroundDimEnabled) {\n            if (isDarkTheme) customBackgroundNightDim else customBackgroundDayDim\n        } else {\n            customBackgroundDim\n        }\n    }\n\n    // Multi-Background Setters\n    fun setMultiBackgroundEnabledState(enabled: Boolean) {\n        isMultiBackgroundEnabled = enabled\n    }\n\n    fun updateHomeBackgroundUri(uri: String?) {\n        homeBackgroundUri = uri\n    }\n\n    fun updateKernelBackgroundUri(uri: String?) {\n        kernelBackgroundUri = uri\n    }\n\n    fun updateSuperuserBackgroundUri(uri: String?) {\n        superuserBackgroundUri = uri\n    }\n\n    fun updateSystemModuleBackgroundUri(uri: String?) {\n        systemModuleBackgroundUri = uri\n    }\n\n    fun updateSettingsBackgroundUri(uri: String?) {\n        settingsBackgroundUri = uri\n    }\n\n    // Advanced Title Style Setters\n    fun setAdvancedTitleStyleEnabledState(enabled: Boolean) {\n        isAdvancedTitleStyleEnabled = enabled\n    }\n\n    fun updateTitleImageUri(uri: String?) {\n        titleImageUri = uri\n    }\n\n    fun setTitleImageDayOpacityValue(opacity: Float) {\n        titleImageDayOpacity = opacity\n    }\n\n    fun setTitleImageNightOpacityValue(opacity: Float) {\n        titleImageNightOpacity = opacity\n    }\n\n    fun getEffectiveTitleImageOpacity(isDarkTheme: Boolean): Float {\n        return if (isDarkTheme) titleImageNightOpacity else titleImageDayOpacity\n    }\n\n    fun setTitleImageDimValue(dim: Float) {\n        titleImageDim = dim\n    }\n\n    fun getEffectiveTitleImageDim(isDarkTheme: Boolean): Float {\n        return titleImageDim\n    }\n\n    fun setTitleImageOffsetXValue(offset: Float) {\n        titleImageOffsetX = offset\n    }\n\n    fun setNavBarGlassEnabledState(enabled: Boolean) {\n        isNavBarGlassEnabled = enabled\n    }\n\n    fun setNavBarGlassBlurStrengthValue(strength: Float) {\n        navBarGlassBlurStrength = strength\n    }\n\n    fun setNavBarGlassTransparencyValue(transparency: Float) {\n        navBarGlassTransparency = transparency\n    }\n\n    fun setNavBarGlassHighlightStrengthValue(strength: Float) {\n        navBarGlassHighlightStrength = strength\n    }\n\n    fun setNavBarGlassSpecularEnabledState(enabled: Boolean) {\n        isNavBarGlassSpecularEnabled = enabled\n    }\n\n    fun setNavBarGlassInnerGlowEnabledState(enabled: Boolean) {\n        isNavBarGlassInnerGlowEnabled = enabled\n    }\n\n    fun setNavBarGlassBorderEnabledState(enabled: Boolean) {\n        isNavBarGlassBorderEnabled = enabled\n    }\n    \n    /**\n     * 保存配置到SharedPreferences\n     */\n    fun save(context: Context) {\n        val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)\n        prefs.edit().apply {\n            putString(KEY_CUSTOM_BACKGROUND_URI, customBackgroundUri)\n            putBoolean(KEY_CUSTOM_BACKGROUND_ENABLED, isCustomBackgroundEnabled)\n            putFloat(KEY_CUSTOM_BACKGROUND_OPACITY, customBackgroundOpacity)\n            putFloat(KEY_CUSTOM_BACKGROUND_BLUR, customBackgroundBlur)\n            putFloat(KEY_CUSTOM_BACKGROUND_DIM, customBackgroundDim)\n            putBoolean(KEY_CUSTOM_BACKGROUND_DUAL_DIM_ENABLED, isDualBackgroundDimEnabled)\n            putFloat(KEY_CUSTOM_BACKGROUND_DAY_DIM, customBackgroundDayDim)\n            putFloat(KEY_CUSTOM_BACKGROUND_NIGHT_DIM, customBackgroundNightDim)\n            \n            putString(KEY_VIDEO_BACKGROUND_URI, videoBackgroundUri)\n            putBoolean(KEY_VIDEO_BACKGROUND_ENABLED, isVideoBackgroundEnabled)\n            putFloat(KEY_VIDEO_VOLUME, videoVolume)\n\n            putString(KEY_GRID_WORKING_CARD_BACKGROUND_URI, gridWorkingCardBackgroundUri)\n            putBoolean(KEY_GRID_WORKING_CARD_BACKGROUND_ENABLED, isGridWorkingCardBackgroundEnabled)\n            putFloat(KEY_GRID_WORKING_CARD_BACKGROUND_OPACITY, gridWorkingCardBackgroundOpacity)\n            putFloat(KEY_GRID_WORKING_CARD_BACKGROUND_DIM, gridWorkingCardBackgroundDim)\n            putBoolean(KEY_GRID_WORKING_CARD_DUAL_OPACITY_ENABLED, isGridDualOpacityEnabled)\n            putFloat(KEY_GRID_WORKING_CARD_BACKGROUND_DAY_OPACITY, gridWorkingCardBackgroundDayOpacity)\n            putFloat(KEY_GRID_WORKING_CARD_BACKGROUND_NIGHT_OPACITY, gridWorkingCardBackgroundNightOpacity)\n            putBoolean(KEY_GRID_WORKING_CARD_CHECK_HIDDEN, isGridWorkingCardCheckHidden)\n            putBoolean(KEY_GRID_WORKING_CARD_TEXT_HIDDEN, isGridWorkingCardTextHidden)\n            putBoolean(KEY_GRID_WORKING_CARD_MODE_HIDDEN, isGridWorkingCardModeHidden)\n\n            putBoolean(KEY_LIST_WORKING_CARD_MODE_HIDDEN, isListWorkingCardModeHidden)\n            putInt(KEY_CUSTOM_BADGE_TEXT_MODE, customBadgeTextMode)\n\n            putBoolean(KEY_BANNER_ENABLED, isBannerEnabled)\n            putBoolean(KEY_FOLK_BANNER_ENABLED, isFolkBannerEnabled)\n            putBoolean(KEY_BANNER_CUSTOM_OPACITY_ENABLED, isBannerCustomOpacityEnabled)\n            putFloat(KEY_BANNER_CUSTOM_OPACITY, bannerCustomOpacity)\n\n            putBoolean(KEY_BANNER_API_MODE_ENABLED, isBannerApiModeEnabled)\n            putString(KEY_BANNER_API_SOURCE, bannerApiSource)\n            // Remove legacy key\n            remove(KEY_FOLK_BANNER_API_SOURCE)\n\n            putBoolean(KEY_MULTI_BACKGROUND_ENABLED, isMultiBackgroundEnabled)\n            putString(KEY_HOME_BACKGROUND_URI, homeBackgroundUri)\n            putString(KEY_KERNEL_BACKGROUND_URI, kernelBackgroundUri)\n            putString(KEY_SUPERUSER_BACKGROUND_URI, superuserBackgroundUri)\n            putString(KEY_SYSTEM_MODULE_BACKGROUND_URI, systemModuleBackgroundUri)\n            putString(KEY_SETTINGS_BACKGROUND_URI, settingsBackgroundUri)\n\n            putBoolean(KEY_ADVANCED_TITLE_STYLE_ENABLED, isAdvancedTitleStyleEnabled)\n            putString(KEY_TITLE_IMAGE_URI, titleImageUri)\n            putFloat(KEY_TITLE_IMAGE_DAY_OPACITY, titleImageDayOpacity)\n            putFloat(KEY_TITLE_IMAGE_NIGHT_OPACITY, titleImageNightOpacity)\n            putFloat(KEY_TITLE_IMAGE_DIM, titleImageDim)\n            putFloat(KEY_TITLE_IMAGE_OFFSET_X, titleImageOffsetX)\n\n            putBoolean(KEY_NAVBAR_GLASS_ENABLED, isNavBarGlassEnabled)\n            putFloat(KEY_NAVBAR_GLASS_BLUR_STRENGTH, navBarGlassBlurStrength)\n            putFloat(KEY_NAVBAR_GLASS_TRANSPARENCY, navBarGlassTransparency)\n            putFloat(KEY_NAVBAR_GLASS_HIGHLIGHT_STRENGTH, navBarGlassHighlightStrength)\n            putBoolean(KEY_NAVBAR_GLASS_SPECULAR_ENABLED, isNavBarGlassSpecularEnabled)\n            putBoolean(KEY_NAVBAR_GLASS_INNER_GLOW_ENABLED, isNavBarGlassInnerGlowEnabled)\n            putBoolean(KEY_NAVBAR_GLASS_BORDER_ENABLED, isNavBarGlassBorderEnabled)\n\n            apply()\n        }\n    }\n    \n    /**\n     * 从SharedPreferences加载配置\n     */\n    fun load(context: Context) {\n        val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)\n        val uri = prefs.getString(KEY_CUSTOM_BACKGROUND_URI, null)\n        val enabled = prefs.getBoolean(KEY_CUSTOM_BACKGROUND_ENABLED, false)\n        val opacity = prefs.getFloat(KEY_CUSTOM_BACKGROUND_OPACITY, 0.5f)\n        val blur = prefs.getFloat(KEY_CUSTOM_BACKGROUND_BLUR, 0.2f)\n        val dim = prefs.getFloat(KEY_CUSTOM_BACKGROUND_DIM, 0.0f)\n        val dualDimEnabled = prefs.getBoolean(KEY_CUSTOM_BACKGROUND_DUAL_DIM_ENABLED, true)\n        val dayDim = prefs.getFloat(KEY_CUSTOM_BACKGROUND_DAY_DIM, 0.0f)\n        val nightDim = prefs.getFloat(KEY_CUSTOM_BACKGROUND_NIGHT_DIM, 0.5f)\n        \n        val videoUri = prefs.getString(KEY_VIDEO_BACKGROUND_URI, null)\n        val videoEnabled = prefs.getBoolean(KEY_VIDEO_BACKGROUND_ENABLED, false)\n        val videoVol = prefs.getFloat(KEY_VIDEO_VOLUME, 0f)\n\n        val gridUri = prefs.getString(KEY_GRID_WORKING_CARD_BACKGROUND_URI, null)\n        val gridEnabled = prefs.getBoolean(KEY_GRID_WORKING_CARD_BACKGROUND_ENABLED, false)\n        val gridOpacity = prefs.getFloat(KEY_GRID_WORKING_CARD_BACKGROUND_OPACITY, 1.0f)\n        val gridDim = prefs.getFloat(KEY_GRID_WORKING_CARD_BACKGROUND_DIM, 0.3f)\n        val gridDualOpacityEnabled = prefs.getBoolean(KEY_GRID_WORKING_CARD_DUAL_OPACITY_ENABLED, false)\n        val gridDayOpacity = prefs.getFloat(KEY_GRID_WORKING_CARD_BACKGROUND_DAY_OPACITY, 1.0f)\n        val gridNightOpacity = prefs.getFloat(KEY_GRID_WORKING_CARD_BACKGROUND_NIGHT_OPACITY, 1.0f)\n        val gridCheckHidden = prefs.getBoolean(KEY_GRID_WORKING_CARD_CHECK_HIDDEN, false)\n        val gridTextHidden = prefs.getBoolean(KEY_GRID_WORKING_CARD_TEXT_HIDDEN, false)\n        val gridModeHidden = prefs.getBoolean(KEY_GRID_WORKING_CARD_MODE_HIDDEN, false)\n\n        val listModeHidden = prefs.getBoolean(KEY_LIST_WORKING_CARD_MODE_HIDDEN, false)\n        val badgeTextMode = prefs.getInt(KEY_CUSTOM_BADGE_TEXT_MODE, 0)\n\n        val bannerEnabled = prefs.getBoolean(KEY_BANNER_ENABLED, true)\n        val folkBannerEnabled = prefs.getBoolean(KEY_FOLK_BANNER_ENABLED, true)\n        val bannerCustomOpacityEnabled = prefs.getBoolean(KEY_BANNER_CUSTOM_OPACITY_ENABLED, false)\n        val bannerCustomOpacity = prefs.getFloat(KEY_BANNER_CUSTOM_OPACITY, 0.5f)\n\n        val bannerApiModeEnabled = prefs.getBoolean(KEY_BANNER_API_MODE_ENABLED, false)\n        val bannerApiSourceValue = prefs.getString(KEY_BANNER_API_SOURCE, \"\") ?: \"\"\n        // Migration logic: if folk banner source exists, use it and it will be saved as main source later\n        val folkBannerApiSourceValue = prefs.getString(KEY_FOLK_BANNER_API_SOURCE, \"\") ?: \"\"\n        val effectiveApiSource = if (folkBannerApiSourceValue.isNotBlank()) folkBannerApiSourceValue else bannerApiSourceValue\n\n        val multiEnabled = prefs.getBoolean(KEY_MULTI_BACKGROUND_ENABLED, false)\n        val homeUri = prefs.getString(KEY_HOME_BACKGROUND_URI, null)\n        val kernelUri = prefs.getString(KEY_KERNEL_BACKGROUND_URI, null)\n        val superuserUri = prefs.getString(KEY_SUPERUSER_BACKGROUND_URI, null)\n        val systemModuleUri = prefs.getString(KEY_SYSTEM_MODULE_BACKGROUND_URI, null)\n        val settingsUri = prefs.getString(KEY_SETTINGS_BACKGROUND_URI, null)\n\n        val advancedTitleStyleEnabled = prefs.getBoolean(KEY_ADVANCED_TITLE_STYLE_ENABLED, false)\n        val titleImageUriValue = prefs.getString(KEY_TITLE_IMAGE_URI, null)\n        val titleDayOpacity = prefs.getFloat(KEY_TITLE_IMAGE_DAY_OPACITY, 1.0f)\n        val titleNightOpacity = prefs.getFloat(KEY_TITLE_IMAGE_NIGHT_OPACITY, 1.0f)\n        val titleDim = prefs.getFloat(KEY_TITLE_IMAGE_DIM, 0.0f)\n        val titleOffsetX = prefs.getFloat(KEY_TITLE_IMAGE_OFFSET_X, 0f)\n\n        val navBarGlassEnabled = prefs.getBoolean(KEY_NAVBAR_GLASS_ENABLED, true)\n        val prefsNavBarGlassBlurStrength = prefs.getFloat(KEY_NAVBAR_GLASS_BLUR_STRENGTH, 0.7f)\n        val prefsNavBarGlassTransparency = prefs.getFloat(KEY_NAVBAR_GLASS_TRANSPARENCY, 0.3f)\n        val prefsNavBarGlassHighlightStrength = prefs.getFloat(KEY_NAVBAR_GLASS_HIGHLIGHT_STRENGTH, 0.5f)\n        val navBarGlassSpecularEnabled = prefs.getBoolean(KEY_NAVBAR_GLASS_SPECULAR_ENABLED, true)\n        val navBarGlassInnerGlowEnabled = prefs.getBoolean(KEY_NAVBAR_GLASS_INNER_GLOW_ENABLED, true)\n        val navBarGlassBorderEnabled = prefs.getBoolean(KEY_NAVBAR_GLASS_BORDER_ENABLED, true)\n        \n        Log.d(TAG, \"加载背景配置: URI=$uri, enabled=$enabled, opacity=$opacity, dim=$dim\")\n        \n        customBackgroundUri = uri\n        isCustomBackgroundEnabled = enabled\n        customBackgroundOpacity = opacity\n        customBackgroundBlur = blur\n        customBackgroundDim = dim\n        isDualBackgroundDimEnabled = dualDimEnabled\n        customBackgroundDayDim = dayDim\n        customBackgroundNightDim = nightDim\n        \n        videoBackgroundUri = videoUri\n        isVideoBackgroundEnabled = videoEnabled\n        videoVolume = videoVol\n        \n        gridWorkingCardBackgroundUri = gridUri\n        isGridWorkingCardBackgroundEnabled = gridEnabled\n        gridWorkingCardBackgroundOpacity = gridOpacity\n        gridWorkingCardBackgroundDim = gridDim\n        isGridDualOpacityEnabled = gridDualOpacityEnabled\n        gridWorkingCardBackgroundDayOpacity = gridDayOpacity\n        gridWorkingCardBackgroundNightOpacity = gridNightOpacity\n        isGridWorkingCardCheckHidden = gridCheckHidden\n        isGridWorkingCardTextHidden = gridTextHidden\n        isGridWorkingCardModeHidden = gridModeHidden\n\n        isListWorkingCardModeHidden = listModeHidden\n        customBadgeTextMode = badgeTextMode\n\n        isBannerEnabled = bannerEnabled\n        isFolkBannerEnabled = folkBannerEnabled\n        isBannerCustomOpacityEnabled = bannerCustomOpacityEnabled\n        this.bannerCustomOpacity = bannerCustomOpacity\n\n        isBannerApiModeEnabled = bannerApiModeEnabled\n        bannerApiSource = effectiveApiSource\n\n        isMultiBackgroundEnabled = multiEnabled\n        homeBackgroundUri = homeUri\n        kernelBackgroundUri = kernelUri\n        superuserBackgroundUri = superuserUri\n        systemModuleBackgroundUri = systemModuleUri\n        settingsBackgroundUri = settingsUri\n\n        isAdvancedTitleStyleEnabled = advancedTitleStyleEnabled\n        titleImageUri = titleImageUriValue\n        titleImageDayOpacity = titleDayOpacity\n        titleImageNightOpacity = titleNightOpacity\n        titleImageDim = titleDim\n        titleImageOffsetX = titleOffsetX\n\n        isNavBarGlassEnabled = navBarGlassEnabled\n        navBarGlassBlurStrength = prefsNavBarGlassBlurStrength\n        navBarGlassTransparency = prefsNavBarGlassTransparency\n        navBarGlassHighlightStrength = prefsNavBarGlassHighlightStrength\n        isNavBarGlassSpecularEnabled = navBarGlassSpecularEnabled\n        isNavBarGlassInnerGlowEnabled = navBarGlassInnerGlowEnabled\n        isNavBarGlassBorderEnabled = navBarGlassBorderEnabled\n    }\n    \n    /**\n     * 重置配置\n     */\n    fun reset() {\n        // Default to custom background disabled\n        customBackgroundUri = null\n        isCustomBackgroundEnabled = false\n        customBackgroundOpacity = 0.5f\n        customBackgroundBlur = 0.2f\n        customBackgroundDim = 0.0f\n        isDualBackgroundDimEnabled = true\n        customBackgroundDayDim = 0.0f\n        customBackgroundNightDim = 0.5f\n        \n        videoBackgroundUri = null\n        isVideoBackgroundEnabled = false\n        videoVolume = 0f\n        \n        gridWorkingCardBackgroundUri = null\n        isGridWorkingCardBackgroundEnabled = false\n        gridWorkingCardBackgroundOpacity = 1.0f\n        gridWorkingCardBackgroundDim = 0.3f\n        isGridDualOpacityEnabled = false\n        gridWorkingCardBackgroundDayOpacity = 1.0f\n        gridWorkingCardBackgroundNightOpacity = 1.0f\n        isGridWorkingCardCheckHidden = false\n        isGridWorkingCardTextHidden = false\n        isGridWorkingCardModeHidden = false\n\n        isListWorkingCardModeHidden = false\n        customBadgeTextMode = 0\n\n        isBannerEnabled = true\n        isFolkBannerEnabled = true\n        isBannerCustomOpacityEnabled = false\n        bannerCustomOpacity = 0.5f\n\n        isBannerApiModeEnabled = false\n        bannerApiSource = \"\"\n\n        isMultiBackgroundEnabled = false\n        homeBackgroundUri = null\n        kernelBackgroundUri = null\n        superuserBackgroundUri = null\n        systemModuleBackgroundUri = null\n        settingsBackgroundUri = null\n\n        isAdvancedTitleStyleEnabled = false\n        titleImageUri = null\n        titleImageDayOpacity = 1.0f\n        titleImageNightOpacity = 1.0f\n        titleImageDim = 0.0f\n        titleImageOffsetX = 0f\n\n        isNavBarGlassEnabled = true\n        navBarGlassBlurStrength = 0.7f\n        navBarGlassTransparency = 0.3f\n        navBarGlassHighlightStrength = 0.5f\n        isNavBarGlassSpecularEnabled = true\n        isNavBarGlassInnerGlowEnabled = true\n        isNavBarGlassBorderEnabled = true\n    }\n}\n\n/**\n * 背景管理器\n */\nobject BackgroundManager {\n    private const val TAG = \"BackgroundManager\"\n    private const val BACKGROUND_FILENAME = \"background.jpg\"\n    private const val VIDEO_BACKGROUND_FILENAME_BASE = \"video_background\"\n    private const val GRID_WORKING_CARD_BACKGROUND_FILENAME = \"grid_working_card_background.jpg\"\n\n    // Multi-Background Filenames\n    private const val HOME_BACKGROUND_FILENAME = \"background_home\"\n    private const val KERNEL_BACKGROUND_FILENAME = \"background_kernel\"\n    private const val SUPERUSER_BACKGROUND_FILENAME = \"background_superuser\"\n    private const val SYSTEM_MODULE_BACKGROUND_FILENAME = \"background_system_module\"\n    private const val SETTINGS_BACKGROUND_FILENAME = \"background_settings\"\n\n    private const val TITLE_IMAGE_FILENAME = \"title_image\"\n\n    /**\n     * 获取文件扩展名\n     */\n    private fun getFileExtension(context: Context, uri: Uri): String {\n        return try {\n            val mimeType = context.contentResolver.getType(uri)\n            when {\n                mimeType?.contains(\"gif\", true) == true -> \".gif\"\n                mimeType?.contains(\"png\", true) == true -> \".png\"\n                mimeType?.contains(\"webp\", true) == true -> \".webp\"\n                mimeType?.contains(\"video/mp4\", true) == true -> \".mp4\"\n                mimeType?.contains(\"video/webm\", true) == true -> \".webm\"\n                mimeType?.contains(\"video/x-matroska\", true) == true -> \".mkv\"\n                else -> \".jpg\"\n            }\n        } catch (e: Exception) {\n            \".jpg\"\n        }\n    }\n\n    /**\n     * 获取背景文件\n     */\n    private fun getBackgroundFile(context: Context, extension: String = \".jpg\"): File {\n        return File(context.filesDir, \"background$extension\")\n    }\n\n    private fun getGridWorkingCardBackgroundFile(context: Context, extension: String = \".jpg\"): File {\n        return File(context.filesDir, \"grid_working_card_background$extension\")\n    }\n\n    /**\n     * 清理旧的背景文件\n     */\n    private fun clearOldFiles(context: Context, baseName: String) {\n        val extensions = listOf(\".jpg\", \".png\", \".gif\", \".webp\", \".mp4\", \".webm\", \".mkv\")\n        extensions.forEach { ext ->\n            val file = File(context.filesDir, \"$baseName$ext\")\n            if (file.exists()) {\n                file.delete()\n            }\n        }\n    }\n    \n    /**\n     * 保存并应用自定义背景\n     */\n    suspend fun saveAndApplyCustomBackground(context: Context, uri: Uri): Boolean {\n        return try {\n            withContext(Dispatchers.IO) {\n                val extension = getFileExtension(context, uri)\n                // 清理旧文件\n                clearOldFiles(context, \"background\")\n                \n                val savedUri = saveImageToInternalStorage(context, uri, getBackgroundFile(context, extension))\n                if (savedUri != null) {\n                    Log.d(TAG, \"图片保存成功，URI: $savedUri\")\n                    BackgroundConfig.updateCustomBackgroundUri(savedUri.toString())\n                    BackgroundConfig.save(context)\n                    Log.d(TAG, \"背景配置保存成功，URI: ${BackgroundConfig.customBackgroundUri}, 启用状态: ${BackgroundConfig.isCustomBackgroundEnabled}\")\n                    true\n                } else {\n                    Log.e(TAG, \"图片保存失败\")\n                    false\n                }\n            }\n        } catch (e: Exception) {\n            Log.e(TAG, \"保存自定义背景失败: ${e.message}\", e)\n            false\n        }\n    }\n\n    /**\n     * 保存并应用视频背景\n     */\n    suspend fun saveAndApplyVideoBackground(context: Context, uri: Uri): Boolean {\n        return try {\n            withContext(Dispatchers.IO) {\n                val extension = getFileExtension(context, uri)\n                // 清理旧文件\n                clearOldFiles(context, VIDEO_BACKGROUND_FILENAME_BASE)\n                \n                val targetFile = File(context.filesDir, \"$VIDEO_BACKGROUND_FILENAME_BASE$extension\")\n                val savedUri = saveImageToInternalStorage(context, uri, targetFile)\n                if (savedUri != null) {\n                    Log.d(TAG, \"视频保存成功，URI: $savedUri\")\n                    BackgroundConfig.updateVideoBackgroundUri(savedUri.toString())\n                    BackgroundConfig.save(context)\n                    true\n                } else {\n                    Log.e(TAG, \"视频保存失败\")\n                    false\n                }\n            }\n        } catch (e: Exception) {\n            Log.e(TAG, \"保存视频背景失败: ${e.message}\", e)\n            false\n        }\n    }\n\n    /**\n     * 保存并应用Grid布局工作中卡片背景\n     */\n    suspend fun saveAndApplyGridWorkingCardBackground(context: Context, uri: Uri): Boolean {\n        return try {\n            withContext(Dispatchers.IO) {\n                val extension = getFileExtension(context, uri)\n                // 清理旧文件\n                clearOldFiles(context, \"grid_working_card_background\")\n                \n                val savedUri = saveImageToInternalStorage(context, uri, getGridWorkingCardBackgroundFile(context, extension))\n                if (savedUri != null) {\n                    Log.d(TAG, \"Grid卡片图片保存成功，URI: $savedUri\")\n                    BackgroundConfig.updateGridWorkingCardBackgroundUri(savedUri.toString())\n                    BackgroundConfig.save(context)\n                    true\n                } else {\n                    Log.e(TAG, \"Grid卡片图片保存失败\")\n                    false\n                }\n            }\n        } catch (e: Exception) {\n            Log.e(TAG, \"保存Grid卡片自定义背景失败: ${e.message}\", e)\n            false\n        }\n    }\n    \n    /**\n     * 清除自定义背景\n     */\n    fun clearCustomBackground(context: Context) {\n        try {\n            // 删除背景文件\n            clearOldFiles(context, \"background\")\n            \n            // 重置配置（只重置全局背景相关）\n            BackgroundConfig.updateCustomBackgroundUri(null)\n            BackgroundConfig.setCustomBackgroundEnabledState(false)\n            BackgroundConfig.save(context)\n        } catch (e: Exception) {\n            Log.e(TAG, \"清除自定义背景失败: ${e.message}\", e)\n        }\n    }\n\n    /**\n     * 清除视频背景\n     */\n    fun clearVideoBackground(context: Context) {\n        try {\n            clearOldFiles(context, VIDEO_BACKGROUND_FILENAME_BASE)\n            BackgroundConfig.updateVideoBackgroundUri(null)\n            BackgroundConfig.save(context)\n        } catch (e: Exception) {\n            Log.e(TAG, \"清除视频背景失败: ${e.message}\", e)\n        }\n    }\n\n    /**\n     * 清除Grid布局工作中卡片背景\n     */\n    fun clearGridWorkingCardBackground(context: Context) {\n        try {\n            // 删除背景文件\n            clearOldFiles(context, \"grid_working_card_background\")\n            \n            // 重置配置\n            BackgroundConfig.updateGridWorkingCardBackgroundUri(null)\n            BackgroundConfig.setGridWorkingCardBackgroundEnabledState(false)\n            BackgroundConfig.setGridWorkingCardBackgroundOpacityValue(1.0f)\n            BackgroundConfig.setGridWorkingCardBackgroundDimValue(0.3f)\n            BackgroundConfig.setGridDualOpacityEnabledState(false)\n            BackgroundConfig.setGridWorkingCardBackgroundDayOpacityValue(1.0f)\n            BackgroundConfig.setGridWorkingCardBackgroundNightOpacityValue(1.0f)\n            BackgroundConfig.save(context)\n        } catch (e: Exception) {\n            Log.e(TAG, \"清除Grid卡片自定义背景失败: ${e.message}\", e)\n        }\n    }\n    \n    /**\n     * Clear generic background\n     */\n    private fun clearGenericBackground(\n        context: Context,\n        filenameBase: String,\n        updateConfigAction: (String?) -> Unit\n    ) {\n        try {\n            clearOldFiles(context, filenameBase)\n            updateConfigAction(null)\n            BackgroundConfig.save(context)\n        } catch (e: Exception) {\n            Log.e(TAG, \"清除 $filenameBase 失败: ${e.message}\", e)\n        }\n    }\n\n    /**\n     * Save and apply generic background\n     */\n    private suspend fun saveAndApplyGenericBackground(\n        context: Context,\n        uri: Uri,\n        filenameBase: String,\n        updateConfigAction: (String) -> Unit\n    ): Boolean {\n        return try {\n            withContext(Dispatchers.IO) {\n                val extension = getFileExtension(context, uri)\n                clearOldFiles(context, filenameBase)\n                \n                val targetFile = File(context.filesDir, \"$filenameBase$extension\")\n                val savedUri = saveImageToInternalStorage(context, uri, targetFile)\n                \n                if (savedUri != null) {\n                    Log.d(TAG, \"$filenameBase 图片保存成功，URI: $savedUri\")\n                    updateConfigAction(savedUri.toString())\n                    BackgroundConfig.save(context)\n                    true\n                } else {\n                    Log.e(TAG, \"$filenameBase 图片保存失败\")\n                    false\n                }\n            }\n        } catch (e: Exception) {\n            Log.e(TAG, \"保存 $filenameBase 失败: ${e.message}\", e)\n            false\n        }\n    }\n\n    // Home Background\n    suspend fun saveAndApplyHomeBackground(context: Context, uri: Uri) = \n        saveAndApplyGenericBackground(context, uri, HOME_BACKGROUND_FILENAME) { BackgroundConfig.updateHomeBackgroundUri(it) }\n    \n    fun clearHomeBackground(context: Context) = \n        clearGenericBackground(context, HOME_BACKGROUND_FILENAME) { BackgroundConfig.updateHomeBackgroundUri(it) }\n\n    // Kernel Background\n    suspend fun saveAndApplyKernelBackground(context: Context, uri: Uri) = \n        saveAndApplyGenericBackground(context, uri, KERNEL_BACKGROUND_FILENAME) { BackgroundConfig.updateKernelBackgroundUri(it) }\n    \n    fun clearKernelBackground(context: Context) = \n        clearGenericBackground(context, KERNEL_BACKGROUND_FILENAME) { BackgroundConfig.updateKernelBackgroundUri(it) }\n\n    // Superuser Background\n    suspend fun saveAndApplySuperuserBackground(context: Context, uri: Uri) = \n        saveAndApplyGenericBackground(context, uri, SUPERUSER_BACKGROUND_FILENAME) { BackgroundConfig.updateSuperuserBackgroundUri(it) }\n    \n    fun clearSuperuserBackground(context: Context) = \n        clearGenericBackground(context, SUPERUSER_BACKGROUND_FILENAME) { BackgroundConfig.updateSuperuserBackgroundUri(it) }\n\n    // System Module Background\n    suspend fun saveAndApplySystemModuleBackground(context: Context, uri: Uri) = \n        saveAndApplyGenericBackground(context, uri, SYSTEM_MODULE_BACKGROUND_FILENAME) { BackgroundConfig.updateSystemModuleBackgroundUri(it) }\n    \n    fun clearSystemModuleBackground(context: Context) = \n        clearGenericBackground(context, SYSTEM_MODULE_BACKGROUND_FILENAME) { BackgroundConfig.updateSystemModuleBackgroundUri(it) }\n\n    // Settings Background\n    suspend fun saveAndApplySettingsBackground(context: Context, uri: Uri) = \n        saveAndApplyGenericBackground(context, uri, SETTINGS_BACKGROUND_FILENAME) { BackgroundConfig.updateSettingsBackgroundUri(it) }\n    \n    fun clearSettingsBackground(context: Context) = \n        clearGenericBackground(context, SETTINGS_BACKGROUND_FILENAME) { BackgroundConfig.updateSettingsBackgroundUri(it) }\n\n    // Title Image\n    suspend fun saveAndApplyTitleImage(context: Context, uri: Uri): Boolean {\n        return try {\n            withContext(Dispatchers.IO) {\n                val extension = getFileExtension(context, uri)\n                clearOldFiles(context, TITLE_IMAGE_FILENAME)\n                \n                val targetFile = File(context.filesDir, \"$TITLE_IMAGE_FILENAME$extension\")\n                val savedUri = saveImageToInternalStorage(context, uri, targetFile)\n                \n                if (savedUri != null) {\n                    Log.d(TAG, \"标题图片保存成功，URI: $savedUri\")\n                    BackgroundConfig.updateTitleImageUri(savedUri.toString())\n                    BackgroundConfig.save(context)\n                    true\n                } else {\n                    Log.e(TAG, \"标题图片保存失败\")\n                    false\n                }\n            }\n        } catch (e: Exception) {\n            Log.e(TAG, \"保存标题图片失败: ${e.message}\", e)\n            false\n        }\n    }\n\n    fun clearTitleImage(context: Context) {\n        try {\n            clearOldFiles(context, TITLE_IMAGE_FILENAME)\n            BackgroundConfig.updateTitleImageUri(null)\n            BackgroundConfig.save(context)\n        } catch (e: Exception) {\n            Log.e(TAG, \"清除标题图片失败: ${e.message}\", e)\n        }\n    }\n    \n    /**\n     * 加载自定义背景\n     */\n    fun loadCustomBackground(context: Context) {\n        BackgroundConfig.load(context)\n    }\n    \n    /**\n     * 保存图片到内部存储\n     */\n    private suspend fun saveImageToInternalStorage(context: Context, uri: Uri, targetFile: File): Uri? {\n        return withContext(Dispatchers.IO) {\n            try {\n                val inputStream = SafeUriResolver.openInputStream(context, uri) ?: return@withContext null\n                // val file = getBackgroundFile(context) // Use targetFile instead\n\n                FileOutputStream(targetFile).use { outputStream ->\n                    val buffer = ByteArray(8 * 1024)\n                    var read: Int\n                    while (inputStream.read(buffer).also { read = it } != -1) {\n                        outputStream.write(buffer, 0, read)\n                    }\n                    outputStream.flush()\n                }\n                inputStream.close()\n\n                // 返回带时间戳的URI，确保Compose重组\n                val fileUri = Uri.fromFile(targetFile).buildUpon()\n                    .appendQueryParameter(\"t\", System.currentTimeMillis().toString())\n                    .build()\n                    \n                Log.d(TAG, \"图片保存成功，文件URI: $fileUri\")\n                fileUri\n            } catch (e: Exception) {\n                Log.e(TAG, \"保存图片到内部存储失败: ${e.message}\", e)\n                null\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/BackgroundLayer.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport android.content.Context\nimport android.net.Uri\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.isSystemInDarkTheme\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.runtime.*\nimport androidx.compose.runtime.livedata.observeAsState\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Brush\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.layout.ContentScale\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.zIndex\nimport androidx.compose.ui.draw.paint\nimport androidx.compose.ui.draw.blur\nimport coil.compose.AsyncImagePainter\nimport coil.compose.rememberAsyncImagePainter\nimport coil.request.ImageRequest\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.DelicateCoroutinesApi\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.APApplication\n\nimport android.view.ViewGroup\nimport android.widget.FrameLayout\nimport android.widget.VideoView\nimport androidx.compose.ui.viewinterop.AndroidView\nimport com.ramcosta.composedestinations.generated.destinations.APModuleScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.HomeScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.KPModuleScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.SettingScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.SuperUserScreenDestination\n\nimport androidx.compose.animation.AnimatedContent\nimport androidx.compose.animation.core.spring\nimport androidx.compose.animation.core.tween\nimport androidx.compose.animation.fadeIn\nimport androidx.compose.animation.fadeOut\nimport androidx.compose.animation.scaleIn\nimport androidx.compose.animation.scaleOut\nimport androidx.compose.animation.slideInHorizontally\nimport androidx.compose.animation.slideInVertically\nimport androidx.compose.animation.slideOutHorizontally\nimport androidx.compose.animation.slideOutVertically\nimport androidx.compose.animation.togetherWith\nimport me.bmax.apatch.ui.screen.BottomBarDestination\n\n/**\n * Background Layer Component\n * Priority: Video > Multi/Single Image > Default\n */\n@Composable\nfun BackgroundLayer(\n    currentRoute: String? = null,\n    folkXEngineEnabled: Boolean = true,\n    folkXAnimationType: String? = \"linear\",\n    folkXAnimationSpeed: Float = 1.0f\n) {\n    val context = LocalContext.current\n    val prefs = APApplication.sharedPreferences\n    var darkThemeFollowSys by remember { mutableStateOf(prefs.getBoolean(\"night_mode_follow_sys\", false)) }\n    var nightModeEnabled by remember { mutableStateOf(prefs.getBoolean(\"night_mode_enabled\", true)) }\n\n    val refreshThemeObserver by refreshTheme.observeAsState(false)\n    LaunchedEffect(refreshThemeObserver) {\n        if (refreshThemeObserver) {\n            darkThemeFollowSys = prefs.getBoolean(\"night_mode_follow_sys\", false)\n            nightModeEnabled = prefs.getBoolean(\"night_mode_enabled\", true)\n            refreshTheme.postValue(false)\n        }\n    }\n\n    val isDarkTheme = if (darkThemeFollowSys) {\n        isSystemInDarkTheme()\n    } else {\n        nightModeEnabled\n    }\n    \n    // Video Background Logic\n    // Only show video if Custom Background is enabled AND Video Background is enabled\n    if (BackgroundConfig.isCustomBackgroundEnabled && BackgroundConfig.isVideoBackgroundEnabled && !BackgroundConfig.videoBackgroundUri.isNullOrEmpty()) {\n        var mediaPlayer by remember { mutableStateOf<android.media.MediaPlayer?>(null) }\n        var videoView by remember { mutableStateOf<VideoView?>(null) }\n\n        DisposableEffect(Unit) {\n            onDispose {\n                try {\n                    videoView?.stopPlayback()\n                    mediaPlayer?.release()\n                } catch (e: Exception) {\n                    e.printStackTrace()\n                }\n            }\n        }\n\n        key(BackgroundConfig.videoBackgroundUri) {\n            AndroidView(\n                factory = { ctx ->\n                    object : VideoView(ctx) {\n                        override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {\n                            val width = getDefaultSize(0, widthMeasureSpec)\n                            val height = getDefaultSize(0, heightMeasureSpec)\n                            setMeasuredDimension(width, height)\n                        }\n                    }.apply {\n                        layoutParams = FrameLayout.LayoutParams(\n                            ViewGroup.LayoutParams.MATCH_PARENT,\n                            ViewGroup.LayoutParams.MATCH_PARENT\n                        )\n                        setVideoPath(BackgroundConfig.videoBackgroundUri)\n                        setOnPreparedListener { mp ->\n                            mp.isLooping = true\n                            mp.setVideoScalingMode(android.media.MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING)\n                            mediaPlayer = mp\n                            val vol = BackgroundConfig.videoVolume\n                            mp.setVolume(vol, vol)\n                            start()\n                        }\n                        setOnErrorListener { _, _, _ ->\n                            true\n                        }\n                        videoView = this\n                    }\n                },\n                update = {\n                    mediaPlayer?.let { mp ->\n                        val vol = BackgroundConfig.videoVolume\n                        mp.setVolume(vol, vol)\n                    }\n                },\n                onRelease = { view ->\n                    try {\n                        view.stopPlayback()\n                    } catch (e: Exception) {\n                        e.printStackTrace()\n                    }\n                },\n                modifier = Modifier.fillMaxSize().zIndex(-2f)\n            )\n        }\n        \n        // Dim overlay for video\n        Box(\n            modifier = Modifier\n                .fillMaxSize()\n                .zIndex(-1f)\n                .background(Color.Black.copy(alpha = BackgroundConfig.getEffectiveBackgroundDim(isDarkTheme)))\n        )\n        return\n    }\n\n    // Default background (fallback)\n    // Fix: When custom background is enabled, MaterialTheme.colorScheme.background is Transparent.\n    // We need a solid color here to prevent the window background (often white) from flashing during animations.\n    val fallbackColor = if (BackgroundConfig.isCustomBackgroundEnabled) {\n        if (isDarkTheme) Color.Black else Color.White\n    } else {\n        MaterialTheme.colorScheme.background\n    }\n    \n    Box(\n        modifier = Modifier\n            .fillMaxSize()\n            .zIndex(-2f)\n            .background(fallbackColor)\n    )\n\n    // Image Background Logic\n    if (BackgroundConfig.isCustomBackgroundEnabled) {\n        // 在单壁纸模式下，所有动画类型都不需要重生成壁纸，保持固定位置\n        val shouldAnimate = folkXEngineEnabled && BackgroundConfig.isMultiBackgroundEnabled\n\n        if (shouldAnimate) {\n            AnimatedContent(\n                targetState = currentRoute,\n                modifier = Modifier.fillMaxSize(),\n                transitionSpec = {\n                    val initialRoute = initialState\n                    val targetRoute = targetState\n                    \n                    val initialIndex = BottomBarDestination.entries.indexOfFirst { it.direction.route == initialRoute }\n                    val targetIndex = BottomBarDestination.entries.indexOfFirst { it.direction.route == targetRoute }\n\n                    val stiffness = 300f * folkXAnimationSpeed * folkXAnimationSpeed\n                    val duration300 = (300 / folkXAnimationSpeed).toInt()\n                    val duration600 = (600 / folkXAnimationSpeed).toInt()\n\n                    if (initialIndex != -1 && targetIndex != -1) {\n                        fadeIn(animationSpec = tween(duration600)) togetherWith fadeOut(animationSpec = tween(duration600))\n                    } else {\n                        // Default fade for other transitions (e.g. to details)\n                        fadeIn(animationSpec = tween(340)) togetherWith fadeOut(animationSpec = tween(340))\n                    }\n                },\n                label = \"BackgroundAnimation\"\n            ) { route ->\n                val rawTargetUri = if (BackgroundConfig.isMultiBackgroundEnabled) {\n                    when (route) {\n                        HomeScreenDestination.route -> BackgroundConfig.homeBackgroundUri\n                        KPModuleScreenDestination.route -> BackgroundConfig.kernelBackgroundUri\n                        SuperUserScreenDestination.route -> BackgroundConfig.superuserBackgroundUri\n                        APModuleScreenDestination.route -> BackgroundConfig.systemModuleBackgroundUri\n                        SettingScreenDestination.route -> BackgroundConfig.settingsBackgroundUri\n                        else -> BackgroundConfig.homeBackgroundUri\n                    }\n                } else {\n                    BackgroundConfig.customBackgroundUri\n                }\n                \n                RenderBackgroundImage(rawTargetUri, isDarkTheme)\n            }\n        } else {\n            // No animation or standard logic\n            val rawTargetUri = if (BackgroundConfig.isMultiBackgroundEnabled) {\n                when (currentRoute) {\n                    HomeScreenDestination.route -> BackgroundConfig.homeBackgroundUri\n                    KPModuleScreenDestination.route -> BackgroundConfig.kernelBackgroundUri\n                    SuperUserScreenDestination.route -> BackgroundConfig.superuserBackgroundUri\n                    APModuleScreenDestination.route -> BackgroundConfig.systemModuleBackgroundUri\n                    SettingScreenDestination.route -> BackgroundConfig.settingsBackgroundUri\n                    else -> BackgroundConfig.homeBackgroundUri\n                }\n            } else {\n                BackgroundConfig.customBackgroundUri\n            }\n            \n            RenderBackgroundImage(rawTargetUri, isDarkTheme)\n        }\n    }\n}\n\n@Composable\nprivate fun RenderBackgroundImage(rawTargetUri: String?, isDarkTheme: Boolean) {\n    val targetModel = rawTargetUri\n\n    if (!targetModel.isNullOrEmpty()) {\n        val painter = rememberAsyncImagePainter(\n            model = targetModel,\n            onError = { error ->\n                android.util.Log.e(\"BackgroundLayer\", \"Failed to load background: ${error.result.throwable.message}\")\n            }\n        )\n\n        // Use Image composable instead of Box + paint for better compatibility with Modifier.blur\n        Image(\n            painter = painter,\n            contentDescription = null,\n            contentScale = ContentScale.Crop,\n            modifier = Modifier\n                .fillMaxSize()\n                .zIndex(-2f)\n                .let {\n                    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S && BackgroundConfig.customBackgroundBlur > 0f) {\n                        it.blur(radius = BackgroundConfig.customBackgroundBlur.dp)\n                    } else {\n                        it\n                    }\n                }\n        )\n\n        // Dim overlay for image\n        Box(\n            modifier = Modifier\n                .fillMaxSize()\n                .zIndex(-1f)\n                .background(Color.Black.copy(alpha = BackgroundConfig.getEffectiveBackgroundDim(isDarkTheme)))\n        )\n    }\n}\n\n/**\n * 扩展函数，用于保存自定义背景\n */\n@OptIn(DelicateCoroutinesApi::class)\nfun Context.saveCustomBackground(uri: Uri?) {\n    if (uri != null) {\n        // 使用IO调度器在后台线程中处理\n        kotlinx.coroutines.GlobalScope.launch(Dispatchers.IO) {\n            BackgroundManager.saveAndApplyCustomBackground(this@saveCustomBackground, uri)\n        }\n    } else {\n        BackgroundManager.clearCustomBackground(this)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/BackupConfig.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport android.content.Context\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport me.bmax.apatch.APApplication\n\nobject BackupConfig {\n    private const val PREF_KEY_BACKUP_ENABLED = \"backup_enabled\"\n    private const val PREF_KEY_WEBDAV_URL = \"webdav_url\"\n    private const val PREF_KEY_WEBDAV_USERNAME = \"webdav_username\"\n    private const val PREF_KEY_WEBDAV_PASSWORD = \"webdav_password\"\n    private const val PREF_KEY_WEBDAV_PATH = \"webdav_path\"\n\n    var isBackupEnabled by mutableStateOf(false)\n    var webdavUrl by mutableStateOf(\"\")\n    var webdavUsername by mutableStateOf(\"\")\n    var webdavPassword by mutableStateOf(\"\")\n    var webdavPath by mutableStateOf(\"/\")\n\n    init {\n        load(APApplication.sharedPreferences)\n    }\n\n    private fun load(prefs: android.content.SharedPreferences) {\n        isBackupEnabled = prefs.getBoolean(PREF_KEY_BACKUP_ENABLED, false)\n        webdavUrl = prefs.getString(PREF_KEY_WEBDAV_URL, \"\") ?: \"\"\n        webdavUsername = prefs.getString(PREF_KEY_WEBDAV_USERNAME, \"\") ?: \"\"\n        webdavPassword = prefs.getString(PREF_KEY_WEBDAV_PASSWORD, \"\") ?: \"\"\n        webdavPath = prefs.getString(PREF_KEY_WEBDAV_PATH, \"/\") ?: \"/\"\n    }\n\n    fun save(context: Context) {\n        val prefs = APApplication.sharedPreferences\n        prefs.edit().apply {\n            putBoolean(PREF_KEY_BACKUP_ENABLED, isBackupEnabled)\n            putString(PREF_KEY_WEBDAV_URL, webdavUrl)\n            putString(PREF_KEY_WEBDAV_USERNAME, webdavUsername)\n            putString(PREF_KEY_WEBDAV_PASSWORD, webdavPassword)\n            putString(PREF_KEY_WEBDAV_PATH, webdavPath)\n            apply()\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/BlueGreyTheme.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lightColorScheme\nimport androidx.compose.ui.graphics.Color\n\nprivate val md_theme_light_primary = Color(0xFF00668A)\nprivate val md_theme_light_onPrimary = Color(0xFFFFFFFF)\nprivate val md_theme_light_primaryContainer = Color(0xFFC4E7FF)\nprivate val md_theme_light_onPrimaryContainer = Color(0xFF001E2C)\nprivate val md_theme_light_secondary = Color(0xFF4E616D)\nprivate val md_theme_light_onSecondary = Color(0xFFFFFFFF)\nprivate val md_theme_light_secondaryContainer = Color(0xFFD1E5F4)\nprivate val md_theme_light_onSecondaryContainer = Color(0xFF0A1E28)\nprivate val md_theme_light_tertiary = Color(0xFF605A7D)\nprivate val md_theme_light_onTertiary = Color(0xFFFFFFFF)\nprivate val md_theme_light_tertiaryContainer = Color(0xFFE6DEFF)\nprivate val md_theme_light_onTertiaryContainer = Color(0xFF1D1736)\nprivate val md_theme_light_error = Color(0xFFBA1A1A)\nprivate val md_theme_light_errorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_light_onError = Color(0xFFFFFFFF)\nprivate val md_theme_light_onErrorContainer = Color(0xFF410002)\nprivate val md_theme_light_background = Color(0xFFFBFCFF)\nprivate val md_theme_light_onBackground = Color(0xFF191C1E)\nprivate val md_theme_light_surface = Color(0xFFFBFCFF)\nprivate val md_theme_light_onSurface = Color(0xFF191C1E)\nprivate val md_theme_light_surfaceVariant = Color(0xFFDCE3E9)\nprivate val md_theme_light_onSurfaceVariant = Color(0xFF41484D)\nprivate val md_theme_light_outline = Color(0xFF71787D)\nprivate val md_theme_light_inverseOnSurface = Color(0xFFF0F1F3)\nprivate val md_theme_light_inverseSurface = Color(0xFF2E3133)\nprivate val md_theme_light_inversePrimary = Color(0xFF7BD0FF)\nprivate val md_theme_light_shadow = Color(0xFF000000)\nprivate val md_theme_light_surfaceTint = Color(0xFF00668A)\nprivate val md_theme_light_outlineVariant = Color(0xFFC0C7CD)\nprivate val md_theme_light_scrim = Color(0xFF000000)\nprivate val md_theme_light_surfaceContainerLowest = Color(0xFFF9FAFD)\nprivate val md_theme_light_surfaceContainerLow = Color(0xFFF7F9FC)\nprivate val md_theme_light_surfaceContainer = Color(0xFFF5F7FB)\nprivate val md_theme_light_surfaceContainerHigh = Color(0xFFF2F5F8)\nprivate val md_theme_light_surfaceContainerHighest = Color(0xFFEFF2F6)\n\n\nprivate val md_theme_dark_primary = Color(0xFF7BD0FF)\nprivate val md_theme_dark_onPrimary = Color(0xFF003549)\nprivate val md_theme_dark_primaryContainer = Color(0xFF004C69)\nprivate val md_theme_dark_onPrimaryContainer = Color(0xFFC4E7FF)\nprivate val md_theme_dark_secondary = Color(0xFFB5C9D7)\nprivate val md_theme_dark_onSecondary = Color(0xFF20333E)\nprivate val md_theme_dark_secondaryContainer = Color(0xFF374955)\nprivate val md_theme_dark_onSecondaryContainer = Color(0xFFD1E5F4)\nprivate val md_theme_dark_tertiary = Color(0xFFCAC1E9)\nprivate val md_theme_dark_onTertiary = Color(0xFF322C4C)\nprivate val md_theme_dark_tertiaryContainer = Color(0xFF484264)\nprivate val md_theme_dark_onTertiaryContainer = Color(0xFFE6DEFF)\nprivate val md_theme_dark_error = Color(0xFFFFB4AB)\nprivate val md_theme_dark_errorContainer = Color(0xFF93000A)\nprivate val md_theme_dark_onError = Color(0xFF690005)\nprivate val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_dark_background = Color(0xFF191C1E)\nprivate val md_theme_dark_onBackground = Color(0xFFE1E2E5)\nprivate val md_theme_dark_surface = Color(0xFF191C1E)\nprivate val md_theme_dark_onSurface = Color(0xFFE1E2E5)\nprivate val md_theme_dark_surfaceVariant = Color(0xFF41484D)\nprivate val md_theme_dark_onSurfaceVariant = Color(0xFFC0C7CD)\nprivate val md_theme_dark_outline = Color(0xFF8B9297)\nprivate val md_theme_dark_inverseOnSurface = Color(0xFF191C1E)\nprivate val md_theme_dark_inverseSurface = Color(0xFFE1E2E5)\nprivate val md_theme_dark_inversePrimary = Color(0xFF00668A)\nprivate val md_theme_dark_shadow = Color(0xFF000000)\nprivate val md_theme_dark_surfaceTint = Color(0xFF7BD0FF)\nprivate val md_theme_dark_outlineVariant = Color(0xFF41484D)\nprivate val md_theme_dark_scrim = Color(0xFF000000)\nprivate val md_theme_dark_surfaceContainerLowest = Color(0xFF1B1E20)\nprivate val md_theme_dark_surfaceContainerLow = Color(0xFF1D2022)\nprivate val md_theme_dark_surfaceContainer = Color(0xFF202326)\nprivate val md_theme_dark_surfaceContainerHigh = Color(0xFF24282B)\nprivate val md_theme_dark_surfaceContainerHighest = Color(0xFF282C2F)\n\n\nval LightBlueGreyTheme = lightColorScheme(\n    primary = md_theme_light_primary,\n    onPrimary = md_theme_light_onPrimary,\n    primaryContainer = md_theme_light_primaryContainer,\n    onPrimaryContainer = md_theme_light_onPrimaryContainer,\n    secondary = md_theme_light_secondary,\n    onSecondary = md_theme_light_onSecondary,\n    secondaryContainer = md_theme_light_secondaryContainer,\n    onSecondaryContainer = md_theme_light_onSecondaryContainer,\n    tertiary = md_theme_light_tertiary,\n    onTertiary = md_theme_light_onTertiary,\n    tertiaryContainer = md_theme_light_tertiaryContainer,\n    onTertiaryContainer = md_theme_light_onTertiaryContainer,\n    error = md_theme_light_error,\n    errorContainer = md_theme_light_errorContainer,\n    onError = md_theme_light_onError,\n    onErrorContainer = md_theme_light_onErrorContainer,\n    background = md_theme_light_background,\n    onBackground = md_theme_light_onBackground,\n    surface = md_theme_light_surface,\n    onSurface = md_theme_light_onSurface,\n    surfaceVariant = md_theme_light_surfaceVariant,\n    onSurfaceVariant = md_theme_light_onSurfaceVariant,\n    outline = md_theme_light_outline,\n    inverseOnSurface = md_theme_light_inverseOnSurface,\n    inverseSurface = md_theme_light_inverseSurface,\n    inversePrimary = md_theme_light_inversePrimary,\n    surfaceTint = md_theme_light_surfaceTint,\n    outlineVariant = md_theme_light_outlineVariant,\n    surfaceContainerLowest = md_theme_light_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_light_surfaceContainerLow,\n    surfaceContainer = md_theme_light_surfaceContainer,\n    surfaceContainerHigh = md_theme_light_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_light_surfaceContainerHighest,\n    scrim = md_theme_light_scrim,\n)\n\nval DarkBlueGreyTheme = darkColorScheme(\n    primary = md_theme_dark_primary,\n    onPrimary = md_theme_dark_onPrimary,\n    primaryContainer = md_theme_dark_primaryContainer,\n    onPrimaryContainer = md_theme_dark_onPrimaryContainer,\n    secondary = md_theme_dark_secondary,\n    onSecondary = md_theme_dark_onSecondary,\n    secondaryContainer = md_theme_dark_secondaryContainer,\n    onSecondaryContainer = md_theme_dark_onSecondaryContainer,\n    tertiary = md_theme_dark_tertiary,\n    onTertiary = md_theme_dark_onTertiary,\n    tertiaryContainer = md_theme_dark_tertiaryContainer,\n    onTertiaryContainer = md_theme_dark_onTertiaryContainer,\n    error = md_theme_dark_error,\n    errorContainer = md_theme_dark_errorContainer,\n    onError = md_theme_dark_onError,\n    onErrorContainer = md_theme_dark_onErrorContainer,\n    background = md_theme_dark_background,\n    onBackground = md_theme_dark_onBackground,\n    surface = md_theme_dark_surface,\n    onSurface = md_theme_dark_onSurface,\n    surfaceVariant = md_theme_dark_surfaceVariant,\n    onSurfaceVariant = md_theme_dark_onSurfaceVariant,\n    outline = md_theme_dark_outline,\n    inverseOnSurface = md_theme_dark_inverseOnSurface,\n    inverseSurface = md_theme_dark_inverseSurface,\n    inversePrimary = md_theme_dark_inversePrimary,\n    surfaceTint = md_theme_dark_surfaceTint,\n    outlineVariant = md_theme_dark_outlineVariant,\n    surfaceContainerLowest = md_theme_dark_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_dark_surfaceContainerLow,\n    surfaceContainer = md_theme_dark_surfaceContainer,\n    surfaceContainerHigh = md_theme_dark_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_dark_surfaceContainerHighest,\n    scrim = md_theme_dark_scrim,\n)"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/BlueTheme.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lightColorScheme\nimport androidx.compose.ui.graphics.Color\n\nprivate val md_theme_light_primary = Color(0xFF0061A4)\nprivate val md_theme_light_onPrimary = Color(0xFFFFFFFF)\nprivate val md_theme_light_primaryContainer = Color(0xFFD1E4FF)\nprivate val md_theme_light_onPrimaryContainer = Color(0xFF001D36)\nprivate val md_theme_light_secondary = Color(0xFF535F70)\nprivate val md_theme_light_onSecondary = Color(0xFFFFFFFF)\nprivate val md_theme_light_secondaryContainer = Color(0xFFD7E3F7)\nprivate val md_theme_light_onSecondaryContainer = Color(0xFF101C2B)\nprivate val md_theme_light_tertiary = Color(0xFF6B5778)\nprivate val md_theme_light_onTertiary = Color(0xFFFFFFFF)\nprivate val md_theme_light_tertiaryContainer = Color(0xFFF2DAFF)\nprivate val md_theme_light_onTertiaryContainer = Color(0xFF251431)\nprivate val md_theme_light_error = Color(0xFFBA1A1A)\nprivate val md_theme_light_errorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_light_onError = Color(0xFFFFFFFF)\nprivate val md_theme_light_onErrorContainer = Color(0xFF410002)\nprivate val md_theme_light_background = Color(0xFFFDFCFF)\nprivate val md_theme_light_onBackground = Color(0xFF1A1C1E)\nprivate val md_theme_light_surface = Color(0xFFFDFCFF)\nprivate val md_theme_light_onSurface = Color(0xFF1A1C1E)\nprivate val md_theme_light_surfaceVariant = Color(0xFFDFE2EB)\nprivate val md_theme_light_onSurfaceVariant = Color(0xFF43474E)\nprivate val md_theme_light_outline = Color(0xFF73777F)\nprivate val md_theme_light_inverseOnSurface = Color(0xFFF1F0F4)\nprivate val md_theme_light_inverseSurface = Color(0xFF2F3033)\nprivate val md_theme_light_inversePrimary = Color(0xFF9ECAFF)\nprivate val md_theme_light_shadow = Color(0xFF000000)\nprivate val md_theme_light_surfaceTint = Color(0xFF0061A4)\nprivate val md_theme_light_outlineVariant = Color(0xFFC3C7CF)\nprivate val md_theme_light_scrim = Color(0xFF000000)\nprivate val md_theme_light_surfaceContainerLowest = Color(0xFFFBFAFE)\nprivate val md_theme_light_surfaceContainerLow = Color(0xFFFAF9FD)\nprivate val md_theme_light_surfaceContainer = Color(0xFFF7F7FB)\nprivate val md_theme_light_surfaceContainerHigh = Color(0xFFF4F4F9)\nprivate val md_theme_light_surfaceContainerHighest = Color(0xFFF1F2F7)\n\n\nprivate val md_theme_dark_primary = Color(0xFF9ECAFF)\nprivate val md_theme_dark_onPrimary = Color(0xFF003258)\nprivate val md_theme_dark_primaryContainer = Color(0xFF00497D)\nprivate val md_theme_dark_onPrimaryContainer = Color(0xFFD1E4FF)\nprivate val md_theme_dark_secondary = Color(0xFFBBC7DB)\nprivate val md_theme_dark_onSecondary = Color(0xFF253140)\nprivate val md_theme_dark_secondaryContainer = Color(0xFF3B4858)\nprivate val md_theme_dark_onSecondaryContainer = Color(0xFFD7E3F7)\nprivate val md_theme_dark_tertiary = Color(0xFFD6BEE4)\nprivate val md_theme_dark_onTertiary = Color(0xFF3B2948)\nprivate val md_theme_dark_tertiaryContainer = Color(0xFF523F5F)\nprivate val md_theme_dark_onTertiaryContainer = Color(0xFFF2DAFF)\nprivate val md_theme_dark_error = Color(0xFFFFB4AB)\nprivate val md_theme_dark_errorContainer = Color(0xFF93000A)\nprivate val md_theme_dark_onError = Color(0xFF690005)\nprivate val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_dark_background = Color(0xFF1A1C1E)\nprivate val md_theme_dark_onBackground = Color(0xFFE2E2E6)\nprivate val md_theme_dark_surface = Color(0xFF1A1C1E)\nprivate val md_theme_dark_onSurface = Color(0xFFE2E2E6)\nprivate val md_theme_dark_surfaceVariant = Color(0xFF43474E)\nprivate val md_theme_dark_onSurfaceVariant = Color(0xFFC3C7CF)\nprivate val md_theme_dark_outline = Color(0xFF8D9199)\nprivate val md_theme_dark_inverseOnSurface = Color(0xFF1A1C1E)\nprivate val md_theme_dark_inverseSurface = Color(0xFFE2E2E6)\nprivate val md_theme_dark_inversePrimary = Color(0xFF0061A4)\nprivate val md_theme_dark_shadow = Color(0xFF000000)\nprivate val md_theme_dark_surfaceTint = Color(0xFF9ECAFF)\nprivate val md_theme_dark_outlineVariant = Color(0xFF43474E)\nprivate val md_theme_dark_scrim = Color(0xFF000000)\nprivate val md_theme_dark_surfaceContainerLowest = Color(0xFF1C1E20)\nprivate val md_theme_dark_surfaceContainerLow = Color(0xFF1E2022)\nprivate val md_theme_dark_surfaceContainer = Color(0xFF212326)\nprivate val md_theme_dark_surfaceContainerHigh = Color(0xFF25282B)\nprivate val md_theme_dark_surfaceContainerHighest = Color(0xFF292C30)\n\n\n\nval LightBlueTheme = lightColorScheme(\n    primary = md_theme_light_primary,\n    onPrimary = md_theme_light_onPrimary,\n    primaryContainer = md_theme_light_primaryContainer,\n    onPrimaryContainer = md_theme_light_onPrimaryContainer,\n    secondary = md_theme_light_secondary,\n    onSecondary = md_theme_light_onSecondary,\n    secondaryContainer = md_theme_light_secondaryContainer,\n    onSecondaryContainer = md_theme_light_onSecondaryContainer,\n    tertiary = md_theme_light_tertiary,\n    onTertiary = md_theme_light_onTertiary,\n    tertiaryContainer = md_theme_light_tertiaryContainer,\n    onTertiaryContainer = md_theme_light_onTertiaryContainer,\n    error = md_theme_light_error,\n    errorContainer = md_theme_light_errorContainer,\n    onError = md_theme_light_onError,\n    onErrorContainer = md_theme_light_onErrorContainer,\n    background = md_theme_light_background,\n    onBackground = md_theme_light_onBackground,\n    surface = md_theme_light_surface,\n    onSurface = md_theme_light_onSurface,\n    surfaceVariant = md_theme_light_surfaceVariant,\n    onSurfaceVariant = md_theme_light_onSurfaceVariant,\n    outline = md_theme_light_outline,\n    inverseOnSurface = md_theme_light_inverseOnSurface,\n    inverseSurface = md_theme_light_inverseSurface,\n    inversePrimary = md_theme_light_inversePrimary,\n    surfaceTint = md_theme_light_surfaceTint,\n    outlineVariant = md_theme_light_outlineVariant,\n    surfaceContainerLowest = md_theme_light_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_light_surfaceContainerLow,\n    surfaceContainer = md_theme_light_surfaceContainer,\n    surfaceContainerHigh = md_theme_light_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_light_surfaceContainerHighest,\n    scrim = md_theme_light_scrim,\n)\n\nval DarkBlueTheme = darkColorScheme(\n    primary = md_theme_dark_primary,\n    onPrimary = md_theme_dark_onPrimary,\n    primaryContainer = md_theme_dark_primaryContainer,\n    onPrimaryContainer = md_theme_dark_onPrimaryContainer,\n    secondary = md_theme_dark_secondary,\n    onSecondary = md_theme_dark_onSecondary,\n    secondaryContainer = md_theme_dark_secondaryContainer,\n    onSecondaryContainer = md_theme_dark_onSecondaryContainer,\n    tertiary = md_theme_dark_tertiary,\n    onTertiary = md_theme_dark_onTertiary,\n    tertiaryContainer = md_theme_dark_tertiaryContainer,\n    onTertiaryContainer = md_theme_dark_onTertiaryContainer,\n    error = md_theme_dark_error,\n    errorContainer = md_theme_dark_errorContainer,\n    onError = md_theme_dark_onError,\n    onErrorContainer = md_theme_dark_onErrorContainer,\n    background = md_theme_dark_background,\n    onBackground = md_theme_dark_onBackground,\n    surface = md_theme_dark_surface,\n    onSurface = md_theme_dark_onSurface,\n    surfaceVariant = md_theme_dark_surfaceVariant,\n    onSurfaceVariant = md_theme_dark_onSurfaceVariant,\n    outline = md_theme_dark_outline,\n    inverseOnSurface = md_theme_dark_inverseOnSurface,\n    inverseSurface = md_theme_dark_inverseSurface,\n    inversePrimary = md_theme_dark_inversePrimary,\n    surfaceTint = md_theme_dark_surfaceTint,\n    outlineVariant = md_theme_dark_outlineVariant,\n    surfaceContainerLowest = md_theme_dark_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_dark_surfaceContainerLow,\n    surfaceContainer = md_theme_dark_surfaceContainer,\n    surfaceContainerHigh = md_theme_dark_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_dark_surfaceContainerHighest,\n    scrim = md_theme_dark_scrim,\n)"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/BrownTheme.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lightColorScheme\nimport androidx.compose.ui.graphics.Color\n\nprivate val md_theme_light_primary = Color(0xFF9A4522)\nprivate val md_theme_light_onPrimary = Color(0xFFFFFFFF)\nprivate val md_theme_light_primaryContainer = Color(0xFFFFDBCF)\nprivate val md_theme_light_onPrimaryContainer = Color(0xFF380D00)\nprivate val md_theme_light_secondary = Color(0xFF77574C)\nprivate val md_theme_light_onSecondary = Color(0xFFFFFFFF)\nprivate val md_theme_light_secondaryContainer = Color(0xFFFFDBCF)\nprivate val md_theme_light_onSecondaryContainer = Color(0xFF2C160D)\nprivate val md_theme_light_tertiary = Color(0xFF695E2F)\nprivate val md_theme_light_onTertiary = Color(0xFFFFFFFF)\nprivate val md_theme_light_tertiaryContainer = Color(0xFFF2E2A8)\nprivate val md_theme_light_onTertiaryContainer = Color(0xFF211B00)\nprivate val md_theme_light_error = Color(0xFFBA1A1A)\nprivate val md_theme_light_errorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_light_onError = Color(0xFFFFFFFF)\nprivate val md_theme_light_onErrorContainer = Color(0xFF410002)\nprivate val md_theme_light_background = Color(0xFFFFFBFF)\nprivate val md_theme_light_onBackground = Color(0xFF201A18)\nprivate val md_theme_light_surface = Color(0xFFFFFBFF)\nprivate val md_theme_light_onSurface = Color(0xFF201A18)\nprivate val md_theme_light_surfaceVariant = Color(0xFFF5DED6)\nprivate val md_theme_light_onSurfaceVariant = Color(0xFF53433E)\nprivate val md_theme_light_outline = Color(0xFF85736D)\nprivate val md_theme_light_inverseOnSurface = Color(0xFFFBEEEA)\nprivate val md_theme_light_inverseSurface = Color(0xFF362F2C)\nprivate val md_theme_light_inversePrimary = Color(0xFFFFB59A)\nprivate val md_theme_light_shadow = Color(0xFF000000)\nprivate val md_theme_light_surfaceTint = Color(0xFF9A4522)\nprivate val md_theme_light_outlineVariant = Color(0xFFD8C2BB)\nprivate val md_theme_light_scrim = Color(0xFF000000)\nprivate val md_theme_light_surfaceContainerLowest = Color(0xFFFEF9FC)\nprivate val md_theme_light_surfaceContainerLow = Color(0xFFFEF8FA)\nprivate val md_theme_light_surfaceContainer = Color(0xFFFDF5F7)\nprivate val md_theme_light_surfaceContainerHigh = Color(0xFFFCF2F3)\nprivate val md_theme_light_surfaceContainerHighest = Color(0xFFFBEFEF)\n\n\nprivate val md_theme_dark_primary = Color(0xFFFFB59A)\nprivate val md_theme_dark_onPrimary = Color(0xFF5B1B00)\nprivate val md_theme_dark_primaryContainer = Color(0xFF7B2E0D)\nprivate val md_theme_dark_onPrimaryContainer = Color(0xFFFFDBCF)\nprivate val md_theme_dark_secondary = Color(0xFFE7BEAF)\nprivate val md_theme_dark_onSecondary = Color(0xFF442A20)\nprivate val md_theme_dark_secondaryContainer = Color(0xFF5D4035)\nprivate val md_theme_dark_onSecondaryContainer = Color(0xFFFFDBCF)\nprivate val md_theme_dark_tertiary = Color(0xFFD5C68E)\nprivate val md_theme_dark_onTertiary = Color(0xFF393005)\nprivate val md_theme_dark_tertiaryContainer = Color(0xFF50471A)\nprivate val md_theme_dark_onTertiaryContainer = Color(0xFFF2E2A8)\nprivate val md_theme_dark_error = Color(0xFFFFB4AB)\nprivate val md_theme_dark_errorContainer = Color(0xFF93000A)\nprivate val md_theme_dark_onError = Color(0xFF690005)\nprivate val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_dark_background = Color(0xFF201A18)\nprivate val md_theme_dark_onBackground = Color(0xFFEDE0DC)\nprivate val md_theme_dark_surface = Color(0xFF201A18)\nprivate val md_theme_dark_onSurface = Color(0xFFEDE0DC)\nprivate val md_theme_dark_surfaceVariant = Color(0xFF53433E)\nprivate val md_theme_dark_onSurfaceVariant = Color(0xFFD8C2BB)\nprivate val md_theme_dark_outline = Color(0xFFA08D86)\nprivate val md_theme_dark_inverseOnSurface = Color(0xFF201A18)\nprivate val md_theme_dark_inverseSurface = Color(0xFFEDE0DC)\nprivate val md_theme_dark_inversePrimary = Color(0xFF9A4522)\nprivate val md_theme_dark_shadow = Color(0xFF000000)\nprivate val md_theme_dark_surfaceTint = Color(0xFFFFB59A)\nprivate val md_theme_dark_outlineVariant = Color(0xFF53433E)\nprivate val md_theme_dark_scrim = Color(0xFF000000)\nprivate val md_theme_dark_surfaceContainerLowest = Color(0xFF221C19)\nprivate val md_theme_dark_surfaceContainerLow = Color(0xFF251E1B)\nprivate val md_theme_dark_surfaceContainer = Color(0xFF29211E)\nprivate val md_theme_dark_surfaceContainerHigh = Color(0xFF2E2522)\nprivate val md_theme_dark_surfaceContainerHighest = Color(0xFF332926)\n\n\nval LightBrownTheme = lightColorScheme(\n    primary = md_theme_light_primary,\n    onPrimary = md_theme_light_onPrimary,\n    primaryContainer = md_theme_light_primaryContainer,\n    onPrimaryContainer = md_theme_light_onPrimaryContainer,\n    secondary = md_theme_light_secondary,\n    onSecondary = md_theme_light_onSecondary,\n    secondaryContainer = md_theme_light_secondaryContainer,\n    onSecondaryContainer = md_theme_light_onSecondaryContainer,\n    tertiary = md_theme_light_tertiary,\n    onTertiary = md_theme_light_onTertiary,\n    tertiaryContainer = md_theme_light_tertiaryContainer,\n    onTertiaryContainer = md_theme_light_onTertiaryContainer,\n    error = md_theme_light_error,\n    errorContainer = md_theme_light_errorContainer,\n    onError = md_theme_light_onError,\n    onErrorContainer = md_theme_light_onErrorContainer,\n    background = md_theme_light_background,\n    onBackground = md_theme_light_onBackground,\n    surface = md_theme_light_surface,\n    onSurface = md_theme_light_onSurface,\n    surfaceVariant = md_theme_light_surfaceVariant,\n    onSurfaceVariant = md_theme_light_onSurfaceVariant,\n    outline = md_theme_light_outline,\n    inverseOnSurface = md_theme_light_inverseOnSurface,\n    inverseSurface = md_theme_light_inverseSurface,\n    inversePrimary = md_theme_light_inversePrimary,\n    surfaceTint = md_theme_light_surfaceTint,\n    outlineVariant = md_theme_light_outlineVariant,\n    surfaceContainerLowest = md_theme_light_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_light_surfaceContainerLow,\n    surfaceContainer = md_theme_light_surfaceContainer,\n    surfaceContainerHigh = md_theme_light_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_light_surfaceContainerHighest,\n    scrim = md_theme_light_scrim,\n)\n\nval DarkBrownTheme = darkColorScheme(\n    primary = md_theme_dark_primary,\n    onPrimary = md_theme_dark_onPrimary,\n    primaryContainer = md_theme_dark_primaryContainer,\n    onPrimaryContainer = md_theme_dark_onPrimaryContainer,\n    secondary = md_theme_dark_secondary,\n    onSecondary = md_theme_dark_onSecondary,\n    secondaryContainer = md_theme_dark_secondaryContainer,\n    onSecondaryContainer = md_theme_dark_onSecondaryContainer,\n    tertiary = md_theme_dark_tertiary,\n    onTertiary = md_theme_dark_onTertiary,\n    tertiaryContainer = md_theme_dark_tertiaryContainer,\n    onTertiaryContainer = md_theme_dark_onTertiaryContainer,\n    error = md_theme_dark_error,\n    errorContainer = md_theme_dark_errorContainer,\n    onError = md_theme_dark_onError,\n    onErrorContainer = md_theme_dark_onErrorContainer,\n    background = md_theme_dark_background,\n    onBackground = md_theme_dark_onBackground,\n    surface = md_theme_dark_surface,\n    onSurface = md_theme_dark_onSurface,\n    surfaceVariant = md_theme_dark_surfaceVariant,\n    onSurfaceVariant = md_theme_dark_onSurfaceVariant,\n    outline = md_theme_dark_outline,\n    inverseOnSurface = md_theme_dark_inverseOnSurface,\n    inverseSurface = md_theme_dark_inverseSurface,\n    inversePrimary = md_theme_dark_inversePrimary,\n    surfaceTint = md_theme_dark_surfaceTint,\n    outlineVariant = md_theme_dark_outlineVariant,\n    surfaceContainerLowest = md_theme_dark_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_dark_surfaceContainerLow,\n    surfaceContainer = md_theme_dark_surfaceContainer,\n    surfaceContainerHigh = md_theme_dark_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_dark_surfaceContainerHighest,\n    scrim = md_theme_dark_scrim,\n)"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/CardManage.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.CardDefaults\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.unit.dp\n\n@Composable\nfun getCardElevation() = CardDefaults.cardElevation(\n    defaultElevation = if (BackgroundConfig.isCustomBackgroundEnabled) 0.dp else 6.dp\n)\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/CyanTheme.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lightColorScheme\nimport androidx.compose.ui.graphics.Color\n\nprivate val md_theme_light_primary = Color(0xFF006876)\nprivate val md_theme_light_onPrimary = Color(0xFFFFFFFF)\nprivate val md_theme_light_primaryContainer = Color(0xFFA1EFFF)\nprivate val md_theme_light_onPrimaryContainer = Color(0xFF001F25)\nprivate val md_theme_light_secondary = Color(0xFF4A6268)\nprivate val md_theme_light_onSecondary = Color(0xFFFFFFFF)\nprivate val md_theme_light_secondaryContainer = Color(0xFFCDE7ED)\nprivate val md_theme_light_onSecondaryContainer = Color(0xFF051F23)\nprivate val md_theme_light_tertiary = Color(0xFF545D7E)\nprivate val md_theme_light_onTertiary = Color(0xFFFFFFFF)\nprivate val md_theme_light_tertiaryContainer = Color(0xFFDBE1FF)\nprivate val md_theme_light_onTertiaryContainer = Color(0xFF101A37)\nprivate val md_theme_light_error = Color(0xFFBA1A1A)\nprivate val md_theme_light_errorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_light_onError = Color(0xFFFFFFFF)\nprivate val md_theme_light_onErrorContainer = Color(0xFF410002)\nprivate val md_theme_light_background = Color(0xFFFBFCFD)\nprivate val md_theme_light_onBackground = Color(0xFF191C1D)\nprivate val md_theme_light_surface = Color(0xFFFBFCFD)\nprivate val md_theme_light_onSurface = Color(0xFF191C1D)\nprivate val md_theme_light_surfaceVariant = Color(0xFFDBE4E6)\nprivate val md_theme_light_onSurfaceVariant = Color(0xFF3F484A)\nprivate val md_theme_light_outline = Color(0xFF6F797B)\nprivate val md_theme_light_inverseOnSurface = Color(0xFFEFF1F2)\nprivate val md_theme_light_inverseSurface = Color(0xFF2E3132)\nprivate val md_theme_light_inversePrimary = Color(0xFF44D8F1)\nprivate val md_theme_light_shadow = Color(0xFF000000)\nprivate val md_theme_light_surfaceTint = Color(0xFF006876)\nprivate val md_theme_light_outlineVariant = Color(0xFFBFC8CA)\nprivate val md_theme_light_scrim = Color(0xFF000000)\nprivate val md_theme_light_surfaceContainerLowest = Color(0xFFF9FAFB)\nprivate val md_theme_light_surfaceContainerLow = Color(0xFFF7F9FA)\nprivate val md_theme_light_surfaceContainer = Color(0xFFF5F7F8)\nprivate val md_theme_light_surfaceContainerHigh = Color(0xFFF2F5F6)\nprivate val md_theme_light_surfaceContainerHighest = Color(0xFFEEF2F4)\n\n\nprivate val md_theme_dark_primary = Color(0xFF44D8F1)\nprivate val md_theme_dark_onPrimary = Color(0xFF00363E)\nprivate val md_theme_dark_primaryContainer = Color(0xFF004E59)\nprivate val md_theme_dark_onPrimaryContainer = Color(0xFFA1EFFF)\nprivate val md_theme_dark_secondary = Color(0xFFB1CBD1)\nprivate val md_theme_dark_onSecondary = Color(0xFF1C3439)\nprivate val md_theme_dark_secondaryContainer = Color(0xFF334A50)\nprivate val md_theme_dark_onSecondaryContainer = Color(0xFFCDE7ED)\nprivate val md_theme_dark_tertiary = Color(0xFFBCC5EB)\nprivate val md_theme_dark_onTertiary = Color(0xFF262F4D)\nprivate val md_theme_dark_tertiaryContainer = Color(0xFF3C4665)\nprivate val md_theme_dark_onTertiaryContainer = Color(0xFFDBE1FF)\nprivate val md_theme_dark_error = Color(0xFFFFB4AB)\nprivate val md_theme_dark_errorContainer = Color(0xFF93000A)\nprivate val md_theme_dark_onError = Color(0xFF690005)\nprivate val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_dark_background = Color(0xFF191C1D)\nprivate val md_theme_dark_onBackground = Color(0xFFE1E3E3)\nprivate val md_theme_dark_surface = Color(0xFF191C1D)\nprivate val md_theme_dark_onSurface = Color(0xFFE1E3E3)\nprivate val md_theme_dark_surfaceVariant = Color(0xFF3F484A)\nprivate val md_theme_dark_onSurfaceVariant = Color(0xFFBFC8CA)\nprivate val md_theme_dark_outline = Color(0xFF899295)\nprivate val md_theme_dark_inverseOnSurface = Color(0xFF191C1D)\nprivate val md_theme_dark_inverseSurface = Color(0xFFE1E3E3)\nprivate val md_theme_dark_inversePrimary = Color(0xFF006876)\nprivate val md_theme_dark_shadow = Color(0xFF000000)\nprivate val md_theme_dark_surfaceTint = Color(0xFF44D8F1)\nprivate val md_theme_dark_outlineVariant = Color(0xFF3F484A)\nprivate val md_theme_dark_scrim = Color(0xFF000000)\nprivate val md_theme_dark_surfaceContainerLowest = Color(0xFF1A1E1F)\nprivate val md_theme_dark_surfaceContainerLow = Color(0xFF1C2021)\nprivate val md_theme_dark_surfaceContainer = Color(0xFF1F2325)\nprivate val md_theme_dark_surfaceContainerHigh = Color(0xFF232829)\nprivate val md_theme_dark_surfaceContainerHighest = Color(0xFF272C2E)\n\n\nval LightCyanTheme = lightColorScheme(\n    primary = md_theme_light_primary,\n    onPrimary = md_theme_light_onPrimary,\n    primaryContainer = md_theme_light_primaryContainer,\n    onPrimaryContainer = md_theme_light_onPrimaryContainer,\n    secondary = md_theme_light_secondary,\n    onSecondary = md_theme_light_onSecondary,\n    secondaryContainer = md_theme_light_secondaryContainer,\n    onSecondaryContainer = md_theme_light_onSecondaryContainer,\n    tertiary = md_theme_light_tertiary,\n    onTertiary = md_theme_light_onTertiary,\n    tertiaryContainer = md_theme_light_tertiaryContainer,\n    onTertiaryContainer = md_theme_light_onTertiaryContainer,\n    error = md_theme_light_error,\n    errorContainer = md_theme_light_errorContainer,\n    onError = md_theme_light_onError,\n    onErrorContainer = md_theme_light_onErrorContainer,\n    background = md_theme_light_background,\n    onBackground = md_theme_light_onBackground,\n    surface = md_theme_light_surface,\n    onSurface = md_theme_light_onSurface,\n    surfaceVariant = md_theme_light_surfaceVariant,\n    onSurfaceVariant = md_theme_light_onSurfaceVariant,\n    outline = md_theme_light_outline,\n    inverseOnSurface = md_theme_light_inverseOnSurface,\n    inverseSurface = md_theme_light_inverseSurface,\n    inversePrimary = md_theme_light_inversePrimary,\n    surfaceTint = md_theme_light_surfaceTint,\n    outlineVariant = md_theme_light_outlineVariant,\n    surfaceContainerLowest = md_theme_light_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_light_surfaceContainerLow,\n    surfaceContainer = md_theme_light_surfaceContainer,\n    surfaceContainerHigh = md_theme_light_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_light_surfaceContainerHighest,\n    scrim = md_theme_light_scrim,\n)\n\nval DarkCyanTheme = darkColorScheme(\n    primary = md_theme_dark_primary,\n    onPrimary = md_theme_dark_onPrimary,\n    primaryContainer = md_theme_dark_primaryContainer,\n    onPrimaryContainer = md_theme_dark_onPrimaryContainer,\n    secondary = md_theme_dark_secondary,\n    onSecondary = md_theme_dark_onSecondary,\n    secondaryContainer = md_theme_dark_secondaryContainer,\n    onSecondaryContainer = md_theme_dark_onSecondaryContainer,\n    tertiary = md_theme_dark_tertiary,\n    onTertiary = md_theme_dark_onTertiary,\n    tertiaryContainer = md_theme_dark_tertiaryContainer,\n    onTertiaryContainer = md_theme_dark_onTertiaryContainer,\n    error = md_theme_dark_error,\n    errorContainer = md_theme_dark_errorContainer,\n    onError = md_theme_dark_onError,\n    onErrorContainer = md_theme_dark_onErrorContainer,\n    background = md_theme_dark_background,\n    onBackground = md_theme_dark_onBackground,\n    surface = md_theme_dark_surface,\n    onSurface = md_theme_dark_onSurface,\n    surfaceVariant = md_theme_dark_surfaceVariant,\n    onSurfaceVariant = md_theme_dark_onSurfaceVariant,\n    outline = md_theme_dark_outline,\n    inverseOnSurface = md_theme_dark_inverseOnSurface,\n    inverseSurface = md_theme_dark_inverseSurface,\n    inversePrimary = md_theme_dark_inversePrimary,\n    surfaceTint = md_theme_dark_surfaceTint,\n    outlineVariant = md_theme_dark_outlineVariant,\n    surfaceContainerLowest = md_theme_dark_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_dark_surfaceContainerLow,\n    surfaceContainer = md_theme_dark_surfaceContainer,\n    surfaceContainerHigh = md_theme_dark_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_dark_surfaceContainerHighest,\n    scrim = md_theme_dark_scrim,\n)"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/DeepOrangeTheme.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lightColorScheme\nimport androidx.compose.ui.graphics.Color\n\nprivate val md_theme_light_primary = Color(0xFFB02F00)\nprivate val md_theme_light_onPrimary = Color(0xFFFFFFFF)\nprivate val md_theme_light_primaryContainer = Color(0xFFFFDBD1)\nprivate val md_theme_light_onPrimaryContainer = Color(0xFF3B0900)\nprivate val md_theme_light_secondary = Color(0xFF77574E)\nprivate val md_theme_light_onSecondary = Color(0xFFFFFFFF)\nprivate val md_theme_light_secondaryContainer = Color(0xFFFFDBD1)\nprivate val md_theme_light_onSecondaryContainer = Color(0xFF2C150F)\nprivate val md_theme_light_tertiary = Color(0xFF6C5D2F)\nprivate val md_theme_light_onTertiary = Color(0xFFFFFFFF)\nprivate val md_theme_light_tertiaryContainer = Color(0xFFF5E1A7)\nprivate val md_theme_light_onTertiaryContainer = Color(0xFF231B00)\nprivate val md_theme_light_error = Color(0xFFBA1A1A)\nprivate val md_theme_light_errorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_light_onError = Color(0xFFFFFFFF)\nprivate val md_theme_light_onErrorContainer = Color(0xFF410002)\nprivate val md_theme_light_background = Color(0xFFFFFBFF)\nprivate val md_theme_light_onBackground = Color(0xFF201A18)\nprivate val md_theme_light_surface = Color(0xFFFFFBFF)\nprivate val md_theme_light_onSurface = Color(0xFF201A18)\nprivate val md_theme_light_surfaceVariant = Color(0xFFF5DED8)\nprivate val md_theme_light_onSurfaceVariant = Color(0xFF53433F)\nprivate val md_theme_light_outline = Color(0xFF85736E)\nprivate val md_theme_light_inverseOnSurface = Color(0xFFFBEEEB)\nprivate val md_theme_light_inverseSurface = Color(0xFF362F2D)\nprivate val md_theme_light_inversePrimary = Color(0xFFFFB5A0)\nprivate val md_theme_light_shadow = Color(0xFF000000)\nprivate val md_theme_light_surfaceTint = Color(0xFFB02F00)\nprivate val md_theme_light_outlineVariant = Color(0xFFD8C2BC)\nprivate val md_theme_light_scrim = Color(0xFF000000)\nprivate val md_theme_light_surfaceContainerLowest = Color(0xFFFEF9FD)\nprivate val md_theme_light_surfaceContainerLow = Color(0xFFFEF8FB)\nprivate val md_theme_light_surfaceContainer = Color(0xFFFDF5F7)\nprivate val md_theme_light_surfaceContainerHigh = Color(0xFFFCF2F4)\nprivate val md_theme_light_surfaceContainerHighest = Color(0xFFFBEFF0)\n\n\nprivate val md_theme_dark_primary = Color(0xFFFFB5A0)\nprivate val md_theme_dark_onPrimary = Color(0xFF5F1500)\nprivate val md_theme_dark_primaryContainer = Color(0xFF862200)\nprivate val md_theme_dark_onPrimaryContainer = Color(0xFFFFDBD1)\nprivate val md_theme_dark_secondary = Color(0xFFE7BDB2)\nprivate val md_theme_dark_onSecondary = Color(0xFF442A22)\nprivate val md_theme_dark_secondaryContainer = Color(0xFF5D4037)\nprivate val md_theme_dark_onSecondaryContainer = Color(0xFFFFDBD1)\nprivate val md_theme_dark_tertiary = Color(0xFFD8C58D)\nprivate val md_theme_dark_onTertiary = Color(0xFF3B2F05)\nprivate val md_theme_dark_tertiaryContainer = Color(0xFF534619)\nprivate val md_theme_dark_onTertiaryContainer = Color(0xFFF5E1A7)\nprivate val md_theme_dark_error = Color(0xFFFFB4AB)\nprivate val md_theme_dark_errorContainer = Color(0xFF93000A)\nprivate val md_theme_dark_onError = Color(0xFF690005)\nprivate val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_dark_background = Color(0xFF201A18)\nprivate val md_theme_dark_onBackground = Color(0xFFEDE0DC)\nprivate val md_theme_dark_surface = Color(0xFF201A18)\nprivate val md_theme_dark_onSurface = Color(0xFFEDE0DC)\nprivate val md_theme_dark_surfaceVariant = Color(0xFF53433F)\nprivate val md_theme_dark_onSurfaceVariant = Color(0xFFD8C2BC)\nprivate val md_theme_dark_outline = Color(0xFFA08C87)\nprivate val md_theme_dark_inverseOnSurface = Color(0xFF201A18)\nprivate val md_theme_dark_inverseSurface = Color(0xFFEDE0DC)\nprivate val md_theme_dark_inversePrimary = Color(0xFFB02F00)\nprivate val md_theme_dark_shadow = Color(0xFF000000)\nprivate val md_theme_dark_surfaceTint = Color(0xFFFFB5A0)\nprivate val md_theme_dark_outlineVariant = Color(0xFF53433F)\nprivate val md_theme_dark_scrim = Color(0xFF000000)\nprivate val md_theme_dark_surfaceContainerLowest = Color(0xFF221C19)\nprivate val md_theme_dark_surfaceContainerLow = Color(0xFF251E1B)\nprivate val md_theme_dark_surfaceContainer = Color(0xFF29211F)\nprivate val md_theme_dark_surfaceContainerHigh = Color(0xFF2E2522)\nprivate val md_theme_dark_surfaceContainerHighest = Color(0xFF332926)\n\n\n\nval LightDeepOrangeTheme = lightColorScheme(\n    primary = md_theme_light_primary,\n    onPrimary = md_theme_light_onPrimary,\n    primaryContainer = md_theme_light_primaryContainer,\n    onPrimaryContainer = md_theme_light_onPrimaryContainer,\n    secondary = md_theme_light_secondary,\n    onSecondary = md_theme_light_onSecondary,\n    secondaryContainer = md_theme_light_secondaryContainer,\n    onSecondaryContainer = md_theme_light_onSecondaryContainer,\n    tertiary = md_theme_light_tertiary,\n    onTertiary = md_theme_light_onTertiary,\n    tertiaryContainer = md_theme_light_tertiaryContainer,\n    onTertiaryContainer = md_theme_light_onTertiaryContainer,\n    error = md_theme_light_error,\n    errorContainer = md_theme_light_errorContainer,\n    onError = md_theme_light_onError,\n    onErrorContainer = md_theme_light_onErrorContainer,\n    background = md_theme_light_background,\n    onBackground = md_theme_light_onBackground,\n    surface = md_theme_light_surface,\n    onSurface = md_theme_light_onSurface,\n    surfaceVariant = md_theme_light_surfaceVariant,\n    onSurfaceVariant = md_theme_light_onSurfaceVariant,\n    outline = md_theme_light_outline,\n    inverseOnSurface = md_theme_light_inverseOnSurface,\n    inverseSurface = md_theme_light_inverseSurface,\n    inversePrimary = md_theme_light_inversePrimary,\n    surfaceTint = md_theme_light_surfaceTint,\n    outlineVariant = md_theme_light_outlineVariant,\n    surfaceContainerLowest = md_theme_light_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_light_surfaceContainerLow,\n    surfaceContainer = md_theme_light_surfaceContainer,\n    surfaceContainerHigh = md_theme_light_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_light_surfaceContainerHighest,\n    scrim = md_theme_light_scrim,\n)\n\nval DarkDeepOrangeTheme = darkColorScheme(\n    primary = md_theme_dark_primary,\n    onPrimary = md_theme_dark_onPrimary,\n    primaryContainer = md_theme_dark_primaryContainer,\n    onPrimaryContainer = md_theme_dark_onPrimaryContainer,\n    secondary = md_theme_dark_secondary,\n    onSecondary = md_theme_dark_onSecondary,\n    secondaryContainer = md_theme_dark_secondaryContainer,\n    onSecondaryContainer = md_theme_dark_onSecondaryContainer,\n    tertiary = md_theme_dark_tertiary,\n    onTertiary = md_theme_dark_onTertiary,\n    tertiaryContainer = md_theme_dark_tertiaryContainer,\n    onTertiaryContainer = md_theme_dark_onTertiaryContainer,\n    error = md_theme_dark_error,\n    errorContainer = md_theme_dark_errorContainer,\n    onError = md_theme_dark_onError,\n    onErrorContainer = md_theme_dark_onErrorContainer,\n    background = md_theme_dark_background,\n    onBackground = md_theme_dark_onBackground,\n    surface = md_theme_dark_surface,\n    onSurface = md_theme_dark_onSurface,\n    surfaceVariant = md_theme_dark_surfaceVariant,\n    onSurfaceVariant = md_theme_dark_onSurfaceVariant,\n    outline = md_theme_dark_outline,\n    inverseOnSurface = md_theme_dark_inverseOnSurface,\n    inverseSurface = md_theme_dark_inverseSurface,\n    inversePrimary = md_theme_dark_inversePrimary,\n    surfaceTint = md_theme_dark_surfaceTint,\n    outlineVariant = md_theme_dark_outlineVariant,\n    surfaceContainerLowest = md_theme_dark_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_dark_surfaceContainerLow,\n    surfaceContainer = md_theme_dark_surfaceContainer,\n    surfaceContainerHigh = md_theme_dark_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_dark_surfaceContainerHighest,\n    scrim = md_theme_dark_scrim,\n)"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/DeepPurpleTheme.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lightColorScheme\nimport androidx.compose.ui.graphics.Color\n\nprivate val md_theme_light_primary = Color(0xFF6F43C0)\nprivate val md_theme_light_onPrimary = Color(0xFFFFFFFF)\nprivate val md_theme_light_primaryContainer = Color(0xFFEBDDFF)\nprivate val md_theme_light_onPrimaryContainer = Color(0xFF250059)\nprivate val md_theme_light_secondary = Color(0xFF635B70)\nprivate val md_theme_light_onSecondary = Color(0xFFFFFFFF)\nprivate val md_theme_light_secondaryContainer = Color(0xFFE9DEF8)\nprivate val md_theme_light_onSecondaryContainer = Color(0xFF1F182B)\nprivate val md_theme_light_tertiary = Color(0xFF7E525D)\nprivate val md_theme_light_onTertiary = Color(0xFFFFFFFF)\nprivate val md_theme_light_tertiaryContainer = Color(0xFFFFD9E1)\nprivate val md_theme_light_onTertiaryContainer = Color(0xFF31101B)\nprivate val md_theme_light_error = Color(0xFFBA1A1A)\nprivate val md_theme_light_errorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_light_onError = Color(0xFFFFFFFF)\nprivate val md_theme_light_onErrorContainer = Color(0xFF410002)\nprivate val md_theme_light_background = Color(0xFFFFFBFF)\nprivate val md_theme_light_onBackground = Color(0xFF1D1B1E)\nprivate val md_theme_light_surface = Color(0xFFFFFBFF)\nprivate val md_theme_light_onSurface = Color(0xFF1D1B1E)\nprivate val md_theme_light_surfaceVariant = Color(0xFFE7E0EB)\nprivate val md_theme_light_onSurfaceVariant = Color(0xFF49454E)\nprivate val md_theme_light_outline = Color(0xFF7A757F)\nprivate val md_theme_light_inverseOnSurface = Color(0xFFF5EFF4)\nprivate val md_theme_light_inverseSurface = Color(0xFF323033)\nprivate val md_theme_light_inversePrimary = Color(0xFFD3BBFF)\nprivate val md_theme_light_shadow = Color(0xFF000000)\nprivate val md_theme_light_surfaceTint = Color(0xFF6F43C0)\nprivate val md_theme_light_outlineVariant = Color(0xFFCBC4CF)\nprivate val md_theme_light_scrim = Color(0xFF000000)\nprivate val md_theme_light_surfaceContainerLowest = Color(0xFFFDF9FE)\nprivate val md_theme_light_surfaceContainerLow = Color(0xFFFCF8FD)\nprivate val md_theme_light_surfaceContainer = Color(0xFFFAF6FB)\nprivate val md_theme_light_surfaceContainerHigh = Color(0xFFF8F3F9)\nprivate val md_theme_light_surfaceContainerHighest = Color(0xFFF5F0F7)\n\n\nprivate val md_theme_dark_primary = Color(0xFFD3BBFF)\nprivate val md_theme_dark_onPrimary = Color(0xFF3F008D)\nprivate val md_theme_dark_primaryContainer = Color(0xFF5727A6)\nprivate val md_theme_dark_onPrimaryContainer = Color(0xFFEBDDFF)\nprivate val md_theme_dark_secondary = Color(0xFFCDC2DB)\nprivate val md_theme_dark_onSecondary = Color(0xFF342D40)\nprivate val md_theme_dark_secondaryContainer = Color(0xFF4B4358)\nprivate val md_theme_dark_onSecondaryContainer = Color(0xFFE9DEF8)\nprivate val md_theme_dark_tertiary = Color(0xFFF0B7C5)\nprivate val md_theme_dark_onTertiary = Color(0xFF4A2530)\nprivate val md_theme_dark_tertiaryContainer = Color(0xFF643B46)\nprivate val md_theme_dark_onTertiaryContainer = Color(0xFFFFD9E1)\nprivate val md_theme_dark_error = Color(0xFFFFB4AB)\nprivate val md_theme_dark_errorContainer = Color(0xFF93000A)\nprivate val md_theme_dark_onError = Color(0xFF690005)\nprivate val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_dark_background = Color(0xFF1D1B1E)\nprivate val md_theme_dark_onBackground = Color(0xFFE6E1E6)\nprivate val md_theme_dark_surface = Color(0xFF1D1B1E)\nprivate val md_theme_dark_onSurface = Color(0xFFE6E1E6)\nprivate val md_theme_dark_surfaceVariant = Color(0xFF49454E)\nprivate val md_theme_dark_onSurfaceVariant = Color(0xFFCBC4CF)\nprivate val md_theme_dark_outline = Color(0xFF948F99)\nprivate val md_theme_dark_inverseOnSurface = Color(0xFF1D1B1E)\nprivate val md_theme_dark_inverseSurface = Color(0xFFE6E1E6)\nprivate val md_theme_dark_inversePrimary = Color(0xFF6F43C0)\nprivate val md_theme_dark_shadow = Color(0xFF000000)\nprivate val md_theme_dark_surfaceTint = Color(0xFFD3BBFF)\nprivate val md_theme_dark_outlineVariant = Color(0xFF49454E)\nprivate val md_theme_dark_scrim = Color(0xFF000000)\nprivate val md_theme_dark_surfaceContainerLowest = Color(0xFF1F1D20)\nprivate val md_theme_dark_surfaceContainerLow = Color(0xFF211F22)\nprivate val md_theme_dark_surfaceContainer = Color(0xFF242226)\nprivate val md_theme_dark_surfaceContainerHigh = Color(0xFF29262B)\nprivate val md_theme_dark_surfaceContainerHighest = Color(0xFF2D2A30)\n\n\nval LightDeepPurpleTheme = lightColorScheme(\n    primary = md_theme_light_primary,\n    onPrimary = md_theme_light_onPrimary,\n    primaryContainer = md_theme_light_primaryContainer,\n    onPrimaryContainer = md_theme_light_onPrimaryContainer,\n    secondary = md_theme_light_secondary,\n    onSecondary = md_theme_light_onSecondary,\n    secondaryContainer = md_theme_light_secondaryContainer,\n    onSecondaryContainer = md_theme_light_onSecondaryContainer,\n    tertiary = md_theme_light_tertiary,\n    onTertiary = md_theme_light_onTertiary,\n    tertiaryContainer = md_theme_light_tertiaryContainer,\n    onTertiaryContainer = md_theme_light_onTertiaryContainer,\n    error = md_theme_light_error,\n    errorContainer = md_theme_light_errorContainer,\n    onError = md_theme_light_onError,\n    onErrorContainer = md_theme_light_onErrorContainer,\n    background = md_theme_light_background,\n    onBackground = md_theme_light_onBackground,\n    surface = md_theme_light_surface,\n    onSurface = md_theme_light_onSurface,\n    surfaceVariant = md_theme_light_surfaceVariant,\n    onSurfaceVariant = md_theme_light_onSurfaceVariant,\n    outline = md_theme_light_outline,\n    inverseOnSurface = md_theme_light_inverseOnSurface,\n    inverseSurface = md_theme_light_inverseSurface,\n    inversePrimary = md_theme_light_inversePrimary,\n    surfaceTint = md_theme_light_surfaceTint,\n    outlineVariant = md_theme_light_outlineVariant,\n    surfaceContainerLowest = md_theme_light_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_light_surfaceContainerLow,\n    surfaceContainer = md_theme_light_surfaceContainer,\n    surfaceContainerHigh = md_theme_light_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_light_surfaceContainerHighest,\n    scrim = md_theme_light_scrim,\n)\n\nval DarkDeepPurpleTheme = darkColorScheme(\n    primary = md_theme_dark_primary,\n    onPrimary = md_theme_dark_onPrimary,\n    primaryContainer = md_theme_dark_primaryContainer,\n    onPrimaryContainer = md_theme_dark_onPrimaryContainer,\n    secondary = md_theme_dark_secondary,\n    onSecondary = md_theme_dark_onSecondary,\n    secondaryContainer = md_theme_dark_secondaryContainer,\n    onSecondaryContainer = md_theme_dark_onSecondaryContainer,\n    tertiary = md_theme_dark_tertiary,\n    onTertiary = md_theme_dark_onTertiary,\n    tertiaryContainer = md_theme_dark_tertiaryContainer,\n    onTertiaryContainer = md_theme_dark_onTertiaryContainer,\n    error = md_theme_dark_error,\n    errorContainer = md_theme_dark_errorContainer,\n    onError = md_theme_dark_onError,\n    onErrorContainer = md_theme_dark_onErrorContainer,\n    background = md_theme_dark_background,\n    onBackground = md_theme_dark_onBackground,\n    surface = md_theme_dark_surface,\n    onSurface = md_theme_dark_onSurface,\n    surfaceVariant = md_theme_dark_surfaceVariant,\n    onSurfaceVariant = md_theme_dark_onSurfaceVariant,\n    outline = md_theme_dark_outline,\n    inverseOnSurface = md_theme_dark_inverseOnSurface,\n    inverseSurface = md_theme_dark_inverseSurface,\n    inversePrimary = md_theme_dark_inversePrimary,\n    surfaceTint = md_theme_dark_surfaceTint,\n    outlineVariant = md_theme_dark_outlineVariant,\n    surfaceContainerLowest = md_theme_dark_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_dark_surfaceContainerLow,\n    surfaceContainer = md_theme_dark_surfaceContainer,\n    surfaceContainerHigh = md_theme_dark_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_dark_surfaceContainerHighest,\n    scrim = md_theme_dark_scrim,\n)"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/FontConfig.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport android.content.Context\nimport android.graphics.Typeface\nimport android.net.Uri\nimport android.util.Log\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.text.font.FontFamily\nimport me.bmax.apatch.util.SafeUriResolver\nimport java.io.File\n\nobject FontConfig {\n    private const val PREFS_NAME = \"font_settings\"\n    private const val KEY_CUSTOM_FONT_ENABLED = \"custom_font_enabled\"\n    private const val KEY_CUSTOM_FONT_PATH = \"custom_font_path\"\n    private const val TAG = \"FontConfig\"\n\n    var isCustomFontEnabled: Boolean by mutableStateOf(false)\n        private set\n        \n    var customFontFilename: String? by mutableStateOf(null)\n        private set\n\n    fun setCustomFontEnabledState(enabled: Boolean) {\n        isCustomFontEnabled = enabled\n    }\n\n    fun load(context: Context) {\n        val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)\n        isCustomFontEnabled = prefs.getBoolean(KEY_CUSTOM_FONT_ENABLED, false)\n        customFontFilename = prefs.getString(KEY_CUSTOM_FONT_PATH, null)\n        \n        // Migration: If enabled but no filename, try to migrate from old fixed filename\n        if (isCustomFontEnabled && customFontFilename == null) {\n            val oldFixedFile = File(context.filesDir, \"custom_font.ttf\")\n            if (oldFixedFile.exists()) {\n                val newName = \"custom_font_${System.currentTimeMillis()}.ttf\"\n                if (oldFixedFile.renameTo(File(context.filesDir, newName))) {\n                    customFontFilename = newName\n                    save(context)\n                }\n            }\n        }\n        \n        // Validate if file exists\n        if (isCustomFontEnabled && customFontFilename != null) {\n            val file = File(context.filesDir, customFontFilename!!)\n            if (!file.exists()) {\n                isCustomFontEnabled = false\n                customFontFilename = null\n                save(context)\n            }\n        }\n    }\n\n    fun save(context: Context) {\n        val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)\n        prefs.edit()\n            .putBoolean(KEY_CUSTOM_FONT_ENABLED, isCustomFontEnabled)\n            .putString(KEY_CUSTOM_FONT_PATH, customFontFilename)\n            .apply()\n    }\n\n    fun applyCustomFont(context: Context, sourceFile: File) {\n        val newFilename = \"custom_font_${System.currentTimeMillis()}.ttf\"\n        val oldFilename = customFontFilename\n        \n        try {\n            val destFile = File(context.filesDir, newFilename)\n            sourceFile.copyTo(destFile, overwrite = true)\n            \n            // Delete old file if it exists and is different\n            if (oldFilename != null && oldFilename != newFilename) {\n                val oldFile = File(context.filesDir, oldFilename)\n                if (oldFile.exists()) {\n                    oldFile.delete()\n                }\n            }\n            \n            isCustomFontEnabled = true\n            customFontFilename = newFilename\n            save(context)\n        } catch (e: Exception) {\n            Log.e(TAG, \"Failed to apply custom font\", e)\n        }\n    }\n\n    fun saveFontFile(context: Context, uri: Uri): Boolean {\n        return try {\n            val newFilename = \"custom_font_${System.currentTimeMillis()}.ttf\"\n            val oldFilename = customFontFilename\n            \n            SafeUriResolver.openInputStream(context, uri)?.use { input ->\n                val file = File(context.filesDir, newFilename)\n                file.outputStream().use { output ->\n                    input.copyTo(output)\n                }\n            }\n            \n            // Delete old file if it exists and is different\n            if (oldFilename != null && oldFilename != newFilename) {\n                val oldFile = File(context.filesDir, oldFilename)\n                if (oldFile.exists()) {\n                    oldFile.delete()\n                }\n            }\n            \n            isCustomFontEnabled = true\n            customFontFilename = newFilename\n            save(context)\n            true\n        } catch (e: Exception) {\n            Log.e(TAG, \"Failed to save font file\", e)\n            false\n        }\n    }\n\n    fun clearFont(context: Context) {\n        if (customFontFilename != null) {\n            val file = File(context.filesDir, customFontFilename!!)\n            if (file.exists()) {\n                file.delete()\n            }\n        }\n        customFontFilename = null\n        save(context)\n    }\n\n    // Font cache to avoid Typeface.createFromFile disk I/O on every recomposition\n    private var cachedFontFamily: FontFamily? = null\n    private var cachedFilename: String? = null\n\n    fun getFontFamily(context: Context): FontFamily {\n        if (isCustomFontEnabled && customFontFilename != null) {\n            // Return cached font if filename hasn't changed\n            if (customFontFilename == cachedFilename && cachedFontFamily != null) {\n                return cachedFontFamily!!\n            }\n            val file = File(context.filesDir, customFontFilename!!)\n            if (file.exists()) {\n                try {\n                    val typeface = Typeface.createFromFile(file)\n                    val family = FontFamily(typeface)\n                    cachedFilename = customFontFilename\n                    cachedFontFamily = family\n                    return family\n                } catch (e: Exception) {\n                    Log.e(TAG, \"Failed to load custom font\", e)\n                }\n            }\n        }\n        // Invalidate cache when custom font is disabled\n        cachedFilename = null\n        cachedFontFamily = null\n        return FontFamily.Default\n    }\n\n    fun invalidateFontCache() {\n        cachedFilename = null\n        cachedFontFamily = null\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/GreenTheme.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lightColorScheme\nimport androidx.compose.ui.graphics.Color\n\nprivate val md_theme_light_primary = Color(0xFF006E1A)\nprivate val md_theme_light_onPrimary = Color(0xFFFFFFFF)\nprivate val md_theme_light_primaryContainer = Color(0xFF96F990)\nprivate val md_theme_light_onPrimaryContainer = Color(0xFF002203)\nprivate val md_theme_light_secondary = Color(0xFF53634F)\nprivate val md_theme_light_onSecondary = Color(0xFFFFFFFF)\nprivate val md_theme_light_secondaryContainer = Color(0xFFD6E8CE)\nprivate val md_theme_light_onSecondaryContainer = Color(0xFF111F0F)\nprivate val md_theme_light_tertiary = Color(0xFF38656A)\nprivate val md_theme_light_onTertiary = Color(0xFFFFFFFF)\nprivate val md_theme_light_tertiaryContainer = Color(0xFFBCEBF0)\nprivate val md_theme_light_onTertiaryContainer = Color(0xFF002023)\nprivate val md_theme_light_error = Color(0xFFBA1A1A)\nprivate val md_theme_light_errorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_light_onError = Color(0xFFFFFFFF)\nprivate val md_theme_light_onErrorContainer = Color(0xFF410002)\nprivate val md_theme_light_background = Color(0xFFFCFDF6)\nprivate val md_theme_light_onBackground = Color(0xFF1A1C19)\nprivate val md_theme_light_surface = Color(0xFFFCFDF6)\nprivate val md_theme_light_onSurface = Color(0xFF1A1C19)\nprivate val md_theme_light_surfaceVariant = Color(0xFFDEE5D8)\nprivate val md_theme_light_onSurfaceVariant = Color(0xFF424940)\nprivate val md_theme_light_outline = Color(0xFF72796F)\nprivate val md_theme_light_inverseOnSurface = Color(0xFFF1F1EB)\nprivate val md_theme_light_inverseSurface = Color(0xFF2F312D)\nprivate val md_theme_light_inversePrimary = Color(0xFF7ADC77)\nprivate val md_theme_light_shadow = Color(0xFF000000)\nprivate val md_theme_light_surfaceTint = Color(0xFF006E1A)\nprivate val md_theme_light_outlineVariant = Color(0xFFC2C8BD)\nprivate val md_theme_light_scrim = Color(0xFF000000)\nprivate val md_theme_light_surfaceContainerLowest = Color(0xFFFAFBF4)\nprivate val md_theme_light_surfaceContainerLow = Color(0xFFF9FAF3)\nprivate val md_theme_light_surfaceContainer = Color(0xFFF6F8F0)\nprivate val md_theme_light_surfaceContainerHigh = Color(0xFFF3F6ED)\nprivate val md_theme_light_surfaceContainerHighest = Color(0xFFF0F3EA)\n\n\nprivate val md_theme_dark_primary = Color(0xFF7ADC77)\nprivate val md_theme_dark_onPrimary = Color(0xFF003909)\nprivate val md_theme_dark_primaryContainer = Color(0xFF005311)\nprivate val md_theme_dark_onPrimaryContainer = Color(0xFF96F990)\nprivate val md_theme_dark_secondary = Color(0xFFBACCB3)\nprivate val md_theme_dark_onSecondary = Color(0xFF253423)\nprivate val md_theme_dark_secondaryContainer = Color(0xFF3B4B38)\nprivate val md_theme_dark_onSecondaryContainer = Color(0xFFD6E8CE)\nprivate val md_theme_dark_tertiary = Color(0xFFA0CFD4)\nprivate val md_theme_dark_onTertiary = Color(0xFF00363B)\nprivate val md_theme_dark_tertiaryContainer = Color(0xFF1E4D52)\nprivate val md_theme_dark_onTertiaryContainer = Color(0xFFBCEBF0)\nprivate val md_theme_dark_error = Color(0xFFFFB4AB)\nprivate val md_theme_dark_errorContainer = Color(0xFF93000A)\nprivate val md_theme_dark_onError = Color(0xFF690005)\nprivate val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_dark_background = Color(0xFF1A1C19)\nprivate val md_theme_dark_onBackground = Color(0xFFE2E3DD)\nprivate val md_theme_dark_surface = Color(0xFF1A1C19)\nprivate val md_theme_dark_onSurface = Color(0xFFE2E3DD)\nprivate val md_theme_dark_surfaceVariant = Color(0xFF424940)\nprivate val md_theme_dark_onSurfaceVariant = Color(0xFFC2C8BD)\nprivate val md_theme_dark_outline = Color(0xFF8C9388)\nprivate val md_theme_dark_inverseOnSurface = Color(0xFF1A1C19)\nprivate val md_theme_dark_inverseSurface = Color(0xFFE2E3DD)\nprivate val md_theme_dark_inversePrimary = Color(0xFF006E1A)\nprivate val md_theme_dark_shadow = Color(0xFF000000)\nprivate val md_theme_dark_surfaceTint = Color(0xFF7ADC77)\nprivate val md_theme_dark_outlineVariant = Color(0xFF424940)\nprivate val md_theme_dark_scrim = Color(0xFF000000)\nprivate val md_theme_dark_surfaceContainerLowest = Color(0xFF1C1E1A)\nprivate val md_theme_dark_surfaceContainerLow = Color(0xFF1E201C)\nprivate val md_theme_dark_surfaceContainer = Color(0xFF212420)\nprivate val md_theme_dark_surfaceContainerHigh = Color(0xFF252823)\nprivate val md_theme_dark_surfaceContainerHighest = Color(0xFF292D27)\n\n\nval LightGreenTheme = lightColorScheme(\n    primary = md_theme_light_primary,\n    onPrimary = md_theme_light_onPrimary,\n    primaryContainer = md_theme_light_primaryContainer,\n    onPrimaryContainer = md_theme_light_onPrimaryContainer,\n    secondary = md_theme_light_secondary,\n    onSecondary = md_theme_light_onSecondary,\n    secondaryContainer = md_theme_light_secondaryContainer,\n    onSecondaryContainer = md_theme_light_onSecondaryContainer,\n    tertiary = md_theme_light_tertiary,\n    onTertiary = md_theme_light_onTertiary,\n    tertiaryContainer = md_theme_light_tertiaryContainer,\n    onTertiaryContainer = md_theme_light_onTertiaryContainer,\n    error = md_theme_light_error,\n    errorContainer = md_theme_light_errorContainer,\n    onError = md_theme_light_onError,\n    onErrorContainer = md_theme_light_onErrorContainer,\n    background = md_theme_light_background,\n    onBackground = md_theme_light_onBackground,\n    surface = md_theme_light_surface,\n    onSurface = md_theme_light_onSurface,\n    surfaceVariant = md_theme_light_surfaceVariant,\n    onSurfaceVariant = md_theme_light_onSurfaceVariant,\n    outline = md_theme_light_outline,\n    inverseOnSurface = md_theme_light_inverseOnSurface,\n    inverseSurface = md_theme_light_inverseSurface,\n    inversePrimary = md_theme_light_inversePrimary,\n    surfaceTint = md_theme_light_surfaceTint,\n    outlineVariant = md_theme_light_outlineVariant,\n    surfaceContainerLowest = md_theme_light_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_light_surfaceContainerLow,\n    surfaceContainer = md_theme_light_surfaceContainer,\n    surfaceContainerHigh = md_theme_light_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_light_surfaceContainerHighest,\n    scrim = md_theme_light_scrim,\n)\n\nval DarkGreenTheme = darkColorScheme(\n    primary = md_theme_dark_primary,\n    onPrimary = md_theme_dark_onPrimary,\n    primaryContainer = md_theme_dark_primaryContainer,\n    onPrimaryContainer = md_theme_dark_onPrimaryContainer,\n    secondary = md_theme_dark_secondary,\n    onSecondary = md_theme_dark_onSecondary,\n    secondaryContainer = md_theme_dark_secondaryContainer,\n    onSecondaryContainer = md_theme_dark_onSecondaryContainer,\n    tertiary = md_theme_dark_tertiary,\n    onTertiary = md_theme_dark_onTertiary,\n    tertiaryContainer = md_theme_dark_tertiaryContainer,\n    onTertiaryContainer = md_theme_dark_onTertiaryContainer,\n    error = md_theme_dark_error,\n    errorContainer = md_theme_dark_errorContainer,\n    onError = md_theme_dark_onError,\n    onErrorContainer = md_theme_dark_onErrorContainer,\n    background = md_theme_dark_background,\n    onBackground = md_theme_dark_onBackground,\n    surface = md_theme_dark_surface,\n    onSurface = md_theme_dark_onSurface,\n    surfaceVariant = md_theme_dark_surfaceVariant,\n    onSurfaceVariant = md_theme_dark_onSurfaceVariant,\n    outline = md_theme_dark_outline,\n    inverseOnSurface = md_theme_dark_inverseOnSurface,\n    inverseSurface = md_theme_dark_inverseSurface,\n    inversePrimary = md_theme_dark_inversePrimary,\n    surfaceTint = md_theme_dark_surfaceTint,\n    outlineVariant = md_theme_dark_outlineVariant,\n    surfaceContainerLowest = md_theme_dark_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_dark_surfaceContainerLow,\n    surfaceContainer = md_theme_dark_surfaceContainer,\n    surfaceContainerHigh = md_theme_dark_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_dark_surfaceContainerHighest,\n    scrim = md_theme_dark_scrim,\n)"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/IndigoTheme.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lightColorScheme\nimport androidx.compose.ui.graphics.Color\n\nprivate val md_theme_light_primary = Color(0xFF4355B9)\nprivate val md_theme_light_onPrimary = Color(0xFFFFFFFF)\nprivate val md_theme_light_primaryContainer = Color(0xFFDEE0FF)\nprivate val md_theme_light_onPrimaryContainer = Color(0xFF00105C)\nprivate val md_theme_light_secondary = Color(0xFF5B5D72)\nprivate val md_theme_light_onSecondary = Color(0xFFFFFFFF)\nprivate val md_theme_light_secondaryContainer = Color(0xFFE0E1F9)\nprivate val md_theme_light_onSecondaryContainer = Color(0xFF181A2C)\nprivate val md_theme_light_tertiary = Color(0xFF77536D)\nprivate val md_theme_light_onTertiary = Color(0xFFFFFFFF)\nprivate val md_theme_light_tertiaryContainer = Color(0xFFFFD7F1)\nprivate val md_theme_light_onTertiaryContainer = Color(0xFF2D1228)\nprivate val md_theme_light_error = Color(0xFFBA1A1A)\nprivate val md_theme_light_errorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_light_onError = Color(0xFFFFFFFF)\nprivate val md_theme_light_onErrorContainer = Color(0xFF410002)\nprivate val md_theme_light_background = Color(0xFFFEFBFF)\nprivate val md_theme_light_onBackground = Color(0xFF1B1B1F)\nprivate val md_theme_light_surface = Color(0xFFFEFBFF)\nprivate val md_theme_light_onSurface = Color(0xFF1B1B1F)\nprivate val md_theme_light_surfaceVariant = Color(0xFFE3E1EC)\nprivate val md_theme_light_onSurfaceVariant = Color(0xFF46464F)\nprivate val md_theme_light_outline = Color(0xFF767680)\nprivate val md_theme_light_inverseOnSurface = Color(0xFFF3F0F4)\nprivate val md_theme_light_inverseSurface = Color(0xFF303034)\nprivate val md_theme_light_inversePrimary = Color(0xFFBAC3FF)\nprivate val md_theme_light_shadow = Color(0xFF000000)\nprivate val md_theme_light_surfaceTint = Color(0xFF4355B9)\nprivate val md_theme_light_outlineVariant = Color(0xFFC7C5D0)\nprivate val md_theme_light_scrim = Color(0xFF000000)\nprivate val md_theme_light_surfaceContainerLowest = Color(0xFFFCF9FE)\nprivate val md_theme_light_surfaceContainerLow = Color(0xFFFBF8FD)\nprivate val md_theme_light_surfaceContainer = Color(0xFFF9F6FB)\nprivate val md_theme_light_surfaceContainerHigh = Color(0xFFF6F3F9)\nprivate val md_theme_light_surfaceContainerHighest = Color(0xFFF3F1F7)\n\n\nprivate val md_theme_dark_primary = Color(0xFFBAC3FF)\nprivate val md_theme_dark_onPrimary = Color(0xFF08218A)\nprivate val md_theme_dark_primaryContainer = Color(0xFF293CA0)\nprivate val md_theme_dark_onPrimaryContainer = Color(0xFFDEE0FF)\nprivate val md_theme_dark_secondary = Color(0xFFC3C5DD)\nprivate val md_theme_dark_onSecondary = Color(0xFF2D2F42)\nprivate val md_theme_dark_secondaryContainer = Color(0xFF434659)\nprivate val md_theme_dark_onSecondaryContainer = Color(0xFFE0E1F9)\nprivate val md_theme_dark_tertiary = Color(0xFFE6BAD7)\nprivate val md_theme_dark_onTertiary = Color(0xFF44263D)\nprivate val md_theme_dark_tertiaryContainer = Color(0xFF5D3C55)\nprivate val md_theme_dark_onTertiaryContainer = Color(0xFFFFD7F1)\nprivate val md_theme_dark_error = Color(0xFFFFB4AB)\nprivate val md_theme_dark_errorContainer = Color(0xFF93000A)\nprivate val md_theme_dark_onError = Color(0xFF690005)\nprivate val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_dark_background = Color(0xFF1B1B1F)\nprivate val md_theme_dark_onBackground = Color(0xFFE4E1E6)\nprivate val md_theme_dark_surface = Color(0xFF1B1B1F)\nprivate val md_theme_dark_onSurface = Color(0xFFE4E1E6)\nprivate val md_theme_dark_surfaceVariant = Color(0xFF46464F)\nprivate val md_theme_dark_onSurfaceVariant = Color(0xFFC7C5D0)\nprivate val md_theme_dark_outline = Color(0xFF90909A)\nprivate val md_theme_dark_inverseOnSurface = Color(0xFF1B1B1F)\nprivate val md_theme_dark_inverseSurface = Color(0xFFE4E1E6)\nprivate val md_theme_dark_inversePrimary = Color(0xFF4355B9)\nprivate val md_theme_dark_shadow = Color(0xFF000000)\nprivate val md_theme_dark_surfaceTint = Color(0xFFBAC3FF)\nprivate val md_theme_dark_outlineVariant = Color(0xFF46464F)\nprivate val md_theme_dark_scrim = Color(0xFF000000)\nprivate val md_theme_dark_surfaceContainerLowest = Color(0xFF1D1D21)\nprivate val md_theme_dark_surfaceContainerLow = Color(0xFF1F1F23)\nprivate val md_theme_dark_surfaceContainer = Color(0xFF222227)\nprivate val md_theme_dark_surfaceContainerHigh = Color(0xFF27272C)\nprivate val md_theme_dark_surfaceContainerHighest = Color(0xFF2B2B31)\n\n\nval LightIndigoTheme = lightColorScheme(\n    primary = md_theme_light_primary,\n    onPrimary = md_theme_light_onPrimary,\n    primaryContainer = md_theme_light_primaryContainer,\n    onPrimaryContainer = md_theme_light_onPrimaryContainer,\n    secondary = md_theme_light_secondary,\n    onSecondary = md_theme_light_onSecondary,\n    secondaryContainer = md_theme_light_secondaryContainer,\n    onSecondaryContainer = md_theme_light_onSecondaryContainer,\n    tertiary = md_theme_light_tertiary,\n    onTertiary = md_theme_light_onTertiary,\n    tertiaryContainer = md_theme_light_tertiaryContainer,\n    onTertiaryContainer = md_theme_light_onTertiaryContainer,\n    error = md_theme_light_error,\n    errorContainer = md_theme_light_errorContainer,\n    onError = md_theme_light_onError,\n    onErrorContainer = md_theme_light_onErrorContainer,\n    background = md_theme_light_background,\n    onBackground = md_theme_light_onBackground,\n    surface = md_theme_light_surface,\n    onSurface = md_theme_light_onSurface,\n    surfaceVariant = md_theme_light_surfaceVariant,\n    onSurfaceVariant = md_theme_light_onSurfaceVariant,\n    outline = md_theme_light_outline,\n    inverseOnSurface = md_theme_light_inverseOnSurface,\n    inverseSurface = md_theme_light_inverseSurface,\n    inversePrimary = md_theme_light_inversePrimary,\n    surfaceTint = md_theme_light_surfaceTint,\n    outlineVariant = md_theme_light_outlineVariant,\n    surfaceContainerLowest = md_theme_light_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_light_surfaceContainerLow,\n    surfaceContainer = md_theme_light_surfaceContainer,\n    surfaceContainerHigh = md_theme_light_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_light_surfaceContainerHighest,\n    scrim = md_theme_light_scrim,\n)\n\nval DarkIndigoTheme = darkColorScheme(\n    primary = md_theme_dark_primary,\n    onPrimary = md_theme_dark_onPrimary,\n    primaryContainer = md_theme_dark_primaryContainer,\n    onPrimaryContainer = md_theme_dark_onPrimaryContainer,\n    secondary = md_theme_dark_secondary,\n    onSecondary = md_theme_dark_onSecondary,\n    secondaryContainer = md_theme_dark_secondaryContainer,\n    onSecondaryContainer = md_theme_dark_onSecondaryContainer,\n    tertiary = md_theme_dark_tertiary,\n    onTertiary = md_theme_dark_onTertiary,\n    tertiaryContainer = md_theme_dark_tertiaryContainer,\n    onTertiaryContainer = md_theme_dark_onTertiaryContainer,\n    error = md_theme_dark_error,\n    errorContainer = md_theme_dark_errorContainer,\n    onError = md_theme_dark_onError,\n    onErrorContainer = md_theme_dark_onErrorContainer,\n    background = md_theme_dark_background,\n    onBackground = md_theme_dark_onBackground,\n    surface = md_theme_dark_surface,\n    onSurface = md_theme_dark_onSurface,\n    surfaceVariant = md_theme_dark_surfaceVariant,\n    onSurfaceVariant = md_theme_dark_onSurfaceVariant,\n    outline = md_theme_dark_outline,\n    inverseOnSurface = md_theme_dark_inverseOnSurface,\n    inverseSurface = md_theme_dark_inverseSurface,\n    inversePrimary = md_theme_dark_inversePrimary,\n    surfaceTint = md_theme_dark_surfaceTint,\n    outlineVariant = md_theme_dark_outlineVariant,\n    surfaceContainerLowest = md_theme_dark_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_dark_surfaceContainerLow,\n    surfaceContainer = md_theme_dark_surfaceContainer,\n    surfaceContainerHigh = md_theme_dark_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_dark_surfaceContainerHighest,\n    scrim = md_theme_dark_scrim,\n)"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/InkWashTheme.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lightColorScheme\nimport androidx.compose.ui.graphics.Color\n\nprivate val md_theme_light_primary = Color(0xFF424242)\nprivate val md_theme_light_onPrimary = Color(0xFFFFFFFF)\nprivate val md_theme_light_primaryContainer = Color(0xFFE0E0E0)\nprivate val md_theme_light_onPrimaryContainer = Color(0xFF1A1A1A)\nprivate val md_theme_light_secondary = Color(0xFF5C4033)\nprivate val md_theme_light_onSecondary = Color(0xFFFFFFFF)\nprivate val md_theme_light_secondaryContainer = Color(0xFFD7CCC8)\nprivate val md_theme_light_onSecondaryContainer = Color(0xFF3E2723)\nprivate val md_theme_light_tertiary = Color(0xFFB71C1C)\nprivate val md_theme_light_onTertiary = Color(0xFFFFFFFF)\nprivate val md_theme_light_tertiaryContainer = Color(0xFFFFCDD2)\nprivate val md_theme_light_onTertiaryContainer = Color(0xFF4A0000)\nprivate val md_theme_light_error = Color(0xFFB71C1C)\nprivate val md_theme_light_errorContainer = Color(0xFFFFEBEE)\nprivate val md_theme_light_onError = Color(0xFFFFFFFF)\nprivate val md_theme_light_onErrorContainer = Color(0xFF4A0000)\nprivate val md_theme_light_background = Color(0xFFFAFAFA)\nprivate val md_theme_light_onBackground = Color(0xFF212121)\nprivate val md_theme_light_surface = Color(0xFFFAFAFA)\nprivate val md_theme_light_onSurface = Color(0xFF212121)\nprivate val md_theme_light_surfaceVariant = Color(0xFFE8E8E8)\nprivate val md_theme_light_onSurfaceVariant = Color(0xFF424242)\nprivate val md_theme_light_outline = Color(0xFF757575)\nprivate val md_theme_light_inverseOnSurface = Color(0xFFF5F5F5)\nprivate val md_theme_light_inverseSurface = Color(0xFF2C2C2C)\nprivate val md_theme_light_inversePrimary = Color(0xFFBDBDBD)\nprivate val md_theme_light_shadow = Color(0xFF000000)\nprivate val md_theme_light_surfaceTint = Color(0xFF424242)\nprivate val md_theme_light_outlineVariant = Color(0xFFBDBDBD)\nprivate val md_theme_light_scrim = Color(0xFF000000)\nprivate val md_theme_light_surfaceContainerLowest = Color(0xFFF9F9F9)\nprivate val md_theme_light_surfaceContainerLow = Color(0xFFF8F8F8)\nprivate val md_theme_light_surfaceContainer = Color(0xFFF6F6F6)\nprivate val md_theme_light_surfaceContainerHigh = Color(0xFFF4F4F4)\nprivate val md_theme_light_surfaceContainerHighest = Color(0xFFF3F3F3)\n\n\nprivate val md_theme_dark_primary = Color(0xFFBDBDBD)\nprivate val md_theme_dark_onPrimary = Color(0xFF1A1A1A)\nprivate val md_theme_dark_primaryContainer = Color(0xFF2C2C2C)\nprivate val md_theme_dark_onPrimaryContainer = Color(0xFFE0E0E0)\nprivate val md_theme_dark_secondary = Color(0xFF8D6E63)\nprivate val md_theme_dark_onSecondary = Color(0xFF3E2723)\nprivate val md_theme_dark_secondaryContainer = Color(0xFF4E342E)\nprivate val md_theme_dark_onSecondaryContainer = Color(0xFFD7CCC8)\nprivate val md_theme_dark_tertiary = Color(0xFFEF5350)\nprivate val md_theme_dark_onTertiary = Color(0xFF4A0000)\nprivate val md_theme_dark_tertiaryContainer = Color(0xFF7F0000)\nprivate val md_theme_dark_onTertiaryContainer = Color(0xFFFFCDD2)\nprivate val md_theme_dark_error = Color(0xFFCF6679)\nprivate val md_theme_dark_errorContainer = Color(0xFF7F0000)\nprivate val md_theme_dark_onError = Color(0xFF4A0000)\nprivate val md_theme_dark_onErrorContainer = Color(0xFFFFEBEE)\nprivate val md_theme_dark_background = Color(0xFF121212)\nprivate val md_theme_dark_onBackground = Color(0xFFE0E0E0)\nprivate val md_theme_dark_surface = Color(0xFF121212)\nprivate val md_theme_dark_onSurface = Color(0xFFE0E0E0)\nprivate val md_theme_dark_surfaceVariant = Color(0xFF2C2C2C)\nprivate val md_theme_dark_onSurfaceVariant = Color(0xFFBDBDBD)\nprivate val md_theme_dark_outline = Color(0xFF757575)\nprivate val md_theme_dark_inverseOnSurface = Color(0xFF121212)\nprivate val md_theme_dark_inverseSurface = Color(0xFFE0E0E0)\nprivate val md_theme_dark_inversePrimary = Color(0xFF424242)\nprivate val md_theme_dark_shadow = Color(0xFF000000)\nprivate val md_theme_dark_surfaceTint = Color(0xFFBDBDBD)\nprivate val md_theme_dark_outlineVariant = Color(0xFF424242)\nprivate val md_theme_dark_scrim = Color(0xFF000000)\nprivate val md_theme_dark_surfaceContainerLowest = Color(0xFF131313)\nprivate val md_theme_dark_surfaceContainerLow = Color(0xFF141414)\nprivate val md_theme_dark_surfaceContainer = Color(0xFF161616)\nprivate val md_theme_dark_surfaceContainerHigh = Color(0xFF191919)\nprivate val md_theme_dark_surfaceContainerHighest = Color(0xFF1B1B1B)\n\n\nval LightInkWashTheme = lightColorScheme(\n    primary = md_theme_light_primary,\n    onPrimary = md_theme_light_onPrimary,\n    primaryContainer = md_theme_light_primaryContainer,\n    onPrimaryContainer = md_theme_light_onPrimaryContainer,\n    secondary = md_theme_light_secondary,\n    onSecondary = md_theme_light_onSecondary,\n    secondaryContainer = md_theme_light_secondaryContainer,\n    onSecondaryContainer = md_theme_light_onSecondaryContainer,\n    tertiary = md_theme_light_tertiary,\n    onTertiary = md_theme_light_onTertiary,\n    tertiaryContainer = md_theme_light_tertiaryContainer,\n    onTertiaryContainer = md_theme_light_onTertiaryContainer,\n    error = md_theme_light_error,\n    errorContainer = md_theme_light_errorContainer,\n    onError = md_theme_light_onError,\n    onErrorContainer = md_theme_light_onErrorContainer,\n    background = md_theme_light_background,\n    onBackground = md_theme_light_onBackground,\n    surface = md_theme_light_surface,\n    onSurface = md_theme_light_onSurface,\n    surfaceVariant = md_theme_light_surfaceVariant,\n    onSurfaceVariant = md_theme_light_onSurfaceVariant,\n    outline = md_theme_light_outline,\n    inverseOnSurface = md_theme_light_inverseOnSurface,\n    inverseSurface = md_theme_light_inverseSurface,\n    inversePrimary = md_theme_light_inversePrimary,\n    surfaceTint = md_theme_light_surfaceTint,\n    outlineVariant = md_theme_light_outlineVariant,\n    surfaceContainerLowest = md_theme_light_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_light_surfaceContainerLow,\n    surfaceContainer = md_theme_light_surfaceContainer,\n    surfaceContainerHigh = md_theme_light_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_light_surfaceContainerHighest,\n    scrim = md_theme_light_scrim,\n)\n\nval DarkInkWashTheme = darkColorScheme(\n    primary = md_theme_dark_primary,\n    onPrimary = md_theme_dark_onPrimary,\n    primaryContainer = md_theme_dark_primaryContainer,\n    onPrimaryContainer = md_theme_dark_onPrimaryContainer,\n    secondary = md_theme_dark_secondary,\n    onSecondary = md_theme_dark_onSecondary,\n    secondaryContainer = md_theme_dark_secondaryContainer,\n    onSecondaryContainer = md_theme_dark_onSecondaryContainer,\n    tertiary = md_theme_dark_tertiary,\n    onTertiary = md_theme_dark_onTertiary,\n    tertiaryContainer = md_theme_dark_tertiaryContainer,\n    onTertiaryContainer = md_theme_dark_onTertiaryContainer,\n    error = md_theme_dark_error,\n    errorContainer = md_theme_dark_errorContainer,\n    onError = md_theme_dark_onError,\n    onErrorContainer = md_theme_dark_onErrorContainer,\n    background = md_theme_dark_background,\n    onBackground = md_theme_dark_onBackground,\n    surface = md_theme_dark_surface,\n    onSurface = md_theme_dark_onSurface,\n    surfaceVariant = md_theme_dark_surfaceVariant,\n    onSurfaceVariant = md_theme_dark_onSurfaceVariant,\n    outline = md_theme_dark_outline,\n    inverseOnSurface = md_theme_dark_inverseOnSurface,\n    inverseSurface = md_theme_dark_inverseSurface,\n    inversePrimary = md_theme_dark_inversePrimary,\n    surfaceTint = md_theme_dark_surfaceTint,\n    outlineVariant = md_theme_dark_outlineVariant,\n    surfaceContainerLowest = md_theme_dark_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_dark_surfaceContainerLow,\n    surfaceContainer = md_theme_dark_surfaceContainer,\n    surfaceContainerHigh = md_theme_dark_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_dark_surfaceContainerHighest,\n    scrim = md_theme_dark_scrim,\n)\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/LightBlueTheme.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lightColorScheme\nimport androidx.compose.ui.graphics.Color\n\nprivate val md_theme_light_primary = Color(0xFF006493)\nprivate val md_theme_light_onPrimary = Color(0xFFFFFFFF)\nprivate val md_theme_light_primaryContainer = Color(0xFFCAE6FF)\nprivate val md_theme_light_onPrimaryContainer = Color(0xFF001E30)\nprivate val md_theme_light_secondary = Color(0xFF50606E)\nprivate val md_theme_light_onSecondary = Color(0xFFFFFFFF)\nprivate val md_theme_light_secondaryContainer = Color(0xFFD3E5F5)\nprivate val md_theme_light_onSecondaryContainer = Color(0xFF0C1D29)\nprivate val md_theme_light_tertiary = Color(0xFF65587B)\nprivate val md_theme_light_onTertiary = Color(0xFFFFFFFF)\nprivate val md_theme_light_tertiaryContainer = Color(0xFFEBDDFF)\nprivate val md_theme_light_onTertiaryContainer = Color(0xFF201634)\nprivate val md_theme_light_error = Color(0xFFBA1A1A)\nprivate val md_theme_light_errorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_light_onError = Color(0xFFFFFFFF)\nprivate val md_theme_light_onErrorContainer = Color(0xFF410002)\nprivate val md_theme_light_background = Color(0xFFFCFCFF)\nprivate val md_theme_light_onBackground = Color(0xFF1A1C1E)\nprivate val md_theme_light_surface = Color(0xFFFCFCFF)\nprivate val md_theme_light_onSurface = Color(0xFF1A1C1E)\nprivate val md_theme_light_surfaceVariant = Color(0xFFDDE3EA)\nprivate val md_theme_light_onSurfaceVariant = Color(0xFF41474D)\nprivate val md_theme_light_outline = Color(0xFF72787E)\nprivate val md_theme_light_inverseOnSurface = Color(0xFFF0F0F3)\nprivate val md_theme_light_inverseSurface = Color(0xFF2E3133)\nprivate val md_theme_light_inversePrimary = Color(0xFF8DCDFF)\nprivate val md_theme_light_shadow = Color(0xFF000000)\nprivate val md_theme_light_surfaceTint = Color(0xFF006493)\nprivate val md_theme_light_outlineVariant = Color(0xFFC1C7CE)\nprivate val md_theme_light_scrim = Color(0xFF000000)\nprivate val md_theme_light_surfaceContainerLowest = Color(0xFFFAFAFD)\nprivate val md_theme_light_surfaceContainerLow = Color(0xFFF8F9FC)\nprivate val md_theme_light_surfaceContainer = Color(0xFFF6F7FB)\nprivate val md_theme_light_surfaceContainerHigh = Color(0xFFF3F5F9)\nprivate val md_theme_light_surfaceContainerHighest = Color(0xFFF0F2F7)\n\n\nprivate val md_theme_dark_primary = Color(0xFF8DCDFF)\nprivate val md_theme_dark_onPrimary = Color(0xFF00344F)\nprivate val md_theme_dark_primaryContainer = Color(0xFF004B70)\nprivate val md_theme_dark_onPrimaryContainer = Color(0xFFCAE6FF)\nprivate val md_theme_dark_secondary = Color(0xFFB7C9D9)\nprivate val md_theme_dark_onSecondary = Color(0xFF22323F)\nprivate val md_theme_dark_secondaryContainer = Color(0xFF384956)\nprivate val md_theme_dark_onSecondaryContainer = Color(0xFFD3E5F5)\nprivate val md_theme_dark_tertiary = Color(0xFFCFC0E8)\nprivate val md_theme_dark_onTertiary = Color(0xFF362B4B)\nprivate val md_theme_dark_tertiaryContainer = Color(0xFF4D4162)\nprivate val md_theme_dark_onTertiaryContainer = Color(0xFFEBDDFF)\nprivate val md_theme_dark_error = Color(0xFFFFB4AB)\nprivate val md_theme_dark_errorContainer = Color(0xFF93000A)\nprivate val md_theme_dark_onError = Color(0xFF690005)\nprivate val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_dark_background = Color(0xFF1A1C1E)\nprivate val md_theme_dark_onBackground = Color(0xFFE2E2E5)\nprivate val md_theme_dark_surface = Color(0xFF1A1C1E)\nprivate val md_theme_dark_onSurface = Color(0xFFE2E2E5)\nprivate val md_theme_dark_surfaceVariant = Color(0xFF41474D)\nprivate val md_theme_dark_onSurfaceVariant = Color(0xFFC1C7CE)\nprivate val md_theme_dark_outline = Color(0xFF8B9198)\nprivate val md_theme_dark_inverseOnSurface = Color(0xFF1A1C1E)\nprivate val md_theme_dark_inverseSurface = Color(0xFFE2E2E5)\nprivate val md_theme_dark_inversePrimary = Color(0xFF006493)\nprivate val md_theme_dark_shadow = Color(0xFF000000)\nprivate val md_theme_dark_surfaceTint = Color(0xFF8DCDFF)\nprivate val md_theme_dark_outlineVariant = Color(0xFF41474D)\nprivate val md_theme_dark_scrim = Color(0xFF000000)\nprivate val md_theme_dark_surfaceContainerLowest = Color(0xFF1B1E20)\nprivate val md_theme_dark_surfaceContainerLow = Color(0xFF1D2022)\nprivate val md_theme_dark_surfaceContainer = Color(0xFF212326)\nprivate val md_theme_dark_surfaceContainerHigh = Color(0xFF24282B)\nprivate val md_theme_dark_surfaceContainerHighest = Color(0xFF282C2F)\n\n\n\nval LightLightBlueTheme = lightColorScheme(\n    primary = md_theme_light_primary,\n    onPrimary = md_theme_light_onPrimary,\n    primaryContainer = md_theme_light_primaryContainer,\n    onPrimaryContainer = md_theme_light_onPrimaryContainer,\n    secondary = md_theme_light_secondary,\n    onSecondary = md_theme_light_onSecondary,\n    secondaryContainer = md_theme_light_secondaryContainer,\n    onSecondaryContainer = md_theme_light_onSecondaryContainer,\n    tertiary = md_theme_light_tertiary,\n    onTertiary = md_theme_light_onTertiary,\n    tertiaryContainer = md_theme_light_tertiaryContainer,\n    onTertiaryContainer = md_theme_light_onTertiaryContainer,\n    error = md_theme_light_error,\n    errorContainer = md_theme_light_errorContainer,\n    onError = md_theme_light_onError,\n    onErrorContainer = md_theme_light_onErrorContainer,\n    background = md_theme_light_background,\n    onBackground = md_theme_light_onBackground,\n    surface = md_theme_light_surface,\n    onSurface = md_theme_light_onSurface,\n    surfaceVariant = md_theme_light_surfaceVariant,\n    onSurfaceVariant = md_theme_light_onSurfaceVariant,\n    outline = md_theme_light_outline,\n    inverseOnSurface = md_theme_light_inverseOnSurface,\n    inverseSurface = md_theme_light_inverseSurface,\n    inversePrimary = md_theme_light_inversePrimary,\n    surfaceTint = md_theme_light_surfaceTint,\n    outlineVariant = md_theme_light_outlineVariant,\n    surfaceContainerLowest = md_theme_light_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_light_surfaceContainerLow,\n    surfaceContainer = md_theme_light_surfaceContainer,\n    surfaceContainerHigh = md_theme_light_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_light_surfaceContainerHighest,\n    scrim = md_theme_light_scrim,\n)\n\nval DarkLightBlueTheme = darkColorScheme(\n    primary = md_theme_dark_primary,\n    onPrimary = md_theme_dark_onPrimary,\n    primaryContainer = md_theme_dark_primaryContainer,\n    onPrimaryContainer = md_theme_dark_onPrimaryContainer,\n    secondary = md_theme_dark_secondary,\n    onSecondary = md_theme_dark_onSecondary,\n    secondaryContainer = md_theme_dark_secondaryContainer,\n    onSecondaryContainer = md_theme_dark_onSecondaryContainer,\n    tertiary = md_theme_dark_tertiary,\n    onTertiary = md_theme_dark_onTertiary,\n    tertiaryContainer = md_theme_dark_tertiaryContainer,\n    onTertiaryContainer = md_theme_dark_onTertiaryContainer,\n    error = md_theme_dark_error,\n    errorContainer = md_theme_dark_errorContainer,\n    onError = md_theme_dark_onError,\n    onErrorContainer = md_theme_dark_onErrorContainer,\n    background = md_theme_dark_background,\n    onBackground = md_theme_dark_onBackground,\n    surface = md_theme_dark_surface,\n    onSurface = md_theme_dark_onSurface,\n    surfaceVariant = md_theme_dark_surfaceVariant,\n    onSurfaceVariant = md_theme_dark_onSurfaceVariant,\n    outline = md_theme_dark_outline,\n    inverseOnSurface = md_theme_dark_inverseOnSurface,\n    inverseSurface = md_theme_dark_inverseSurface,\n    inversePrimary = md_theme_dark_inversePrimary,\n    surfaceTint = md_theme_dark_surfaceTint,\n    outlineVariant = md_theme_dark_outlineVariant,\n    surfaceContainerLowest = md_theme_dark_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_dark_surfaceContainerLow,\n    surfaceContainer = md_theme_dark_surfaceContainer,\n    surfaceContainerHigh = md_theme_dark_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_dark_surfaceContainerHighest,\n    scrim = md_theme_dark_scrim,\n)"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/LightGreenTheme.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lightColorScheme\nimport androidx.compose.ui.graphics.Color\n\nprivate val md_theme_light_primary = Color(0xFF006C48)\nprivate val md_theme_light_onPrimary = Color(0xFFFFFFFF)\nprivate val md_theme_light_primaryContainer = Color(0xFF8DF7C2)\nprivate val md_theme_light_onPrimaryContainer = Color(0xFF002113)\nprivate val md_theme_light_secondary = Color(0xFF4D6356)\nprivate val md_theme_light_onSecondary = Color(0xFFFFFFFF)\nprivate val md_theme_light_secondaryContainer = Color(0xFFD0E8D8)\nprivate val md_theme_light_onSecondaryContainer = Color(0xFF0A1F15)\nprivate val md_theme_light_tertiary = Color(0xFF3C6472)\nprivate val md_theme_light_onTertiary = Color(0xFFFFFFFF)\nprivate val md_theme_light_tertiaryContainer = Color(0xFFC0E9FA)\nprivate val md_theme_light_onTertiaryContainer = Color(0xFF001F28)\nprivate val md_theme_light_error = Color(0xFFBA1A1A)\nprivate val md_theme_light_errorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_light_onError = Color(0xFFFFFFFF)\nprivate val md_theme_light_onErrorContainer = Color(0xFF410002)\nprivate val md_theme_light_background = Color(0xFFFBFDF8)\nprivate val md_theme_light_onBackground = Color(0xFF191C1A)\nprivate val md_theme_light_surface = Color(0xFFFBFDF8)\nprivate val md_theme_light_onSurface = Color(0xFF191C1A)\nprivate val md_theme_light_surfaceVariant = Color(0xFFDCE5DD)\nprivate val md_theme_light_onSurfaceVariant = Color(0xFF404943)\nprivate val md_theme_light_outline = Color(0xFF707973)\nprivate val md_theme_light_inverseOnSurface = Color(0xFFEFF1ED)\nprivate val md_theme_light_inverseSurface = Color(0xFF2E312F)\nprivate val md_theme_light_inversePrimary = Color(0xFF70DBA7)\nprivate val md_theme_light_shadow = Color(0xFF000000)\nprivate val md_theme_light_surfaceTint = Color(0xFF006C48)\nprivate val md_theme_light_outlineVariant = Color(0xFFC0C9C1)\nprivate val md_theme_light_scrim = Color(0xFF000000)\nprivate val md_theme_light_surfaceContainerLowest = Color(0xFFF9FBF6)\nprivate val md_theme_light_surfaceContainerLow = Color(0xFFF7FAF5)\nprivate val md_theme_light_surfaceContainer = Color(0xFFF5F8F3)\nprivate val md_theme_light_surfaceContainerHigh = Color(0xFFF2F6F0)\nprivate val md_theme_light_surfaceContainerHighest = Color(0xFFEFF3ED)\n\n\nprivate val md_theme_dark_primary = Color(0xFF70DBA7)\nprivate val md_theme_dark_onPrimary = Color(0xFF003824)\nprivate val md_theme_dark_primaryContainer = Color(0xFF005235)\nprivate val md_theme_dark_onPrimaryContainer = Color(0xFF8DF7C2)\nprivate val md_theme_dark_secondary = Color(0xFFB4CCBC)\nprivate val md_theme_dark_onSecondary = Color(0xFF20352A)\nprivate val md_theme_dark_secondaryContainer = Color(0xFF364B3F)\nprivate val md_theme_dark_onSecondaryContainer = Color(0xFFD0E8D8)\nprivate val md_theme_dark_tertiary = Color(0xFFA4CDDE)\nprivate val md_theme_dark_onTertiary = Color(0xFF063543)\nprivate val md_theme_dark_tertiaryContainer = Color(0xFF234C5A)\nprivate val md_theme_dark_onTertiaryContainer = Color(0xFFC0E9FA)\nprivate val md_theme_dark_error = Color(0xFFFFB4AB)\nprivate val md_theme_dark_errorContainer = Color(0xFF93000A)\nprivate val md_theme_dark_onError = Color(0xFF690005)\nprivate val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_dark_background = Color(0xFF191C1A)\nprivate val md_theme_dark_onBackground = Color(0xFFE1E3DF)\nprivate val md_theme_dark_surface = Color(0xFF191C1A)\nprivate val md_theme_dark_onSurface = Color(0xFFE1E3DF)\nprivate val md_theme_dark_surfaceVariant = Color(0xFF404943)\nprivate val md_theme_dark_onSurfaceVariant = Color(0xFFC0C9C1)\nprivate val md_theme_dark_outline = Color(0xFF8A938C)\nprivate val md_theme_dark_inverseOnSurface = Color(0xFF191C1A)\nprivate val md_theme_dark_inverseSurface = Color(0xFFE1E3DF)\nprivate val md_theme_dark_inversePrimary = Color(0xFF006C48)\nprivate val md_theme_dark_shadow = Color(0xFF000000)\nprivate val md_theme_dark_surfaceTint = Color(0xFF70DBA7)\nprivate val md_theme_dark_outlineVariant = Color(0xFF404943)\nprivate val md_theme_dark_scrim = Color(0xFF000000)\nprivate val md_theme_dark_surfaceContainerLowest = Color(0xFF1A1E1C)\nprivate val md_theme_dark_surfaceContainerLow = Color(0xFF1C201E)\nprivate val md_theme_dark_surfaceContainer = Color(0xFF202421)\nprivate val md_theme_dark_surfaceContainerHigh = Color(0xFF232825)\nprivate val md_theme_dark_surfaceContainerHighest = Color(0xFF272D29)\n\n\nval LightLightGreenTheme = lightColorScheme(\n    primary = md_theme_light_primary,\n    onPrimary = md_theme_light_onPrimary,\n    primaryContainer = md_theme_light_primaryContainer,\n    onPrimaryContainer = md_theme_light_onPrimaryContainer,\n    secondary = md_theme_light_secondary,\n    onSecondary = md_theme_light_onSecondary,\n    secondaryContainer = md_theme_light_secondaryContainer,\n    onSecondaryContainer = md_theme_light_onSecondaryContainer,\n    tertiary = md_theme_light_tertiary,\n    onTertiary = md_theme_light_onTertiary,\n    tertiaryContainer = md_theme_light_tertiaryContainer,\n    onTertiaryContainer = md_theme_light_onTertiaryContainer,\n    error = md_theme_light_error,\n    errorContainer = md_theme_light_errorContainer,\n    onError = md_theme_light_onError,\n    onErrorContainer = md_theme_light_onErrorContainer,\n    background = md_theme_light_background,\n    onBackground = md_theme_light_onBackground,\n    surface = md_theme_light_surface,\n    onSurface = md_theme_light_onSurface,\n    surfaceVariant = md_theme_light_surfaceVariant,\n    onSurfaceVariant = md_theme_light_onSurfaceVariant,\n    outline = md_theme_light_outline,\n    inverseOnSurface = md_theme_light_inverseOnSurface,\n    inverseSurface = md_theme_light_inverseSurface,\n    inversePrimary = md_theme_light_inversePrimary,\n    surfaceTint = md_theme_light_surfaceTint,\n    outlineVariant = md_theme_light_outlineVariant,\n    surfaceContainerLowest = md_theme_light_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_light_surfaceContainerLow,\n    surfaceContainer = md_theme_light_surfaceContainer,\n    surfaceContainerHigh = md_theme_light_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_light_surfaceContainerHighest,\n    scrim = md_theme_light_scrim,\n)\n\nval DarkLightGreenTheme = darkColorScheme(\n    primary = md_theme_dark_primary,\n    onPrimary = md_theme_dark_onPrimary,\n    primaryContainer = md_theme_dark_primaryContainer,\n    onPrimaryContainer = md_theme_dark_onPrimaryContainer,\n    secondary = md_theme_dark_secondary,\n    onSecondary = md_theme_dark_onSecondary,\n    secondaryContainer = md_theme_dark_secondaryContainer,\n    onSecondaryContainer = md_theme_dark_onSecondaryContainer,\n    tertiary = md_theme_dark_tertiary,\n    onTertiary = md_theme_dark_onTertiary,\n    tertiaryContainer = md_theme_dark_tertiaryContainer,\n    onTertiaryContainer = md_theme_dark_onTertiaryContainer,\n    error = md_theme_dark_error,\n    errorContainer = md_theme_dark_errorContainer,\n    onError = md_theme_dark_onError,\n    onErrorContainer = md_theme_dark_onErrorContainer,\n    background = md_theme_dark_background,\n    onBackground = md_theme_dark_onBackground,\n    surface = md_theme_dark_surface,\n    onSurface = md_theme_dark_onSurface,\n    surfaceVariant = md_theme_dark_surfaceVariant,\n    onSurfaceVariant = md_theme_dark_onSurfaceVariant,\n    outline = md_theme_dark_outline,\n    inverseOnSurface = md_theme_dark_inverseOnSurface,\n    inverseSurface = md_theme_dark_inverseSurface,\n    inversePrimary = md_theme_dark_inversePrimary,\n    surfaceTint = md_theme_dark_surfaceTint,\n    outlineVariant = md_theme_dark_outlineVariant,\n    surfaceContainerLowest = md_theme_dark_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_dark_surfaceContainerLow,\n    surfaceContainer = md_theme_dark_surfaceContainer,\n    surfaceContainerHigh = md_theme_dark_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_dark_surfaceContainerHighest,\n    scrim = md_theme_dark_scrim,\n)"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/LimeTheme.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lightColorScheme\nimport androidx.compose.ui.graphics.Color\n\nprivate val md_theme_light_primary = Color(0xFF5B6300)\nprivate val md_theme_light_onPrimary = Color(0xFFFFFFFF)\nprivate val md_theme_light_primaryContainer = Color(0xFFDDED49)\nprivate val md_theme_light_onPrimaryContainer = Color(0xFF1A1D00)\nprivate val md_theme_light_secondary = Color(0xFF5E6044)\nprivate val md_theme_light_onSecondary = Color(0xFFFFFFFF)\nprivate val md_theme_light_secondaryContainer = Color(0xFFE4E5C1)\nprivate val md_theme_light_onSecondaryContainer = Color(0xFF1B1D07)\nprivate val md_theme_light_tertiary = Color(0xFF3C665A)\nprivate val md_theme_light_onTertiary = Color(0xFFFFFFFF)\nprivate val md_theme_light_tertiaryContainer = Color(0xFFBEECDC)\nprivate val md_theme_light_onTertiaryContainer = Color(0xFF002019)\nprivate val md_theme_light_error = Color(0xFFBA1A1A)\nprivate val md_theme_light_errorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_light_onError = Color(0xFFFFFFFF)\nprivate val md_theme_light_onErrorContainer = Color(0xFF410002)\nprivate val md_theme_light_background = Color(0xFFFEFFD8)\nprivate val md_theme_light_onBackground = Color(0xFF1C1C17)\nprivate val md_theme_light_surface = Color(0xFFFEFFD8)\nprivate val md_theme_light_onSurface = Color(0xFF1C1C17)\nprivate val md_theme_light_surfaceVariant = Color(0xFFE5E3D2)\nprivate val md_theme_light_onSurfaceVariant = Color(0xFF47483B)\nprivate val md_theme_light_outline = Color(0xFF787869)\nprivate val md_theme_light_inverseOnSurface = Color(0xFFF3F1E8)\nprivate val md_theme_light_inverseSurface = Color(0xFF31312B)\nprivate val md_theme_light_inversePrimary = Color(0xFFC1D02C)\nprivate val md_theme_light_shadow = Color(0xFF000000)\nprivate val md_theme_light_surfaceTint = Color(0xFF5B6300)\nprivate val md_theme_light_outlineVariant = Color(0xFFC8C7B7)\nprivate val md_theme_light_scrim = Color(0xFF000000)\nprivate val md_theme_light_surfaceContainerLowest = Color(0xFFFCFDD7)\nprivate val md_theme_light_surfaceContainerLow = Color(0xFFFBFCD7)\nprivate val md_theme_light_surfaceContainer = Color(0xFFF9F9D6)\nprivate val md_theme_light_surfaceContainerHigh = Color(0xFFF7F7D6)\nprivate val md_theme_light_surfaceContainerHighest = Color(0xFFF4F4D5)\n\n\nprivate val md_theme_dark_primary = Color(0xFFC1D02C)\nprivate val md_theme_dark_onPrimary = Color(0xFF2F3300)\nprivate val md_theme_dark_primaryContainer = Color(0xFF444B00)\nprivate val md_theme_dark_onPrimaryContainer = Color(0xFFDDED49)\nprivate val md_theme_dark_secondary = Color(0xFFC7C9A6)\nprivate val md_theme_dark_onSecondary = Color(0xFF30321A)\nprivate val md_theme_dark_secondaryContainer = Color(0xFF46492E)\nprivate val md_theme_dark_onSecondaryContainer = Color(0xFFE4E5C1)\nprivate val md_theme_dark_tertiary = Color(0xFFA2D0C1)\nprivate val md_theme_dark_onTertiary = Color(0xFF07372D)\nprivate val md_theme_dark_tertiaryContainer = Color(0xFF234E43)\nprivate val md_theme_dark_onTertiaryContainer = Color(0xFFBEECDC)\nprivate val md_theme_dark_error = Color(0xFFFFB4AB)\nprivate val md_theme_dark_errorContainer = Color(0xFF93000A)\nprivate val md_theme_dark_onError = Color(0xFF690005)\nprivate val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_dark_background = Color(0xFF1C1C17)\nprivate val md_theme_dark_onBackground = Color(0xFFE5E2DA)\nprivate val md_theme_dark_surface = Color(0xFF1C1C17)\nprivate val md_theme_dark_onSurface = Color(0xFFE5E2DA)\nprivate val md_theme_dark_surfaceVariant = Color(0xFF47483B)\nprivate val md_theme_dark_onSurfaceVariant = Color(0xFFC8C7B7)\nprivate val md_theme_dark_outline = Color(0xFF929282)\nprivate val md_theme_dark_inverseOnSurface = Color(0xFF1C1C17)\nprivate val md_theme_dark_inverseSurface = Color(0xFFE5E2DA)\nprivate val md_theme_dark_inversePrimary = Color(0xFF5B6300)\nprivate val md_theme_dark_shadow = Color(0xFF000000)\nprivate val md_theme_dark_surfaceTint = Color(0xFFC1D02C)\nprivate val md_theme_dark_outlineVariant = Color(0xFF47483B)\nprivate val md_theme_dark_scrim = Color(0xFF000000)\nprivate val md_theme_dark_surfaceContainerLowest = Color(0xFF1E1E18)\nprivate val md_theme_dark_surfaceContainerLow = Color(0xFF20201A)\nprivate val md_theme_dark_surfaceContainer = Color(0xFF23231D)\nprivate val md_theme_dark_surfaceContainerHigh = Color(0xFF282821)\nprivate val md_theme_dark_surfaceContainerHighest = Color(0xFF2C2C24)\n\n\nval LightLimeTheme = lightColorScheme(\n    primary = md_theme_light_primary,\n    onPrimary = md_theme_light_onPrimary,\n    primaryContainer = md_theme_light_primaryContainer,\n    onPrimaryContainer = md_theme_light_onPrimaryContainer,\n    secondary = md_theme_light_secondary,\n    onSecondary = md_theme_light_onSecondary,\n    secondaryContainer = md_theme_light_secondaryContainer,\n    onSecondaryContainer = md_theme_light_onSecondaryContainer,\n    tertiary = md_theme_light_tertiary,\n    onTertiary = md_theme_light_onTertiary,\n    tertiaryContainer = md_theme_light_tertiaryContainer,\n    onTertiaryContainer = md_theme_light_onTertiaryContainer,\n    error = md_theme_light_error,\n    errorContainer = md_theme_light_errorContainer,\n    onError = md_theme_light_onError,\n    onErrorContainer = md_theme_light_onErrorContainer,\n    background = md_theme_light_background,\n    onBackground = md_theme_light_onBackground,\n    surface = md_theme_light_surface,\n    onSurface = md_theme_light_onSurface,\n    surfaceVariant = md_theme_light_surfaceVariant,\n    onSurfaceVariant = md_theme_light_onSurfaceVariant,\n    outline = md_theme_light_outline,\n    inverseOnSurface = md_theme_light_inverseOnSurface,\n    inverseSurface = md_theme_light_inverseSurface,\n    inversePrimary = md_theme_light_inversePrimary,\n    surfaceTint = md_theme_light_surfaceTint,\n    outlineVariant = md_theme_light_outlineVariant,\n    surfaceContainerLowest = md_theme_light_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_light_surfaceContainerLow,\n    surfaceContainer = md_theme_light_surfaceContainer,\n    surfaceContainerHigh = md_theme_light_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_light_surfaceContainerHighest,\n    scrim = md_theme_light_scrim,\n)\n\nval DarkLimeTheme = darkColorScheme(\n    primary = md_theme_dark_primary,\n    onPrimary = md_theme_dark_onPrimary,\n    primaryContainer = md_theme_dark_primaryContainer,\n    onPrimaryContainer = md_theme_dark_onPrimaryContainer,\n    secondary = md_theme_dark_secondary,\n    onSecondary = md_theme_dark_onSecondary,\n    secondaryContainer = md_theme_dark_secondaryContainer,\n    onSecondaryContainer = md_theme_dark_onSecondaryContainer,\n    tertiary = md_theme_dark_tertiary,\n    onTertiary = md_theme_dark_onTertiary,\n    tertiaryContainer = md_theme_dark_tertiaryContainer,\n    onTertiaryContainer = md_theme_dark_onTertiaryContainer,\n    error = md_theme_dark_error,\n    errorContainer = md_theme_dark_errorContainer,\n    onError = md_theme_dark_onError,\n    onErrorContainer = md_theme_dark_onErrorContainer,\n    background = md_theme_dark_background,\n    onBackground = md_theme_dark_onBackground,\n    surface = md_theme_dark_surface,\n    onSurface = md_theme_dark_onSurface,\n    surfaceVariant = md_theme_dark_surfaceVariant,\n    onSurfaceVariant = md_theme_dark_onSurfaceVariant,\n    outline = md_theme_dark_outline,\n    inverseOnSurface = md_theme_dark_inverseOnSurface,\n    inverseSurface = md_theme_dark_inverseSurface,\n    inversePrimary = md_theme_dark_inversePrimary,\n    surfaceTint = md_theme_dark_surfaceTint,\n    outlineVariant = md_theme_dark_outlineVariant,\n    surfaceContainerLowest = md_theme_dark_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_dark_surfaceContainerLow,\n    surfaceContainer = md_theme_dark_surfaceContainer,\n    surfaceContainerHigh = md_theme_dark_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_dark_surfaceContainerHighest,\n    scrim = md_theme_dark_scrim,\n)"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/MusicConfig.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport android.content.Context\nimport android.net.Uri\nimport android.util.Log\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableFloatStateOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport me.bmax.apatch.util.SafeUriResolver\nimport java.io.File\n\nobject MusicConfig {\n    private const val PREFS_NAME = \"music_settings\"\n    private const val KEY_MUSIC_ENABLED = \"music_enabled\"\n    private const val KEY_MUSIC_FILENAME = \"music_filename\"\n    private const val KEY_AUTO_PLAY = \"auto_play\"\n    private const val KEY_LOOPING_ENABLED = \"looping_enabled\"\n    private const val KEY_VOLUME = \"volume\"\n    private const val TAG = \"MusicConfig\"\n\n    var isMusicEnabled: Boolean by mutableStateOf(false)\n        private set\n        \n    var musicFilename: String? by mutableStateOf(null)\n        private set\n\n    var isAutoPlayEnabled: Boolean by mutableStateOf(false)\n        private set\n\n    var isLoopingEnabled: Boolean by mutableStateOf(false)\n        private set\n\n    var volume: Float by mutableFloatStateOf(1.0f)\n        private set\n\n    fun setMusicEnabledState(enabled: Boolean) {\n        isMusicEnabled = enabled\n    }\n\n    fun setAutoPlayEnabledState(enabled: Boolean) {\n        isAutoPlayEnabled = enabled\n    }\n\n    fun setLoopingEnabledState(enabled: Boolean) {\n        isLoopingEnabled = enabled\n    }\n\n    fun setMusicFilenameValue(filename: String?) {\n        musicFilename = filename\n    }\n\n    fun setVolumeValue(value: Float) {\n        volume = value\n    }\n\n    fun getMusicDir(context: Context): File {\n        val dir = File(context.filesDir, \"music\")\n        if (!dir.exists()) {\n            dir.mkdirs()\n        }\n        return dir\n    }\n\n    fun load(context: Context) {\n        val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)\n        isMusicEnabled = prefs.getBoolean(KEY_MUSIC_ENABLED, false)\n        musicFilename = prefs.getString(KEY_MUSIC_FILENAME, null)\n        isAutoPlayEnabled = prefs.getBoolean(KEY_AUTO_PLAY, false)\n        isLoopingEnabled = prefs.getBoolean(KEY_LOOPING_ENABLED, false)\n        volume = prefs.getFloat(KEY_VOLUME, 1.0f)\n        \n        // Migrate old music file if needed\n        if (musicFilename != null) {\n            val oldFile = File(context.filesDir, musicFilename!!)\n            if (oldFile.exists()) {\n                val newDir = getMusicDir(context)\n                val newFile = File(newDir, musicFilename!!)\n                try {\n                    oldFile.renameTo(newFile)\n                } catch (e: Exception) {\n                    Log.e(TAG, \"Failed to migrate music file\", e)\n                }\n            }\n        }\n\n        // Validate if file exists\n        if (isMusicEnabled && musicFilename != null) {\n            val file = File(getMusicDir(context), musicFilename!!)\n            if (!file.exists()) {\n                isMusicEnabled = false\n                musicFilename = null\n                save(context)\n            }\n        }\n    }\n\n    fun save(context: Context) {\n        val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)\n        prefs.edit()\n            .putBoolean(KEY_MUSIC_ENABLED, isMusicEnabled)\n            .putString(KEY_MUSIC_FILENAME, musicFilename)\n            .putBoolean(KEY_AUTO_PLAY, isAutoPlayEnabled)\n            .putBoolean(KEY_LOOPING_ENABLED, isLoopingEnabled)\n            .putFloat(KEY_VOLUME, volume)\n            .apply()\n    }\n\n    fun saveMusicFile(context: Context, uri: Uri): Boolean {\n        return try {\n            val newFilename = \"background_music_${System.currentTimeMillis()}.mp3\"\n            val oldFilename = musicFilename\n            \n            SafeUriResolver.openInputStream(context, uri)?.use { input ->\n                val file = File(getMusicDir(context), newFilename)\n                file.outputStream().use { output ->\n                    input.copyTo(output)\n                }\n            }\n            \n            // Delete old file if it exists and is different\n            if (oldFilename != null && oldFilename != newFilename) {\n                val oldFile = File(getMusicDir(context), oldFilename)\n                if (oldFile.exists()) {\n                    oldFile.delete()\n                }\n            }\n            \n            isMusicEnabled = true\n            musicFilename = newFilename\n            save(context)\n            true\n        } catch (e: Exception) {\n            Log.e(TAG, \"Failed to save music file\", e)\n            false\n        }\n    }\n\n    fun clearMusic(context: Context) {\n        val filename = musicFilename\n        if (filename != null) {\n            val file = File(getMusicDir(context), filename)\n            if (file.exists()) {\n                file.delete()\n            }\n        }\n        isMusicEnabled = false\n        musicFilename = null\n        save(context)\n    }\n\n    fun getMusicFile(context: Context): File? {\n        return if (musicFilename != null) {\n            val file = File(getMusicDir(context), musicFilename!!)\n            if (file.exists()) file else null\n        } else {\n            null\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/OrangeTheme.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lightColorScheme\nimport androidx.compose.ui.graphics.Color\n\nprivate val md_theme_light_primary = Color(0xFF8B5000)\nprivate val md_theme_light_onPrimary = Color(0xFFFFFFFF)\nprivate val md_theme_light_primaryContainer = Color(0xFFFFDCBE)\nprivate val md_theme_light_onPrimaryContainer = Color(0xFF2C1600)\nprivate val md_theme_light_secondary = Color(0xFF725A42)\nprivate val md_theme_light_onSecondary = Color(0xFFFFFFFF)\nprivate val md_theme_light_secondaryContainer = Color(0xFFFFDCBE)\nprivate val md_theme_light_onSecondaryContainer = Color(0xFF291806)\nprivate val md_theme_light_tertiary = Color(0xFF58633A)\nprivate val md_theme_light_onTertiary = Color(0xFFFFFFFF)\nprivate val md_theme_light_tertiaryContainer = Color(0xFFDCE8B4)\nprivate val md_theme_light_onTertiaryContainer = Color(0xFF161E01)\nprivate val md_theme_light_error = Color(0xFFBA1A1A)\nprivate val md_theme_light_errorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_light_onError = Color(0xFFFFFFFF)\nprivate val md_theme_light_onErrorContainer = Color(0xFF410002)\nprivate val md_theme_light_background = Color(0xFFFFFBFF)\nprivate val md_theme_light_onBackground = Color(0xFF201B16)\nprivate val md_theme_light_surface = Color(0xFFFFFBFF)\nprivate val md_theme_light_onSurface = Color(0xFF201B16)\nprivate val md_theme_light_surfaceVariant = Color(0xFFF2DFD1)\nprivate val md_theme_light_onSurfaceVariant = Color(0xFF51453A)\nprivate val md_theme_light_outline = Color(0xFF837468)\nprivate val md_theme_light_inverseOnSurface = Color(0xFFFAEFE7)\nprivate val md_theme_light_inverseSurface = Color(0xFF352F2B)\nprivate val md_theme_light_inversePrimary = Color(0xFFFFB870)\nprivate val md_theme_light_shadow = Color(0xFF000000)\nprivate val md_theme_light_surfaceTint = Color(0xFF8B5000)\nprivate val md_theme_light_outlineVariant = Color(0xFFD5C3B5)\nprivate val md_theme_light_scrim = Color(0xFF000000)\nprivate val md_theme_light_surfaceContainerLowest = Color(0xFFFEF9FC)\nprivate val md_theme_light_surfaceContainerLow = Color(0xFFFDF8FA)\nprivate val md_theme_light_surfaceContainer = Color(0xFFFCF5F6)\nprivate val md_theme_light_surfaceContainerHigh = Color(0xFFFBF3F2)\nprivate val md_theme_light_surfaceContainerHighest = Color(0xFFFAF0ED)\n\n\nprivate val md_theme_dark_primary = Color(0xFFFFB870)\nprivate val md_theme_dark_onPrimary = Color(0xFF4A2800)\nprivate val md_theme_dark_primaryContainer = Color(0xFF693C00)\nprivate val md_theme_dark_onPrimaryContainer = Color(0xFFFFDCBE)\nprivate val md_theme_dark_secondary = Color(0xFFE1C1A4)\nprivate val md_theme_dark_onSecondary = Color(0xFF402C18)\nprivate val md_theme_dark_secondaryContainer = Color(0xFF59422C)\nprivate val md_theme_dark_onSecondaryContainer = Color(0xFFFFDCBE)\nprivate val md_theme_dark_tertiary = Color(0xFFC0CC9A)\nprivate val md_theme_dark_onTertiary = Color(0xFF2B3410)\nprivate val md_theme_dark_tertiaryContainer = Color(0xFF414B24)\nprivate val md_theme_dark_onTertiaryContainer = Color(0xFFDCE8B4)\nprivate val md_theme_dark_error = Color(0xFFFFB4AB)\nprivate val md_theme_dark_errorContainer = Color(0xFF93000A)\nprivate val md_theme_dark_onError = Color(0xFF690005)\nprivate val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_dark_background = Color(0xFF201B16)\nprivate val md_theme_dark_onBackground = Color(0xFFEBE0D9)\nprivate val md_theme_dark_surface = Color(0xFF201B16)\nprivate val md_theme_dark_onSurface = Color(0xFFEBE0D9)\nprivate val md_theme_dark_surfaceVariant = Color(0xFF51453A)\nprivate val md_theme_dark_onSurfaceVariant = Color(0xFFD5C3B5)\nprivate val md_theme_dark_outline = Color(0xFF9D8E81)\nprivate val md_theme_dark_inverseOnSurface = Color(0xFF201B16)\nprivate val md_theme_dark_inverseSurface = Color(0xFFEBE0D9)\nprivate val md_theme_dark_inversePrimary = Color(0xFF8B5000)\nprivate val md_theme_dark_shadow = Color(0xFF000000)\nprivate val md_theme_dark_surfaceTint = Color(0xFFFFB870)\nprivate val md_theme_dark_outlineVariant = Color(0xFF51453A)\nprivate val md_theme_dark_scrim = Color(0xFF000000)\nprivate val md_theme_dark_surfaceContainerLowest = Color(0xFF221D17)\nprivate val md_theme_dark_surfaceContainerLow = Color(0xFF241F19)\nprivate val md_theme_dark_surfaceContainer = Color(0xFF28221C)\nprivate val md_theme_dark_surfaceContainerHigh = Color(0xFF2D2620)\nprivate val md_theme_dark_surfaceContainerHighest = Color(0xFF322A23)\n\n\nval LightOrangeTheme = lightColorScheme(\n    primary = md_theme_light_primary,\n    onPrimary = md_theme_light_onPrimary,\n    primaryContainer = md_theme_light_primaryContainer,\n    onPrimaryContainer = md_theme_light_onPrimaryContainer,\n    secondary = md_theme_light_secondary,\n    onSecondary = md_theme_light_onSecondary,\n    secondaryContainer = md_theme_light_secondaryContainer,\n    onSecondaryContainer = md_theme_light_onSecondaryContainer,\n    tertiary = md_theme_light_tertiary,\n    onTertiary = md_theme_light_onTertiary,\n    tertiaryContainer = md_theme_light_tertiaryContainer,\n    onTertiaryContainer = md_theme_light_onTertiaryContainer,\n    error = md_theme_light_error,\n    errorContainer = md_theme_light_errorContainer,\n    onError = md_theme_light_onError,\n    onErrorContainer = md_theme_light_onErrorContainer,\n    background = md_theme_light_background,\n    onBackground = md_theme_light_onBackground,\n    surface = md_theme_light_surface,\n    onSurface = md_theme_light_onSurface,\n    surfaceVariant = md_theme_light_surfaceVariant,\n    onSurfaceVariant = md_theme_light_onSurfaceVariant,\n    outline = md_theme_light_outline,\n    inverseOnSurface = md_theme_light_inverseOnSurface,\n    inverseSurface = md_theme_light_inverseSurface,\n    inversePrimary = md_theme_light_inversePrimary,\n    surfaceTint = md_theme_light_surfaceTint,\n    outlineVariant = md_theme_light_outlineVariant,\n    surfaceContainerLowest = md_theme_light_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_light_surfaceContainerLow,\n    surfaceContainer = md_theme_light_surfaceContainer,\n    surfaceContainerHigh = md_theme_light_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_light_surfaceContainerHighest,\n    scrim = md_theme_light_scrim,\n)\n\nval DarkOrangeTheme = darkColorScheme(\n    primary = md_theme_dark_primary,\n    onPrimary = md_theme_dark_onPrimary,\n    primaryContainer = md_theme_dark_primaryContainer,\n    onPrimaryContainer = md_theme_dark_onPrimaryContainer,\n    secondary = md_theme_dark_secondary,\n    onSecondary = md_theme_dark_onSecondary,\n    secondaryContainer = md_theme_dark_secondaryContainer,\n    onSecondaryContainer = md_theme_dark_onSecondaryContainer,\n    tertiary = md_theme_dark_tertiary,\n    onTertiary = md_theme_dark_onTertiary,\n    tertiaryContainer = md_theme_dark_tertiaryContainer,\n    onTertiaryContainer = md_theme_dark_onTertiaryContainer,\n    error = md_theme_dark_error,\n    errorContainer = md_theme_dark_errorContainer,\n    onError = md_theme_dark_onError,\n    onErrorContainer = md_theme_dark_onErrorContainer,\n    background = md_theme_dark_background,\n    onBackground = md_theme_dark_onBackground,\n    surface = md_theme_dark_surface,\n    onSurface = md_theme_dark_onSurface,\n    surfaceVariant = md_theme_dark_surfaceVariant,\n    onSurfaceVariant = md_theme_dark_onSurfaceVariant,\n    outline = md_theme_dark_outline,\n    inverseOnSurface = md_theme_dark_inverseOnSurface,\n    inverseSurface = md_theme_dark_inverseSurface,\n    inversePrimary = md_theme_dark_inversePrimary,\n    surfaceTint = md_theme_dark_surfaceTint,\n    outlineVariant = md_theme_dark_outlineVariant,\n    surfaceContainerLowest = md_theme_dark_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_dark_surfaceContainerLow,\n    surfaceContainer = md_theme_dark_surfaceContainer,\n    surfaceContainerHigh = md_theme_dark_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_dark_surfaceContainerHighest,\n    scrim = md_theme_dark_scrim,\n)"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/PinkTheme.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lightColorScheme\nimport androidx.compose.ui.graphics.Color\n\nprivate val md_theme_light_primary = Color(0xFFBC004B)\nprivate val md_theme_light_onPrimary = Color(0xFFFFFFFF)\nprivate val md_theme_light_primaryContainer = Color(0xFFFFD9DE)\nprivate val md_theme_light_onPrimaryContainer = Color(0xFF400014)\nprivate val md_theme_light_secondary = Color(0xFF75565B)\nprivate val md_theme_light_onSecondary = Color(0xFFFFFFFF)\nprivate val md_theme_light_secondaryContainer = Color(0xFFFFD9DE)\nprivate val md_theme_light_onSecondaryContainer = Color(0xFF2C1519)\nprivate val md_theme_light_tertiary = Color(0xFF795831)\nprivate val md_theme_light_onTertiary = Color(0xFFFFFFFF)\nprivate val md_theme_light_tertiaryContainer = Color(0xFFFFDDBA)\nprivate val md_theme_light_onTertiaryContainer = Color(0xFF2B1700)\nprivate val md_theme_light_error = Color(0xFFBA1A1A)\nprivate val md_theme_light_errorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_light_onError = Color(0xFFFFFFFF)\nprivate val md_theme_light_onErrorContainer = Color(0xFF410002)\nprivate val md_theme_light_background = Color(0xFFFFFBFF)\nprivate val md_theme_light_onBackground = Color(0xFF201A1B)\nprivate val md_theme_light_surface = Color(0xFFFFFBFF)\nprivate val md_theme_light_onSurface = Color(0xFF201A1B)\nprivate val md_theme_light_surfaceVariant = Color(0xFFF3DDDF)\nprivate val md_theme_light_onSurfaceVariant = Color(0xFF524345)\nprivate val md_theme_light_outline = Color(0xFF847375)\nprivate val md_theme_light_inverseOnSurface = Color(0xFFFBEEEE)\nprivate val md_theme_light_inverseSurface = Color(0xFF362F2F)\nprivate val md_theme_light_inversePrimary = Color(0xFFFFB2BE)\nprivate val md_theme_light_shadow = Color(0xFF000000)\nprivate val md_theme_light_surfaceTint = Color(0xFFBC004B)\nprivate val md_theme_light_outlineVariant = Color(0xFFD6C2C3)\nprivate val md_theme_light_scrim = Color(0xFF000000)\nprivate val md_theme_light_surfaceContainerLowest = Color(0xFFFEF9FD)\nprivate val md_theme_light_surfaceContainerLow = Color(0xFFFDF8FB)\nprivate val md_theme_light_surfaceContainer = Color(0xFFFCF5F9)\nprivate val md_theme_light_surfaceContainerHigh = Color(0xFFFBF2F6)\nprivate val md_theme_light_surfaceContainerHighest = Color(0xFFFAEFF2)\n\n\nprivate val md_theme_dark_primary = Color(0xFFFFB2BE)\nprivate val md_theme_dark_onPrimary = Color(0xFF660025)\nprivate val md_theme_dark_primaryContainer = Color(0xFF900038)\nprivate val md_theme_dark_onPrimaryContainer = Color(0xFFFFD9DE)\nprivate val md_theme_dark_secondary = Color(0xFFE5BDC2)\nprivate val md_theme_dark_onSecondary = Color(0xFF43292D)\nprivate val md_theme_dark_secondaryContainer = Color(0xFF5C3F43)\nprivate val md_theme_dark_onSecondaryContainer = Color(0xFFFFD9DE)\nprivate val md_theme_dark_tertiary = Color(0xFFEBBF90)\nprivate val md_theme_dark_onTertiary = Color(0xFF452B08)\nprivate val md_theme_dark_tertiaryContainer = Color(0xFF5F411C)\nprivate val md_theme_dark_onTertiaryContainer = Color(0xFFFFDDBA)\nprivate val md_theme_dark_error = Color(0xFFFFB4AB)\nprivate val md_theme_dark_errorContainer = Color(0xFF93000A)\nprivate val md_theme_dark_onError = Color(0xFF690005)\nprivate val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_dark_background = Color(0xFF201A1B)\nprivate val md_theme_dark_onBackground = Color(0xFFECE0E0)\nprivate val md_theme_dark_surface = Color(0xFF201A1B)\nprivate val md_theme_dark_onSurface = Color(0xFFECE0E0)\nprivate val md_theme_dark_surfaceVariant = Color(0xFF524345)\nprivate val md_theme_dark_onSurfaceVariant = Color(0xFFD6C2C3)\nprivate val md_theme_dark_outline = Color(0xFF9F8C8E)\nprivate val md_theme_dark_inverseOnSurface = Color(0xFF201A1B)\nprivate val md_theme_dark_inverseSurface = Color(0xFFECE0E0)\nprivate val md_theme_dark_inversePrimary = Color(0xFFBC004B)\nprivate val md_theme_dark_shadow = Color(0xFF000000)\nprivate val md_theme_dark_surfaceTint = Color(0xFFFFB2BE)\nprivate val md_theme_dark_outlineVariant = Color(0xFF524345)\nprivate val md_theme_dark_scrim = Color(0xFF000000)\nprivate val md_theme_dark_surfaceContainerLowest = Color(0xFF221C1D)\nprivate val md_theme_dark_surfaceContainerLow = Color(0xFF251E1F)\nprivate val md_theme_dark_surfaceContainer = Color(0xFF292122)\nprivate val md_theme_dark_surfaceContainerHigh = Color(0xFF2E2526)\nprivate val md_theme_dark_surfaceContainerHighest = Color(0xFF33292A)\n\n\nval LightPinkTheme = lightColorScheme(\n    primary = md_theme_light_primary,\n    onPrimary = md_theme_light_onPrimary,\n    primaryContainer = md_theme_light_primaryContainer,\n    onPrimaryContainer = md_theme_light_onPrimaryContainer,\n    secondary = md_theme_light_secondary,\n    onSecondary = md_theme_light_onSecondary,\n    secondaryContainer = md_theme_light_secondaryContainer,\n    onSecondaryContainer = md_theme_light_onSecondaryContainer,\n    tertiary = md_theme_light_tertiary,\n    onTertiary = md_theme_light_onTertiary,\n    tertiaryContainer = md_theme_light_tertiaryContainer,\n    onTertiaryContainer = md_theme_light_onTertiaryContainer,\n    error = md_theme_light_error,\n    errorContainer = md_theme_light_errorContainer,\n    onError = md_theme_light_onError,\n    onErrorContainer = md_theme_light_onErrorContainer,\n    background = md_theme_light_background,\n    onBackground = md_theme_light_onBackground,\n    surface = md_theme_light_surface,\n    onSurface = md_theme_light_onSurface,\n    surfaceVariant = md_theme_light_surfaceVariant,\n    onSurfaceVariant = md_theme_light_onSurfaceVariant,\n    outline = md_theme_light_outline,\n    inverseOnSurface = md_theme_light_inverseOnSurface,\n    inverseSurface = md_theme_light_inverseSurface,\n    inversePrimary = md_theme_light_inversePrimary,\n    surfaceTint = md_theme_light_surfaceTint,\n    outlineVariant = md_theme_light_outlineVariant,\n    surfaceContainerLowest = md_theme_light_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_light_surfaceContainerLow,\n    surfaceContainer = md_theme_light_surfaceContainer,\n    surfaceContainerHigh = md_theme_light_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_light_surfaceContainerHighest,\n    scrim = md_theme_light_scrim,\n)\n\nval DarkPinkTheme = darkColorScheme(\n    primary = md_theme_dark_primary,\n    onPrimary = md_theme_dark_onPrimary,\n    primaryContainer = md_theme_dark_primaryContainer,\n    onPrimaryContainer = md_theme_dark_onPrimaryContainer,\n    secondary = md_theme_dark_secondary,\n    onSecondary = md_theme_dark_onSecondary,\n    secondaryContainer = md_theme_dark_secondaryContainer,\n    onSecondaryContainer = md_theme_dark_onSecondaryContainer,\n    tertiary = md_theme_dark_tertiary,\n    onTertiary = md_theme_dark_onTertiary,\n    tertiaryContainer = md_theme_dark_tertiaryContainer,\n    onTertiaryContainer = md_theme_dark_onTertiaryContainer,\n    error = md_theme_dark_error,\n    errorContainer = md_theme_dark_errorContainer,\n    onError = md_theme_dark_onError,\n    onErrorContainer = md_theme_dark_onErrorContainer,\n    background = md_theme_dark_background,\n    onBackground = md_theme_dark_onBackground,\n    surface = md_theme_dark_surface,\n    onSurface = md_theme_dark_onSurface,\n    surfaceVariant = md_theme_dark_surfaceVariant,\n    onSurfaceVariant = md_theme_dark_onSurfaceVariant,\n    outline = md_theme_dark_outline,\n    inverseOnSurface = md_theme_dark_inverseOnSurface,\n    inverseSurface = md_theme_dark_inverseSurface,\n    inversePrimary = md_theme_dark_inversePrimary,\n    surfaceTint = md_theme_dark_surfaceTint,\n    outlineVariant = md_theme_dark_outlineVariant,\n    surfaceContainerLowest = md_theme_dark_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_dark_surfaceContainerLow,\n    surfaceContainer = md_theme_dark_surfaceContainer,\n    surfaceContainerHigh = md_theme_dark_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_dark_surfaceContainerHighest,\n    scrim = md_theme_dark_scrim,\n)"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/PurpleTheme.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lightColorScheme\nimport androidx.compose.ui.graphics.Color\n\nprivate val md_theme_light_primary = Color(0xFF9A25AE)\nprivate val md_theme_light_onPrimary = Color(0xFFFFFFFF)\nprivate val md_theme_light_primaryContainer = Color(0xFFFFD6FE)\nprivate val md_theme_light_onPrimaryContainer = Color(0xFF35003F)\nprivate val md_theme_light_secondary = Color(0xFF6B586B)\nprivate val md_theme_light_onSecondary = Color(0xFFFFFFFF)\nprivate val md_theme_light_secondaryContainer = Color(0xFFF4DBF1)\nprivate val md_theme_light_onSecondaryContainer = Color(0xFF251626)\nprivate val md_theme_light_tertiary = Color(0xFF82524A)\nprivate val md_theme_light_onTertiary = Color(0xFFFFFFFF)\nprivate val md_theme_light_tertiaryContainer = Color(0xFFFFDAD4)\nprivate val md_theme_light_onTertiaryContainer = Color(0xFF33110C)\nprivate val md_theme_light_error = Color(0xFFBA1A1A)\nprivate val md_theme_light_errorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_light_onError = Color(0xFFFFFFFF)\nprivate val md_theme_light_onErrorContainer = Color(0xFF410002)\nprivate val md_theme_light_background = Color(0xFFFFFBFF)\nprivate val md_theme_light_onBackground = Color(0xFF1E1A1D)\nprivate val md_theme_light_surface = Color(0xFFFFFBFF)\nprivate val md_theme_light_onSurface = Color(0xFF1E1A1D)\nprivate val md_theme_light_surfaceVariant = Color(0xFFECDFE8)\nprivate val md_theme_light_onSurfaceVariant = Color(0xFF4D444C)\nprivate val md_theme_light_outline = Color(0xFF7F747D)\nprivate val md_theme_light_inverseOnSurface = Color(0xFFF7EEF3)\nprivate val md_theme_light_inverseSurface = Color(0xFF332F32)\nprivate val md_theme_light_inversePrimary = Color(0xFFF9ABFF)\nprivate val md_theme_light_shadow = Color(0xFF000000)\nprivate val md_theme_light_surfaceTint = Color(0xFF9A25AE)\nprivate val md_theme_light_outlineVariant = Color(0xFFD0C3CC)\nprivate val md_theme_light_scrim = Color(0xFF000000)\nprivate val md_theme_light_surfaceContainerLowest = Color(0xFFFEF9FD)\nprivate val md_theme_light_surfaceContainerLow = Color(0xFFFDF8FC)\nprivate val md_theme_light_surfaceContainer = Color(0xFFFBF5FA)\nprivate val md_theme_light_surfaceContainerHigh = Color(0xFFF9F3F8)\nprivate val md_theme_light_surfaceContainerHighest = Color(0xFFF7F0F6)\n\n\nprivate val md_theme_dark_primary = Color(0xFFF9ABFF)\nprivate val md_theme_dark_onPrimary = Color(0xFF570066)\nprivate val md_theme_dark_primaryContainer = Color(0xFF7B008F)\nprivate val md_theme_dark_onPrimaryContainer = Color(0xFFFFD6FE)\nprivate val md_theme_dark_secondary = Color(0xFFD7BFD5)\nprivate val md_theme_dark_onSecondary = Color(0xFF3B2B3C)\nprivate val md_theme_dark_secondaryContainer = Color(0xFF534153)\nprivate val md_theme_dark_onSecondaryContainer = Color(0xFFF4DBF1)\nprivate val md_theme_dark_tertiary = Color(0xFFF6B8AD)\nprivate val md_theme_dark_onTertiary = Color(0xFF4C251F)\nprivate val md_theme_dark_tertiaryContainer = Color(0xFF673B34)\nprivate val md_theme_dark_onTertiaryContainer = Color(0xFFFFDAD4)\nprivate val md_theme_dark_error = Color(0xFFFFB4AB)\nprivate val md_theme_dark_errorContainer = Color(0xFF93000A)\nprivate val md_theme_dark_onError = Color(0xFF690005)\nprivate val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_dark_background = Color(0xFF1E1A1D)\nprivate val md_theme_dark_onBackground = Color(0xFFE9E0E4)\nprivate val md_theme_dark_surface = Color(0xFF1E1A1D)\nprivate val md_theme_dark_onSurface = Color(0xFFE9E0E4)\nprivate val md_theme_dark_surfaceVariant = Color(0xFF4D444C)\nprivate val md_theme_dark_onSurfaceVariant = Color(0xFFD0C3CC)\nprivate val md_theme_dark_outline = Color(0xFF998D96)\nprivate val md_theme_dark_inverseOnSurface = Color(0xFF1E1A1D)\nprivate val md_theme_dark_inverseSurface = Color(0xFFE9E0E4)\nprivate val md_theme_dark_inversePrimary = Color(0xFF9A25AE)\nprivate val md_theme_dark_shadow = Color(0xFF000000)\nprivate val md_theme_dark_surfaceTint = Color(0xFFF9ABFF)\nprivate val md_theme_dark_outlineVariant = Color(0xFF4D444C)\nprivate val md_theme_dark_scrim = Color(0xFF000000)\nprivate val md_theme_dark_surfaceContainerLowest = Color(0xFF201C1F)\nprivate val md_theme_dark_surfaceContainerLow = Color(0xFF221E21)\nprivate val md_theme_dark_surfaceContainer = Color(0xFF262125)\nprivate val md_theme_dark_surfaceContainerHigh = Color(0xFF2B252A)\nprivate val md_theme_dark_surfaceContainerHighest = Color(0xFF2F292E)\n\n\nval LightPurpleTheme = lightColorScheme(\n    primary = md_theme_light_primary,\n    onPrimary = md_theme_light_onPrimary,\n    primaryContainer = md_theme_light_primaryContainer,\n    onPrimaryContainer = md_theme_light_onPrimaryContainer,\n    secondary = md_theme_light_secondary,\n    onSecondary = md_theme_light_onSecondary,\n    secondaryContainer = md_theme_light_secondaryContainer,\n    onSecondaryContainer = md_theme_light_onSecondaryContainer,\n    tertiary = md_theme_light_tertiary,\n    onTertiary = md_theme_light_onTertiary,\n    tertiaryContainer = md_theme_light_tertiaryContainer,\n    onTertiaryContainer = md_theme_light_onTertiaryContainer,\n    error = md_theme_light_error,\n    errorContainer = md_theme_light_errorContainer,\n    onError = md_theme_light_onError,\n    onErrorContainer = md_theme_light_onErrorContainer,\n    background = md_theme_light_background,\n    onBackground = md_theme_light_onBackground,\n    surface = md_theme_light_surface,\n    onSurface = md_theme_light_onSurface,\n    surfaceVariant = md_theme_light_surfaceVariant,\n    onSurfaceVariant = md_theme_light_onSurfaceVariant,\n    outline = md_theme_light_outline,\n    inverseOnSurface = md_theme_light_inverseOnSurface,\n    inverseSurface = md_theme_light_inverseSurface,\n    inversePrimary = md_theme_light_inversePrimary,\n    surfaceTint = md_theme_light_surfaceTint,\n    outlineVariant = md_theme_light_outlineVariant,\n    surfaceContainerLowest = md_theme_light_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_light_surfaceContainerLow,\n    surfaceContainer = md_theme_light_surfaceContainer,\n    surfaceContainerHigh = md_theme_light_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_light_surfaceContainerHighest,\n    scrim = md_theme_light_scrim,\n)\n\nval DarkPurpleTheme = darkColorScheme(\n    primary = md_theme_dark_primary,\n    onPrimary = md_theme_dark_onPrimary,\n    primaryContainer = md_theme_dark_primaryContainer,\n    onPrimaryContainer = md_theme_dark_onPrimaryContainer,\n    secondary = md_theme_dark_secondary,\n    onSecondary = md_theme_dark_onSecondary,\n    secondaryContainer = md_theme_dark_secondaryContainer,\n    onSecondaryContainer = md_theme_dark_onSecondaryContainer,\n    tertiary = md_theme_dark_tertiary,\n    onTertiary = md_theme_dark_onTertiary,\n    tertiaryContainer = md_theme_dark_tertiaryContainer,\n    onTertiaryContainer = md_theme_dark_onTertiaryContainer,\n    error = md_theme_dark_error,\n    errorContainer = md_theme_dark_errorContainer,\n    onError = md_theme_dark_onError,\n    onErrorContainer = md_theme_dark_onErrorContainer,\n    background = md_theme_dark_background,\n    onBackground = md_theme_dark_onBackground,\n    surface = md_theme_dark_surface,\n    onSurface = md_theme_dark_onSurface,\n    surfaceVariant = md_theme_dark_surfaceVariant,\n    onSurfaceVariant = md_theme_dark_onSurfaceVariant,\n    outline = md_theme_dark_outline,\n    inverseOnSurface = md_theme_dark_inverseOnSurface,\n    inverseSurface = md_theme_dark_inverseSurface,\n    inversePrimary = md_theme_dark_inversePrimary,\n    surfaceTint = md_theme_dark_surfaceTint,\n    outlineVariant = md_theme_dark_outlineVariant,\n    surfaceContainerLowest = md_theme_dark_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_dark_surfaceContainerLow,\n    surfaceContainer = md_theme_dark_surfaceContainer,\n    surfaceContainerHigh = md_theme_dark_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_dark_surfaceContainerHighest,\n    scrim = md_theme_dark_scrim,\n)"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/RedTheme.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lightColorScheme\nimport androidx.compose.ui.graphics.Color\n\nprivate val md_theme_light_primary = Color(0xFFBB1614)\nprivate val md_theme_light_onPrimary = Color(0xFFFFFFFF)\nprivate val md_theme_light_primaryContainer = Color(0xFFFFDAD5)\nprivate val md_theme_light_onPrimaryContainer = Color(0xFF410001)\nprivate val md_theme_light_secondary = Color(0xFF775652)\nprivate val md_theme_light_onSecondary = Color(0xFFFFFFFF)\nprivate val md_theme_light_secondaryContainer = Color(0xFFFFDAD5)\nprivate val md_theme_light_onSecondaryContainer = Color(0xFF2C1512)\nprivate val md_theme_light_tertiary = Color(0xFF705C2E)\nprivate val md_theme_light_onTertiary = Color(0xFFFFFFFF)\nprivate val md_theme_light_tertiaryContainer = Color(0xFFFCDFA6)\nprivate val md_theme_light_onTertiaryContainer = Color(0xFF261A00)\nprivate val md_theme_light_error = Color(0xFFBA1A1A)\nprivate val md_theme_light_errorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_light_onError = Color(0xFFFFFFFF)\nprivate val md_theme_light_onErrorContainer = Color(0xFF410002)\nprivate val md_theme_light_background = Color(0xFFFFFBFF)\nprivate val md_theme_light_onBackground = Color(0xFF201A19)\nprivate val md_theme_light_surface = Color(0xFFFFFBFF)\nprivate val md_theme_light_onSurface = Color(0xFF201A19)\nprivate val md_theme_light_surfaceVariant = Color(0xFFF5DDDA)\nprivate val md_theme_light_onSurfaceVariant = Color(0xFF534341)\nprivate val md_theme_light_outline = Color(0xFF857370)\nprivate val md_theme_light_inverseOnSurface = Color(0xFFFBEEEC)\nprivate val md_theme_light_inverseSurface = Color(0xFF362F2E)\nprivate val md_theme_light_inversePrimary = Color(0xFFFFB4A9)\nprivate val md_theme_light_shadow = Color(0xFF000000)\nprivate val md_theme_light_surfaceTint = Color(0xFFBB1614)\nprivate val md_theme_light_outlineVariant = Color(0xFFD8C2BE)\nprivate val md_theme_light_scrim = Color(0xFF000000)\nprivate val md_theme_light_surfaceContainerLowest = Color(0xFFFEF9FD)\nprivate val md_theme_light_surfaceContainerLow = Color(0xFFFEF8FB)\nprivate val md_theme_light_surfaceContainer = Color(0xFFFDF5F8)\nprivate val md_theme_light_surfaceContainerHigh = Color(0xFFFCF2F4)\nprivate val md_theme_light_surfaceContainerHighest = Color(0xFFFBEFF0)\n\n\nprivate val md_theme_dark_primary = Color(0xFFFFB4A9)\nprivate val md_theme_dark_onPrimary = Color(0xFF690002)\nprivate val md_theme_dark_primaryContainer = Color(0xFF930005)\nprivate val md_theme_dark_onPrimaryContainer = Color(0xFFFFDAD5)\nprivate val md_theme_dark_secondary = Color(0xFFE7BDB7)\nprivate val md_theme_dark_onSecondary = Color(0xFF442926)\nprivate val md_theme_dark_secondaryContainer = Color(0xFF5D3F3B)\nprivate val md_theme_dark_onSecondaryContainer = Color(0xFFFFDAD5)\nprivate val md_theme_dark_tertiary = Color(0xFFDFC38C)\nprivate val md_theme_dark_onTertiary = Color(0xFF3E2E04)\nprivate val md_theme_dark_tertiaryContainer = Color(0xFF574419)\nprivate val md_theme_dark_onTertiaryContainer = Color(0xFFFCDFA6)\nprivate val md_theme_dark_error = Color(0xFFFFB4AB)\nprivate val md_theme_dark_errorContainer = Color(0xFF93000A)\nprivate val md_theme_dark_onError = Color(0xFF690005)\nprivate val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_dark_background = Color(0xFF201A19)\nprivate val md_theme_dark_onBackground = Color(0xFFEDE0DE)\nprivate val md_theme_dark_surface = Color(0xFF201A19)\nprivate val md_theme_dark_onSurface = Color(0xFFEDE0DE)\nprivate val md_theme_dark_surfaceVariant = Color(0xFF534341)\nprivate val md_theme_dark_onSurfaceVariant = Color(0xFFD8C2BE)\nprivate val md_theme_dark_outline = Color(0xFFA08C89)\nprivate val md_theme_dark_inverseOnSurface = Color(0xFF201A19)\nprivate val md_theme_dark_inverseSurface = Color(0xFFEDE0DE)\nprivate val md_theme_dark_inversePrimary = Color(0xFFBB1614)\nprivate val md_theme_dark_shadow = Color(0xFF000000)\nprivate val md_theme_dark_surfaceTint = Color(0xFFFFB4A9)\nprivate val md_theme_dark_outlineVariant = Color(0xFF534341)\nprivate val md_theme_dark_scrim = Color(0xFF000000)\nprivate val md_theme_dark_surfaceContainerLowest = Color(0xFF221C1B)\nprivate val md_theme_dark_surfaceContainerLow = Color(0xFF251E1D)\nprivate val md_theme_dark_surfaceContainer = Color(0xFF292120)\nprivate val md_theme_dark_surfaceContainerHigh = Color(0xFF2E2524)\nprivate val md_theme_dark_surfaceContainerHighest = Color(0xFF332928)\n\n\nval LightRedTheme = lightColorScheme(\n    primary = md_theme_light_primary,\n    onPrimary = md_theme_light_onPrimary,\n    primaryContainer = md_theme_light_primaryContainer,\n    onPrimaryContainer = md_theme_light_onPrimaryContainer,\n    secondary = md_theme_light_secondary,\n    onSecondary = md_theme_light_onSecondary,\n    secondaryContainer = md_theme_light_secondaryContainer,\n    onSecondaryContainer = md_theme_light_onSecondaryContainer,\n    tertiary = md_theme_light_tertiary,\n    onTertiary = md_theme_light_onTertiary,\n    tertiaryContainer = md_theme_light_tertiaryContainer,\n    onTertiaryContainer = md_theme_light_onTertiaryContainer,\n    error = md_theme_light_error,\n    errorContainer = md_theme_light_errorContainer,\n    onError = md_theme_light_onError,\n    onErrorContainer = md_theme_light_onErrorContainer,\n    background = md_theme_light_background,\n    onBackground = md_theme_light_onBackground,\n    surface = md_theme_light_surface,\n    onSurface = md_theme_light_onSurface,\n    surfaceVariant = md_theme_light_surfaceVariant,\n    onSurfaceVariant = md_theme_light_onSurfaceVariant,\n    outline = md_theme_light_outline,\n    inverseOnSurface = md_theme_light_inverseOnSurface,\n    inverseSurface = md_theme_light_inverseSurface,\n    inversePrimary = md_theme_light_inversePrimary,\n    surfaceTint = md_theme_light_surfaceTint,\n    outlineVariant = md_theme_light_outlineVariant,\n    surfaceContainerLowest = md_theme_light_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_light_surfaceContainerLow,\n    surfaceContainer = md_theme_light_surfaceContainer,\n    surfaceContainerHigh = md_theme_light_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_light_surfaceContainerHighest,\n    scrim = md_theme_light_scrim,\n)\n\nval DarkRedTheme = darkColorScheme(\n    primary = md_theme_dark_primary,\n    onPrimary = md_theme_dark_onPrimary,\n    primaryContainer = md_theme_dark_primaryContainer,\n    onPrimaryContainer = md_theme_dark_onPrimaryContainer,\n    secondary = md_theme_dark_secondary,\n    onSecondary = md_theme_dark_onSecondary,\n    secondaryContainer = md_theme_dark_secondaryContainer,\n    onSecondaryContainer = md_theme_dark_onSecondaryContainer,\n    tertiary = md_theme_dark_tertiary,\n    onTertiary = md_theme_dark_onTertiary,\n    tertiaryContainer = md_theme_dark_tertiaryContainer,\n    onTertiaryContainer = md_theme_dark_onTertiaryContainer,\n    error = md_theme_dark_error,\n    errorContainer = md_theme_dark_errorContainer,\n    onError = md_theme_dark_onError,\n    onErrorContainer = md_theme_dark_onErrorContainer,\n    background = md_theme_dark_background,\n    onBackground = md_theme_dark_onBackground,\n    surface = md_theme_dark_surface,\n    onSurface = md_theme_dark_onSurface,\n    surfaceVariant = md_theme_dark_surfaceVariant,\n    onSurfaceVariant = md_theme_dark_onSurfaceVariant,\n    outline = md_theme_dark_outline,\n    inverseOnSurface = md_theme_dark_inverseOnSurface,\n    inverseSurface = md_theme_dark_inverseSurface,\n    inversePrimary = md_theme_dark_inversePrimary,\n    surfaceTint = md_theme_dark_surfaceTint,\n    outlineVariant = md_theme_dark_outlineVariant,\n    surfaceContainerLowest = md_theme_dark_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_dark_surfaceContainerLow,\n    surfaceContainer = md_theme_dark_surfaceContainer,\n    surfaceContainerHigh = md_theme_dark_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_dark_surfaceContainerHighest,\n    scrim = md_theme_dark_scrim,\n)"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/SakuraTheme.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lightColorScheme\nimport androidx.compose.ui.graphics.Color\n\nprivate val md_theme_light_primary = Color(0xFF9B404F)\nprivate val md_theme_light_onPrimary = Color(0xFFFFFFFF)\nprivate val md_theme_light_primaryContainer = Color(0xFFFFD9DC)\nprivate val md_theme_light_onPrimaryContainer = Color(0xFF400011)\nprivate val md_theme_light_secondary = Color(0xFF765659)\nprivate val md_theme_light_onSecondary = Color(0xFFFFFFFF)\nprivate val md_theme_light_secondaryContainer = Color(0xFFFFD9DC)\nprivate val md_theme_light_onSecondaryContainer = Color(0xFF2C1518)\nprivate val md_theme_light_tertiary = Color(0xFF785830)\nprivate val md_theme_light_onTertiary = Color(0xFFFFFFFF)\nprivate val md_theme_light_tertiaryContainer = Color(0xFFFFDDB7)\nprivate val md_theme_light_onTertiaryContainer = Color(0xFF2A1700)\nprivate val md_theme_light_error = Color(0xFFBA1A1A)\nprivate val md_theme_light_errorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_light_onError = Color(0xFFFFFFFF)\nprivate val md_theme_light_onErrorContainer = Color(0xFF410002)\nprivate val md_theme_light_background = Color(0xFFFFFBFF)\nprivate val md_theme_light_onBackground = Color(0xFF201A1A)\nprivate val md_theme_light_surface = Color(0xFFFFFBFF)\nprivate val md_theme_light_onSurface = Color(0xFF201A1A)\nprivate val md_theme_light_surfaceVariant = Color(0xFFF4DDDE)\nprivate val md_theme_light_onSurfaceVariant = Color(0xFF524344)\nprivate val md_theme_light_outline = Color(0xFF847374)\nprivate val md_theme_light_inverseOnSurface = Color(0xFFFBEEEE)\nprivate val md_theme_light_inverseSurface = Color(0xFF362F2F)\nprivate val md_theme_light_inversePrimary = Color(0xFFFFB2BA)\nprivate val md_theme_light_shadow = Color(0xFF000000)\nprivate val md_theme_light_surfaceTint = Color(0xFF9B404F)\nprivate val md_theme_light_outlineVariant = Color(0xFFD7C1C3)\nprivate val md_theme_light_scrim = Color(0xFF000000)\nprivate val md_theme_light_surfaceContainerLowest = Color(0xFFFEF9FD)\nprivate val md_theme_light_surfaceContainerLow = Color(0xFFFDF8FB)\nprivate val md_theme_light_surfaceContainer = Color(0xFFFDF5F9)\nprivate val md_theme_light_surfaceContainerHigh = Color(0xFFFBF2F5)\nprivate val md_theme_light_surfaceContainerHighest = Color(0xFFFAEFF2)\n\n\nprivate val md_theme_dark_primary = Color(0xFFFFB2BA)\nprivate val md_theme_dark_onPrimary = Color(0xFF5F1223)\nprivate val md_theme_dark_primaryContainer = Color(0xFF7D2939)\nprivate val md_theme_dark_onPrimaryContainer = Color(0xFFFFD9DC)\nprivate val md_theme_dark_secondary = Color(0xFFE5BDC0)\nprivate val md_theme_dark_onSecondary = Color(0xFF43292C)\nprivate val md_theme_dark_secondaryContainer = Color(0xFF5C3F42)\nprivate val md_theme_dark_onSecondaryContainer = Color(0xFFFFD9DC)\nprivate val md_theme_dark_tertiary = Color(0xFFE9BF8F)\nprivate val md_theme_dark_onTertiary = Color(0xFF442B07)\nprivate val md_theme_dark_tertiaryContainer = Color(0xFF5E411B)\nprivate val md_theme_dark_onTertiaryContainer = Color(0xFFFFDDB7)\nprivate val md_theme_dark_error = Color(0xFFFFB4AB)\nprivate val md_theme_dark_errorContainer = Color(0xFF93000A)\nprivate val md_theme_dark_onError = Color(0xFF690005)\nprivate val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_dark_background = Color(0xFF201A1A)\nprivate val md_theme_dark_onBackground = Color(0xFFECE0E0)\nprivate val md_theme_dark_surface = Color(0xFF201A1A)\nprivate val md_theme_dark_onSurface = Color(0xFFECE0E0)\nprivate val md_theme_dark_surfaceVariant = Color(0xFF524344)\nprivate val md_theme_dark_onSurfaceVariant = Color(0xFFD7C1C3)\nprivate val md_theme_dark_outline = Color(0xFF9F8C8D)\nprivate val md_theme_dark_inverseOnSurface = Color(0xFF201A1A)\nprivate val md_theme_dark_inverseSurface = Color(0xFFECE0E0)\nprivate val md_theme_dark_inversePrimary = Color(0xFF9B404F)\nprivate val md_theme_dark_shadow = Color(0xFF000000)\nprivate val md_theme_dark_surfaceTint = Color(0xFFFFB2BA)\nprivate val md_theme_dark_outlineVariant = Color(0xFF524344)\nprivate val md_theme_dark_scrim = Color(0xFF000000)\nprivate val md_theme_dark_surfaceContainerLowest = Color(0xFF221C1C)\nprivate val md_theme_dark_surfaceContainerLow = Color(0xFF251E1E)\nprivate val md_theme_dark_surfaceContainer = Color(0xFF292121)\nprivate val md_theme_dark_surfaceContainerHigh = Color(0xFF2E2525)\nprivate val md_theme_dark_surfaceContainerHighest = Color(0xFF332929)\n\n\nval LightSakuraTheme = lightColorScheme(\n    primary = md_theme_light_primary,\n    onPrimary = md_theme_light_onPrimary,\n    primaryContainer = md_theme_light_primaryContainer,\n    onPrimaryContainer = md_theme_light_onPrimaryContainer,\n    secondary = md_theme_light_secondary,\n    onSecondary = md_theme_light_onSecondary,\n    secondaryContainer = md_theme_light_secondaryContainer,\n    onSecondaryContainer = md_theme_light_onSecondaryContainer,\n    tertiary = md_theme_light_tertiary,\n    onTertiary = md_theme_light_onTertiary,\n    tertiaryContainer = md_theme_light_tertiaryContainer,\n    onTertiaryContainer = md_theme_light_onTertiaryContainer,\n    error = md_theme_light_error,\n    errorContainer = md_theme_light_errorContainer,\n    onError = md_theme_light_onError,\n    onErrorContainer = md_theme_light_onErrorContainer,\n    background = md_theme_light_background,\n    onBackground = md_theme_light_onBackground,\n    surface = md_theme_light_surface,\n    onSurface = md_theme_light_onSurface,\n    surfaceVariant = md_theme_light_surfaceVariant,\n    onSurfaceVariant = md_theme_light_onSurfaceVariant,\n    outline = md_theme_light_outline,\n    inverseOnSurface = md_theme_light_inverseOnSurface,\n    inverseSurface = md_theme_light_inverseSurface,\n    inversePrimary = md_theme_light_inversePrimary,\n    surfaceTint = md_theme_light_surfaceTint,\n    outlineVariant = md_theme_light_outlineVariant,\n    surfaceContainerLowest = md_theme_light_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_light_surfaceContainerLow,\n    surfaceContainer = md_theme_light_surfaceContainer,\n    surfaceContainerHigh = md_theme_light_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_light_surfaceContainerHighest,\n    scrim = md_theme_light_scrim,\n)\n\nval DarkSakuraTheme = darkColorScheme(\n    primary = md_theme_dark_primary,\n    onPrimary = md_theme_dark_onPrimary,\n    primaryContainer = md_theme_dark_primaryContainer,\n    onPrimaryContainer = md_theme_dark_onPrimaryContainer,\n    secondary = md_theme_dark_secondary,\n    onSecondary = md_theme_dark_onSecondary,\n    secondaryContainer = md_theme_dark_secondaryContainer,\n    onSecondaryContainer = md_theme_dark_onSecondaryContainer,\n    tertiary = md_theme_dark_tertiary,\n    onTertiary = md_theme_dark_onTertiary,\n    tertiaryContainer = md_theme_dark_tertiaryContainer,\n    onTertiaryContainer = md_theme_dark_onTertiaryContainer,\n    error = md_theme_dark_error,\n    errorContainer = md_theme_dark_errorContainer,\n    onError = md_theme_dark_onError,\n    onErrorContainer = md_theme_dark_onErrorContainer,\n    background = md_theme_dark_background,\n    onBackground = md_theme_dark_onBackground,\n    surface = md_theme_dark_surface,\n    onSurface = md_theme_dark_onSurface,\n    surfaceVariant = md_theme_dark_surfaceVariant,\n    onSurfaceVariant = md_theme_dark_onSurfaceVariant,\n    outline = md_theme_dark_outline,\n    inverseOnSurface = md_theme_dark_inverseOnSurface,\n    inverseSurface = md_theme_dark_inverseSurface,\n    inversePrimary = md_theme_dark_inversePrimary,\n    surfaceTint = md_theme_dark_surfaceTint,\n    outlineVariant = md_theme_dark_outlineVariant,\n    surfaceContainerLowest = md_theme_dark_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_dark_surfaceContainerLow,\n    surfaceContainer = md_theme_dark_surfaceContainer,\n    surfaceContainerHigh = md_theme_dark_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_dark_surfaceContainerHighest,\n    scrim = md_theme_dark_scrim,\n)"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/SoundEffectConfig.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport android.content.Context\nimport android.util.Log\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport me.bmax.apatch.util.SafeUriResolver\nimport java.io.File\n\nobject SoundEffectConfig {\n    private const val PREFS_NAME = \"sound_effect_settings\"\n    private const val KEY_ENABLED = \"sound_effect_enabled\"\n    private const val KEY_FILENAME = \"sound_effect_filename\"\n    private const val KEY_SCOPE = \"sound_effect_scope\" // \"global\" or \"bottom_bar\"\n    private const val KEY_SOURCE_TYPE = \"sound_effect_source_type\"\n    private const val KEY_PRESET_NAME = \"sound_effect_preset_name\"\n    private const val KEY_STARTUP_ENABLED = \"startup_sound_enabled\"\n    private const val KEY_STARTUP_FILENAME = \"startup_sound_filename\"\n    private const val KEY_STARTUP_SOURCE_TYPE = \"startup_sound_source_type\"\n    private const val KEY_STARTUP_PRESET_NAME = \"startup_sound_preset_name\"\n    private const val TAG = \"SoundEffectConfig\"\n\n    const val SCOPE_GLOBAL = \"global\"\n    const val SCOPE_BOTTOM_BAR = \"bottom_bar\"\n    \n    const val SOURCE_TYPE_LOCAL = \"local\"\n    const val SOURCE_TYPE_PRESET = \"preset\"\n\n    val PRESETS = listOf(\n        \"Zako\",\n        \"Zako2\",\n        \"Imoi\",\n        \"Ehe\",\n        \"Baka\",\n        \"Ciallo\"\n    )\n\n    val STARTUP_PRESETS = listOf(\n        \"Zako\",\n        \"Hentai\"\n    )\n\n    var isSoundEffectEnabled: Boolean by mutableStateOf(false)\n        private set\n\n    var soundEffectFilename: String? by mutableStateOf(null)\n        private set\n\n    var scope: String by mutableStateOf(SCOPE_GLOBAL)\n        private set\n        \n    var sourceType: String by mutableStateOf(SOURCE_TYPE_LOCAL)\n        private set\n        \n    var presetName: String by mutableStateOf(PRESETS[0])\n        private set\n\n    var isStartupSoundEnabled: Boolean by mutableStateOf(false)\n        private set\n\n    var startupSoundFilename: String? by mutableStateOf(null)\n        private set\n\n    var startupSourceType: String by mutableStateOf(SOURCE_TYPE_LOCAL)\n        private set\n\n    var startupPresetName: String by mutableStateOf(STARTUP_PRESETS[0])\n        private set\n\n    fun setEnabledState(enabled: Boolean) {\n        isSoundEffectEnabled = enabled\n    }\n\n    fun setFilenameValue(filename: String?) {\n        soundEffectFilename = filename\n    }\n\n    fun setScopeValue(value: String) {\n        scope = value\n    }\n    \n    fun setSourceTypeValue(value: String) {\n        sourceType = value\n    }\n    \n    fun setPresetNameValue(value: String) {\n        presetName = value\n    }\n\n    fun setStartupEnabledState(enabled: Boolean) {\n        isStartupSoundEnabled = enabled\n    }\n\n    fun setStartupSourceTypeValue(value: String) {\n        startupSourceType = value\n    }\n\n    fun setStartupPresetNameValue(value: String) {\n        startupPresetName = value\n    }\n\n    fun getSoundEffectDir(context: Context): File {\n        val dir = File(context.filesDir, \"sound_effects\")\n        if (!dir.exists()) {\n            dir.mkdirs()\n        }\n        return dir\n    }\n\n    fun getSoundEffectFile(context: Context): File? {\n        if (soundEffectFilename == null) return null\n        return File(getSoundEffectDir(context), soundEffectFilename!!)\n    }\n\n    fun getStartupSoundFile(context: Context): File? {\n        if (startupSoundFilename == null) return null\n        return File(getSoundEffectDir(context), startupSoundFilename!!)\n    }\n\n    fun load(context: Context) {\n        val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)\n        isSoundEffectEnabled = prefs.getBoolean(KEY_ENABLED, false)\n        soundEffectFilename = prefs.getString(KEY_FILENAME, null)\n        scope = prefs.getString(KEY_SCOPE, SCOPE_GLOBAL) ?: SCOPE_GLOBAL\n        sourceType = prefs.getString(KEY_SOURCE_TYPE, SOURCE_TYPE_LOCAL) ?: SOURCE_TYPE_LOCAL\n        presetName = prefs.getString(KEY_PRESET_NAME, PRESETS[0]) ?: PRESETS[0]\n\n        isStartupSoundEnabled = prefs.getBoolean(KEY_STARTUP_ENABLED, false)\n        startupSoundFilename = prefs.getString(KEY_STARTUP_FILENAME, null)\n        startupSourceType = prefs.getString(KEY_STARTUP_SOURCE_TYPE, SOURCE_TYPE_LOCAL) ?: SOURCE_TYPE_LOCAL\n        startupPresetName = prefs.getString(KEY_STARTUP_PRESET_NAME, STARTUP_PRESETS[0]) ?: STARTUP_PRESETS[0]\n\n        // Validate if file exists only if local type is selected\n        if (isSoundEffectEnabled && sourceType == SOURCE_TYPE_LOCAL && soundEffectFilename != null) {\n            val file = File(getSoundEffectDir(context), soundEffectFilename!!)\n            if (!file.exists()) {\n                isSoundEffectEnabled = false\n                soundEffectFilename = null\n                save(context)\n            }\n        }\n\n        if (isStartupSoundEnabled && startupSourceType == SOURCE_TYPE_LOCAL && startupSoundFilename != null) {\n            val file = File(getSoundEffectDir(context), startupSoundFilename!!)\n            if (!file.exists()) {\n                isStartupSoundEnabled = false\n                startupSoundFilename = null\n                save(context)\n            }\n        }\n    }\n\n    fun save(context: Context) {\n        val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)\n        prefs.edit()\n            .putBoolean(KEY_ENABLED, isSoundEffectEnabled)\n            .putString(KEY_FILENAME, soundEffectFilename)\n            .putString(KEY_SCOPE, scope)\n            .putString(KEY_SOURCE_TYPE, sourceType)\n            .putString(KEY_PRESET_NAME, presetName)\n            .putBoolean(KEY_STARTUP_ENABLED, isStartupSoundEnabled)\n            .putString(KEY_STARTUP_FILENAME, startupSoundFilename)\n            .putString(KEY_STARTUP_SOURCE_TYPE, startupSourceType)\n            .putString(KEY_STARTUP_PRESET_NAME, startupPresetName)\n            .apply()\n    }\n\n    fun saveSoundEffectFile(context: Context, uri: android.net.Uri): Boolean {\n        return try {\n            val extension = android.webkit.MimeTypeMap.getSingleton().getExtensionFromMimeType(context.contentResolver.getType(uri)) ?: \"mp3\"\n            val newFilename = \"sound_effect_${System.currentTimeMillis()}.$extension\"\n            val oldFilename = soundEffectFilename\n            \n            SafeUriResolver.openInputStream(context, uri)?.use { input ->\n                val file = File(getSoundEffectDir(context), newFilename)\n                file.outputStream().use { output ->\n                    input.copyTo(output)\n                }\n            }\n            \n            // Delete old file if it exists and is different\n            if (oldFilename != null && oldFilename != newFilename) {\n                val oldFile = File(getSoundEffectDir(context), oldFilename)\n                if (oldFile.exists()) {\n                    oldFile.delete()\n                }\n            }\n            \n            isSoundEffectEnabled = true\n            soundEffectFilename = newFilename\n            sourceType = SOURCE_TYPE_LOCAL\n            save(context)\n            true\n        } catch (e: Exception) {\n            Log.e(TAG, \"Failed to save sound effect file\", e)\n            false\n        }\n    }\n\n    fun clearSoundEffect(context: Context) {\n        val filename = soundEffectFilename\n        if (filename != null) {\n            val file = File(getSoundEffectDir(context), filename)\n            if (file.exists()) {\n                file.delete()\n            }\n        }\n        isSoundEffectEnabled = false\n        soundEffectFilename = null\n        save(context)\n    }\n\n    fun saveStartupSoundFile(context: Context, uri: android.net.Uri): Boolean {\n        return try {\n            val extension = android.webkit.MimeTypeMap.getSingleton().getExtensionFromMimeType(context.contentResolver.getType(uri)) ?: \"mp3\"\n            val newFilename = \"startup_sound_${System.currentTimeMillis()}.$extension\"\n            val oldFilename = startupSoundFilename\n            \n            SafeUriResolver.openInputStream(context, uri)?.use { input ->\n                val file = File(getSoundEffectDir(context), newFilename)\n                file.outputStream().use { output ->\n                    input.copyTo(output)\n                }\n            }\n            \n            // Delete old file if it exists and is different\n            if (oldFilename != null && oldFilename != newFilename) {\n                val oldFile = File(getSoundEffectDir(context), oldFilename)\n                if (oldFile.exists()) {\n                    oldFile.delete()\n                }\n            }\n            \n            isStartupSoundEnabled = true\n            startupSoundFilename = newFilename\n            startupSourceType = SOURCE_TYPE_LOCAL\n            save(context)\n            true\n        } catch (e: Exception) {\n            Log.e(TAG, \"Failed to save startup sound file\", e)\n            false\n        }\n    }\n\n    fun clearStartupSound(context: Context) {\n        val filename = startupSoundFilename\n        if (filename != null) {\n            val file = File(getSoundEffectDir(context), filename)\n            if (file.exists()) {\n                file.delete()\n            }\n        }\n        isStartupSoundEnabled = false\n        startupSoundFilename = null\n        save(context)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/TealTheme.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lightColorScheme\nimport androidx.compose.ui.graphics.Color\n\nprivate val md_theme_light_primary = Color(0xFF006A60)\nprivate val md_theme_light_onPrimary = Color(0xFFFFFFFF)\nprivate val md_theme_light_primaryContainer = Color(0xFF74F8E5)\nprivate val md_theme_light_onPrimaryContainer = Color(0xFF00201C)\nprivate val md_theme_light_secondary = Color(0xFF4A635F)\nprivate val md_theme_light_onSecondary = Color(0xFFFFFFFF)\nprivate val md_theme_light_secondaryContainer = Color(0xFFCCE8E2)\nprivate val md_theme_light_onSecondaryContainer = Color(0xFF05201C)\nprivate val md_theme_light_tertiary = Color(0xFF456179)\nprivate val md_theme_light_onTertiary = Color(0xFFFFFFFF)\nprivate val md_theme_light_tertiaryContainer = Color(0xFFCCE5FF)\nprivate val md_theme_light_onTertiaryContainer = Color(0xFF001E31)\nprivate val md_theme_light_error = Color(0xFFBA1A1A)\nprivate val md_theme_light_errorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_light_onError = Color(0xFFFFFFFF)\nprivate val md_theme_light_onErrorContainer = Color(0xFF410002)\nprivate val md_theme_light_background = Color(0xFFFAFDFB)\nprivate val md_theme_light_onBackground = Color(0xFF191C1B)\nprivate val md_theme_light_surface = Color(0xFFFAFDFB)\nprivate val md_theme_light_onSurface = Color(0xFF191C1B)\nprivate val md_theme_light_surfaceVariant = Color(0xFFDAE5E1)\nprivate val md_theme_light_onSurfaceVariant = Color(0xFF3F4947)\nprivate val md_theme_light_outline = Color(0xFF6F7977)\nprivate val md_theme_light_inverseOnSurface = Color(0xFFEFF1EF)\nprivate val md_theme_light_inverseSurface = Color(0xFF2D3130)\nprivate val md_theme_light_inversePrimary = Color(0xFF53DBC9)\nprivate val md_theme_light_shadow = Color(0xFF000000)\nprivate val md_theme_light_surfaceTint = Color(0xFF006A60)\nprivate val md_theme_light_outlineVariant = Color(0xFFBEC9C6)\nprivate val md_theme_light_scrim = Color(0xFF000000)\nprivate val md_theme_light_surfaceContainerLowest = Color(0xFFF8FBF9)\nprivate val md_theme_light_surfaceContainerLow = Color(0xFFF6FAF8)\nprivate val md_theme_light_surfaceContainer = Color(0xFFF4F8F6)\nprivate val md_theme_light_surfaceContainerHigh = Color(0xFFF1F6F3)\nprivate val md_theme_light_surfaceContainerHighest = Color(0xFFEDF3F1)\n\n\nprivate val md_theme_dark_primary = Color(0xFF53DBC9)\nprivate val md_theme_dark_onPrimary = Color(0xFF003731)\nprivate val md_theme_dark_primaryContainer = Color(0xFF005048)\nprivate val md_theme_dark_onPrimaryContainer = Color(0xFF74F8E5)\nprivate val md_theme_dark_secondary = Color(0xFFB1CCC6)\nprivate val md_theme_dark_onSecondary = Color(0xFF1C3531)\nprivate val md_theme_dark_secondaryContainer = Color(0xFF334B47)\nprivate val md_theme_dark_onSecondaryContainer = Color(0xFFCCE8E2)\nprivate val md_theme_dark_tertiary = Color(0xFFADCAE6)\nprivate val md_theme_dark_onTertiary = Color(0xFF153349)\nprivate val md_theme_dark_tertiaryContainer = Color(0xFF2D4961)\nprivate val md_theme_dark_onTertiaryContainer = Color(0xFFCCE5FF)\nprivate val md_theme_dark_error = Color(0xFFFFB4AB)\nprivate val md_theme_dark_errorContainer = Color(0xFF93000A)\nprivate val md_theme_dark_onError = Color(0xFF690005)\nprivate val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_dark_background = Color(0xFF191C1B)\nprivate val md_theme_dark_onBackground = Color(0xFFE0E3E1)\nprivate val md_theme_dark_surface = Color(0xFF191C1B)\nprivate val md_theme_dark_onSurface = Color(0xFFE0E3E1)\nprivate val md_theme_dark_surfaceVariant = Color(0xFF3F4947)\nprivate val md_theme_dark_onSurfaceVariant = Color(0xFFBEC9C6)\nprivate val md_theme_dark_outline = Color(0xFF899390)\nprivate val md_theme_dark_inverseOnSurface = Color(0xFF191C1B)\nprivate val md_theme_dark_inverseSurface = Color(0xFFE0E3E1)\nprivate val md_theme_dark_inversePrimary = Color(0xFF006A60)\nprivate val md_theme_dark_shadow = Color(0xFF000000)\nprivate val md_theme_dark_surfaceTint = Color(0xFF53DBC9)\nprivate val md_theme_dark_outlineVariant = Color(0xFF3F4947)\nprivate val md_theme_dark_scrim = Color(0xFF000000)\nprivate val md_theme_dark_surfaceContainerLowest = Color(0xFF1A1E1D)\nprivate val md_theme_dark_surfaceContainerLow = Color(0xFF1C201F)\nprivate val md_theme_dark_surfaceContainer = Color(0xFF1F2422)\nprivate val md_theme_dark_surfaceContainerHigh = Color(0xFF232827)\nprivate val md_theme_dark_surfaceContainerHighest = Color(0xFF272D2B)\n\n\n\nval LightTealTheme = lightColorScheme(\n    primary = md_theme_light_primary,\n    onPrimary = md_theme_light_onPrimary,\n    primaryContainer = md_theme_light_primaryContainer,\n    onPrimaryContainer = md_theme_light_onPrimaryContainer,\n    secondary = md_theme_light_secondary,\n    onSecondary = md_theme_light_onSecondary,\n    secondaryContainer = md_theme_light_secondaryContainer,\n    onSecondaryContainer = md_theme_light_onSecondaryContainer,\n    tertiary = md_theme_light_tertiary,\n    onTertiary = md_theme_light_onTertiary,\n    tertiaryContainer = md_theme_light_tertiaryContainer,\n    onTertiaryContainer = md_theme_light_onTertiaryContainer,\n    error = md_theme_light_error,\n    errorContainer = md_theme_light_errorContainer,\n    onError = md_theme_light_onError,\n    onErrorContainer = md_theme_light_onErrorContainer,\n    background = md_theme_light_background,\n    onBackground = md_theme_light_onBackground,\n    surface = md_theme_light_surface,\n    onSurface = md_theme_light_onSurface,\n    surfaceVariant = md_theme_light_surfaceVariant,\n    onSurfaceVariant = md_theme_light_onSurfaceVariant,\n    outline = md_theme_light_outline,\n    inverseOnSurface = md_theme_light_inverseOnSurface,\n    inverseSurface = md_theme_light_inverseSurface,\n    inversePrimary = md_theme_light_inversePrimary,\n    surfaceTint = md_theme_light_surfaceTint,\n    outlineVariant = md_theme_light_outlineVariant,\n    surfaceContainerLowest = md_theme_light_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_light_surfaceContainerLow,\n    surfaceContainer = md_theme_light_surfaceContainer,\n    surfaceContainerHigh = md_theme_light_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_light_surfaceContainerHighest,\n    scrim = md_theme_light_scrim,\n)\n\nval DarkTealTheme = darkColorScheme(\n    primary = md_theme_dark_primary,\n    onPrimary = md_theme_dark_onPrimary,\n    primaryContainer = md_theme_dark_primaryContainer,\n    onPrimaryContainer = md_theme_dark_onPrimaryContainer,\n    secondary = md_theme_dark_secondary,\n    onSecondary = md_theme_dark_onSecondary,\n    secondaryContainer = md_theme_dark_secondaryContainer,\n    onSecondaryContainer = md_theme_dark_onSecondaryContainer,\n    tertiary = md_theme_dark_tertiary,\n    onTertiary = md_theme_dark_onTertiary,\n    tertiaryContainer = md_theme_dark_tertiaryContainer,\n    onTertiaryContainer = md_theme_dark_onTertiaryContainer,\n    error = md_theme_dark_error,\n    errorContainer = md_theme_dark_errorContainer,\n    onError = md_theme_dark_onError,\n    onErrorContainer = md_theme_dark_onErrorContainer,\n    background = md_theme_dark_background,\n    onBackground = md_theme_dark_onBackground,\n    surface = md_theme_dark_surface,\n    onSurface = md_theme_dark_onSurface,\n    surfaceVariant = md_theme_dark_surfaceVariant,\n    onSurfaceVariant = md_theme_dark_onSurfaceVariant,\n    outline = md_theme_dark_outline,\n    inverseOnSurface = md_theme_dark_inverseOnSurface,\n    inverseSurface = md_theme_dark_inverseSurface,\n    inversePrimary = md_theme_dark_inversePrimary,\n    surfaceTint = md_theme_dark_surfaceTint,\n    outlineVariant = md_theme_dark_outlineVariant,\n    surfaceContainerLowest = md_theme_dark_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_dark_surfaceContainerLow,\n    surfaceContainer = md_theme_dark_surfaceContainer,\n    surfaceContainerHigh = md_theme_dark_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_dark_surfaceContainerHighest,\n    scrim = md_theme_dark_scrim,\n)"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/Theme.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport android.os.Build\nimport androidx.activity.ComponentActivity\nimport androidx.activity.SystemBarStyle\nimport androidx.activity.enableEdgeToEdge\nimport androidx.compose.foundation.isSystemInDarkTheme\nimport androidx.compose.material3.ColorScheme\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.dynamicDarkColorScheme\nimport androidx.compose.material3.dynamicLightColorScheme\nimport androidx.compose.runtime.Composable\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.Image\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.SideEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.livedata.observeAsState\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.layout.ContentScale\nimport androidx.compose.ui.graphics.Brush\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.asImageBitmap\nimport androidx.compose.ui.graphics.toArgb\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.lifecycle.MutableLiveData\nimport coil.compose.rememberAsyncImagePainter\nimport coil.request.ImageRequest\nimport android.net.Uri\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.ui.webui.MonetColorsProvider\nimport androidx.compose.ui.draw.paint\nimport androidx.compose.ui.zIndex\nimport androidx.navigation.NavHostController\nimport androidx.navigation.compose.currentBackStackEntryAsState\nimport com.ramcosta.composedestinations.generated.destinations.SettingScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.HomeScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.KPModuleScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.SuperUserScreenDestination\nimport com.ramcosta.composedestinations.generated.destinations.APModuleScreenDestination\n\n@Composable\nprivate fun SystemBarStyle(\n    darkMode: Boolean,\n    statusBarScrim: Color = Color.Transparent,\n    navigationBarScrim: Color = Color.Transparent\n) {\n    val context = LocalContext.current\n    val activity = context as ComponentActivity\n\n    SideEffect {\n        activity.enableEdgeToEdge(\n            statusBarStyle = SystemBarStyle.auto(\n                statusBarScrim.toArgb(),\n                statusBarScrim.toArgb(),\n            ) { darkMode }, navigationBarStyle = when {\n                darkMode -> SystemBarStyle.dark(\n                    navigationBarScrim.toArgb()\n                )\n\n                else -> SystemBarStyle.light(\n                    navigationBarScrim.toArgb(),\n                    navigationBarScrim.toArgb(),\n                )\n            }\n        )\n    }\n}\n\nfun ColorScheme.toAmoled(): ColorScheme = copy(\n    background = Color.Black,\n    surface = Color.Black,\n    surfaceContainerLowest = Color.Black,\n    surfaceContainerLow = Color(0xFF050505),\n    surfaceDim = Color(0xFF0D0D0D),\n    surfaceContainer = Color(0xFF0A0A0A),\n    surfaceVariant = Color(0xFF121212),\n    surfaceContainerHigh = Color(0xFF121212),\n    surfaceContainerHighest = Color(0xFF1A1A1A),\n    surfaceBright = Color(0xFF1F1F1F),\n)\n\nval refreshTheme = MutableLiveData(false)\n\n@Composable\nfun APatchTheme(\n    isSettingsScreen: Boolean = false,\n    allowCustomBackground: Boolean = true,\n    content: @Composable () -> Unit\n) {\n    val context = LocalContext.current\n    val prefs = APApplication.sharedPreferences\n\n    var darkThemeFollowSys by remember {\n        mutableStateOf(\n            prefs.getBoolean(\n                \"night_mode_follow_sys\",\n                false\n            )\n        )\n    }\n    var nightModeEnabled by remember {\n        mutableStateOf(\n            prefs.getBoolean(\n                \"night_mode_enabled\",\n                false\n            )\n        )\n    }\n    // Dynamic color is available on Android 12+, and custom 1t!\n    var dynamicColor by remember {\n        mutableStateOf(\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) prefs.getBoolean(\n                \"use_system_color_theme\",\n                false\n            ) else false\n        )\n    }\n    var customColorScheme by remember { mutableStateOf(prefs.getString(\"custom_color\", \"indigo\")) }\n    var amoledTheme by remember { mutableStateOf(prefs.getBoolean(\"amoled_theme\", false)) }\n\n    val refreshThemeObserver by refreshTheme.observeAsState(false)\n    LaunchedEffect(refreshThemeObserver) {\n        if (refreshThemeObserver == true) {\n            darkThemeFollowSys = prefs.getBoolean(\"night_mode_follow_sys\", false)\n            nightModeEnabled = prefs.getBoolean(\"night_mode_enabled\", true)\n            dynamicColor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) prefs.getBoolean(\n                \"use_system_color_theme\",\n                false\n            ) else false\n            customColorScheme = prefs.getString(\"custom_color\", \"indigo\")\n            amoledTheme = prefs.getBoolean(\"amoled_theme\", false)\n            refreshTheme.postValue(false)\n        }\n    }\n\n    val darkTheme = if (darkThemeFollowSys) {\n        isSystemInDarkTheme()\n    } else {\n        nightModeEnabled\n    }\n\n    val baseColorScheme = if (!dynamicColor) {\n        if (darkTheme) {\n            when (customColorScheme) {\n                \"amber\" -> DarkAmberTheme\n                \"blue_grey\" -> DarkBlueGreyTheme\n                \"blue\" -> DarkBlueTheme\n                \"brown\" -> DarkBrownTheme\n                \"cyan\" -> DarkCyanTheme\n                \"deep_orange\" -> DarkDeepOrangeTheme\n                \"deep_purple\" -> DarkDeepPurpleTheme\n                \"green\" -> DarkGreenTheme\n                \"indigo\" -> DarkIndigoTheme\n                \"light_blue\" -> DarkLightBlueTheme\n                \"light_green\" -> DarkLightGreenTheme\n                \"lime\" -> DarkLimeTheme\n                \"orange\" -> DarkOrangeTheme\n                \"pink\" -> DarkPinkTheme\n                \"purple\" -> DarkPurpleTheme\n                \"red\" -> DarkRedTheme\n                \"sakura\" -> DarkSakuraTheme\n                \"teal\" -> DarkTealTheme\n                \"yellow\" -> DarkYellowTheme\n                \"ink_wash\" -> DarkInkWashTheme\n                else -> DarkBlueTheme\n            }\n        } else {\n            when (customColorScheme) {\n                \"amber\" -> LightAmberTheme\n                \"blue_grey\" -> LightBlueGreyTheme\n                \"blue\" -> LightBlueTheme\n                \"brown\" -> LightBrownTheme\n                \"cyan\" -> LightCyanTheme\n                \"deep_orange\" -> LightDeepOrangeTheme\n                \"deep_purple\" -> LightDeepPurpleTheme\n                \"green\" -> LightGreenTheme\n                \"indigo\" -> LightIndigoTheme\n                \"light_blue\" -> LightLightBlueTheme\n                \"light_green\" -> LightLightGreenTheme\n                \"lime\" -> LightLimeTheme\n                \"orange\" -> LightOrangeTheme\n                \"pink\" -> LightPinkTheme\n                \"purple\" -> LightPurpleTheme\n                \"red\" -> LightRedTheme\n                \"sakura\" -> LightSakuraTheme\n                \"teal\" -> LightTealTheme\n                \"yellow\" -> LightYellowTheme\n                \"ink_wash\" -> LightInkWashTheme\n                else -> LightBlueTheme\n            }\n        }\n    } else {\n        when {\n            Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {\n                if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)\n            }\n\n            darkTheme -> DarkBlueTheme\n            else -> LightBlueTheme\n        }\n    }\n    \n    val useCustomBackground = allowCustomBackground && BackgroundConfig.isCustomBackgroundEnabled\n    val colorScheme = if (darkTheme && amoledTheme && !useCustomBackground) {\n        baseColorScheme.toAmoled()\n    } else {\n        baseColorScheme.copy(\n            background = if (useCustomBackground) Color.Transparent else baseColorScheme.background,\n            surface = if (useCustomBackground) {\n                baseColorScheme.surface.copy(alpha = BackgroundConfig.customBackgroundOpacity)\n            } else {\n                baseColorScheme.surface\n            },\n            primary = baseColorScheme.primary,\n            secondary = baseColorScheme.secondary,\n            secondaryContainer = if (useCustomBackground) {\n                baseColorScheme.secondaryContainer.copy(alpha = BackgroundConfig.customBackgroundOpacity)\n            } else {\n                baseColorScheme.secondaryContainer\n            },\n            surfaceContainer = if (useCustomBackground) {\n                baseColorScheme.surfaceContainer.copy(alpha = BackgroundConfig.customBackgroundOpacity)\n            } else {\n                baseColorScheme.surfaceContainer\n            }\n        )\n    }\n\n    SystemBarStyle(\n        darkMode = darkTheme\n    )\n\n    val fontFamily = remember(\n        FontConfig.isCustomFontEnabled,\n        FontConfig.customFontFilename\n    ) {\n        FontConfig.getFontFamily(context)\n    }\n    val typography = remember(fontFamily) { getTypography(fontFamily) }\n\n    MaterialTheme(\n        colorScheme = colorScheme,\n        typography = typography,\n        content = {\n            MonetColorsProvider.UpdateCss()\n            content()\n        }\n    )\n}\n\n@Composable\nfun APatchThemeWithBackground(\n    navController: NavHostController? = null,\n    folkXEngineEnabled: Boolean = true,\n    folkXAnimationType: String? = \"linear\",\n    folkXAnimationSpeed: Float = 1.0f,\n    content: @Composable () -> Unit\n) {\n    val context = LocalContext.current\n\n    // Check current route\n    val currentRoute = navController?.currentBackStackEntryAsState()?.value?.destination?.route\n    val isSettingsScreen = currentRoute == SettingScreenDestination.route\n\n    // Load background/font config once (synchronously for first frame), then only reload on theme change\n    var isConfigLoaded by remember { mutableStateOf(false) }\n    if (!isConfigLoaded) {\n        BackgroundManager.loadCustomBackground(context)\n        FontConfig.load(context)\n        isConfigLoaded = true\n    }\n\n    // 监听refreshTheme的变化，重新加载背景配置\n    val refreshThemeObserver by refreshTheme.observeAsState(false)\n    LaunchedEffect(refreshThemeObserver) {\n        if (refreshThemeObserver) {\n            BackgroundManager.loadCustomBackground(context)\n            FontConfig.load(context)\n            refreshTheme.postValue(false)\n        }\n    }\n    \n    APatchTheme(isSettingsScreen = isSettingsScreen) {\n        Box(modifier = Modifier.fillMaxSize()) {\n            // Always show background layer if enabled\n            BackgroundLayer(\n                currentRoute = currentRoute,\n                folkXEngineEnabled = folkXEngineEnabled,\n                folkXAnimationType = folkXAnimationType,\n                folkXAnimationSpeed = folkXAnimationSpeed\n            )\n            \n            // Content layer - add zIndex to ensure it's above the background\n            Box(modifier = Modifier.fillMaxSize().zIndex(1f)) {\n                content()\n            }\n        }\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/ThemeManager.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport android.content.Context\nimport android.net.Uri\nimport android.util.Log\nimport androidx.lifecycle.MutableLiveData\nimport kotlinx.coroutines.CompletableDeferred\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport kotlinx.coroutines.withContext\nimport androidx.appcompat.app.AppCompatDelegate\nimport androidx.core.os.LocaleListCompat\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.util.MusicManager\nimport me.bmax.apatch.util.SafeUriResolver\nimport org.json.JSONObject\nimport java.io.BufferedInputStream\nimport java.io.BufferedOutputStream\nimport java.io.File\nimport java.io.FileInputStream\nimport java.io.FileOutputStream\nimport java.security.MessageDigest\nimport java.security.SecureRandom\nimport java.util.zip.ZipEntry\nimport java.util.zip.ZipInputStream\nimport java.util.zip.ZipOutputStream\nimport javax.crypto.Cipher\nimport javax.crypto.CipherInputStream\nimport javax.crypto.CipherOutputStream\nimport javax.crypto.spec.IvParameterSpec\nimport javax.crypto.spec.SecretKeySpec\n\nobject ThemeManager {\n    private const val TAG = \"ThemeManager\"\n    private const val THEME_CONFIG_FILENAME = \"theme.json\"\n    private const val BACKGROUND_FILENAME = \"background.jpg\"\n    private const val FONT_FILENAME = \"font.ttf\"\n    private const val KEY_STR = \"FolkPatchThemeSecretKey2025\"\n    private val importMutex = Mutex()\n    private var activeImportKey: String? = null\n    private var activeImportDeferred: CompletableDeferred<Boolean>? = null\n\n    private fun getSecretKey(): SecretKeySpec {\n        val digest = MessageDigest.getInstance(\"SHA-256\")\n        val bytes = digest.digest(KEY_STR.toByteArray())\n        return SecretKeySpec(bytes, \"AES\")\n    }\n\n    data class ThemeConfig(\n        val isBackgroundEnabled: Boolean,\n        val backgroundOpacity: Float,\n        val backgroundBlur: Float = 0f,\n        val backgroundDim: Float,\n        val isDualBackgroundDimEnabled: Boolean = false,\n        val backgroundDayDim: Float = 0.0f,\n        val backgroundNightDim: Float = 0.0f,\n        val isFontEnabled: Boolean,\n        val customColor: String,\n        val homeLayoutStyle: String,\n        val statsTopLayout: String = \"list\",\n        val nightModeEnabled: Boolean,\n        val nightModeFollowSys: Boolean,\n        val useSystemDynamicColor: Boolean,\n        val appLanguage: String?,\n        // Grid Working Card Background\n        val isGridWorkingCardBackgroundEnabled: Boolean = false,\n        val gridWorkingCardBackgroundOpacity: Float = 1.0f,\n        val isGridDualOpacityEnabled: Boolean = false,\n        val gridWorkingCardBackgroundDayOpacity: Float = 1.0f,\n        val gridWorkingCardBackgroundNightOpacity: Float = 1.0f,\n        val gridWorkingCardBackgroundDim: Float = 0.3f,\n        val isGridWorkingCardCheckHidden: Boolean = false,\n        val isGridWorkingCardTextHidden: Boolean = false,\n        val isGridWorkingCardModeHidden: Boolean = false,\n        val isListWorkingCardModeHidden: Boolean = false,\n        // Multi-Background Mode\n        val isMultiBackgroundEnabled: Boolean = false,\n        // Music Config\n        val isMusicEnabled: Boolean = false,\n        val musicVolume: Float = 1.0f,\n        val isAutoPlayEnabled: Boolean = false,\n        val isLoopingEnabled: Boolean = false,\n        val musicFilename: String? = null,\n        // Sound Effect Config\n        val isSoundEffectEnabled: Boolean = false,\n        val soundEffectFilename: String? = null,\n        val soundEffectScope: String = SoundEffectConfig.SCOPE_GLOBAL,\n        // Video Background\n        val isVideoBackgroundEnabled: Boolean = false,\n        val videoVolume: Float = 0f,\n        // Advanced Title Style\n        val isAdvancedTitleStyleEnabled: Boolean = false,\n        val titleImageDayOpacity: Float = 1.0f,\n        val titleImageNightOpacity: Float = 1.0f,\n        val titleImageDim: Float = 0.0f,\n        val titleImageOffsetX: Float = 0f\n    )\n\n    data class ThemeMetadata(\n        val name: String,\n        val type: String, // \"phone\" or \"tablet\"\n        val version: String,\n        val author: String,\n        val description: String\n    )\n\n    suspend fun exportTheme(context: Context, uri: Uri, metadata: ThemeMetadata): Boolean {\n        return withContext(Dispatchers.IO) {\n            val cacheDir = File(context.cacheDir, \"theme_export\")\n            if (cacheDir.exists()) cacheDir.deleteRecursively()\n            cacheDir.mkdirs()\n\n            try {\n                // 1. Collect Config\n                val prefs = APApplication.sharedPreferences\n                val config = ThemeConfig(\n                    isBackgroundEnabled = BackgroundConfig.isCustomBackgroundEnabled,\n                    backgroundOpacity = BackgroundConfig.customBackgroundOpacity,\n                    backgroundBlur = BackgroundConfig.customBackgroundBlur,\n                    backgroundDim = BackgroundConfig.customBackgroundDim,\n                    isDualBackgroundDimEnabled = BackgroundConfig.isDualBackgroundDimEnabled,\n                    backgroundDayDim = BackgroundConfig.customBackgroundDayDim,\n                    backgroundNightDim = BackgroundConfig.customBackgroundNightDim,\n                    isFontEnabled = FontConfig.isCustomFontEnabled,\n                    customColor = prefs.getString(\"custom_color\", \"indigo\") ?: \"indigo\",\n                    homeLayoutStyle = prefs.getString(\"home_layout_style\", \"circle\") ?: \"circle\",\n                    statsTopLayout = prefs.getString(\"stats_top_layout\", \"list\") ?: \"list\",\n                    nightModeEnabled = prefs.getBoolean(\"night_mode_enabled\", true),\n                    nightModeFollowSys = prefs.getBoolean(\"night_mode_follow_sys\", false),\n                    useSystemDynamicColor = prefs.getBoolean(\"use_system_color_theme\", false),\n                    appLanguage = AppCompatDelegate.getApplicationLocales().toLanguageTags(),\n                    isGridWorkingCardBackgroundEnabled = BackgroundConfig.isGridWorkingCardBackgroundEnabled,\n                    gridWorkingCardBackgroundOpacity = BackgroundConfig.gridWorkingCardBackgroundOpacity,\n                    isGridDualOpacityEnabled = BackgroundConfig.isGridDualOpacityEnabled,\n                    gridWorkingCardBackgroundDayOpacity = BackgroundConfig.gridWorkingCardBackgroundDayOpacity,\n                    gridWorkingCardBackgroundNightOpacity = BackgroundConfig.gridWorkingCardBackgroundNightOpacity,\n                    gridWorkingCardBackgroundDim = BackgroundConfig.gridWorkingCardBackgroundDim,\n                    isGridWorkingCardCheckHidden = BackgroundConfig.isGridWorkingCardCheckHidden,\n                    isGridWorkingCardTextHidden = BackgroundConfig.isGridWorkingCardTextHidden,\n                    isGridWorkingCardModeHidden = BackgroundConfig.isGridWorkingCardModeHidden,\n                    isListWorkingCardModeHidden = BackgroundConfig.isListWorkingCardModeHidden,\n                    isMultiBackgroundEnabled = BackgroundConfig.isMultiBackgroundEnabled,\n                    isMusicEnabled = MusicConfig.isMusicEnabled,\n                    musicVolume = MusicConfig.volume,\n                    isAutoPlayEnabled = MusicConfig.isAutoPlayEnabled,\n                    isLoopingEnabled = MusicConfig.isLoopingEnabled,\n                    musicFilename = MusicConfig.musicFilename,\n                    isSoundEffectEnabled = SoundEffectConfig.isSoundEffectEnabled,\n                    soundEffectFilename = SoundEffectConfig.soundEffectFilename,\n                    soundEffectScope = SoundEffectConfig.scope,\n                    isVideoBackgroundEnabled = BackgroundConfig.isVideoBackgroundEnabled,\n                    videoVolume = BackgroundConfig.videoVolume,\n                    // Advanced Title Style\n                    isAdvancedTitleStyleEnabled = BackgroundConfig.isAdvancedTitleStyleEnabled,\n                    titleImageDayOpacity = BackgroundConfig.titleImageDayOpacity,\n                    titleImageNightOpacity = BackgroundConfig.titleImageNightOpacity,\n                    titleImageDim = BackgroundConfig.titleImageDim,\n                    titleImageOffsetX = BackgroundConfig.titleImageOffsetX\n                )\n\n                // 2. Write Config JSON\n                val json = JSONObject().apply {\n                    put(\"isBackgroundEnabled\", config.isBackgroundEnabled)\n                    put(\"backgroundOpacity\", config.backgroundOpacity.toDouble())\n                    put(\"backgroundBlur\", config.backgroundBlur.toDouble())\n                    put(\"backgroundDim\", config.backgroundDim.toDouble())\n                    put(\"isDualBackgroundDimEnabled\", config.isDualBackgroundDimEnabled)\n                    put(\"backgroundDayDim\", config.backgroundDayDim.toDouble())\n                    put(\"backgroundNightDim\", config.backgroundNightDim.toDouble())\n                    put(\"isFontEnabled\", config.isFontEnabled)\n                    put(\"customColor\", config.customColor)\n                    put(\"homeLayoutStyle\", config.homeLayoutStyle)\n                    put(\"statsTopLayout\", config.statsTopLayout)\n                    put(\"nightModeEnabled\", config.nightModeEnabled)\n                    put(\"nightModeFollowSys\", config.nightModeFollowSys)\n                    put(\"useSystemDynamicColor\", config.useSystemDynamicColor)\n                    put(\"appLanguage\", config.appLanguage)\n                    \n                    // Grid Working Card Background\n                    put(\"isGridWorkingCardBackgroundEnabled\", config.isGridWorkingCardBackgroundEnabled)\n                    put(\"gridWorkingCardBackgroundOpacity\", config.gridWorkingCardBackgroundOpacity.toDouble())\n                    put(\"isGridDualOpacityEnabled\", config.isGridDualOpacityEnabled)\n                    put(\"gridWorkingCardBackgroundDayOpacity\", config.gridWorkingCardBackgroundDayOpacity.toDouble())\n                    put(\"gridWorkingCardBackgroundNightOpacity\", config.gridWorkingCardBackgroundNightOpacity.toDouble())\n                    put(\"gridWorkingCardBackgroundDim\", config.gridWorkingCardBackgroundDim.toDouble())\n                    put(\"isGridWorkingCardCheckHidden\", config.isGridWorkingCardCheckHidden)\n                    put(\"isGridWorkingCardTextHidden\", config.isGridWorkingCardTextHidden)\n                    put(\"isGridWorkingCardModeHidden\", config.isGridWorkingCardModeHidden)\n                    put(\"isListWorkingCardModeHidden\", config.isListWorkingCardModeHidden)\n\n                    // Multi-Background Mode\n                    put(\"isMultiBackgroundEnabled\", config.isMultiBackgroundEnabled)\n\n                    // Music Config\n                    put(\"isMusicEnabled\", config.isMusicEnabled)\n                    put(\"musicVolume\", config.musicVolume.toDouble())\n                    put(\"isAutoPlayEnabled\", config.isAutoPlayEnabled)\n                    put(\"isLoopingEnabled\", config.isLoopingEnabled)\n                    put(\"musicFilename\", config.musicFilename)\n\n                    // Sound Effect Config\n                    put(\"isSoundEffectEnabled\", config.isSoundEffectEnabled)\n                    put(\"soundEffectFilename\", config.soundEffectFilename)\n                    put(\"soundEffectScope\", config.soundEffectScope)\n\n                    // Video Background\n                    put(\"isVideoBackgroundEnabled\", config.isVideoBackgroundEnabled)\n                    put(\"videoVolume\", config.videoVolume.toDouble())\n\n                    // Advanced Title Style\n                    put(\"isAdvancedTitleStyleEnabled\", config.isAdvancedTitleStyleEnabled)\n                    put(\"titleImageDayOpacity\", config.titleImageDayOpacity.toDouble())\n                    put(\"titleImageNightOpacity\", config.titleImageNightOpacity.toDouble())\n                    put(\"titleImageDim\", config.titleImageDim.toDouble())\n                    put(\"titleImageOffsetX\", config.titleImageOffsetX.toDouble())\n\n                    // Add metadata\n                    put(\"meta_name\", metadata.name)\n                    put(\"meta_type\", metadata.type)\n                    put(\"meta_version\", metadata.version)\n                    put(\"meta_author\", metadata.author)\n                    put(\"meta_description\", metadata.description)\n                }\n                File(cacheDir, THEME_CONFIG_FILENAME).writeText(json.toString())\n\n\n                // 3. Copy Background if enabled\n                if (config.isBackgroundEnabled) {\n                    val extensions = listOf(\".jpg\", \".png\", \".gif\", \".webp\")\n                    for (ext in extensions) {\n                        val bgFile = File(context.filesDir, \"background$ext\")\n                        if (bgFile.exists()) {\n                            bgFile.copyTo(File(cacheDir, \"background$ext\"))\n                            break // Only one background file should exist\n                        }\n                    }\n                }\n                \n                // Copy Grid Working Card Background if enabled\n                if (config.isGridWorkingCardBackgroundEnabled) {\n                    val extensions = listOf(\".jpg\", \".png\", \".gif\", \".webp\")\n                    for (ext in extensions) {\n                        val bgFile = File(context.filesDir, \"grid_working_card_background$ext\")\n                        if (bgFile.exists()) {\n                            bgFile.copyTo(File(cacheDir, \"grid_working_card_background$ext\"))\n                            break \n                        }\n                    }\n                }\n\n                // Copy Multi-Backgrounds if enabled\n                if (config.isMultiBackgroundEnabled) {\n                    val multiBackgrounds = listOf(\n                        \"background_home\",\n                        \"background_kernel\",\n                        \"background_superuser\",\n                        \"background_system_module\",\n                        \"background_settings\"\n                    )\n                    val extensions = listOf(\".jpg\", \".png\", \".gif\", \".webp\")\n                    \n                    for (bgName in multiBackgrounds) {\n                        for (ext in extensions) {\n                            val bgFile = File(context.filesDir, \"$bgName$ext\")\n                            if (bgFile.exists()) {\n                                bgFile.copyTo(File(cacheDir, \"$bgName$ext\"))\n                                break\n                            }\n                        }\n                    }\n                }\n\n                // 4. Copy Font if enabled\n                if (config.isFontEnabled) {\n                    val fontName = FontConfig.customFontFilename\n                    if (fontName != null) {\n                        val fontFile = File(context.filesDir, fontName)\n                        if (fontFile.exists()) {\n                            fontFile.copyTo(File(cacheDir, FONT_FILENAME))\n                        }\n                    }\n                }\n\n                // 6. Copy Music if enabled\n                if (config.isMusicEnabled) {\n                    val musicName = config.musicFilename\n                    if (musicName != null) {\n                        val musicFile = MusicConfig.getMusicFile(context)\n                        if (musicFile != null && musicFile.exists()) {\n                            musicFile.copyTo(File(cacheDir, musicName))\n                        }\n                    }\n                }\n\n                // Copy Sound Effect if enabled\n                if (config.isSoundEffectEnabled) {\n                    val soundEffectName = config.soundEffectFilename\n                    if (soundEffectName != null) {\n                        val soundEffectFile = SoundEffectConfig.getSoundEffectFile(context)\n                        if (soundEffectFile != null && soundEffectFile.exists()) {\n                            soundEffectFile.copyTo(File(cacheDir, soundEffectName))\n                        }\n                    }\n                }\n\n                // 7. Copy Video Background if enabled\n                if (config.isVideoBackgroundEnabled) {\n                    val extensions = listOf(\".mp4\", \".webm\", \".mkv\")\n                    for (ext in extensions) {\n                        val videoFile = File(context.filesDir, \"video_background$ext\")\n                        if (videoFile.exists()) {\n                            videoFile.copyTo(File(cacheDir, \"video_background$ext\"))\n                            break\n                        }\n                    }\n                }\n\n                // 8. Copy Title Image if enabled\n                if (config.isAdvancedTitleStyleEnabled) {\n                    val extensions = listOf(\".jpg\", \".png\", \".gif\", \".webp\")\n                    for (ext in extensions) {\n                        val titleImageFile = File(context.filesDir, \"title_image$ext\")\n                        if (titleImageFile.exists()) {\n                            titleImageFile.copyTo(File(cacheDir, \"title_image$ext\"))\n                            break\n                        }\n                    }\n                }\n\n                // 9. Encrypt and Zip to Uri\n                context.contentResolver.openOutputStream(uri)?.use { os ->\n                    // Init Cipher\n                    val cipher = Cipher.getInstance(\"AES/CBC/PKCS5Padding\")\n                    val iv = ByteArray(16).also { SecureRandom().nextBytes(it) }\n                    cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(), IvParameterSpec(iv))\n\n                    // Write IV first\n                    os.write(iv)\n\n                    CipherOutputStream(os, cipher).use { cos ->\n                        ZipOutputStream(BufferedOutputStream(cos)).use { zos ->\n                            cacheDir.listFiles()?.forEach { file ->\n                                val entry = ZipEntry(file.name)\n                                zos.putNextEntry(entry)\n                                FileInputStream(file).use { fis ->\n                                    fis.copyTo(zos)\n                                }\n                                zos.closeEntry()\n                            }\n                        }\n                    }\n                }\n                true\n            } catch (e: Exception) {\n                Log.e(TAG, \"Export failed\", e)\n                false\n            } finally {\n                cacheDir.deleteRecursively()\n            }\n        }\n    }\n\n    suspend fun readThemeMetadata(context: Context, uri: Uri): ThemeMetadata? {\n        return withContext(Dispatchers.IO) {\n            try {\n                SafeUriResolver.openInputStream(context, uri)?.use { `is` ->\n                    // Read IV\n                    val iv = ByteArray(16)\n                    if (`is`.read(iv) != 16) return@withContext null\n\n                    val cipher = Cipher.getInstance(\"AES/CBC/PKCS5Padding\")\n                    cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), IvParameterSpec(iv))\n\n                    CipherInputStream(`is`, cipher).use { cis ->\n                        ZipInputStream(BufferedInputStream(cis)).use { zis ->\n                            var entry: ZipEntry?\n                            while (zis.nextEntry.also { entry = it } != null) {\n                                if (entry!!.name == THEME_CONFIG_FILENAME) {\n                                    // Read the JSON content\n                                    val jsonStr = zis.bufferedReader().use { it.readText() }\n                                    val json = JSONObject(jsonStr)\n                                    return@withContext ThemeMetadata(\n                                        name = json.optString(\"meta_name\", \"\"),\n                                        type = json.optString(\"meta_type\", \"phone\"),\n                                        version = json.optString(\"meta_version\", \"\"),\n                                        author = json.optString(\"meta_author\", \"\"),\n                                        description = json.optString(\"meta_description\", \"\")\n                                    )\n                                }\n                            }\n                        }\n                    }\n                }\n            } catch (e: Exception) {\n                Log.e(TAG, \"Failed to read theme metadata\", e)\n            }\n            null\n        }\n    }\n\n    suspend fun importTheme(context: Context, uri: Uri): Boolean {\n        val key = uri.toString()\n        val (deferred, shouldStart) = importMutex.withLock {\n            val existing = activeImportDeferred\n            if (activeImportKey == key && existing != null && !existing.isCompleted) {\n                return@withLock existing to false\n            }\n            val newDeferred = CompletableDeferred<Boolean>()\n            activeImportKey = key\n            activeImportDeferred = newDeferred\n            newDeferred to true\n        }\n\n        if (!shouldStart) {\n            return deferred.await()\n        }\n\n        val result = try {\n            withContext(Dispatchers.IO) {\n            val cacheDir = File(context.cacheDir, \"theme_import\")\n            if (cacheDir.exists()) cacheDir.deleteRecursively()\n            cacheDir.mkdirs()\n\n            try {\n                // 1. Decrypt and Unzip\n                SafeUriResolver.openInputStream(context, uri)?.use { `is` ->\n                    // Read IV\n                    val iv = ByteArray(16)\n                    if (`is`.read(iv) != 16) throw Exception(\"Invalid theme file\")\n\n                    val cipher = Cipher.getInstance(\"AES/CBC/PKCS5Padding\")\n                    cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), IvParameterSpec(iv))\n\n                    CipherInputStream(`is`, cipher).use { cis ->\n                        ZipInputStream(BufferedInputStream(cis)).use { zis ->\n                            var entry: ZipEntry?\n                            while (zis.nextEntry.also { entry = it } != null) {\n                                val file = File(cacheDir, entry!!.name)\n                                // Prevent path traversal\n                                if (!file.canonicalPath.startsWith(cacheDir.canonicalPath)) {\n                                    continue\n                                }\n                                FileOutputStream(file).use { fos ->\n                                    zis.copyTo(fos)\n                                }\n                            }\n                        }\n                    }\n                }\n\n                // 2. Read Config\n                val configFile = File(cacheDir, THEME_CONFIG_FILENAME)\n                if (!configFile.exists()) return@withContext false\n                \n                val json = JSONObject(configFile.readText())\n                val isBackgroundEnabled = json.optBoolean(\"isBackgroundEnabled\", false)\n                val backgroundOpacity = json.optDouble(\"backgroundOpacity\", 0.5).toFloat()\n                val backgroundBlur = json.optDouble(\"backgroundBlur\", 0.0).toFloat()\n                val backgroundDim = json.optDouble(\"backgroundDim\", 0.2).toFloat()\n                val isDualBackgroundDimEnabled = json.optBoolean(\"isDualBackgroundDimEnabled\", false)\n                val backgroundDayDim = json.optDouble(\"backgroundDayDim\", backgroundDim.toDouble()).toFloat()\n                val backgroundNightDim = json.optDouble(\"backgroundNightDim\", backgroundDim.toDouble()).toFloat()\n                val isFontEnabled = json.optBoolean(\"isFontEnabled\", false)\n                val customColor = json.optString(\"customColor\", \"indigo\")\n                val homeLayoutStyle = json.optString(\"homeLayoutStyle\", \"sign\")\n                val statsTopLayout = json.optString(\"statsTopLayout\", \"list\")\n                val nightModeEnabled = json.optBoolean(\"nightModeEnabled\", true)\n                val nightModeFollowSys = json.optBoolean(\"nightModeFollowSys\", true)\n                val useSystemDynamicColor = json.optBoolean(\"useSystemDynamicColor\", true)\n                val appLanguage = json.optString(\"appLanguage\", \"\")\n                \n                // Grid Working Card Background\n                val isGridWorkingCardBackgroundEnabled = json.optBoolean(\"isGridWorkingCardBackgroundEnabled\", false)\n                val gridWorkingCardBackgroundOpacity = json.optDouble(\"gridWorkingCardBackgroundOpacity\", 1.0).toFloat()\n                val isGridDualOpacityEnabled = json.optBoolean(\"isGridDualOpacityEnabled\", false)\n                val gridWorkingCardBackgroundDayOpacity = json.optDouble(\"gridWorkingCardBackgroundDayOpacity\", gridWorkingCardBackgroundOpacity.toDouble()).toFloat()\n                val gridWorkingCardBackgroundNightOpacity = json.optDouble(\"gridWorkingCardBackgroundNightOpacity\", gridWorkingCardBackgroundOpacity.toDouble()).toFloat()\n                val gridWorkingCardBackgroundDim = json.optDouble(\"gridWorkingCardBackgroundDim\", 0.3).toFloat()\n                val isGridWorkingCardCheckHidden = json.optBoolean(\"isGridWorkingCardCheckHidden\", false)\n                val isGridWorkingCardTextHidden = json.optBoolean(\"isGridWorkingCardTextHidden\", false)\n                val isGridWorkingCardModeHidden = json.optBoolean(\"isGridWorkingCardModeHidden\", false)\n                val isListWorkingCardModeHidden = json.optBoolean(\"isListWorkingCardModeHidden\", false)\n\n                // Video Background\n                val isVideoBackgroundEnabled = json.optBoolean(\"isVideoBackgroundEnabled\", false)\n                val videoVolume = json.optDouble(\"videoVolume\", 0.0).toFloat()\n\n                // Advanced Title Style\n                val isAdvancedTitleStyleEnabled = json.optBoolean(\"isAdvancedTitleStyleEnabled\", false)\n                val titleImageDayOpacity = json.optDouble(\"titleImageDayOpacity\", 1.0).toFloat()\n                val titleImageNightOpacity = json.optDouble(\"titleImageNightOpacity\", 1.0).toFloat()\n                val titleImageDim = json.optDouble(\"titleImageDim\", 0.0).toFloat()\n                val titleImageOffsetX = json.optDouble(\"titleImageOffsetX\", 0.0).toFloat()\n\n                // Multi-Background Mode\n                val isMultiBackgroundEnabled = json.optBoolean(\"isMultiBackgroundEnabled\", false)\n\n                // Music Config\n                val isMusicEnabled = json.optBoolean(\"isMusicEnabled\", false)\n                val musicVolume = json.optDouble(\"musicVolume\", 1.0).toFloat()\n                val isAutoPlayEnabled = json.optBoolean(\"isAutoPlayEnabled\", false)\n                val isLoopingEnabled = json.optBoolean(\"isLoopingEnabled\", false)\n                val musicFilename = json.optString(\"musicFilename\", \"\")\n\n                // Sound Effect Config\n                val isSoundEffectEnabled = json.optBoolean(\"isSoundEffectEnabled\", false)\n                val soundEffectFilename = json.optString(\"soundEffectFilename\", \"\")\n                val soundEffectScope = json.optString(\"soundEffectScope\", SoundEffectConfig.SCOPE_GLOBAL)\n\n                // 3. Apply Background\n                BackgroundConfig.setCustomBackgroundOpacityValue(backgroundOpacity)\n                BackgroundConfig.setCustomBackgroundBlurValue(backgroundBlur)\n                BackgroundConfig.setCustomBackgroundDimValue(backgroundDim)\n                BackgroundConfig.setDualBackgroundDimEnabledState(isDualBackgroundDimEnabled)\n                BackgroundConfig.setCustomBackgroundDayDimValue(backgroundDayDim)\n                BackgroundConfig.setCustomBackgroundNightDimValue(backgroundNightDim)\n                BackgroundConfig.setCustomBackgroundEnabledState(isBackgroundEnabled)\n\n                if (isBackgroundEnabled) {\n                    val extensions = listOf(\".jpg\", \".png\", \".gif\", \".webp\")\n                    var bgFound = false\n                    for (ext in extensions) {\n                        val bgFile = File(cacheDir, \"background$ext\")\n                        if (bgFile.exists()) {\n                            // Clear old background files first\n                            for (oldExt in extensions) {\n                                val oldFile = File(context.filesDir, \"background$oldExt\")\n                                if (oldFile.exists()) oldFile.delete()\n                            }\n                            \n                            val destFile = File(context.filesDir, \"background$ext\")\n                            bgFile.copyTo(destFile, overwrite = true)\n                            // Update URI to point to local file with timestamp to force refresh\n                             val fileUri = Uri.fromFile(destFile).buildUpon()\n                                .appendQueryParameter(\"t\", System.currentTimeMillis().toString())\n                                .build()\n                             BackgroundConfig.updateCustomBackgroundUri(fileUri.toString())\n                             bgFound = true\n                             break\n                        }\n                    }\n                    if (!bgFound) {\n                        // Fallback logic if needed, or disable background\n                    }\n                } else {\n                     // Maybe clear if we want to enforce theme state exactly\n                     // But user might want to keep files.\n                     // The requirement implies importing the theme as is.\n                }\n                \n                // Apply Grid Working Card Background\n                BackgroundConfig.setGridWorkingCardBackgroundOpacityValue(gridWorkingCardBackgroundOpacity)\n                BackgroundConfig.setGridDualOpacityEnabledState(isGridDualOpacityEnabled)\n                BackgroundConfig.setGridWorkingCardBackgroundDayOpacityValue(gridWorkingCardBackgroundDayOpacity)\n                BackgroundConfig.setGridWorkingCardBackgroundNightOpacityValue(gridWorkingCardBackgroundNightOpacity)\n                BackgroundConfig.setGridWorkingCardBackgroundDimValue(gridWorkingCardBackgroundDim)\n                BackgroundConfig.setGridWorkingCardBackgroundEnabledState(isGridWorkingCardBackgroundEnabled)\n                BackgroundConfig.setGridWorkingCardCheckHiddenState(isGridWorkingCardCheckHidden)\n                BackgroundConfig.setGridWorkingCardTextHiddenState(isGridWorkingCardTextHidden)\n                BackgroundConfig.setGridWorkingCardModeHiddenState(isGridWorkingCardModeHidden)\n                BackgroundConfig.setListWorkingCardModeHiddenState(isListWorkingCardModeHidden)\n                \n                if (isGridWorkingCardBackgroundEnabled) {\n                    val extensions = listOf(\".jpg\", \".png\", \".gif\", \".webp\")\n                    for (ext in extensions) {\n                        val bgFile = File(cacheDir, \"grid_working_card_background$ext\")\n                        if (bgFile.exists()) {\n                            // Clear old files\n                            for (oldExt in extensions) {\n                                val oldFile = File(context.filesDir, \"grid_working_card_background$oldExt\")\n                                if (oldFile.exists()) oldFile.delete()\n                            }\n                            \n                            val destFile = File(context.filesDir, \"grid_working_card_background$ext\")\n                            bgFile.copyTo(destFile, overwrite = true)\n                            // Update URI\n                             val fileUri = Uri.fromFile(destFile).buildUpon()\n                                .appendQueryParameter(\"t\", System.currentTimeMillis().toString())\n                                .build()\n                             BackgroundConfig.updateGridWorkingCardBackgroundUri(fileUri.toString())\n                             break\n                        }\n                    }\n                }\n                \n                // Apply Multi-Background Mode\n                BackgroundConfig.setMultiBackgroundEnabledState(isMultiBackgroundEnabled)\n                \n                if (isMultiBackgroundEnabled) {\n                    val multiBackgrounds = listOf(\n                        \"background_home\" to { uri: String? -> BackgroundConfig.updateHomeBackgroundUri(uri) },\n                        \"background_kernel\" to { uri: String? -> BackgroundConfig.updateKernelBackgroundUri(uri) },\n                        \"background_superuser\" to { uri: String? -> BackgroundConfig.updateSuperuserBackgroundUri(uri) },\n                        \"background_system_module\" to { uri: String? -> BackgroundConfig.updateSystemModuleBackgroundUri(uri) },\n                        \"background_settings\" to { uri: String? -> BackgroundConfig.updateSettingsBackgroundUri(uri) }\n                    )\n                    val extensions = listOf(\".jpg\", \".png\", \".gif\", \".webp\")\n\n                    for ((bgName, updateAction) in multiBackgrounds) {\n                        var bgFound = false\n                        for (ext in extensions) {\n                            val bgFile = File(cacheDir, \"$bgName$ext\")\n                            if (bgFile.exists()) {\n                                // Clear old files\n                                for (oldExt in extensions) {\n                                    val oldFile = File(context.filesDir, \"$bgName$oldExt\")\n                                    if (oldFile.exists()) oldFile.delete()\n                                }\n                                \n                                val destFile = File(context.filesDir, \"$bgName$ext\")\n                                bgFile.copyTo(destFile, overwrite = true)\n                                \n                                val fileUri = Uri.fromFile(destFile).buildUpon()\n                                    .appendQueryParameter(\"t\", System.currentTimeMillis().toString())\n                                    .build()\n                                updateAction(fileUri.toString())\n                                bgFound = true\n                                break\n                            }\n                        }\n                        \n                        if (!bgFound) {\n                             // Clear existing if not found in theme\n                             for (oldExt in extensions) {\n                                val oldFile = File(context.filesDir, \"$bgName$oldExt\")\n                                if (oldFile.exists()) oldFile.delete()\n                            }\n                            updateAction(null)\n                        }\n                    }\n                } else {\n                    // If multi-background is disabled in the theme, disable it here.\n                    // We might also want to clear the files or at least reset the URIs in config?\n                    // BackgroundConfig.setMultiBackgroundEnabledState(false) is already called above.\n                    // We don't necessarily delete the files, just like other background settings don't strictly delete files when disabled.\n                }\n\n                // Apply Video Background\n                BackgroundConfig.setVideoBackgroundEnabledState(isVideoBackgroundEnabled)\n                BackgroundConfig.setVideoVolumeValue(videoVolume)\n\n                if (isVideoBackgroundEnabled) {\n                    val extensions = listOf(\".mp4\", \".webm\", \".mkv\")\n                    for (ext in extensions) {\n                        val videoFile = File(cacheDir, \"video_background$ext\")\n                        if (videoFile.exists()) {\n                            // Clear old files\n                            BackgroundManager.clearVideoBackground(context)\n                            \n                            val destFile = File(context.filesDir, \"video_background$ext\")\n                            videoFile.copyTo(destFile, overwrite = true)\n                            \n                            val fileUri = Uri.fromFile(destFile).toString()\n                            BackgroundConfig.updateVideoBackgroundUri(fileUri)\n                            // Restore enabled state as clearVideoBackground resets it\n                            BackgroundConfig.setVideoBackgroundEnabledState(true)\n                            break\n                        }\n                    }\n                }\n\n                // Apply Advanced Title Style\n                BackgroundConfig.setAdvancedTitleStyleEnabledState(isAdvancedTitleStyleEnabled)\n                BackgroundConfig.setTitleImageDayOpacityValue(titleImageDayOpacity)\n                BackgroundConfig.setTitleImageNightOpacityValue(titleImageNightOpacity)\n                BackgroundConfig.setTitleImageDimValue(titleImageDim)\n                BackgroundConfig.setTitleImageOffsetXValue(titleImageOffsetX)\n\n                if (isAdvancedTitleStyleEnabled) {\n                    val extensions = listOf(\".jpg\", \".png\", \".gif\", \".webp\")\n                    for (ext in extensions) {\n                        val titleImageFile = File(cacheDir, \"title_image$ext\")\n                        if (titleImageFile.exists()) {\n                            // Clear old files\n                            for (oldExt in extensions) {\n                                val oldFile = File(context.filesDir, \"title_image$oldExt\")\n                                if (oldFile.exists()) oldFile.delete()\n                            }\n                            \n                            val destFile = File(context.filesDir, \"title_image$ext\")\n                            titleImageFile.copyTo(destFile, overwrite = true)\n                            \n                            val fileUri = Uri.fromFile(destFile).buildUpon()\n                                .appendQueryParameter(\"t\", System.currentTimeMillis().toString())\n                                .build()\n                            BackgroundConfig.updateTitleImageUri(fileUri.toString())\n                            break\n                        }\n                    }\n                } else {\n                    // Clear title image if disabled in theme\n                    val extensions = listOf(\".jpg\", \".png\", \".gif\", \".webp\")\n                    for (ext in extensions) {\n                        val oldFile = File(context.filesDir, \"title_image$ext\")\n                        if (oldFile.exists()) oldFile.delete()\n                    }\n                    BackgroundConfig.updateTitleImageUri(null)\n                }\n\n                BackgroundConfig.save(context)\n\n                // Apply Music Config\n                // First clear existing music to remove old file\n                MusicConfig.clearMusic(context)\n                \n                // Set new configuration\n                MusicConfig.setMusicEnabledState(isMusicEnabled)\n                MusicConfig.setVolumeValue(musicVolume)\n                MusicConfig.setAutoPlayEnabledState(isAutoPlayEnabled)\n                MusicConfig.setLoopingEnabledState(isLoopingEnabled)\n\n                if (isMusicEnabled && musicFilename.isNotEmpty() && musicFilename != \"null\") {\n                    val musicFile = File(cacheDir, musicFilename)\n                    if (musicFile.exists()) {\n                         val destFile = File(MusicConfig.getMusicDir(context), musicFilename)\n                         musicFile.copyTo(destFile, overwrite = true)\n                         MusicConfig.setMusicFilenameValue(musicFilename)\n                    }\n                }\n                MusicConfig.save(context)\n\n                if (isMusicEnabled && isAutoPlayEnabled) {\n                    withContext(Dispatchers.Main) {\n                        MusicManager.reload()\n                    }\n                }\n\n                // Apply Sound Effect Config\n                SoundEffectConfig.clearSoundEffect(context)\n                \n                SoundEffectConfig.setEnabledState(isSoundEffectEnabled)\n                SoundEffectConfig.setScopeValue(soundEffectScope)\n                \n                if (isSoundEffectEnabled && soundEffectFilename.isNotEmpty() && soundEffectFilename != \"null\") {\n                    val soundEffectFile = File(cacheDir, soundEffectFilename)\n                    if (soundEffectFile.exists()) {\n                        val destFile = File(SoundEffectConfig.getSoundEffectDir(context), soundEffectFilename)\n                        soundEffectFile.copyTo(destFile, overwrite = true)\n                        SoundEffectConfig.setFilenameValue(soundEffectFilename)\n                    }\n                }\n                SoundEffectConfig.save(context)\n\n                // 4. Apply Font\n                if (isFontEnabled) {\n                     val fontFile = File(cacheDir, FONT_FILENAME)\n                     if (fontFile.exists()) {\n                         FontConfig.applyCustomFont(context, fontFile)\n                     }\n                } else {\n                    FontConfig.clearFont(context)\n                }\n                \n                // 5. Apply Color and Home Layout Style\n                withContext(Dispatchers.Main) {\n                    if (appLanguage.isNotEmpty()) {\n                        AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(appLanguage))\n                    } else {\n                        // If empty, it might mean default/system or old theme file. \n                        // We can choose to leave it as is or reset to empty (system default).\n                        // Let's assume we keep current user preference if theme doesn't specify.\n                        // Or if explicit empty string was saved (system default), we apply it.\n                        // But json.optString returns \"\" if key missing.\n                        if (json.has(\"appLanguage\")) {\n                             AppCompatDelegate.setApplicationLocales(LocaleListCompat.getEmptyLocaleList())\n                        }\n                    }\n                }\n\n                APApplication.sharedPreferences.edit()\n                    .putString(\"custom_color\", customColor)\n                    .putString(\"home_layout_style\", homeLayoutStyle)\n                    .putString(\"stats_top_layout\", statsTopLayout)\n                    .putBoolean(\"night_mode_enabled\", nightModeEnabled)\n                    .putBoolean(\"night_mode_follow_sys\", nightModeFollowSys)\n                    .putBoolean(\"use_system_color_theme\", useSystemDynamicColor)\n                    .apply()\n                \n                // 6. Refresh Theme\n                refreshTheme.postValue(true)\n                \n                true\n            } catch (e: Exception) {\n                Log.e(TAG, \"Import failed\", e)\n                false\n            } finally {\n                cacheDir.deleteRecursively()\n            }\n        }\n        } catch (e: Exception) {\n            false\n        }\n\n        deferred.complete(result)\n        importMutex.withLock {\n            if (activeImportDeferred == deferred) {\n                activeImportDeferred = null\n                activeImportKey = null\n            }\n        }\n        return result\n    }\n\n    val refreshTheme = MutableLiveData<Boolean>()\n\n    suspend fun resetTheme(context: Context): Boolean {\n        return withContext(Dispatchers.IO) {\n            try {\n                val prefs = APApplication.sharedPreferences\n\n           \n                prefs.edit()\n                    .putBoolean(\"night_mode_enabled\", true)\n                    .putBoolean(\"night_mode_follow_sys\", true)\n                    .putBoolean(\"use_system_color_theme\", true)\n                    .putString(\"custom_color\", \"indigo\")\n                    .putString(\"home_layout_style\", \"circle\")\n                    .putString(\"stats_top_layout\", \"list\")\n                    .remove(\"appLanguage\")\n                    .apply()\n\n           \n                BackgroundConfig.reset()\n                BackgroundConfig.save(context)\n\n       \n                val filesDir = context.filesDir\n                val backgroundFiles = listOf(\n                    \"background.jpg\",\n                    \"background.png\",\n                    \"background.gif\",\n                    \"background.webp\",\n                    \"video_background.mp4\",\n                    \"video_background.webm\",\n                    \"video_background.mkv\",\n                    \"grid_working_card_background.jpg\",\n                    \"grid_working_card_background.png\",\n                    \"grid_working_card_background.gif\",\n                    \"grid_working_card_background.webp\",\n                    \"background_home.jpg\",\n                    \"background_home.png\",\n                    \"background_home.gif\",\n                    \"background_home.webp\",\n                    \"background_kernel.jpg\",\n                    \"background_kernel.png\",\n                    \"background_kernel.gif\",\n                    \"background_kernel.webp\",\n                    \"background_superuser.jpg\",\n                    \"background_superuser.png\",\n                    \"background_superuser.gif\",\n                    \"background_superuser.webp\",\n                    \"background_system_module.jpg\",\n                    \"background_system_module.png\",\n                    \"background_system_module.gif\",\n                    \"background_system_module.webp\",\n                    \"background_settings.jpg\",\n                    \"background_settings.png\",\n                    \"background_settings.gif\",\n                    \"background_settings.webp\",\n                    \"title_image.jpg\",\n                    \"title_image.png\",\n                    \"title_image.gif\",\n                    \"title_image.webp\"\n                )\n\n                for (filename in backgroundFiles) {\n                    val file = File(filesDir, filename)\n                    if (file.exists()) {\n                        file.delete()\n                    }\n                }\n\n      \n                FontConfig.clearFont(context)\n\n      \n                MusicConfig.clearMusic(context)\n\n                SoundEffectConfig.clearSoundEffect(context)\n                SoundEffectConfig.clearStartupSound(context)\n                SoundEffectConfig.save(context)\n\n        \n                withContext(Dispatchers.Main) {\n                    AppCompatDelegate.setApplicationLocales(LocaleListCompat.getEmptyLocaleList())\n                }\n\n              \n                refreshTheme.postValue(true)\n\n                Log.i(TAG, \"Theme reset to default successfully\")\n                true\n            } catch (e: Exception) {\n                Log.e(TAG, \"Failed to reset theme\", e)\n                false\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/Type.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.ui.text.TextStyle\nimport androidx.compose.ui.text.font.FontFamily\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.sp\n\nimport androidx.compose.material3.Typography\n\n// Set of Material typography styles to start with\nval Typography = Typography(\n    bodyLarge = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Normal,\n        fontSize = 16.sp,\n        lineHeight = 24.sp,\n        letterSpacing = 0.5.sp\n    )\n)\n\nfun getTypography(fontFamily: FontFamily): Typography {\n    return Typography(\n        displayLarge = Typography.displayLarge.copy(fontFamily = fontFamily),\n        displayMedium = Typography.displayMedium.copy(fontFamily = fontFamily),\n        displaySmall = Typography.displaySmall.copy(fontFamily = fontFamily),\n        headlineLarge = Typography.headlineLarge.copy(fontFamily = fontFamily),\n        headlineMedium = Typography.headlineMedium.copy(fontFamily = fontFamily),\n        headlineSmall = Typography.headlineSmall.copy(fontFamily = fontFamily),\n        titleLarge = Typography.titleLarge.copy(fontFamily = fontFamily),\n        titleMedium = Typography.titleMedium.copy(fontFamily = fontFamily),\n        titleSmall = Typography.titleSmall.copy(fontFamily = fontFamily),\n        bodyLarge = Typography.bodyLarge.copy(fontFamily = fontFamily),\n        bodyMedium = Typography.bodyMedium.copy(fontFamily = fontFamily),\n        bodySmall = Typography.bodySmall.copy(fontFamily = fontFamily),\n        labelLarge = Typography.labelLarge.copy(fontFamily = fontFamily),\n        labelMedium = Typography.labelMedium.copy(fontFamily = fontFamily),\n        labelSmall = Typography.labelSmall.copy(fontFamily = fontFamily)\n    )\n}"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/VibrationConfig.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport android.content.Context\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\n\nobject VibrationConfig {\n    private const val PREFS_NAME = \"vibration_settings\"\n    private const val KEY_ENABLED = \"vibration_enabled\"\n    private const val KEY_INTENSITY = \"vibration_intensity\"\n    private const val KEY_SCOPE = \"vibration_scope\"\n\n    const val SCOPE_GLOBAL = \"global\"\n    const val SCOPE_BOTTOM_BAR = \"bottom_bar\"\n\n    var isVibrationEnabled: Boolean by mutableStateOf(false)\n        private set\n\n    var vibrationIntensity: Float by mutableStateOf(0.5f)\n        private set\n\n    var scope: String by mutableStateOf(SCOPE_GLOBAL)\n        private set\n\n    fun setEnabledState(enabled: Boolean) {\n        isVibrationEnabled = enabled\n    }\n\n    fun setIntensityValue(value: Float) {\n        vibrationIntensity = value.coerceIn(0f, 1f)\n    }\n\n    fun setScopeValue(value: String) {\n        scope = value\n    }\n\n    fun load(context: Context) {\n        val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)\n        isVibrationEnabled = prefs.getBoolean(KEY_ENABLED, false)\n        vibrationIntensity = prefs.getFloat(KEY_INTENSITY, 0.5f)\n        scope = prefs.getString(KEY_SCOPE, SCOPE_GLOBAL) ?: SCOPE_GLOBAL\n    }\n\n    fun save(context: Context) {\n        val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)\n        prefs.edit()\n            .putBoolean(KEY_ENABLED, isVibrationEnabled)\n            .putFloat(KEY_INTENSITY, vibrationIntensity)\n            .putString(KEY_SCOPE, scope)\n            .apply()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/YellowTheme.kt",
    "content": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lightColorScheme\nimport androidx.compose.ui.graphics.Color\n\nprivate val md_theme_light_primary = Color(0xFF695F00)\nprivate val md_theme_light_onPrimary = Color(0xFFFFFFFF)\nprivate val md_theme_light_primaryContainer = Color(0xFFF9E534)\nprivate val md_theme_light_onPrimaryContainer = Color(0xFF201C00)\nprivate val md_theme_light_secondary = Color(0xFF645F41)\nprivate val md_theme_light_onSecondary = Color(0xFFFFFFFF)\nprivate val md_theme_light_secondaryContainer = Color(0xFFEBE3BD)\nprivate val md_theme_light_onSecondaryContainer = Color(0xFF1F1C05)\nprivate val md_theme_light_tertiary = Color(0xFF406652)\nprivate val md_theme_light_onTertiary = Color(0xFFFFFFFF)\nprivate val md_theme_light_tertiaryContainer = Color(0xFFC2ECD3)\nprivate val md_theme_light_onTertiaryContainer = Color(0xFF002113)\nprivate val md_theme_light_error = Color(0xFFBA1A1A)\nprivate val md_theme_light_errorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_light_onError = Color(0xFFFFFFFF)\nprivate val md_theme_light_onErrorContainer = Color(0xFF410002)\nprivate val md_theme_light_background = Color(0xFFFFFBFF)\nprivate val md_theme_light_onBackground = Color(0xFF1D1C16)\nprivate val md_theme_light_surface = Color(0xFFFFFBFF)\nprivate val md_theme_light_onSurface = Color(0xFF1D1C16)\nprivate val md_theme_light_surfaceVariant = Color(0xFFE8E2D0)\nprivate val md_theme_light_onSurfaceVariant = Color(0xFF4A473A)\nprivate val md_theme_light_outline = Color(0xFF7B7768)\nprivate val md_theme_light_inverseOnSurface = Color(0xFFF5F0E7)\nprivate val md_theme_light_inverseSurface = Color(0xFF32302A)\nprivate val md_theme_light_inversePrimary = Color(0xFFDBC90A)\nprivate val md_theme_light_shadow = Color(0xFF000000)\nprivate val md_theme_light_surfaceTint = Color(0xFF695F00)\nprivate val md_theme_light_outlineVariant = Color(0xFFCBC6B5)\nprivate val md_theme_light_scrim = Color(0xFF000000)\nprivate val md_theme_light_surfaceContainerLowest = Color(0xFFFDF9FC)\nprivate val md_theme_light_surfaceContainerLow = Color(0xFFFCF8FA)\nprivate val md_theme_light_surfaceContainer = Color(0xFFFAF6F6)\nprivate val md_theme_light_surfaceContainerHigh = Color(0xFFF8F4F1)\nprivate val md_theme_light_surfaceContainerHighest = Color(0xFFF6F1ED)\n\n\nprivate val md_theme_dark_primary = Color(0xFFDBC90A)\nprivate val md_theme_dark_onPrimary = Color(0xFF363100)\nprivate val md_theme_dark_primaryContainer = Color(0xFF4F4800)\nprivate val md_theme_dark_onPrimaryContainer = Color(0xFFF9E534)\nprivate val md_theme_dark_secondary = Color(0xFFCEC7A3)\nprivate val md_theme_dark_onSecondary = Color(0xFF343117)\nprivate val md_theme_dark_secondaryContainer = Color(0xFF4B472B)\nprivate val md_theme_dark_onSecondaryContainer = Color(0xFFEBE3BD)\nprivate val md_theme_dark_tertiary = Color(0xFFA7D0B7)\nprivate val md_theme_dark_onTertiary = Color(0xFF103726)\nprivate val md_theme_dark_tertiaryContainer = Color(0xFF294E3B)\nprivate val md_theme_dark_onTertiaryContainer = Color(0xFFC2ECD3)\nprivate val md_theme_dark_error = Color(0xFFFFB4AB)\nprivate val md_theme_dark_errorContainer = Color(0xFF93000A)\nprivate val md_theme_dark_onError = Color(0xFF690005)\nprivate val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)\nprivate val md_theme_dark_background = Color(0xFF1D1C16)\nprivate val md_theme_dark_onBackground = Color(0xFFE7E2D9)\nprivate val md_theme_dark_surface = Color(0xFF1D1C16)\nprivate val md_theme_dark_onSurface = Color(0xFFE7E2D9)\nprivate val md_theme_dark_surfaceVariant = Color(0xFF4A473A)\nprivate val md_theme_dark_onSurfaceVariant = Color(0xFFCBC6B5)\nprivate val md_theme_dark_outline = Color(0xFF959181)\nprivate val md_theme_dark_inverseOnSurface = Color(0xFF1D1C16)\nprivate val md_theme_dark_inverseSurface = Color(0xFFE7E2D9)\nprivate val md_theme_dark_inversePrimary = Color(0xFF695F00)\nprivate val md_theme_dark_shadow = Color(0xFF000000)\nprivate val md_theme_dark_surfaceTint = Color(0xFFDBC90A)\nprivate val md_theme_dark_outlineVariant = Color(0xFF4A473A)\nprivate val md_theme_dark_scrim = Color(0xFF000000)\nprivate val md_theme_dark_surfaceContainerLowest = Color(0xFF1F1E17)\nprivate val md_theme_dark_surfaceContainerLow = Color(0xFF212019)\nprivate val md_theme_dark_surfaceContainer = Color(0xFF25231C)\nprivate val md_theme_dark_surfaceContainerHigh = Color(0xFF292820)\nprivate val md_theme_dark_surfaceContainerHighest = Color(0xFF2E2C23)\n\n\nval LightYellowTheme = lightColorScheme(\n    primary = md_theme_light_primary,\n    onPrimary = md_theme_light_onPrimary,\n    primaryContainer = md_theme_light_primaryContainer,\n    onPrimaryContainer = md_theme_light_onPrimaryContainer,\n    secondary = md_theme_light_secondary,\n    onSecondary = md_theme_light_onSecondary,\n    secondaryContainer = md_theme_light_secondaryContainer,\n    onSecondaryContainer = md_theme_light_onSecondaryContainer,\n    tertiary = md_theme_light_tertiary,\n    onTertiary = md_theme_light_onTertiary,\n    tertiaryContainer = md_theme_light_tertiaryContainer,\n    onTertiaryContainer = md_theme_light_onTertiaryContainer,\n    error = md_theme_light_error,\n    errorContainer = md_theme_light_errorContainer,\n    onError = md_theme_light_onError,\n    onErrorContainer = md_theme_light_onErrorContainer,\n    background = md_theme_light_background,\n    onBackground = md_theme_light_onBackground,\n    surface = md_theme_light_surface,\n    onSurface = md_theme_light_onSurface,\n    surfaceVariant = md_theme_light_surfaceVariant,\n    onSurfaceVariant = md_theme_light_onSurfaceVariant,\n    outline = md_theme_light_outline,\n    inverseOnSurface = md_theme_light_inverseOnSurface,\n    inverseSurface = md_theme_light_inverseSurface,\n    inversePrimary = md_theme_light_inversePrimary,\n    surfaceTint = md_theme_light_surfaceTint,\n    outlineVariant = md_theme_light_outlineVariant,\n    surfaceContainerLowest = md_theme_light_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_light_surfaceContainerLow,\n    surfaceContainer = md_theme_light_surfaceContainer,\n    surfaceContainerHigh = md_theme_light_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_light_surfaceContainerHighest,\n    scrim = md_theme_light_scrim,\n)\n\nval DarkYellowTheme = darkColorScheme(\n    primary = md_theme_dark_primary,\n    onPrimary = md_theme_dark_onPrimary,\n    primaryContainer = md_theme_dark_primaryContainer,\n    onPrimaryContainer = md_theme_dark_onPrimaryContainer,\n    secondary = md_theme_dark_secondary,\n    onSecondary = md_theme_dark_onSecondary,\n    secondaryContainer = md_theme_dark_secondaryContainer,\n    onSecondaryContainer = md_theme_dark_onSecondaryContainer,\n    tertiary = md_theme_dark_tertiary,\n    onTertiary = md_theme_dark_onTertiary,\n    tertiaryContainer = md_theme_dark_tertiaryContainer,\n    onTertiaryContainer = md_theme_dark_onTertiaryContainer,\n    error = md_theme_dark_error,\n    errorContainer = md_theme_dark_errorContainer,\n    onError = md_theme_dark_onError,\n    onErrorContainer = md_theme_dark_onErrorContainer,\n    background = md_theme_dark_background,\n    onBackground = md_theme_dark_onBackground,\n    surface = md_theme_dark_surface,\n    onSurface = md_theme_dark_onSurface,\n    surfaceVariant = md_theme_dark_surfaceVariant,\n    onSurfaceVariant = md_theme_dark_onSurfaceVariant,\n    outline = md_theme_dark_outline,\n    inverseOnSurface = md_theme_dark_inverseOnSurface,\n    inverseSurface = md_theme_dark_inverseSurface,\n    inversePrimary = md_theme_dark_inversePrimary,\n    surfaceTint = md_theme_dark_surfaceTint,\n    outlineVariant = md_theme_dark_outlineVariant,\n    surfaceContainerLowest = md_theme_dark_surfaceContainerLowest,\n    surfaceContainerLow = md_theme_dark_surfaceContainerLow,\n    surfaceContainer = md_theme_dark_surfaceContainer,\n    surfaceContainerHigh = md_theme_dark_surfaceContainerHigh,\n    surfaceContainerHighest = md_theme_dark_surfaceContainerHighest,\n    scrim = md_theme_dark_scrim,\n)"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/viewmodel/APModuleViewModel.kt",
    "content": "package me.bmax.apatch.ui.viewmodel\n\nimport android.content.SharedPreferences\nimport android.os.SystemClock\nimport android.util.Log\nimport androidx.compose.runtime.derivedStateOf\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateMapOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.sync.Semaphore\nimport kotlinx.coroutines.sync.withPermit\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.apApp\nimport me.bmax.apatch.util.getRootShell\nimport me.bmax.apatch.util.listModules\nimport me.bmax.apatch.util.toggleModule\nimport org.json.JSONArray\nimport org.json.JSONObject\nimport java.text.Collator\nimport java.text.DecimalFormat\nimport java.util.Locale\nimport kotlin.math.log10\nimport kotlin.math.pow\n\nclass APModuleViewModel : ViewModel() {\n    companion object {\n        private const val TAG = \"ModuleViewModel\"\n        private var modules by mutableStateOf<List<ModuleInfo>>(emptyList())\n        private val zygiskModuleIds = listOf(\n            \"zygisksu\",\n            \"zygisknext\",\n            \"rezygisk\",\n            \"neozygisk\",\n            \"shirokozygisk\"\n        )\n    }\n\n    class ModuleInfo(\n        val id: String,\n        val name: String,\n        val author: String,\n        val version: String,\n        val versionCode: Int,\n        val description: String,\n        val enabled: Boolean,\n        val update: Boolean,\n        val remove: Boolean,\n        val updateJson: String,\n        val hasWebUi: Boolean,\n        val hasActionScript: Boolean,\n        val webuiIcon: String?,\n        val actionIcon: String?,\n        val isMetamodule: Boolean,\n        val isZygisk: Boolean,\n        val isLSPosed: Boolean,\n    )\n\n    data class ModuleUpdateInfo(\n        val version: String,\n        val versionCode: Int,\n        val zipUrl: String,\n        val changelog: String,\n    )\n\n    data class BannerInfo(\n        val bytes: ByteArray?,\n        val url: String?\n    )\n\n    var isRefreshing by mutableStateOf(false)\n        private set\n\n    private val prefs = APApplication.sharedPreferences\n    var sortOptimizationEnabled by mutableStateOf(prefs.getBoolean(\"module_sort_optimization\", true))\n    private val bannerCache = mutableStateMapOf<String, BannerInfo>()\n    private val moduleSizeCache = mutableStateMapOf<String, Long>()\n    private val updateSemaphore = Semaphore(3)\n    val bannerSemaphore = Semaphore(4)\n\n    private val preferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->\n        if (key == \"module_sort_optimization\") {\n            sortOptimizationEnabled = prefs.getBoolean(\"module_sort_optimization\", true)\n        }\n    }\n\n    init {\n        prefs.registerOnSharedPreferenceChangeListener(preferenceChangeListener)\n    }\n\n    override fun onCleared() {\n        super.onCleared()\n        prefs.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener)\n    }\n\n    private val collator = Collator.getInstance(Locale.getDefault())\n\n    val moduleList by derivedStateOf {\n        if (sortOptimizationEnabled) {\n            modules.sortedWith(\n                compareByDescending<ModuleInfo> { it.isMetamodule }\n                    .thenByDescending { it.isZygisk }\n                    .thenByDescending { it.isLSPosed }\n                    .thenByDescending { it.hasWebUi }\n                    .thenByDescending { it.hasActionScript }\n                    .thenByDescending { it.name.contains(\"vector\", ignoreCase = true) }\n                    .thenBy(collator) { it.id }\n            )\n        } else {\n            modules.sortedWith(compareBy(collator, ModuleInfo::id))\n        }\n    }\n\n    var isNeedRefresh by mutableStateOf(false)\n        private set\n\n    fun markNeedRefresh() {\n        isNeedRefresh = true\n    }\n\n    fun disableAllModules() {\n        viewModelScope.launch(Dispatchers.IO) {\n            isRefreshing = true\n            modules.forEach { \n                if (it.enabled) {\n                    toggleModule(it.id, false)\n                }\n            }\n            fetchModuleList()\n        }\n    }\n\n    fun getBannerInfo(id: String): BannerInfo? = bannerCache[id]\n\n    fun putBannerInfo(id: String, info: BannerInfo) {\n        bannerCache[id] = info\n    }\n\n    fun removeBannerInfo(id: String) {\n        bannerCache.remove(id)\n    }\n\n    fun clearBannerCache() {\n        bannerCache.clear()\n    }\n\n    private fun pruneBannerCache(validIds: Set<String>) {\n        val keysToRemove = bannerCache.keys.filter { it !in validIds }\n        keysToRemove.forEach { bannerCache.remove(it) }\n    }\n\n    private fun pruneModuleSizeCache(validIds: Set<String>) {\n        val keysToRemove = moduleSizeCache.keys.filter { it !in validIds }\n        keysToRemove.forEach { moduleSizeCache.remove(it) }\n    }\n\n    private val updateCache = mutableStateMapOf<String, Triple<String, String, String>>()\n\n    fun fetchModuleList() {\n        viewModelScope.launch(Dispatchers.IO) {\n            isRefreshing = true\n\n            try {\n                val start = SystemClock.elapsedRealtime()\n\n                val result = listModules()\n\n                Log.i(TAG, \"result: $result\")\n\n                val array = JSONArray(result)\n                modules = (0 until array.length())\n                    .asSequence()\n                    .map { array.getJSONObject(it) }\n                    .map { obj ->\n                        ModuleInfo(\n                            obj.getString(\"id\"),\n\n                            obj.optString(\"name\"),\n                            obj.optString(\"author\", \"Unknown\"),\n                            obj.optString(\"version\", \"Unknown\"),\n                            obj.optInt(\"versionCode\", 0),\n                            obj.optString(\"description\"),\n                            obj.getBoolean(\"enabled\"),\n                            obj.getBoolean(\"update\"),\n                            obj.getBoolean(\"remove\"),\n                            obj.optString(\"updateJson\"),\n                            obj.optBoolean(\"web\"),\n                            obj.optBoolean(\"action\"),\n                            obj.optString(\"webuiIcon\").trim().ifEmpty { null },\n                            obj.optString(\"actionIcon\").trim().ifEmpty { null },\n                            obj.optString(\"metamodule\").let { it == \"1\" || it.equals(\"true\", ignoreCase = true) },\n                            zygiskModuleIds.contains(obj.getString(\"id\")),\n                            obj.optString(\"name\").contains(\"LSPosed\", ignoreCase = true)\n                        )\n                    }.toList()\n                isNeedRefresh = false\n\n                // Non-critical: run in background without blocking module display\n                val ids = modules.map { it.id }\n                pruneBannerCache(ids.toSet())\n                pruneModuleSizeCache(ids.toSet())\n                viewModelScope.launch(Dispatchers.IO) { prefetchModuleSizes(ids) }\n                viewModelScope.launch(Dispatchers.IO) { batchCheckUpdates() }\n\n                Log.i(TAG, \"load cost: ${SystemClock.elapsedRealtime() - start}, modules: $modules\")\n            } catch (e: Exception) {\n                Log.e(TAG, \"fetchModuleList: \", e)\n            } finally {\n                isRefreshing = false\n            }\n        }\n    }\n\n    private fun sanitizeVersionString(version: String): String {\n        return version.replace(Regex(\"[^a-zA-Z0-9.\\\\-_]\"), \"_\")\n    }\n\n    suspend fun checkUpdate(m: ModuleInfo): Triple<String, String, String> {\n        updateCache[m.id]?.let { return it }\n        updateSemaphore.withPermit {\n            val result = checkUpdateInternal(m)\n            if (result.first.isNotEmpty()) {\n                updateCache[m.id] = result\n            }\n            return result\n        }\n    }\n\n    fun getCachedUpdate(moduleId: String): Triple<String, String, String> {\n        return updateCache[moduleId] ?: Triple(\"\", \"\", \"\")\n    }\n\n    fun batchCheckUpdates() {\n        viewModelScope.launch(Dispatchers.IO) {\n            val eligible = modules.filter {\n                it.updateJson.isNotEmpty() && !it.remove && !it.update && it.enabled\n            }\n            eligible.forEach { m ->\n                if (updateCache.containsKey(m.id)) return@forEach\n                try {\n                    val result = updateSemaphore.withPermit { checkUpdateInternal(m) }\n                    if (result.first.isNotEmpty()) {\n                        updateCache[m.id] = result\n                    }\n                } catch (_: Exception) {}\n            }\n        }\n    }\n\n    private fun checkUpdateInternal(m: ModuleInfo): Triple<String, String, String> {\n        val empty = Triple(\"\", \"\", \"\")\n        if (prefs.getBoolean(\"disable_module_update_check\", false)) {\n            return empty\n        }\n        if (m.updateJson.isEmpty() || m.remove || m.update || !m.enabled) {\n            return empty\n        }\n        // download updateJson\n        val result = kotlin.runCatching {\n            val url = m.updateJson\n            Log.i(TAG, \"checkUpdate url: $url\")\n            val response = apApp.okhttpClient\n                .newCall(\n                    okhttp3.Request.Builder()\n                        .url(url)\n                        .build()\n                ).execute()\n            Log.d(TAG, \"checkUpdate code: ${response.code}\")\n            if (response.isSuccessful) {\n                response.body?.string() ?: \"\"\n            } else {\n                \"\"\n            }\n        }.getOrDefault(\"\")\n        Log.i(TAG, \"checkUpdate result: $result\")\n\n        if (result.isEmpty()) {\n            return empty\n        }\n\n        val updateJson = kotlin.runCatching {\n            JSONObject(result)\n        }.getOrNull() ?: return empty\n\n        val version = sanitizeVersionString(updateJson.optString(\"version\", \"\"))\n        val versionCode = updateJson.optInt(\"versionCode\", 0)\n        val zipUrl = updateJson.optString(\"zipUrl\", \"\")\n        val changelog = updateJson.optString(\"changelog\", \"\")\n        if (versionCode <= m.versionCode || zipUrl.isEmpty()) {\n            return empty\n        }\n\n        return Triple(zipUrl, version, changelog)\n    }\n\n    private fun prefetchModuleSizes(moduleIds: List<String>) {\n        if (moduleIds.isEmpty()) return\n        val idsToFetch = moduleIds.filter { it !in moduleSizeCache }\n        if (idsToFetch.isEmpty()) return\n\n        runCatching {\n            val sb = StringBuilder(\"for d in\")\n            for (id in idsToFetch) {\n                sb.append(\" /data/adb/modules/\")\n                sb.append(id.replace(\" \", \"\\\\ \"))\n            }\n            sb.append(\"; do if [ -d \\\"\\$d\\\" ]; then /data/adb/ap/bin/busybox du -sb \\\"\\$d\\\" 2>/dev/null; fi; done\")\n            val result = getRootShell().newJob().add(sb.toString()).to(ArrayList(), null).exec()\n            if (result.isSuccess) {\n                for (line in result.out) {\n                    val parts = line.split(\"\\t\", limit = 2)\n                    if (parts.size == 2) {\n                        val size = parts[0].trim().toLongOrNull() ?: continue\n                        val path = parts[1].trim()\n                        val id = path.removePrefix(\"/data/adb/modules/\")\n                        if (id in idsToFetch) {\n                            moduleSizeCache[id] = size\n                        }\n                    }\n                }\n            }\n        }.onFailure { e ->\n            Log.e(TAG, \"Error prefetching module sizes\", e)\n        }\n    }\n\n    fun getModuleSize(moduleId: String): String {\n        moduleSizeCache[moduleId]?.let { cachedBytes ->\n            return formatFileSize(cachedBytes)\n        }\n        return \"0 KB\"\n    }\n}\n\n/**\n * 格式化文件大小显示\n *\n * This function is derived from SukiSU-Ultra project\n * Project: https://github.com/SukiSU-Ultra/SukiSU-Ultra\n * Original source: manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/ModuleViewModel.kt\n * Commit: 787c88ab2d070f3c6ec7ddff2f4ace1f3ebdd0c3\n * View at: https://github.com/SukiSU-Ultra/SukiSU-Ultra/blob/787c88ab2d070f3c6ec7ddff2f4ace1f3ebdd0c3/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/ModuleViewModel.kt\n *\n * SukiSU-Ultra is a Kernel-based Android Root Solution & KPM\n */\nprivate fun formatFileSize(bytes: Long): String {\n    if (bytes <= 0) return \"0 KB\"\n\n    val units = arrayOf(\"B\", \"KB\", \"MB\", \"GB\", \"TB\")\n    val digitGroups = (log10(bytes.toDouble()) / log10(1024.0)).toInt().coerceIn(0, units.lastIndex)\n\n    return DecimalFormat(\"#,##0.#\").format(\n        bytes / 1024.0.pow(digitGroups.toDouble())\n    ) + \" \" + units[digitGroups]\n}\n\n// End of file\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/viewmodel/ApiMarketplaceViewModel.kt",
    "content": "package me.bmax.apatch.ui.viewmodel\n\nimport android.util.Log\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport me.bmax.apatch.apApp\nimport me.bmax.apatch.Natives\nimport me.bmax.apatch.ui.model.ApiMarketplaceItem\nimport me.bmax.apatch.ui.screen.BannerApiService\nimport me.bmax.apatch.ui.theme.BackgroundConfig\nimport me.bmax.apatch.util.FolkApiClient\nimport okhttp3.Request\nimport org.json.JSONArray\nimport java.net.SocketTimeoutException\nimport java.net.UnknownHostException\nimport java.util.concurrent.TimeUnit\n\nclass ApiMarketplaceViewModel : ViewModel() {\n    companion object {\n        private const val TAG = \"ApiMarketplaceViewModel\"\n        private const val MARKETPLACE_URL = \"https://folk.mysqil.com/api/banners\"\n        private const val VERIFICATION_TIMEOUT_SECONDS = 10L\n        private val VALID_IMAGE_TYPES = listOf(\"image/jpeg\", \"image/png\", \"image/webp\", \"image/gif\", \"image/bmp\")\n    }\n\n    // Marketplace items state\n    var items by mutableStateOf<List<ApiMarketplaceItem>>(emptyList())\n        private set\n\n    var isLoading by mutableStateOf(false)\n        private set\n\n    var errorMessage by mutableStateOf<String?>(null)\n        private set\n\n    // Verification state for applying API\n    sealed class VerificationState {\n        data object Idle : VerificationState()\n        data object Loading : VerificationState()\n        data class Success(val url: String) : VerificationState()\n        data class Error(val message: String) : VerificationState()\n    }\n\n    var verificationState by mutableStateOf<VerificationState>(VerificationState.Idle)\n        private set\n\n    /**\n     * Fetch marketplace items from the API\n     */\n    fun fetchMarketplaceItems() {\n        viewModelScope.launch(Dispatchers.IO) {\n            isLoading = true\n            errorMessage = null\n            try {\n                val token = Natives.getApiToken(apApp)\n                val url = \"$MARKETPLACE_URL?token=$token\"\n\n                val result = FolkApiClient.fetchJson(url)\n                val jsonString = result.getOrNull()\n                if (jsonString != null) {\n                    val jsonArray = JSONArray(jsonString)\n                    val list = ArrayList<ApiMarketplaceItem>()\n\n                    for (i in 0 until jsonArray.length()) {\n                        val obj = jsonArray.getJSONObject(i)\n                        list.add(\n                            ApiMarketplaceItem(\n                                name = obj.optString(\"name\"),\n                                url = obj.optString(\"url\"),\n                                description = obj.optString(\"description\"),\n                                descriptionEn = obj.optString(\"description_en\")\n                            )\n                        )\n                    }\n                    items = list\n                    Log.d(TAG, \"Fetched ${list.size} marketplace items\")\n                } else {\n                    val exception = result.exceptionOrNull()\n                    Log.e(TAG, \"Failed to fetch marketplace: ${exception?.message}\")\n                    errorMessage = \"Error: ${exception?.message}\"\n                }\n            } catch (e: Exception) {\n                Log.e(TAG, \"Error fetching marketplace\", e)\n                errorMessage = \"Error: ${e.message}\"\n            } finally {\n                isLoading = false\n            }\n        }\n    }\n\n    /**\n     * Verify and apply an API source\n     * First verifies the API returns a valid image, then applies it\n     */\n    fun verifyAndApplyApi(url: String) {\n        viewModelScope.launch(Dispatchers.IO) {\n            verificationState = VerificationState.Loading\n            Log.d(TAG, \"Verifying API: $url\")\n\n            try {\n                // Create a client with shorter timeout for verification\n                val client = apApp.okhttpClient.newBuilder()\n                    .connectTimeout(VERIFICATION_TIMEOUT_SECONDS, TimeUnit.SECONDS)\n                    .readTimeout(VERIFICATION_TIMEOUT_SECONDS, TimeUnit.SECONDS)\n                    .followRedirects(true)\n                    .followSslRedirects(true)\n                    .build()\n\n                val request = Request.Builder()\n                    .url(url)\n                    .header(\"User-Agent\", \"FolkPatch-BannerAPI/1.0\")\n                    .build()\n\n                val response = client.newCall(request).execute()\n\n                when {\n                    !response.isSuccessful -> {\n                        val errorMsg = \"HTTP ${response.code}: ${response.message}\"\n                        Log.w(TAG, \"API verification failed: $errorMsg\")\n                        verificationState = VerificationState.Error(errorMsg)\n                    }\n\n                    isValidImageResponse(response) -> {\n                        Log.d(TAG, \"API verification successful: $url\")\n                        // Apply to FolkBannerAPI (higher priority than user-configured API)\n                        BackgroundConfig.setBannerApiSourceValue(url)\n                        // Enable API mode if not enabled\n                        BackgroundConfig.setBannerApiModeEnabledState(true)\n                        // Persist the changes to SharedPreferences\n                        BackgroundConfig.save(apApp.applicationContext)\n                        // No need to clear cache - each API source has its own cache namespace\n                        verificationState = VerificationState.Success(url)\n                    }\n\n                    else -> {\n                        val errorMsg = \"Invalid response: Not an image format\"\n                        Log.w(TAG, errorMsg)\n                        verificationState = VerificationState.Error(errorMsg)\n                    }\n                }\n            } catch (e: SocketTimeoutException) {\n                val errorMsg = \"Connection timeout: API did not respond in time\"\n                Log.e(TAG, errorMsg, e)\n                verificationState = VerificationState.Error(errorMsg)\n            } catch (e: UnknownHostException) {\n                val errorMsg = \"Network error: Unable to reach server\"\n                Log.e(TAG, errorMsg, e)\n                verificationState = VerificationState.Error(errorMsg)\n            } catch (e: Exception) {\n                val errorMsg = \"Verification failed: ${e.message}\"\n                Log.e(TAG, errorMsg, e)\n                verificationState = VerificationState.Error(errorMsg)\n            }\n        }\n    }\n\n    /**\n     * Check if the response is a valid image\n     */\n    private fun isValidImageResponse(response: okhttp3.Response): Boolean {\n        val contentType = response.body?.contentType()?.toString()?.lowercase() ?: \"\"\n        return VALID_IMAGE_TYPES.any { contentType.contains(it, ignoreCase = true) }\n    }\n\n    /**\n     * Reset verification state\n     */\n    fun resetVerificationState() {\n        verificationState = VerificationState.Idle\n    }\n\n    /**\n     * Retry fetching marketplace items\n     */\n    fun retry() {\n        fetchMarketplaceItems()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/viewmodel/DashboardViewModel.kt",
    "content": "package me.bmax.apatch.ui.viewmodel\n\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.net.TrafficStats\nimport android.os.BatteryManager\nimport android.os.StatFs\nimport android.util.Log\nimport androidx.lifecycle.LiveData\nimport androidx.lifecycle.Observer\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.topjohnwu.superuser.Shell\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.launchIn\nimport kotlinx.coroutines.flow.onEach\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.R\nimport me.bmax.apatch.apApp\nimport me.bmax.apatch.util.AppData\nimport me.bmax.apatch.util.HardwareMonitor\nimport me.bmax.apatch.util.Version\n\ndata class SystemMonitorState(\n    val cpuUsage: Int = 0,\n    val gpuUsage: Int = 0,\n    val cpuTemp: Float = 0f,\n    val cpuFrequencies: List<HardwareMonitor.CpuFreqInfo> = emptyList(),\n    val memoryInfo: HardwareMonitor.MemoryInfo? = null,\n    val storageUsedPercent: Float = 0f,\n    val batteryLevel: Int = 0,\n    val batteryTemp: Float = 0f,\n    val batteryCharging: Boolean = false,\n    val networkRxBytes: Long = 0L,\n    val networkTxBytes: Long = 0L,\n    val storagePartitions: List<HardwareMonitor.StoragePartitionInfo> = emptyList()\n)\n\ndata class TimeSeriesData(\n    val cpuHistory: List<Float> = emptyList(),\n    val gpuHistory: List<Float> = emptyList(),\n    val cpuTempHistory: List<Float> = emptyList(),\n    val ramHistory: List<Float> = emptyList()\n)\n\ndata class ModuleStatsState(\n    val kernelModuleCount: Int = 0,\n    val apmModuleCount: Int = 0,\n    val superuserCount: Int = 0\n)\n\ndata class PatchStatusState(\n    val kpState: APApplication.State = APApplication.State.UNKNOWN_STATE,\n    val apState: APApplication.State = APApplication.State.UNKNOWN_STATE,\n    val kpVersion: String = \"\",\n    val managerVersion: String = \"\",\n    val selinuxStatus: String = \"\"\n)\n\ndata class DashboardUiState(\n    val patchStatus: PatchStatusState = PatchStatusState(),\n    val systemMonitor: SystemMonitorState = SystemMonitorState(),\n    val moduleStats: ModuleStatsState = ModuleStatsState(),\n    val isLoading: Boolean = true\n)\n\nclass DashboardViewModel : ViewModel() {\n\n    companion object {\n        private const val TAG = \"DashboardViewModel\"\n        private const val CPU_POLL_INTERVAL_MS = 3_000L\n        private const val GPU_POLL_INTERVAL_MS = 1_000L\n        private const val MEMORY_POLL_INTERVAL_MS = 5_000L\n        private const val STORAGE_BATTERY_POLL_INTERVAL_MS = 10_000L\n        private const val TIME_SERIES_MAX_SIZE = 30\n    }\n\n    private val _dashboardUiState = MutableStateFlow(DashboardUiState())\n    val dashboardUiState: StateFlow<DashboardUiState> = _dashboardUiState.asStateFlow()\n\n    private val _timeSeriesData = MutableStateFlow(TimeSeriesData())\n    val timeSeriesData: StateFlow<TimeSeriesData> = _timeSeriesData.asStateFlow()\n\n    private var cpuGpuJob: Job? = null\n    private var gpuOnlyJob: Job? = null\n    private var memoryJob: Job? = null\n    private var storageBatteryJob: Job? = null\n    private var cpuTempJob: Job? = null\n    private var storagePartitionsJob: Job? = null\n\n    private val liveDataObservers = mutableListOf<Observer<*>>()\n\n    init {\n        loadOneTimeData()\n        observeLiveDataSources()\n        collectModuleCounts()\n    }\n\n    private fun loadOneTimeData() {\n        viewModelScope.launch(Dispatchers.IO) {\n            val current = _dashboardUiState.value\n\n            runCatching {\n                AppData.DataRefreshManager.ensureCountsLoaded(force = false)\n            }.onFailure { e ->\n                Log.e(TAG, \"Error loading module counts\", e)\n            }\n\n            val newState = current.copy(\n                patchStatus = current.patchStatus.copy(\n                    kpVersion = runCatching { Version.installedKPVString() }.getOrDefault(\"\"),\n                    managerVersion = runCatching { Version.getManagerVersion().first }.getOrDefault(\"\"),\n                    selinuxStatus = fetchSELinuxStatus(apApp)\n                ),\n                systemMonitor = current.systemMonitor.copy(\n                    storageUsedPercent = fetchStorageUsedPercent(),\n                    batteryLevel = fetchBatteryLevel(apApp),\n                    batteryTemp = fetchBatteryTemp(apApp)\n                ),\n                isLoading = false\n            )\n\n            _dashboardUiState.value = newState\n        }\n    }\n\n    private fun observeLiveDataSources() {\n        observeLiveData(APApplication.kpStateLiveData) { state ->\n            val current = _dashboardUiState.value\n            _dashboardUiState.value = current.copy(\n                patchStatus = current.patchStatus.copy(kpState = state)\n            )\n        }\n\n        observeLiveData(APApplication.apStateLiveData) { state ->\n            val current = _dashboardUiState.value\n            _dashboardUiState.value = current.copy(\n                patchStatus = current.patchStatus.copy(apState = state)\n            )\n        }\n    }\n\n    private fun <T> observeLiveData(liveData: LiveData<T>, onChanged: (T) -> Unit) {\n        val observer = Observer<T> { value ->\n            onChanged(value)\n        }\n        @Suppress(\"UNCHECKED_CAST\")\n        liveDataObservers.add(observer as Observer<*>)\n        liveData.observeForever(observer)\n    }\n\n    private fun collectModuleCounts() {\n        AppData.DataRefreshManager.superuserCount\n            .onEach { count ->\n                val current = _dashboardUiState.value\n                _dashboardUiState.value = current.copy(\n                    moduleStats = current.moduleStats.copy(superuserCount = count)\n                )\n            }\n            .catch { e -> Log.e(TAG, \"Error collecting superuserCount\", e) }\n            .launchIn(viewModelScope)\n\n        AppData.DataRefreshManager.apmModuleCount\n            .onEach { count ->\n                val current = _dashboardUiState.value\n                _dashboardUiState.value = current.copy(\n                    moduleStats = current.moduleStats.copy(apmModuleCount = count)\n                )\n            }\n            .catch { e -> Log.e(TAG, \"Error collecting apmModuleCount\", e) }\n            .launchIn(viewModelScope)\n\n        AppData.DataRefreshManager.kernelModuleCount\n            .onEach { count ->\n                val current = _dashboardUiState.value\n                _dashboardUiState.value = current.copy(\n                    moduleStats = current.moduleStats.copy(kernelModuleCount = count)\n                )\n            }\n            .catch { e -> Log.e(TAG, \"Error collecting kernelModuleCount\", e) }\n            .launchIn(viewModelScope)\n    }\n\n    fun startPeriodicPolling() {\n        cpuGpuJob = viewModelScope.launch(Dispatchers.IO) {\n            while (true) {\n                kotlin.runCatching {\n                    val cpu = HardwareMonitor.getCpuUsage()\n                    val cpuFreqs = HardwareMonitor.getCpuFrequencies()\n\n                    withContext(Dispatchers.Main) {\n                        val current = _dashboardUiState.value\n                        _dashboardUiState.value = current.copy(\n                            systemMonitor = current.systemMonitor.copy(\n                                cpuUsage = cpu,\n                                cpuFrequencies = cpuFreqs\n                            )\n                        )\n\n                        val ts = _timeSeriesData.value\n                        _timeSeriesData.value = ts.copy(\n                            cpuHistory = (ts.cpuHistory + cpu.toFloat()).takeLast(TIME_SERIES_MAX_SIZE)\n                        )\n                    }\n                }.onFailure { e ->\n                    Log.e(TAG, \"Error polling CPU\", e)\n                }\n\n                kotlinx.coroutines.delay(CPU_POLL_INTERVAL_MS)\n            }\n        }\n\n        gpuOnlyJob = viewModelScope.launch(Dispatchers.IO) {\n            while (true) {\n                kotlin.runCatching {\n                    val gpu = HardwareMonitor.getGpuUsage()\n\n                    withContext(Dispatchers.Main) {\n                        val current = _dashboardUiState.value\n                        _dashboardUiState.value = current.copy(\n                            systemMonitor = current.systemMonitor.copy(\n                                gpuUsage = gpu\n                            )\n                        )\n\n                        val ts = _timeSeriesData.value\n                        _timeSeriesData.value = ts.copy(\n                            gpuHistory = (ts.gpuHistory + gpu.toFloat()).takeLast(TIME_SERIES_MAX_SIZE)\n                        )\n                    }\n                }.onFailure { e ->\n                    Log.e(TAG, \"Error polling GPU\", e)\n                }\n\n                kotlinx.coroutines.delay(GPU_POLL_INTERVAL_MS)\n            }\n        }\n\n        cpuTempJob = viewModelScope.launch(Dispatchers.IO) {\n            kotlinx.coroutines.delay(1_000L)\n\n            while (true) {\n                kotlin.runCatching {\n                    val temp = HardwareMonitor.getCpuTemperature()\n\n                    withContext(Dispatchers.Main) {\n                        val current = _dashboardUiState.value\n                        _dashboardUiState.value = current.copy(\n                            systemMonitor = current.systemMonitor.copy(cpuTemp = temp)\n                        )\n\n                        val ts = _timeSeriesData.value\n                        _timeSeriesData.value = ts.copy(\n                            cpuTempHistory = (ts.cpuTempHistory + temp).takeLast(TIME_SERIES_MAX_SIZE)\n                        )\n                    }\n                }.onFailure { e ->\n                    Log.e(TAG, \"Error polling CPU temp\", e)\n                }\n\n                kotlinx.coroutines.delay(MEMORY_POLL_INTERVAL_MS)\n            }\n        }\n\n        memoryJob = viewModelScope.launch(Dispatchers.IO) {\n            kotlinx.coroutines.delay(2_000L)\n\n            while (true) {\n                kotlin.runCatching {\n                    val memInfo = HardwareMonitor.getMemoryInfo()\n\n                    withContext(Dispatchers.Main) {\n                        val current = _dashboardUiState.value\n                        _dashboardUiState.value = current.copy(\n                            systemMonitor = current.systemMonitor.copy(\n                                memoryInfo = memInfo\n                            )\n                        )\n\n                        val ramPercent = if (memInfo.ramTotal > 0) {\n                            (memInfo.ramUsed.toFloat() / memInfo.ramTotal.toFloat() * 100f)\n                        } else 0f\n                        val ts = _timeSeriesData.value\n                        _timeSeriesData.value = ts.copy(\n                            ramHistory = (ts.ramHistory + ramPercent).takeLast(TIME_SERIES_MAX_SIZE)\n                        )\n                    }\n                }.onFailure { e ->\n                    Log.e(TAG, \"Error polling Memory\", e)\n                }\n\n                kotlinx.coroutines.delay(MEMORY_POLL_INTERVAL_MS)\n            }\n        }\n\n        storageBatteryJob = viewModelScope.launch(Dispatchers.IO) {\n            kotlinx.coroutines.delay(5_000L)\n\n            while (true) {\n                kotlin.runCatching {\n                    val storagePercent = fetchStorageUsedPercent()\n                    val batteryLevel = fetchBatteryLevel(apApp)\n                    val batteryTemp = fetchBatteryTemp(apApp)\n                    val batteryCharging = fetchBatteryCharging(apApp)\n                    val networkRx = TrafficStats.getTotalRxBytes()\n                    val networkTx = TrafficStats.getTotalTxBytes()\n\n                    withContext(Dispatchers.Main) {\n                        val current = _dashboardUiState.value\n                        _dashboardUiState.value = current.copy(\n                            systemMonitor = current.systemMonitor.copy(\n                                storageUsedPercent = storagePercent,\n                                batteryLevel = batteryLevel,\n                                batteryTemp = batteryTemp,\n                                batteryCharging = batteryCharging,\n                                networkRxBytes = networkRx,\n                                networkTxBytes = networkTx\n                            )\n                        )\n                    }\n                }.onFailure { e ->\n                    Log.e(TAG, \"Error polling Storage/Battery/Network\", e)\n                }\n\n                kotlinx.coroutines.delay(STORAGE_BATTERY_POLL_INTERVAL_MS)\n            }\n        }\n\n        storagePartitionsJob = viewModelScope.launch(Dispatchers.IO) {\n            kotlinx.coroutines.delay(8_000L)\n\n            while (true) {\n                kotlin.runCatching {\n                    val partitions = HardwareMonitor.getStoragePartitions()\n\n                    withContext(Dispatchers.Main) {\n                        val current = _dashboardUiState.value\n                        _dashboardUiState.value = current.copy(\n                            systemMonitor = current.systemMonitor.copy(\n                                storagePartitions = partitions\n                            )\n                        )\n                    }\n                }.onFailure { e ->\n                    Log.e(TAG, \"Error polling Storage Partitions\", e)\n                }\n\n                kotlinx.coroutines.delay(30_000L)\n            }\n        }\n    }\n\n    fun refresh() {\n        viewModelScope.launch(Dispatchers.IO) {\n            val current = _dashboardUiState.value\n\n            _dashboardUiState.value = current.copy(isLoading = true)\n\n            val newState = current.copy(\n                patchStatus = current.patchStatus.copy(\n                    kpVersion = runCatching { Version.installedKPVString() }.getOrDefault(current.patchStatus.kpVersion),\n                    managerVersion = runCatching { Version.getManagerVersion().first }.getOrDefault(current.patchStatus.managerVersion),\n                    selinuxStatus = fetchSELinuxStatus(apApp)\n                ),\n                systemMonitor = current.systemMonitor.copy(\n                    storageUsedPercent = fetchStorageUsedPercent(),\n                    batteryLevel = fetchBatteryLevel(apApp),\n                    batteryTemp = fetchBatteryTemp(apApp)\n                ),\n                isLoading = false\n            )\n\n            _dashboardUiState.value = newState\n\n            runCatching {\n                AppData.DataRefreshManager.ensureCountsLoaded(force = true)\n            }.onFailure { e ->\n                Log.e(TAG, \"Error refreshing module counts\", e)\n            }\n        }\n    }\n\n    fun stopPeriodicPolling() {\n        cpuGpuJob?.cancel()\n        gpuOnlyJob?.cancel()\n        memoryJob?.cancel()\n        storageBatteryJob?.cancel()\n        cpuTempJob?.cancel()\n        storagePartitionsJob?.cancel()\n        cpuGpuJob = null\n        gpuOnlyJob = null\n        memoryJob = null\n        storageBatteryJob = null\n        cpuTempJob = null\n        storagePartitionsJob = null\n        Log.d(TAG, \"Periodic polling stopped\")\n    }\n\n    override fun onCleared() {\n        super.onCleared()\n        liveDataObservers.forEach { observer ->\n            @Suppress(\"UNCHECKED_CAST\")\n            APApplication.kpStateLiveData.removeObserver(observer as Observer<APApplication.State>)\n            @Suppress(\"UNCHECKED_CAST\")\n            APApplication.apStateLiveData.removeObserver(observer as Observer<APApplication.State>)\n        }\n        liveDataObservers.clear()\n\n        stopPeriodicPolling()\n        Log.d(TAG, \"DashboardViewModel cleared, all polling jobs cancelled\")\n    }\n\n    private suspend fun fetchSELinuxStatus(context: Context): String = withContext(Dispatchers.IO) {\n        try {\n            val enforcing = context.getString(R.string.home_selinux_status_enforcing)\n            val permissive = context.getString(R.string.home_selinux_status_permissive)\n            val disabled = context.getString(R.string.home_selinux_status_disabled)\n            val unknown = context.getString(R.string.home_selinux_status_unknown)\n\n            val shell = Shell.Builder.create().build(\"sh\")\n            val list = ArrayList<String>()\n            val result = shell.newJob().add(\"getenforce\").to(list, list).exec()\n            val output = result.out.joinToString(\"\\n\").trim()\n            shell.close()\n\n            if (result.isSuccess) {\n                when (output) {\n                    \"Enforcing\" -> enforcing\n                    \"Permissive\" -> permissive\n                    \"Disabled\" -> disabled\n                    else -> unknown\n                }\n            } else if (output.endsWith(\"Permission denied\")) {\n                enforcing\n            } else {\n                unknown\n            }\n        } catch (e: Exception) {\n            Log.e(TAG, \"Error fetching SELinux status\", e)\n            context.getString(R.string.home_selinux_status_unknown)\n        }\n    }\n\n    private fun fetchStorageUsedPercent(): Float {\n        return try {\n            val statFs = StatFs(\"/data\")\n            val totalBytes = statFs.totalBytes.toLong()\n            val availableBytes = statFs.availableBytes.toLong()\n\n            if (totalBytes > 0) {\n                ((totalBytes - availableBytes).toFloat() / totalBytes.toFloat() * 100f)\n            } else {\n                0f\n            }\n        } catch (e: Exception) {\n            Log.e(TAG, \"Error fetching storage info\", e)\n            0f\n        }\n    }\n\n    private fun fetchBatteryLevel(context: Context): Int {\n        return try {\n            val batteryManager = context.getSystemService(Context.BATTERY_SERVICE) as? BatteryManager\n            batteryManager?.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY) ?: 0\n        } catch (e: Exception) {\n            Log.e(TAG, \"Error fetching battery level\", e)\n            0\n        }\n    }\n\n    private fun fetchBatteryTemp(context: Context): Float {\n        return try {\n            val intent = context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))\n            val tempTenths = intent?.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0) ?: 0\n            tempTenths / 10f\n        } catch (e: Exception) {\n            Log.e(TAG, \"Error fetching battery temperature\", e)\n            0f\n        }\n    }\n\n    private fun fetchBatteryCharging(context: Context): Boolean {\n        return try {\n            val intent = context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))\n            val status = intent?.getIntExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN)\n                ?: BatteryManager.BATTERY_STATUS_UNKNOWN\n            status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL\n        } catch (e: Exception) {\n            Log.e(TAG, \"Error fetching battery charging status\", e)\n            false\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/viewmodel/KPModel.kt",
    "content": "package me.bmax.apatch.ui.viewmodel\n\nimport android.os.Parcelable\nimport androidx.annotation.Keep\nimport androidx.compose.runtime.Immutable\nimport kotlinx.parcelize.Parcelize\n\nobject KPModel {\n\n    enum class TriggerEvent(val event: String) {\n        PAGING_INIT(\"paging-init\"),\n        PRE_KERNEL_INIT(\"pre-kernel-init\"),\n        POST_KERNEL_INIT(\"post-kernel-init\"),\n    }\n\n\n    enum class ExtraType(val desc: String) {\n        NONE(\"none\"),\n        KPM(\"kpm\"),\n        SHELL(\"shell\"),\n        EXEC(\"exec\"),\n        RAW(\"raw\"),\n        ANDROID_RC(\"android_rc\");\n    }\n\n    interface IExtraInfo : Parcelable {\n        var type: ExtraType\n        var name: String\n        var event: String\n        var args: String\n    }\n\n    @Immutable\n    @Parcelize\n    @Keep\n    data class KPMInfo(\n        override var type: ExtraType,\n        override var name: String,\n        override var event: String,\n        override var args: String,\n        var version: String,\n        var license: String,\n        var author: String,\n        var description: String,\n    ) : IExtraInfo\n\n    @Immutable\n    @Parcelize\n    @Keep\n    data class KPImgInfo(\n        var version: String,\n        var compileTime: String,\n        var config: String,\n        var superKey: String,\n        var rootSuperkey: String\n    ) : Parcelable\n\n    @Immutable\n    @Parcelize\n    @Keep\n    data class KImgInfo(\n        var banner: String,\n        var patched: Boolean,\n    ) : Parcelable\n\n}"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/viewmodel/KPModuleViewModel.kt",
    "content": "package me.bmax.apatch.ui.viewmodel\n\nimport android.os.SystemClock\nimport android.util.Log\nimport androidx.compose.runtime.derivedStateOf\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.sync.Semaphore\nimport me.bmax.apatch.Natives\nimport java.text.Collator\nimport java.util.Locale\n\nclass KPModuleViewModel : ViewModel() {\n    companion object {\n        private const val TAG = \"KPModuleViewModel\"\n        private var modules by mutableStateOf<List<KPModel.KPMInfo>>(emptyList())\n        val bannerSemaphore = Semaphore(4)\n    }\n\n    var isRefreshing by mutableStateOf(false)\n        private set\n\n    val moduleList by derivedStateOf {\n        val comparator = compareBy(Collator.getInstance(Locale.getDefault()), KPModel.KPMInfo::name)\n        modules.sortedWith(comparator).also {\n            isRefreshing = false\n        }\n    }\n\n    var isNeedRefresh by mutableStateOf(false)\n        private set\n\n    fun markNeedRefresh() {\n        isNeedRefresh = true\n    }\n\n    fun fetchModuleList() {\n        viewModelScope.launch(Dispatchers.IO) {\n            isRefreshing = true\n            val oldModuleList = modules\n            val start = SystemClock.elapsedRealtime()\n\n            kotlin.runCatching {\n                var names = Natives.kernelPatchModuleList()\n                if (Natives.kernelPatchModuleNum() <= 0)\n                    names = \"\"\n                val nameList = names.split('\\n').toList()\n                Log.d(TAG, \"kpm list: $nameList\")\n                modules = nameList.filter { it.isNotEmpty() }.map {\n                    val infoline = Natives.kernelPatchModuleInfo(it)\n                    val spi = infoline.split('\\n')\n                    val name = spi.find { it.startsWith(\"name=\") }?.removePrefix(\"name=\")\n                    val version = spi.find { it.startsWith(\"version=\") }?.removePrefix(\"version=\")\n                    val license = spi.find { it.startsWith(\"license=\") }?.removePrefix(\"license=\")\n                    val author = spi.find { it.startsWith(\"author=\") }?.removePrefix(\"author=\")\n                    val description =\n                        spi.find { it.startsWith(\"description=\") }?.removePrefix(\"description=\")\n                    val rawArgs = spi.find { it.startsWith(\"args=\") }?.removePrefix(\"args=\")?.trim()\n                    val args = if (rawArgs.isNullOrEmpty() || rawArgs == \"(null)\") \"\" else rawArgs\n                    val info = KPModel.KPMInfo(\n                        KPModel.ExtraType.KPM,\n                        name ?: \"\",\n                        \"\",\n                        args ?: \"\",\n                        version ?: \"\",\n                        license ?: \"\",\n                        author ?: \"\",\n                        description ?: \"\"\n                    )\n                    info\n                }\n                isNeedRefresh = false\n            }.onFailure { e ->\n                Log.e(TAG, \"fetchModuleList: \", e)\n                isRefreshing = false\n            }\n\n            // when both old and new is kotlin.collections.EmptyList\n            // moduleList update will don't trigger\n            if (oldModuleList === modules) {\n                isRefreshing = false\n            }\n\n            Log.i(TAG, \"load cost: ${SystemClock.elapsedRealtime() - start}, modules: $modules\")\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/viewmodel/OnlineKPMViewModel.kt",
    "content": "package me.bmax.apatch.ui.viewmodel\n\nimport android.util.Log\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport me.bmax.apatch.apApp\nimport me.bmax.apatch.util.FolkApiClient\nimport org.json.JSONArray\nimport java.util.Locale\n\nclass OnlineKPMViewModel : ViewModel() {\n    companion object {\n        private const val TAG = \"OnlineKPMViewModel\"\n        // Placeholder URL. User should update this.\n        const val MODULES_URL = \"https://folk.mysqil.com/api/modules?type=kpm\"\n    }\n\n    data class OnlineKPM(\n        val name: String,\n        val version: String,\n        val url: String,\n        val description: String,\n        val needControl: Boolean\n    )\n\n    var modules by mutableStateOf<List<OnlineKPM>>(emptyList())\n        private set\n\n    private var allModules = listOf<OnlineKPM>()\n\n    var searchQuery by mutableStateOf(\"\")\n        private set\n\n    fun onSearchQueryChange(query: String) {\n        searchQuery = query\n        if (query.isBlank()) {\n            modules = allModules\n        } else {\n            modules = allModules.filter {\n                it.name.contains(query, ignoreCase = true) ||\n                it.description.contains(query, ignoreCase = true)\n            }\n        }\n    }\n\n    var isRefreshing by mutableStateOf(false)\n        private set\n\n    var errorMessage by mutableStateOf<String?>(null)\n        private set\n\n    fun fetchModules() {\n        viewModelScope.launch(Dispatchers.IO) {\n            isRefreshing = true\n            errorMessage = null\n            try {\n                val locale = Locale.getDefault()\n                val language = locale.language\n                val lang = if (language == \"zh\" || language == \"mgl\") \"zh\" else \"en\"\n                val token = me.bmax.apatch.Natives.getApiToken(apApp)\n                val url = \"$MODULES_URL&lang=$lang&token=$token\"\n\n                val result = FolkApiClient.fetchJson(url)\n                val jsonString = result.getOrNull()\n                if (jsonString != null) {\n                    val jsonArray = JSONArray(jsonString)\n                    val list = ArrayList<OnlineKPM>()\n                    for (i in 0 until jsonArray.length()) {\n                        val obj = jsonArray.getJSONObject(i)\n                        val descZh = obj.optString(\"description\")\n                        val descEn = obj.optString(\"description_en\")\n                        val finalDesc = if (lang == \"zh\") {\n                            descZh\n                        } else {\n                            if (descEn.isNotEmpty()) descEn else descZh\n                        }\n                        \n                        list.add(\n                            OnlineKPM(\n                                name = obj.optString(\"name\"),\n                                version = obj.optString(\"version\"),\n                                url = obj.optString(\"url\"),\n                                description = finalDesc,\n                                needControl = obj.optInt(\"parameter\", 0) == 1\n                            )\n                        )\n                    }\n                    allModules = list\n                    onSearchQueryChange(searchQuery)\n                } else {\n                    val exception = result.exceptionOrNull()\n                    Log.e(TAG, \"Failed to fetch modules: ${exception?.message}\")\n                    errorMessage = \"Error: ${exception?.message}\"\n                }\n            } catch (e: Exception) {\n                Log.e(TAG, \"Error fetching modules\", e)\n                errorMessage = \"Error: ${e.message}\"\n            } finally {\n                isRefreshing = false\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/viewmodel/OnlineModuleViewModel.kt",
    "content": "package me.bmax.apatch.ui.viewmodel\n\nimport android.util.Log\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport me.bmax.apatch.apApp\nimport me.bmax.apatch.util.FolkApiClient\nimport org.json.JSONArray\nimport android.net.Uri\nimport java.util.Locale\n\nclass OnlineModuleViewModel : ViewModel() {\n    companion object {\n        private const val TAG = \"OnlineModuleViewModel\"\n        // Placeholder URL. User should update this.\n        const val MODULES_URL = \"https://folk.mysqil.com/api/modules?type=apm\"\n    }\n\n    data class OnlineModule(\n        val name: String,\n        val version: String,\n        val url: String,\n        val description: String\n    )\n\n    var modules by mutableStateOf<List<OnlineModule>>(emptyList())\n        private set\n\n    private var allModules = listOf<OnlineModule>()\n\n    var searchQuery by mutableStateOf(\"\")\n        private set\n\n    fun onSearchQueryChange(query: String) {\n        searchQuery = query\n        if (query.isBlank()) {\n            modules = allModules\n        } else {\n            modules = allModules.filter {\n                it.name.contains(query, ignoreCase = true) ||\n                it.description.contains(query, ignoreCase = true)\n            }\n        }\n    }\n\n    var isRefreshing by mutableStateOf(false)\n        private set\n\n    var errorMessage by mutableStateOf<String?>(null)\n        private set\n    private val downloadingModules = mutableSetOf<String>()\n    \n    // Track recently completed downloads to show completion messages\n    private val recentlyCompletedDownloads = mutableSetOf<String>()\n    var currentDownloadId by mutableStateOf<Long?>(null)\n        private set\n\n    fun fetchModules() {\n        viewModelScope.launch(Dispatchers.IO) {\n            isRefreshing = true\n            errorMessage = null\n            try {\n                val locale = Locale.getDefault()\n                val language = locale.language\n                val lang = if (language == \"zh\" || language == \"mgl\") \"zh\" else \"en\"\n                val token = me.bmax.apatch.Natives.getApiToken(apApp)\n                val url = \"$MODULES_URL&lang=$lang&token=$token\"\n\n                val result = FolkApiClient.fetchJson(url)\n                val jsonString = result.getOrNull()\n                if (jsonString != null) {\n                    val jsonArray = JSONArray(jsonString)\n                    val list = ArrayList<OnlineModule>()\n                    for (i in 0 until jsonArray.length()) {\n                        val obj = jsonArray.getJSONObject(i)\n                        val descZh = obj.optString(\"description\")\n                        val descEn = obj.optString(\"description_en\")\n                        val finalDesc = if (lang == \"zh\") {\n                            descZh\n                        } else {\n                            if (descEn.isNotEmpty()) descEn else descZh\n                        }\n\n                        list.add(\n                            OnlineModule(\n                                name = obj.optString(\"name\"),\n                                version = obj.optString(\"version\"),\n                                url = obj.optString(\"url\"),\n                                description = finalDesc\n                            )\n                        )\n                    }\n                    modules = list\n                    allModules = list\n                    onSearchQueryChange(searchQuery)\n                } else {\n                    val exception = result.exceptionOrNull()\n                    Log.e(TAG, \"Failed to fetch modules: ${exception?.message}\")\n                    errorMessage = \"Error: ${exception?.message}\"\n                }\n            } catch (e: Exception) {\n                Log.e(TAG, \"Error fetching modules\", e)\n                errorMessage = \"Error: ${e.message}\"\n            } finally {\n                isRefreshing = false\n            }\n        }\n    }\n    \n    fun isDownloading(moduleName: String): Boolean {\n        return downloadingModules.contains(moduleName)\n    }\n    \n    fun startDownload(moduleName: String) {\n        downloadingModules.add(moduleName)\n        // Remove from recently completed if it was there\n        recentlyCompletedDownloads.remove(moduleName)\n    }\n    \n    fun finishDownload(moduleName: String) {\n        downloadingModules.remove(moduleName)\n        recentlyCompletedDownloads.add(moduleName)\n    }\n    \n    fun wasDownloadJustCompleted(moduleName: String): Boolean {\n        return if (recentlyCompletedDownloads.contains(moduleName)) {\n            recentlyCompletedDownloads.remove(moduleName)\n            true\n        } else {\n            false\n        }\n    }\n    \n    fun handleDownloadComplete(uri: Uri) {\n        // Try to determine which module completed from the URI\n        val uriString = uri.toString()\n        \n        // Simple approach: try to match the URI with module URLs\n        for (module in modules) {\n            if (uriString.contains(module.name) || uriString.contains(\"${module.name}-${module.version}.zip\")) {\n                finishDownload(module.name)\n                return\n            }\n        }\n        \n        // If we can't determine the module, clear all downloads as a fallback\n        val currentlyDownloading = downloadingModules.toList()\n        for (moduleName in currentlyDownloading) {\n            finishDownload(moduleName)\n        }\n        \n        currentDownloadId = null\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/viewmodel/OnlineScriptViewModel.kt",
    "content": "package me.bmax.apatch.ui.viewmodel\n\nimport android.util.Log\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport me.bmax.apatch.apApp\nimport me.bmax.apatch.util.FolkApiClient\nimport org.json.JSONArray\nimport java.util.Locale\n\nclass OnlineScriptViewModel : ViewModel() {\n    companion object {\n        private const val TAG = \"OnlineScriptViewModel\"\n        const val MODULES_URL = \"https://folk.mysqil.com/api/modules?type=script\"\n    }\n\n    data class OnlineScript(\n        val name: String,\n        val version: String,\n        val url: String,\n        val description: String\n    )\n\n    var modules by mutableStateOf<List<OnlineScript>>(emptyList())\n        private set\n\n    private var allModules = listOf<OnlineScript>()\n\n    var searchQuery by mutableStateOf(\"\")\n        private set\n\n    fun onSearchQueryChange(query: String) {\n        searchQuery = query\n        if (query.isBlank()) {\n            modules = allModules\n        } else {\n            modules = allModules.filter {\n                it.name.contains(query, ignoreCase = true) ||\n                it.description.contains(query, ignoreCase = true)\n            }\n        }\n    }\n\n    var isRefreshing by mutableStateOf(false)\n        private set\n\n    var errorMessage by mutableStateOf<String?>(null)\n        private set\n\n    fun fetchModules() {\n        viewModelScope.launch(Dispatchers.IO) {\n            isRefreshing = true\n            errorMessage = null\n            try {\n                val locale = Locale.getDefault()\n                val language = locale.language\n                val lang = if (language == \"zh\" || language == \"mgl\") \"zh\" else \"en\"\n                val token = me.bmax.apatch.Natives.getApiToken(apApp)\n                val url = \"$MODULES_URL&lang=$lang&token=$token\"\n\n                val result = FolkApiClient.fetchJson(url)\n                val jsonString = result.getOrNull()\n                if (jsonString != null) {\n                    val jsonArray = JSONArray(jsonString)\n                    val list = ArrayList<OnlineScript>()\n                    for (i in 0 until jsonArray.length()) {\n                        val obj = jsonArray.getJSONObject(i)\n                        val descZh = obj.optString(\"description\")\n                        val descEn = obj.optString(\"description_en\")\n                        val finalDesc = if (lang == \"zh\") {\n                            descZh\n                        } else {\n                            if (descEn.isNotEmpty()) descEn else descZh\n                        }\n                        \n                        list.add(\n                            OnlineScript(\n                                name = obj.optString(\"name\"),\n                                version = obj.optString(\"version\"),\n                                url = obj.optString(\"url\"),\n                                description = finalDesc\n                            )\n                        )\n                    }\n                    allModules = list\n                    onSearchQueryChange(searchQuery)\n                } else {\n                    val exception = result.exceptionOrNull()\n                    Log.e(TAG, \"Failed to fetch scripts: ${exception?.message}\")\n                    errorMessage = \"Error: ${exception?.message}\"\n                }\n            } catch (e: Exception) {\n                Log.e(TAG, \"Error fetching modules\", e)\n                errorMessage = \"Error: ${e.message}\"\n            } finally {\n                isRefreshing = false\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/viewmodel/PatchesViewModel.kt",
    "content": "package me.bmax.apatch.ui.viewmodel\n\nimport android.content.ContentValues\nimport android.content.Context\nimport android.net.Uri\nimport android.os.Build\nimport android.os.Environment\nimport android.provider.MediaStore\nimport android.system.Os\nimport android.util.Log\nimport androidx.annotation.RequiresApi\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateListOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.core.content.FileProvider\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.topjohnwu.superuser.CallbackList\nimport com.topjohnwu.superuser.Shell\nimport com.topjohnwu.superuser.nio.ExtendedFile\nimport com.topjohnwu.superuser.nio.FileSystemManager\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.BuildConfig\nimport me.bmax.apatch.R\nimport me.bmax.apatch.apApp\nimport me.bmax.apatch.util.Version\nimport me.bmax.apatch.util.getSafeDownloadsDir\nimport me.bmax.apatch.util.copyAndClose\nimport me.bmax.apatch.util.copyAndCloseOut\nimport me.bmax.apatch.util.createRootShell\nimport me.bmax.apatch.util.inputStream\nimport me.bmax.apatch.util.shellForResult\nimport me.bmax.apatch.util.writeTo\nimport org.ini4j.Ini\nimport java.io.BufferedReader\nimport java.io.File\nimport java.io.FileNotFoundException\nimport java.io.IOException\nimport java.io.InputStreamReader\nimport java.io.StringReader\nimport java.text.SimpleDateFormat\nimport java.util.Date\nimport java.util.Locale\n\nimport me.bmax.apatch.util.getFileNameFromUri\nimport me.bmax.apatch.util.ModuleBackupUtils\nimport me.bmax.apatch.util.SafeUriResolver\nimport me.bmax.apatch.ui.screen.selectedKPImg\n\nprivate const val TAG = \"PatchViewModel\"\n\nclass PatchesViewModel : ViewModel() {\n\n    enum class PatchMode(val sId: Int) {\n        PATCH_ONLY(R.string.patch_mode_bootimg_patch),\n        PATCH_AND_INSTALL(R.string.patch_mode_patch_and_install),\n        INSTALL_TO_NEXT_SLOT(R.string.patch_mode_install_to_next_slot),\n        RESTORE(R.string.patch_mode_restore),\n        UNPATCH(R.string.patch_mode_uninstall_patch)\n    }\n\n    var bootSlot by mutableStateOf(\"\")\n    var bootDev by mutableStateOf(\"\")\n    var kimgInfo by mutableStateOf(KPModel.KImgInfo(\"\", false))\n    var kpimgInfo by mutableStateOf(KPModel.KPImgInfo(\"\", \"\", \"\", \"\", \"\"))\n    var superkey by mutableStateOf(APApplication.superKey)\n    var existedExtras = mutableStateListOf<KPModel.IExtraInfo>()\n    var newExtras = mutableStateListOf<KPModel.IExtraInfo>()\n    var newExtrasFileName = mutableListOf<String>()\n\n    var running by mutableStateOf(false)\n    var patching by mutableStateOf(false)\n    var patchdone by mutableStateOf(false)\n    var needReboot by mutableStateOf(false)\n    var useCustomKPImg by mutableStateOf(false)\n    var customKPImgFileName by mutableStateOf(\"\")\n\n    var error by mutableStateOf(\"\")\n    var patchLog by mutableStateOf(\"\")\n\n    private val patchDir: ExtendedFile = FileSystemManager.getLocal().getFile(apApp.filesDir.parent, \"patch\")\n    private var srcBoot: ExtendedFile = patchDir.getChildFile(\"boot.img\")\n    private var shell: Shell = createRootShell()\n    private var prepared: Boolean = false\n\n    private fun prepare() {\n        // Force clean with root to avoid permission issues from previous root operations\n        shell.newJob().add(\"rm -rf ${patchDir.path}\").exec()\n        \n        patchDir.deleteRecursively()\n        patchDir.mkdirs()\n        val execs = listOf(\n            \"libkptools.so\", \"libbusybox.so\", \"libkpatch.so\", \"libbootctl.so\"\n        )\n        error = \"\"\n\n        val info = apApp.applicationInfo\n        val libs = File(info.nativeLibraryDir).listFiles { _, name ->\n            execs.contains(name)\n        } ?: emptyArray()\n\n        for (lib in libs) {\n            val name = lib.name.substring(3, lib.name.length - 3)\n            try {\n                Os.symlink(lib.path, \"$patchDir/$name\")\n            } catch (e: Exception) {\n                lib.inputStream().copyAndClose(File(patchDir, name).outputStream())\n            }\n        }\n\n        // Extract scripts\n        for (script in listOf(\n            \"boot_patch.sh\", \"boot_unpatch.sh\", \"boot_extract.sh\", \"util_functions.sh\", \"kpimg\", \"boot_flash.sh\"\n        )) {\n            val dest = File(patchDir, script)\n            apApp.assets.open(script).writeTo(dest)\n        }\n\n    }\n\n    private fun parseKpimg() {\n        val result = shellForResult(\n            shell, \"cd $patchDir\", \"./kptools -l -k kpimg\"\n        )\n\n        if (result.isSuccess) {\n            try {\n                val ini = Ini(StringReader(result.out.joinToString(\"\\n\")))\n                val kpimg = ini[\"kpimg\"]\n                if (kpimg != null) {\n                    kpimgInfo = KPModel.KPImgInfo(\n                        kpimg[\"version\"].toString(),\n                        kpimg[\"compile_time\"].toString(),\n                        kpimg[\"config\"].toString(),\n                        APApplication.superKey,     // current key\n                        kpimg[\"root_superkey\"].toString(),   // empty\n                    )\n                } else {\n                    error += \"parse kpimg error\\n\"\n                }\n            } catch (e: Exception) {\n                Log.e(TAG, \"parseKpimg INI error: ${e.message}\")\n                error += \"parse kpimg error: ${e.message}\\n\"\n            }\n        } else {\n            error = result.err.joinToString(\"\\n\")\n        }\n    }\n\n    private fun parseBootimg(bootimg: String) {\n        val result = shellForResult(\n            shell,\n            \"cd $patchDir\",\n            \"./kptools unpacknolog $bootimg\",\n            \"./kptools -l -i kernel\",\n        )\n        if (result.isSuccess) {\n            try {\n                val ini = Ini(StringReader(result.out.joinToString(\"\\n\")))\n                Log.d(TAG, \"kernel image info: $ini\")\n\n                val kernel = ini[\"kernel\"]\n                if (kernel == null) {\n                    error += \"empty kernel section\"\n                    Log.d(TAG, error)\n                    return\n                }\n                kimgInfo = KPModel.KImgInfo(kernel[\"banner\"].toString(), kernel[\"patched\"].toBoolean())\n                if (kimgInfo.patched) {\n                    val superkey = ini[\"kpimg\"]?.getOrDefault(\"superkey\", \"\") ?: \"\"\n                    kpimgInfo.superKey = superkey\n                    if (checkSuperKeyValidation(superkey)) {\n                        this.superkey = superkey\n                    }\n                    var kpmNum = kernel[\"extra_num\"]?.toInt()\n                    if (kpmNum == null) {\n                        val extras = ini[\"extras\"]\n                        kpmNum = extras?.get(\"num\")?.toInt()\n                    }\n                    if (kpmNum != null && kpmNum > 0) {\n                        for (i in 0..<kpmNum) {\n                            val extra = ini[\"extra $i\"]\n                            if (extra == null) {\n                                error += \"empty extra section\"\n                                break\n                            }\n                            val type = KPModel.ExtraType.valueOf(extra[\"type\"]!!.uppercase())\n                            val name = extra[\"name\"].toString()\n                            val args = extra[\"args\"].toString()\n                            var event = extra[\"event\"].toString()\n                            if (event.isEmpty()) {\n                                event = KPModel.TriggerEvent.PRE_KERNEL_INIT.event\n                            }\n                            if (type == KPModel.ExtraType.KPM) {\n                                val kpmInfo = KPModel.KPMInfo(\n                                    type, name, event, args,\n                                    extra[\"version\"].toString(),\n                                    extra[\"license\"].toString(),\n                                    extra[\"author\"].toString(),\n                                    extra[\"description\"].toString(),\n                                )\n                                existedExtras.add(kpmInfo)\n                            }\n                        }\n\n                    }\n                }\n            } catch (e: Exception) {\n                Log.e(TAG, \"parseBootimg INI error: ${e.message}\")\n                error += \"parse boot image error: ${e.message}\\n\"\n            }\n        } else {\n            error += result.err.joinToString(\"\\n\")\n        }\n    }\n\n    val checkSuperKeyValidation: (superKey: String) -> Boolean = { superKey ->\n        superKey.length in 8..63 && superKey.all { it.isLetterOrDigit() } && superKey.any { it.isDigit() } && superKey.any { it.isLetter() }\n    }\n\n    fun copyAndParseBootimg(uri: Uri) {\n        viewModelScope.launch(Dispatchers.IO) {\n            if (running) return@launch\n            running = true\n            try {\n                uri.inputStream().buffered().use { src ->\n                    srcBoot.also {\n                        src.copyAndCloseOut(it.newOutputStream())\n                    }\n                }\n            } catch (e: IOException) {\n                Log.e(TAG, \"copy boot image error: $e\")\n            }\n            parseBootimg(srcBoot.path)\n            running = false\n        }\n    }\n\n    private fun extractAndParseBootimg(mode: PatchMode) {\n        var cmdBuilder = \"./boot_extract.sh\"\n\n        if (mode == PatchMode.INSTALL_TO_NEXT_SLOT) {\n            cmdBuilder += \" true\"\n        }\n\n        val result = shellForResult(\n            shell,\n            \"export ASH_STANDALONE=1\",\n            \"cd $patchDir\",\n            \"./busybox sh $cmdBuilder\",\n        )\n\n        if (result.isSuccess) {\n            bootSlot = if (!result.out.toString().contains(\"SLOT=\")) {\n                \"\"\n            } else {\n                result.out.filter { it.startsWith(\"SLOT=\") }[0].removePrefix(\"SLOT=\")\n            }\n            bootDev =\n                result.out.filter { it.startsWith(\"BOOTIMAGE=\") }[0].removePrefix(\"BOOTIMAGE=\")\n            Log.i(TAG, \"current slot: $bootSlot\")\n            Log.i(TAG, \"current bootimg: $bootDev\")\n            srcBoot = FileSystemManager.getLocal().getFile(bootDev)\n            parseBootimg(bootDev)\n        } else {\n            error = result.err.joinToString(\"\\n\")\n        }\n        running = false\n    }\n\n    fun prepare(mode: PatchMode) {\n        viewModelScope.launch(Dispatchers.IO) {\n            if (prepared) return@launch\n            prepared = true\n\n            running = true\n            try {\n                prepare()\n\n                if (selectedKPImg != null && mode == PatchMode.PATCH_ONLY) {\n                    try {\n                        val kpimgFile = File(patchDir, \"kpimg\")\n                        selectedKPImg!!.inputStream().buffered().use { src ->\n                            kpimgFile.also {\n                                src.copyAndCloseOut(it.outputStream())\n                            }\n                        }\n                        customKPImgFileName = getFileNameFromUri(apApp, selectedKPImg!!) ?: \"kpimg\"\n                        useCustomKPImg = true\n                    } catch (e: IOException) {\n                        Log.e(TAG, \"Copy custom kpimg error: $e\")\n                        error += \"Copy custom kpimg error: ${e.message}\\n\"\n                    }\n                }\n\n                if (mode != PatchMode.UNPATCH) {\n                    parseKpimg()\n                }\n                if (mode == PatchMode.PATCH_AND_INSTALL || mode == PatchMode.UNPATCH || mode == PatchMode.INSTALL_TO_NEXT_SLOT || mode == PatchMode.RESTORE) {\n                    extractAndParseBootimg(mode)\n                }\n            } catch (e: Exception) {\n                Log.e(TAG, \"Prepare failed\", e)\n                error = \"Initialization failed: ${e.message}\"\n            } finally {\n                running = false\n            }\n        }\n    }\n\n    fun embedKPM(uri: Uri) {\n        viewModelScope.launch(Dispatchers.IO) {\n            if (running) return@launch\n            running = true\n            error = \"\"\n\n            val rand = (1..4).map { ('a'..'z').random() }.joinToString(\"\")\n            val kpmFileName = \"${rand}.kpm\"\n            val kpmFile: ExtendedFile = patchDir.getChildFile(kpmFileName)\n\n            Log.i(TAG, \"copy kpm to: \" + kpmFile.path)\n            try {\n                uri.inputStream().buffered().use { src ->\n                    kpmFile.also {\n                        src.copyAndCloseOut(it.newOutputStream())\n                    }\n                }\n\n                // Auto Backup Logic\n                val originalFileName = getFileNameFromUri(apApp, uri)\n                launch(Dispatchers.IO) {\n                     val result = ModuleBackupUtils.autoBackupModule(\n                        apApp,\n                        kpmFile,\n                        originalFileName,\n                        \"KPM\"\n                    )\n                    if (result != null && !result.startsWith(\"Duplicate\")) {\n                        Log.e(TAG, \"KPM Auto backup failed: $result\")\n                    } else {\n                        Log.d(TAG, \"KPM Auto backup success\")\n                    }\n                }\n\n            } catch (e: IOException) {\n                Log.e(TAG, \"Copy kpm error: $e\")\n            }\n\n            val result = shellForResult(\n                shell, \"cd $patchDir\", \"./kptools -l -M ${kpmFile.path}\"\n            )\n\n            if (result.isSuccess) {\n                try {\n                    val ini = Ini(StringReader(result.out.joinToString(\"\\n\")))\n                    val kpm = ini[\"kpm\"]\n                    if (kpm != null) {\n                        val kpmInfo = KPModel.KPMInfo(\n                            KPModel.ExtraType.KPM,\n                            kpm[\"name\"].toString(),\n                            KPModel.TriggerEvent.PRE_KERNEL_INIT.event,\n                            \"\",\n                            kpm[\"version\"].toString(),\n                            kpm[\"license\"].toString(),\n                            kpm[\"author\"].toString(),\n                            kpm[\"description\"].toString(),\n                        )\n                        newExtras.add(kpmInfo)\n                        newExtrasFileName.add(kpmFileName)\n                    }\n                } catch (e: Exception) {\n                    Log.e(TAG, \"embedKPM INI error: ${e.message}\")\n                    error = \"Invalid KPM: parse error\\n\"\n                }\n            } else {\n                error = \"Invalid KPM\\n\"\n            }\n            running = false\n        }\n    }\n\n    fun setCustomKPImg(uri: Uri) {\n        viewModelScope.launch(Dispatchers.IO) {\n            if (running) return@launch\n            running = true\n            error = \"\"\n\n            val kpimgFile = File(patchDir, \"kpimg\")\n            try {\n                uri.inputStream().buffered().use { src ->\n                    kpimgFile.also {\n                        src.copyAndCloseOut(it.outputStream())\n                    }\n                }\n            } catch (e: IOException) {\n                Log.e(TAG, \"Copy custom kpimg error: $e\")\n                error = \"Copy custom kpimg error: ${e.message}\\n\"\n                running = false\n                return@launch\n            }\n\n            customKPImgFileName = getFileNameFromUri(apApp, uri) ?: \"kpimg\"\n            useCustomKPImg = true\n            parseKpimg()\n            running = false\n        }\n    }\n\n    fun doUnpatch() {\n        viewModelScope.launch(Dispatchers.IO) {\n            patching = true\n            patchLog = \"\"\n            Log.i(TAG, \"starting unpatching...\")\n\n            val logs = object : CallbackList<String>() {\n                override fun onAddElement(e: String?) {\n                    patchLog += e\n                    Log.i(TAG, \"\" + e)\n                    patchLog += \"\\n\"\n                }\n            }\n\n            val result = shell.newJob().add(\n                \"export ASH_STANDALONE=1\",\n                \"cd $patchDir\",\n                \"cp /data/adb/ap/ori.img new-boot.img\",\n                \"./busybox sh ./boot_unpatch.sh $bootDev\",\n                \"rm -f ${APApplication.APD_PATH}\",\n                \"rm -rf ${APApplication.APATCH_FOLDER}\",\n            ).to(logs, logs).exec()\n\n            if (result.isSuccess) {\n                logs.add(\" Unpatch successful\")\n                needReboot = true\n                APApplication.markNeedReboot()\n            } else {\n                logs.add(\" Unpatched failed\")\n                error = result.err.joinToString(\"\\n\")\n            }\n            logs.add(\"****************************\")\n\n            patchdone = true\n            patching = false\n        }\n    }\n    fun isSuExecutable(): Boolean {\n        val suFile = File(\"/system/bin/su\")\n        return suFile.exists() && suFile.canExecute()\n    }\n    fun doPatch(mode: PatchMode, useKey: Boolean) {\n        viewModelScope.launch(Dispatchers.IO) {\n            patching = true\n            Log.d(TAG, \"starting patching...\")\n\n            val apVer = Version.getManagerVersion().second\n            val rand = (1..4).map { ('a'..'z').random() }.joinToString(\"\")\n            val outFilename = \"folk_patched_${apVer}_${BuildConfig.buildKPV}_${rand}.img\"\n\n            val logs = object : CallbackList<String>() {\n                override fun onAddElement(e: String?) {\n                    patchLog += e\n                    Log.d(TAG, \"\" + e)\n                    patchLog += \"\\n\"\n                }\n            }\n            // Auto Backup Boot\n            val prefs = APApplication.sharedPreferences\n            if (prefs.getBoolean(\"auto_backup_boot\", false)) {\n                logs.add(\"****************************\")\n                logs.add(\" Backing up boot image...\")\n                try {\n                    val backupDir = File(\"/storage/emulated/0/Download/FolkPatch/BootBackups/\")\n                    if (!backupDir.exists()) {\n                        backupDir.mkdirs()\n                    }\n                    val timestamp = SimpleDateFormat(\"yyyyMMdd_HHmmss\", Locale.getDefault()).format(Date())\n                    val backupFile = File(backupDir, \"boot_backup_$timestamp.img\")\n                    srcBoot.inputStream().use { input ->\n                        backupFile.outputStream().use { output ->\n                            input.copyTo(output)\n                        }\n                    }\n                    logs.add(\" Boot image backed up to ${backupFile.absolutePath}\")\n                } catch (e: Exception) {\n                    logs.add(\" Backup failed: ${e.message}\")\n                    Log.e(TAG, \"Backup failed\", e)\n                }\n            }\n\n            if (mode == PatchMode.RESTORE) {\n                logs.add(\" Restoring boot image...\")\n                val restoreCommand = mutableListOf(\"./busybox\", \"sh\", \"boot_flash.sh\", bootDev, srcBoot.path)\n                \n                val result = shell.newJob().add(\n                    \"export ASH_STANDALONE=1\",\n                    \"cd $patchDir\",\n                    restoreCommand.joinToString(\" \"),\n                ).to(logs, logs).exec()\n\n                if (result.isSuccess) {\n                    logs.add(\" Restore successful\")\n                    needReboot = true\n                    APApplication.markNeedReboot()\n                } else {\n                    logs.add(\" Restore failed\")\n                    error = result.err.joinToString(\"\\n\")\n                }\n                logs.add(\"****************************\")\n                patchdone = true\n                patching = false\n                return@launch\n            }\n\n            var patchCommand = mutableListOf(\"./busybox sh boot_patch.sh \\\"$0\\\" \\\"$@\\\"\")\n\n            // adapt for 0.10.7 and lower KP\n            var isKpOld = false\n\n            val superkey = if (useKey && this@PatchesViewModel.superkey.isNotEmpty()) {\n                this@PatchesViewModel.superkey\n            } else {\n                val chars = ('a'..'z') + ('A'..'Z') + ('0'..'9')\n                (1..16).map { chars.random() }.joinToString(\"\")\n            }\n\n            if (mode == PatchMode.PATCH_AND_INSTALL || mode == PatchMode.INSTALL_TO_NEXT_SLOT) {\n\n                val KPCheck = shell.newJob().add(\"truncate ${APApplication.superKey} -Z u:r:magisk:s0 -c whoami\").exec()\n\n                if (KPCheck.isSuccess && !isSuExecutable()) {\n                    patchCommand.addAll(0, listOf(APApplication.SUPERCMD, APApplication.superKey, \"-Z\", APApplication.MAGISK_SCONTEXT, \"-c\"))\n                    patchCommand.addAll(listOf(superkey, srcBoot.path, \"true\"))\n                } else {\n                    patchCommand = mutableListOf(\"./busybox\", \"sh\", \"boot_patch.sh\")\n                    patchCommand.addAll(listOf(superkey, srcBoot.path, \"true\"))\n                    isKpOld = true\n                }\n\n            } else {\n                patchCommand.addAll(0, listOf(\"sh\", \"-c\"))\n                patchCommand.addAll(listOf(superkey, srcBoot.path))\n            }\n\n            for (i in 0..<newExtrasFileName.size) {\n                patchCommand.addAll(listOf(\"-M\", newExtrasFileName[i]))\n                val extra = newExtras[i]\n                if (extra.args.isNotEmpty()) {\n                    patchCommand.addAll(listOf(\"-A\", extra.args))\n                }\n                if (extra.event.isNotEmpty()) {\n                    patchCommand.addAll(listOf(\"-V\", extra.event))\n                }\n                patchCommand.addAll(listOf(\"-T\", extra.type.desc))\n            }\n\n            for (i in 0..<existedExtras.size) {\n                val extra = existedExtras[i]\n                patchCommand.addAll(listOf(\"-E\", extra.name))\n                if (extra.args.isNotEmpty()) {\n                    patchCommand.addAll(listOf(\"-A\", extra.args))\n                }\n                if (extra.event.isNotEmpty()) {\n                    patchCommand.addAll(listOf(\"-V\", extra.event))\n                }\n                patchCommand.addAll(listOf(\"-T\", extra.type.desc))\n            }\n\n            val builder = ProcessBuilder(patchCommand)\n\n            Log.i(TAG, \"patchCommand: $patchCommand\")\n\n            var succ = false\n\n            if (isKpOld) {\n                val resultString = \"\\\"\" + patchCommand.joinToString(separator = \"\\\" \\\"\") + \"\\\"\"\n                val result = shell.newJob().add(\n                    \"export ASH_STANDALONE=1\",\n                    \"cd $patchDir\",\n                    resultString,\n                ).to(logs, logs).exec()\n                succ = result.isSuccess\n            } else {\n                builder.environment().put(\"ASH_STANDALONE\", \"1\")\n                builder.directory(patchDir)\n                builder.redirectErrorStream(true)\n\n                val process = builder.start()\n\n                Thread {\n                    BufferedReader(InputStreamReader(process.inputStream)).use { reader ->\n                        var line: String?\n                        while (reader.readLine().also { line = it } != null) {\n                            patchLog += line\n                            Log.i(TAG, \"\" + line)\n                            patchLog += \"\\n\"\n                        }\n                    }\n                }.start()\n                succ = process.waitFor() == 0\n            }\n\n            if (!succ) {\n                val msg = \" Patch failed.\"\n                error = msg\n//                error += result.err.joinToString(\"\\n\")\n                logs.add(error)\n                logs.add(\"****************************\")\n                patching = false\n                return@launch\n            }\n\n            withContext(Dispatchers.Main) {\n                APApplication.updateSuperKeyQuietly(superkey)\n            }\n\n            if (mode == PatchMode.PATCH_AND_INSTALL) {\n                logs.add(\"- Reboot to finish the installation...\")\n                needReboot = true\n                APApplication.markNeedReboot()\n            } else if (mode == PatchMode.INSTALL_TO_NEXT_SLOT) {\n                logs.add(\"- Connecting boot hal...\")\n                val bootctlStatus = shell.newJob().add(\n                    \"cd $patchDir\", \"chmod 0777 $patchDir/bootctl\", \"./bootctl hal-info\"\n                ).to(logs, logs).exec()\n                if (!bootctlStatus.isSuccess) {\n                    logs.add(\"[X] Failed to connect to boot hal, you may need switch slot manually\")\n                } else {\n                    val currSlot = shellForResult(\n                        shell, \"cd $patchDir\", \"./bootctl get-current-slot\"\n                    ).out.toString()\n                    val targetSlot = if (currSlot.contains(\"0\")) {\n                        1\n                    } else {\n                        0\n                    }\n                    logs.add(\"- Switching to next slot: $targetSlot...\")\n                    val setNextActiveSlot = shell.newJob().add(\n                        \"cd $patchDir\", \"./bootctl set-active-boot-slot $targetSlot\"\n                    ).exec()\n                    if (setNextActiveSlot.isSuccess) {\n                        logs.add(\"- Switch done\")\n                        logs.add(\"- Writing boot marker script...\")\n                        val markBootableScript = shell.newJob().add(\n                            \"mkdir -p /data/adb/post-fs-data.d && rm -rf /data/adb/post-fs-data.d/post_ota.sh && touch /data/adb/post-fs-data.d/post_ota.sh\",\n                            \"echo \\\"chmod 0777 $patchDir/bootctl\\\" > /data/adb/post-fs-data.d/post_ota.sh\",\n                            \"echo \\\"chown root:root 0777 $patchDir/bootctl\\\" > /data/adb/post-fs-data.d/post_ota.sh\",\n                            \"echo \\\"$patchDir/bootctl mark-boot-successful\\\" > /data/adb/post-fs-data.d/post_ota.sh\",\n                            \"echo >> /data/adb/post-fs-data.d/post_ota.sh\",\n                            \"echo \\\"rm -rf $patchDir\\\" >> /data/adb/post-fs-data.d/post_ota.sh\",\n                            \"echo >> /data/adb/post-fs-data.d/post_ota.sh\",\n                            \"echo \\\"rm -f /data/adb/post-fs-data.d/post_ota.sh\\\" >> /data/adb/post-fs-data.d/post_ota.sh\",\n                            \"chmod 0777 /data/adb/post-fs-data.d/post_ota.sh\",\n                            \"chown root:root /data/adb/post-fs-data.d/post_ota.sh\",\n                        ).to(logs, logs).exec()\n                        if (markBootableScript.isSuccess) {\n                            logs.add(\"- Boot marker script write done\")\n                        } else {\n                            logs.add(\"[X] Boot marker scripts write failed\")\n                        }\n                    }\n                }\n                logs.add(\"- Reboot to finish the installation...\")\n                needReboot = true\n                APApplication.markNeedReboot()\n            } else if (mode == PatchMode.PATCH_ONLY) {\n                val newBootFile = patchDir.getChildFile(\"new-boot.img\")\n                val outDir = getSafeDownloadsDir(apApp)\n                if (!outDir.exists()) outDir.mkdirs()\n                val outPath = File(outDir, outFilename)\n                val inputUri = newBootFile.getUri(apApp)\n\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n                    val outUri = createDownloadUri(apApp, outFilename)\n                    succ = insertDownload(apApp, outUri, inputUri)\n                } else {\n                    newBootFile.inputStream().copyAndClose(outPath.outputStream())\n                }\n                if (succ) {\n                    logs.add(apApp.getString(R.string.patch_output_written_to))\n                    logs.add(\" ${outPath.path}\")\n                } else {\n                    logs.add(apApp.getString(R.string.patch_write_failed))\n                }\n            }\n            logs.add(\"****************************\")\n            patchdone = true\n            patching = false\n        }\n    }\n\n    @RequiresApi(Build.VERSION_CODES.Q)\n    fun createDownloadUri(context: Context, outFilename: String): Uri? {\n        val contentValues = ContentValues().apply {\n            put(MediaStore.Downloads.DISPLAY_NAME, outFilename)\n            put(MediaStore.Downloads.MIME_TYPE, \"application/octet-stream\")\n            put(MediaStore.Downloads.IS_PENDING, 1)\n        }\n\n        val resolver = context.contentResolver\n        return resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)\n    }\n\n    @RequiresApi(Build.VERSION_CODES.Q)\n    fun insertDownload(context: Context, outUri: Uri?, inputUri: Uri): Boolean {\n        if (outUri == null) return false\n\n        try {\n            val resolver = context.contentResolver\n            SafeUriResolver.openInputStream(context, inputUri)?.use { inputStream ->\n                resolver.openOutputStream(outUri)?.use { outputStream ->\n                    inputStream.copyTo(outputStream)\n                }\n            }\n            val contentValues = ContentValues().apply {\n                put(MediaStore.Downloads.IS_PENDING, 0)\n            }\n            resolver.update(outUri, contentValues, null, null)\n\n            return true\n        } catch (_: FileNotFoundException) {\n            return false\n        }\n    }\n\n    fun File.getUri(context: Context): Uri {\n        val authority = \"${context.packageName}.fileprovider\"\n        return FileProvider.getUriForFile(context, authority, this)\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/viewmodel/ScriptLibraryViewModel.kt",
    "content": "package me.bmax.apatch.ui.viewmodel\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.data.ScriptInfo\nimport me.bmax.apatch.util.ScriptLibraryManager\nimport java.io.File\n\nclass ScriptLibraryViewModel : ViewModel() {\n\n    private val _scripts = MutableStateFlow<List<ScriptInfo>>(emptyList())\n    val scripts: StateFlow<List<ScriptInfo>> = _scripts.asStateFlow()\n\n    private val _isLoading = MutableStateFlow(false)\n    val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()\n\n    private val _error = MutableStateFlow<String?>(null)\n    val error: StateFlow<String?> = _error.asStateFlow()\n\n    init {\n        loadScripts()\n    }\n\n    fun loadScripts() {\n        viewModelScope.launch(Dispatchers.IO) {\n            _isLoading.value = true\n            _error.value = null\n            try {\n                val loadedScripts = ScriptLibraryManager.loadScripts()\n                _scripts.value = loadedScripts\n            } catch (e: Exception) {\n                _error.value = e.message\n            } finally {\n                _isLoading.value = false\n            }\n        }\n    }\n\n    fun addScript(sourceFile: File, alias: String, onSuccess: () -> Unit, onError: (String) -> Unit) {\n        viewModelScope.launch(Dispatchers.IO) {\n            _isLoading.value = true\n            _error.value = null\n            try {\n                val scriptInfo = ScriptLibraryManager.addScript(sourceFile, alias)\n                if (scriptInfo != null) {\n                    val updatedList = _scripts.value.toMutableList()\n                    updatedList.add(scriptInfo)\n                    _scripts.value = updatedList\n                    \n                    val saveSuccess = ScriptLibraryManager.saveScripts(updatedList)\n                    if (saveSuccess) {\n                        withContext(Dispatchers.Main) {\n                            onSuccess()\n                        }\n                    } else {\n                        withContext(Dispatchers.Main) {\n                            onError(\"Failed to save configuration\")\n                        }\n                    }\n                } else {\n                    withContext(Dispatchers.Main) {\n                        onError(\"Failed to copy script file\")\n                    }\n                }\n            } catch (e: Exception) {\n                withContext(Dispatchers.Main) {\n                    onError(e.message ?: \"Unknown error\")\n                }\n            } finally {\n                _isLoading.value = false\n            }\n        }\n    }\n\n    fun removeScript(scriptInfo: ScriptInfo, onSuccess: () -> Unit, onError: (String) -> Unit) {\n        viewModelScope.launch(Dispatchers.IO) {\n            _isLoading.value = true\n            _error.value = null\n            try {\n                val removeSuccess = ScriptLibraryManager.removeScript(scriptInfo)\n                if (removeSuccess) {\n                    val updatedList = _scripts.value.toMutableList()\n                    updatedList.remove(scriptInfo)\n                    _scripts.value = updatedList\n                    \n                    val saveSuccess = ScriptLibraryManager.saveScripts(updatedList)\n                    if (saveSuccess) {\n                        withContext(Dispatchers.Main) {\n                            onSuccess()\n                        }\n                    } else {\n                        withContext(Dispatchers.Main) {\n                            onError(\"Failed to save configuration\")\n                        }\n                    }\n                } else {\n                    withContext(Dispatchers.Main) {\n                        onError(\"Failed to delete script file\")\n                    }\n                }\n            } catch (e: Exception) {\n                withContext(Dispatchers.Main) {\n                    onError(e.message ?: \"Unknown error\")\n                }\n            } finally {\n                _isLoading.value = false\n            }\n        }\n    }\n\n    fun executeScript(scriptInfo: ScriptInfo, onComplete: (ScriptLibraryManager.ScriptExecutionResult) -> Unit) {\n        viewModelScope.launch(Dispatchers.IO) {\n            _isLoading.value = true\n            _error.value = null\n            try {\n                val result = ScriptLibraryManager.executeScript(scriptInfo)\n                withContext(Dispatchers.Main) {\n                    onComplete(result)\n                }\n            } catch (e: Exception) {\n                withContext(Dispatchers.Main) {\n                    onComplete(\n                        ScriptLibraryManager.ScriptExecutionResult(\n                            success = false,\n                            exitCode = -1,\n                            output = \"\",\n                            error = e.message ?: \"Execution failed\"\n                        )\n                    )\n                }\n            } finally {\n                _isLoading.value = false\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/viewmodel/SuperUserViewModel.kt",
    "content": "package me.bmax.apatch.ui.viewmodel\n\nimport android.content.ComponentName\nimport android.content.Context\nimport android.content.Intent\nimport android.content.ServiceConnection\nimport android.content.pm.ApplicationInfo\nimport android.content.pm.PackageInfo\nimport android.content.pm.PackageManager\nimport android.graphics.drawable.Drawable\nimport android.os.IBinder\nimport android.os.Parcelable\nimport android.util.Log\nimport androidx.compose.runtime.derivedStateOf\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.lifecycle.ViewModel\nimport com.topjohnwu.superuser.Shell\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport kotlinx.coroutines.withTimeoutOrNull\nimport kotlinx.parcelize.Parcelize\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.IAPRootService\nimport me.bmax.apatch.Natives\nimport me.bmax.apatch.apApp\nimport me.bmax.apatch.services.RootServices\nimport me.bmax.apatch.util.APatchCli\nimport me.bmax.apatch.util.HanziToPinyin\nimport me.bmax.apatch.util.PkgConfig\nimport me.bmax.apatch.util.SafeUriResolver\nimport me.bmax.apatch.util.getRootShell\nimport java.text.Collator\nimport java.util.Locale\nimport kotlin.concurrent.thread\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.resumeWithException\nimport kotlin.coroutines.suspendCoroutine\n\n\nimport android.net.Uri\nimport androidx.lifecycle.viewModelScope\nimport kotlinx.coroutines.launch\nimport org.json.JSONArray\nimport org.json.JSONObject\n\nclass SuperUserViewModel : ViewModel() {\n    companion object {\n        private const val TAG = \"SuperUserViewModel\"\n        private val appsLock = Any()\n        var apps by mutableStateOf<List<AppInfo>>(emptyList())\n\n        fun getAppIconDrawable(context: Context, packageName: String): Drawable? {\n            val appList = synchronized(appsLock) { apps }\n            val appDetail = appList.find { it.packageName == packageName }\n            return appDetail?.packageInfo?.applicationInfo?.loadIcon(context.packageManager)\n        }\n    }\n\n    @Parcelize\n    data class AppInfo(\n        val label: String, val packageInfo: PackageInfo, val config: PkgConfig.Config\n    ) : Parcelable {\n        val packageName: String\n            get() = packageInfo.packageName\n        val uid: Int\n            get() = packageInfo.applicationInfo!!.uid\n    }\n\n    var search by mutableStateOf(\"\")\n    var showSystemApps by mutableStateOf(false)\n    var isRefreshing by mutableStateOf(false)\n        private set\n\n    private val sortedList by derivedStateOf {\n        val comparator = compareBy<AppInfo> {\n            when {\n                it.config.allow != 0 -> 0\n                it.config.exclude == 1 -> 1\n                else -> 2\n            }\n        }.then(compareBy(Collator.getInstance(Locale.getDefault()), AppInfo::label))\n        apps.sortedWith(comparator).also {\n            isRefreshing = false\n        }\n    }\n\n    val appList by derivedStateOf {\n        sortedList.filter {\n            it.label.lowercase().contains(search.lowercase()) || it.packageName.lowercase()\n                .contains(search.lowercase()) || HanziToPinyin.getInstance()\n                .toPinyinString(it.label).contains(search.lowercase())\n        }.filter {\n            it.uid == 2000 // Always show shell\n                    || showSystemApps || it.packageInfo.applicationInfo!!.flags.and(ApplicationInfo.FLAG_SYSTEM) == 0\n        }\n    }\n\n    private suspend inline fun connectRootService(\n        crossinline onDisconnect: () -> Unit = {}\n    ): Pair<IBinder, ServiceConnection> = suspendCoroutine { continuation ->\n        val connection = object : ServiceConnection {\n            override fun onServiceDisconnected(name: ComponentName?) {\n                Log.w(TAG, \"onServiceDisconnected: $name\")\n                onDisconnect()\n            }\n\n            override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {\n                Log.i(TAG, \"onServiceConnected: $name\")\n                if (binder != null) {\n                    try {\n                        continuation.resume(binder to this)\n                    } catch (e: IllegalStateException) {\n                        Log.w(TAG, \"Service connected but continuation already resumed\", e)\n                    }\n                } else {\n                    Log.e(TAG, \"Service connected but binder is null\")\n                    // If binder is null, we can't really resume successfully, but we should unblock.\n                    // However, normally binder is not null.\n                }\n            }\n        }\n        val intent = Intent(apApp, RootServices::class.java)\n        \n        Log.d(TAG, \"Attempting to bind RootService. Shell isRoot: ${APatchCli.SHELL.isRoot}\")\n        Log.d(TAG, \"Shell info: ${APatchCli.SHELL}\")\n        \n        // Ensure binding happens on the main thread as required by Android's bindService\n        val task = RootServices.bindOrTask(\n            intent,\n            Shell.EXECUTOR,\n            connection,\n        )\n        \n        if (task == null) {\n            Log.e(TAG, \"RootServices.bindOrTask returned null\")\n            continuation.resumeWithException(IllegalStateException(\"bindOrTask returned null\"))\n        } else {\n            val shell = APatchCli.SHELL\n            Log.d(TAG, \"Executing bind task...\")\n            \n            // Execute the binding task\n            shell.execTask(task)\n        }\n    }\n\n    fun excludeAll() {\n        val modifiedConfigs = mutableListOf<PkgConfig.Config>()\n        val currentApps = apps\n\n        currentApps.forEach { app ->\n            if ((app.packageInfo.applicationInfo!!.flags and ApplicationInfo.FLAG_SYSTEM) != 0) return@forEach\n            if (app.packageName == apApp.packageName) return@forEach\n            if (app.config.allow == 0 && app.config.exclude == 0) {\n                app.config.exclude = 1\n                app.config.profile.scontext = APApplication.DEFAULT_SCONTEXT\n                Natives.setUidExclude(app.uid, 1)\n                modifiedConfigs.add(app.config)\n            }\n        }\n\n        if (modifiedConfigs.isNotEmpty()) {\n            PkgConfig.batchChangeConfigs(modifiedConfigs)\n            // Force UI update\n            apps = ArrayList(currentApps)\n        }\n    }\n\n    fun reverseExcludeAll() {\n        val modifiedConfigs = mutableListOf<PkgConfig.Config>()\n        val currentApps = apps\n\n        currentApps.forEach { app ->\n            if ((app.packageInfo.applicationInfo!!.flags and ApplicationInfo.FLAG_SYSTEM) != 0) return@forEach\n            if (app.packageName == apApp.packageName) return@forEach\n            if (app.config.allow == 0) {\n                val newExclude = if (app.config.exclude == 1) 0 else 1\n                app.config.exclude = newExclude\n                if (newExclude == 1) {\n                    app.config.profile.scontext = APApplication.DEFAULT_SCONTEXT\n                }\n                Natives.setUidExclude(app.uid, newExclude)\n                modifiedConfigs.add(app.config)\n            }\n        }\n\n        if (modifiedConfigs.isNotEmpty()) {\n            PkgConfig.batchChangeConfigs(modifiedConfigs)\n            // Force UI update\n            apps = ArrayList(currentApps)\n        }\n    }\n\n    private fun stopRootService() {\n        val intent = Intent(apApp, RootServices::class.java)\n        RootServices.stop(intent)\n    }\n\n    /**\n     * Fallback method to get packages using PackageManager when RootService fails.\n     * This is needed for devices where LibSU's RootServerMain can't initialize\n     * (e.g., ONYX e-readers with modified frameworks).\n     *\n     * Note: This only gets packages for the current user, not all users.\n     */\n    private fun getPackagesViaPackageManager(): List<PackageInfo> {\n        return try {\n            val pm = apApp.packageManager\n            pm.getInstalledPackages(PackageManager.GET_META_DATA)\n        } catch (e: Exception) {\n            Log.e(TAG, \"getPackagesViaPackageManager failed\", e)\n            emptyList()\n        }\n    }\n\n    fun backupAppList(context: Context, uri: Uri) {\n        viewModelScope.launch(Dispatchers.IO) {\n            try {\n                // Ensure we have the latest configs\n                var configs: HashMap<Int, PkgConfig.Config> = HashMap()\n                thread {\n                    Natives.su()\n                    configs = PkgConfig.readConfigs()\n                }.join()\n                \n                val jsonArray = JSONArray()\n\n                configs.values.forEach { config ->\n                    val jsonObj = JSONObject()\n                    jsonObj.put(\"pkg\", config.pkg)\n                    jsonObj.put(\"allow\", config.allow)\n                    jsonObj.put(\"exclude\", config.exclude)\n                    jsonObj.put(\"scontext\", config.profile.scontext)\n                    jsonArray.put(jsonObj)\n                }\n\n                context.contentResolver.openOutputStream(uri)?.use { outputStream ->\n                    outputStream.write(jsonArray.toString(4).toByteArray())\n                }\n                withContext(Dispatchers.Main) {\n                    me.bmax.apatch.util.ui.showToast(context, me.bmax.apatch.R.string.backup_success)\n                }\n            } catch (e: Exception) {\n                Log.e(TAG, \"Backup failed\", e)\n                withContext(Dispatchers.Main) {\n                    me.bmax.apatch.util.ui.showToast(context, \"Backup failed: ${e.message}\")\n                }\n            }\n        }\n    }\n\n    fun restoreAppList(context: Context, uri: Uri) {\n        viewModelScope.launch(Dispatchers.IO) {\n            try {\n                val jsonStr = SafeUriResolver.openInputStream(context, uri)?.use { inputStream ->\n                    inputStream.bufferedReader().use { it.readText() }\n                } ?: return@launch\n\n                val jsonArray = JSONArray(jsonStr)\n                val newConfigs = mutableListOf<PkgConfig.Config>()\n                val pm = context.packageManager\n\n                for (i in 0 until jsonArray.length()) {\n                    val jsonObj = jsonArray.getJSONObject(i)\n                    val pkgName = jsonObj.optString(\"pkg\")\n\n                    if (pkgName.isEmpty()) continue\n\n                    try {\n                        val pkgInfo = pm.getPackageInfo(pkgName, 0)\n                        val uid = pkgInfo.applicationInfo!!.uid\n\n                        val allow = jsonObj.optInt(\"allow\", 0)\n                        val exclude = jsonObj.optInt(\"exclude\", 0)\n                        val scontext = jsonObj.optString(\"scontext\", APApplication.DEFAULT_SCONTEXT)\n\n                        val profile = Natives.Profile(uid = uid, toUid = 0, scontext = scontext)\n                        val config = PkgConfig.Config(pkg = pkgName, exclude = exclude, allow = allow, profile = profile)\n\n                        newConfigs.add(config)\n\n                        // Apply to kernel immediately\n                        if (allow == 1) {\n                            Natives.grantSu(uid, 0, scontext)\n                            Natives.setUidExclude(uid, 0)\n                        } else {\n                            Natives.revokeSu(uid)\n                            if (exclude == 1) {\n                                Natives.setUidExclude(uid, 1)\n                            } else {\n                                Natives.setUidExclude(uid, 0)\n                            }\n                        }\n\n                    } catch (e: PackageManager.NameNotFoundException) {\n                        Log.w(TAG, \"Package $pkgName not found during restore\")\n                    }\n                }\n\n                if (newConfigs.isNotEmpty()) {\n                    // Start a thread to perform root operations\n                    thread {\n                        Natives.su()\n\n                        // 1. Clear ALL existing configurations in Kernel\n                        val oldConfigs = PkgConfig.readConfigs()\n                        oldConfigs.values.forEach { config ->\n                            val uid = config.profile.uid\n                            Natives.revokeSu(uid)\n                            Natives.setUidExclude(uid, 0)\n                        }\n                        \n                        // 2. Apply to kernel\n                        newConfigs.forEach { config ->\n                            val uid = config.profile.uid\n                            val allow = config.allow\n                            val exclude = config.exclude\n                            val scontext = config.profile.scontext\n                            \n                            if (allow == 1) {\n                                Natives.grantSu(uid, 0, scontext)\n                                Natives.setUidExclude(uid, 0)\n                            } else {\n                                Natives.revokeSu(uid)\n                                if (exclude == 1) {\n                                    Natives.setUidExclude(uid, 1)\n                                } else {\n                                    Natives.setUidExclude(uid, 0)\n                                }\n                            }\n                        }\n\n                        // 3. Overwrite config file\n                        PkgConfig.overwriteConfigs(newConfigs)\n                    }.join()\n\n                    fetchAppList()\n                    withContext(Dispatchers.Main) {\n                        me.bmax.apatch.util.ui.showToast(context, me.bmax.apatch.R.string.restore_success)\n                    }\n                }\n            } catch (e: Exception) {\n                Log.e(TAG, \"Restore failed\", e)\n                withContext(Dispatchers.Main) {\n                    me.bmax.apatch.util.ui.showToast(context, \"Restore failed: ${e.message}\")\n                }\n            }\n        }\n    }\n\n    suspend fun fetchAppList() {\n        isRefreshing = true\n        try {\n\n        val prefs = APApplication.sharedPreferences\n        val loadingScheme = prefs.getString(\"app_list_loading_scheme\", \"root_service\")\n\n        // Try RootService with timeout, fallback to PackageManager if it fails\n        val allPackages: List<PackageInfo> = withContext(Dispatchers.IO) {\n            if (loadingScheme == \"package_manager\") {\n                Log.i(TAG, \"Using PackageManager to load app list (user preference)\")\n                getPackagesViaPackageManager()\n            } else {\n                Log.i(TAG, \"Using RootService to load app list (user preference)\")\n                try {\n                    // Use withTimeoutOrNull to avoid hanging forever if RootService fails to connect\n                    val result = withTimeoutOrNull(10000L) {\n                        withContext(Dispatchers.Main) {\n                            connectRootService {\n                                Log.w(TAG, \"RootService disconnected\")\n                            }\n                        }\n                    }\n\n                    if (result != null) {\n                        val binder = result.first\n                        val packages = IAPRootService.Stub.asInterface(binder).getPackages(0)\n                        Log.i(TAG, \"RootService connected and retrieved ${packages.list.size} packages\")\n                        withContext(Dispatchers.Main) {\n                            stopRootService()\n                        }\n                        packages.list\n                    } else {\n                        Log.w(TAG, \"RootService connection timed out, using PackageManager fallback\")\n                        getPackagesViaPackageManager()\n                    }\n                } catch (e: Exception) {\n                    Log.w(TAG, \"RootService failed: ${e.message}\", e)\n                    getPackagesViaPackageManager()\n                }\n            }\n        }\n\n        if (allPackages.isEmpty()) {\n            Log.e(TAG, \"Failed to get package list\")\n            isRefreshing = false\n            return\n        }\n\n        withContext(Dispatchers.IO) {\n            val uids = Natives.suUids().toList()\n            Log.d(TAG, \"all allows: $uids\")\n\n            var configs: HashMap<Int, PkgConfig.Config> = HashMap()\n            thread {\n                Natives.su()\n                configs = PkgConfig.readConfigs()\n            }.join()\n\n            Log.d(TAG, \"all configs: $configs\")\n\n            val newApps = allPackages.map {\n                val appInfo = it.applicationInfo\n                val uid = appInfo!!.uid\n                val actProfile = if (uids.contains(uid)) Natives.suProfile(uid) else null\n                val config = configs.getOrDefault(\n                    uid, PkgConfig.Config(appInfo.packageName, Natives.isUidExcluded(uid), 0, Natives.Profile(uid = uid))\n                )\n                config.allow = 0\n\n                // from kernel\n                if (actProfile != null) {\n                    config.allow = 1\n                    config.profile = actProfile\n                }\n                AppInfo(\n                    label = appInfo.loadLabel(apApp.packageManager).toString(),\n                    packageInfo = it,\n                    config = config\n                )\n            }\n\n            synchronized(appsLock) {\n                apps = newApps\n            }\n        }\n\n        } finally {\n            isRefreshing = false\n        }\n    }\n\n    fun launchApp(context: Context, packageName: String): Boolean {\n        return try {\n            val pm = context.packageManager\n            val launchIntent = pm.getLaunchIntentForPackage(packageName)\n            if (launchIntent != null) {\n                context.startActivity(launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))\n                true\n            } else {\n                // Fallback to monkey command\n                val shell = getRootShell()\n                val result = shell.newJob().add(\"monkey -p $packageName -c android.intent.category.LAUNCHER 1\").exec()\n                result.isSuccess\n            }\n        } catch (e: Exception) {\n            Log.e(TAG, \"Failed to launch app: $packageName\", e)\n            false\n        }\n    }\n\n    fun forceStopApp(packageName: String): Boolean {\n        return try {\n            val shell = getRootShell()\n            val result = shell.newJob().add(\"am force-stop $packageName\").exec()\n            result.isSuccess\n        } catch (e: Exception) {\n            Log.e(TAG, \"Failed to force stop app: $packageName\", e)\n            false\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/viewmodel/ThemeStoreViewModel.kt",
    "content": "package me.bmax.apatch.ui.viewmodel\n\nimport android.content.Context\nimport android.net.Uri\nimport android.util.Log\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.ViewModelProvider\nimport androidx.lifecycle.viewModelScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.apApp\nimport me.bmax.apatch.ui.theme.ThemeManager\nimport me.bmax.apatch.util.DownloadStatus\nimport me.bmax.apatch.util.DownloadProgress\nimport me.bmax.apatch.util.FolkApiClient\nimport me.bmax.apatch.util.ThemeDownloader\nimport org.json.JSONArray\nimport java.io.File\n\nclass ThemeStoreViewModel(private val context: Context) : ViewModel() {\n    companion object {\n        private const val TAG = \"ThemeStoreViewModel\"\n        private const val THEMES_URL = \"https://folk.mysqil.com/api/themes\"\n        \n        // SharedPreferences 文件名\n        private const val PREFS_NAME = \"theme_store_prefs\"\n        private const val KEY_DOWNLOADED_THEMES = \"downloaded_themes\"\n    }\n\n    data class RemoteTheme(\n        val id: String,\n        val name: String,\n        val author: String,\n        val description: String,\n        val version: String,\n        val previewUrl: String,\n        val downloadUrl: String,\n        val type: String,\n        val source: String\n    )\n\n    data class LocalTheme(\n        val id: String,\n        val name: String,\n        val author: String,\n        val description: String,\n        val version: String,\n        val previewUrl: String,\n        val downloadUrl: String,\n        val type: String,\n        val source: String,\n        val localPath: String,\n        val previewImagePath: String,\n        val downloadProgress: Float = 1f,\n        val isDownloading: Boolean = false,\n        val downloadStatus: DownloadStatus = DownloadStatus.COMPLETED\n    )\n\n    // Original full list\n    private var allThemes = listOf<RemoteTheme>()\n\n    var themes by mutableStateOf<List<RemoteTheme>>(emptyList())\n        private set\n\n    var searchQuery by mutableStateOf(\"\")\n        private set\n\n    var filterAuthor by mutableStateOf(\"\")\n        private set\n    var filterSource by mutableStateOf(\"all\")\n        private set\n    var filterTypePhone by mutableStateOf(true)\n        private set\n    var filterTypeTablet by mutableStateOf(true)\n        private set\n\n    // 下载器\n    private val themeDownloader = ThemeDownloader(context)\n    \n    // 本地主题列表\n    var localThemes by mutableStateOf<List<LocalTheme>>(emptyList())\n        private set\n    \n    // 本地主题搜索查询\n    var localSearchQuery by mutableStateOf(\"\")\n        private set\n    \n    // 过滤后的本地主题列表\n    private var filteredLocalThemes = mutableListOf<LocalTheme>()\n    \n    // 下载任务\n    private val downloadJobs = mutableMapOf<String, Job>()\n\n    fun updateFilters(author: String, source: String, phone: Boolean, tablet: Boolean) {\n        filterAuthor = author\n        filterSource = source\n        filterTypePhone = phone\n        filterTypeTablet = tablet\n        applyFilters()\n    }\n\n    private fun applyFilters() {\n        themes = allThemes.filter { theme ->\n            val matchesSearch = if (searchQuery.isBlank()) true else {\n                theme.name.contains(searchQuery, ignoreCase = true) ||\n                theme.author.contains(searchQuery, ignoreCase = true) ||\n                theme.description.contains(searchQuery, ignoreCase = true)\n            }\n\n            val matchesAuthor = if (filterAuthor.isBlank()) true else {\n                theme.author.contains(filterAuthor, ignoreCase = true)\n            }\n\n            val matchesSource = when (filterSource) {\n                \"official\" -> theme.source == \"official\"\n                \"third_party\" -> theme.source != \"official\"\n                else -> true\n            }\n\n            val matchesType = (filterTypePhone && theme.type == \"phone\") ||\n                              (filterTypeTablet && theme.type == \"tablet\")\n\n            matchesSearch && matchesAuthor && matchesSource && matchesType\n        }\n    }\n\n    fun onSearchQueryChange(query: String) {\n        searchQuery = query\n        applyFilters()\n    }\n\n    var isRefreshing by mutableStateOf(false)\n        private set\n\n    var errorMessage by mutableStateOf<String?>(null)\n        private set\n\n    fun fetchThemes() {\n        viewModelScope.launch(Dispatchers.IO) {\n            isRefreshing = true\n            errorMessage = null\n            try {\n                val token = me.bmax.apatch.Natives.getApiToken(apApp)\n                val url = if (THEMES_URL.contains(\"?\")) \"$THEMES_URL&token=$token\" else \"$THEMES_URL?token=$token\"\n\n                val result = FolkApiClient.fetchJson(url)\n                val jsonString = result.getOrNull()\n                if (jsonString != null) {\n                    val jsonArray = JSONArray(jsonString)\n                    val list = ArrayList<RemoteTheme>()\n                    for (i in 0 until jsonArray.length()) {\n                        val obj = jsonArray.getJSONObject(i)\n                        list.add(\n                            RemoteTheme(\n                                id = obj.optString(\"id\"),\n                                name = obj.optString(\"name\"),\n                                author = obj.optString(\"author\"),\n                                description = obj.optString(\"description\"),\n                                version = obj.optString(\"version\"),\n                                previewUrl = obj.optString(\"preview_url\"),\n                                downloadUrl = obj.optString(\"download_url\"),\n                                type = obj.optString(\"type\", \"phone\"),\n                                source = obj.optString(\"source\", \"third_party\")\n                            )\n                        )\n                    }\n                    allThemes = list\n                    onSearchQueryChange(searchQuery)\n                    loadLocalThemes()\n                } else {\n                    val exception = result.exceptionOrNull()\n                    Log.e(TAG, \"Failed to fetch themes: ${exception?.message}\")\n                    errorMessage = \"Error: ${exception?.message}\"\n                }\n            } catch (e: Exception) {\n                Log.e(TAG, \"Error fetching themes\", e)\n                errorMessage = \"Error: ${e.message}\"\n            } finally {\n                isRefreshing = false\n            }\n        }\n    }\n\n    // ==================== 下载相关方法 ====================\n\n    /**\n     * 开始下载主题\n     */\n    fun startDownload(remoteTheme: RemoteTheme) {\n        val themeId = remoteTheme.id\n        \n        // 检查是否已在下载\n        if (downloadJobs.containsKey(themeId)) {\n            Log.d(TAG, \"Theme $themeId is already downloading\")\n            return\n        }\n\n        // 检查是否已下载\n        if (isThemeDownloaded(remoteTheme.id)) {\n            Log.d(TAG, \"Theme $themeId is already downloaded\")\n            return\n        }\n\n        downloadJobs[themeId] = viewModelScope.launch {\n            themeDownloader.downloadTheme(remoteTheme).collect { progress ->\n                when (progress.status) {\n                    DownloadStatus.COMPLETED -> {\n                        // 下载完成，添加到本地主题列表\n                        addLocalTheme(remoteTheme)\n                        downloadJobs.remove(themeId)\n                    }\n                    DownloadStatus.FAILED -> {\n                        Log.e(TAG, \"Download failed: ${progress.errorMessage}\")\n                        downloadJobs.remove(themeId)\n                    }\n                    else -> {\n                        // 更新下载进度\n                        Log.d(TAG, \"Download progress: ${progress.overallProgress * 100}%\")\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * 取消下载\n     */\n    fun cancelDownload(themeId: String) {\n        viewModelScope.launch {\n            themeDownloader.cancelDownload(themeId)\n            downloadJobs.remove(themeId)\n        }\n    }\n\n    /**\n     * 获取下载进度\n     */\n    fun getDownloadProgress(themeId: String): DownloadProgress? {\n        return themeDownloader.getProgress(themeId)\n    }\n\n    /**\n     * 获取下载进度 StateFlow\n     */\n    fun getDownloadProgressFlow(): StateFlow<Map<String, DownloadProgress>> {\n        return themeDownloader.downloadProgress\n    }\n\n    // ==================== 本地主题管理 ====================\n\n    /**\n     * 加载本地主题列表\n     */\n    fun loadLocalThemes() {\n        viewModelScope.launch(Dispatchers.IO) {\n            val themesDir = themeDownloader.getThemesDir()\n            val themes = mutableListOf<LocalTheme>()\n            \n            // 遍历作者目录\n            themesDir.listFiles()?.forEach { authorDir ->\n                if (authorDir.isDirectory) {\n                    // 遍历主题目录\n                    authorDir.listFiles()?.forEach { themeDir ->\n                        if (themeDir.isDirectory) {\n                            // 查找主题文件\n                            val themeFile = themeDir.listFiles { file ->\n                                file.extension == \"fpt\"\n                            }?.firstOrNull()\n                            \n                            if (themeFile != null) {\n                                val previewFile = File(themeDir, \"Theme.webp\")\n                                val metaFile = File(themeDir, \"theme_meta.json\")\n                                \n                                // 从文件名提取主题名\n                                val themeName = themeFile.nameWithoutExtension\n                                val author = authorDir.name\n                                \n                                // 优先从 JSON 文件加载元数据\n                                val localTheme = if (metaFile.exists()) {\n                                    try {\n                                        val json = org.json.JSONObject(metaFile.readText())\n                                        LocalTheme(\n                                            id = json.optString(\"id\", \"${author}_$themeName\"),\n                                            name = json.optString(\"name\", themeName),\n                                            author = json.optString(\"author\", author),\n                                            description = json.optString(\"description\", \"\"),\n                                            version = json.optString(\"version\", \"unknown\"),\n                                            previewUrl = json.optString(\"previewUrl\", \"\"),\n                                            downloadUrl = json.optString(\"downloadUrl\", \"\"),\n                                            type = json.optString(\"type\", \"phone\"),\n                                            source = json.optString(\"source\", \"third_party\"),\n                                            localPath = themeFile.absolutePath,\n                                            previewImagePath = previewFile.absolutePath\n                                        )\n                                    } catch (e: Exception) {\n                                        Log.e(TAG, \"Failed to read metadata for $themeName\", e)\n                                        // JSON 读取失败，回退到基本模式\n                                        createBasicLocalTheme(themeFile, previewFile, author, themeName)\n                                    }\n                                } else {\n                                    // 没有 JSON 文件，尝试从 API 获取信息（旧主题迁移）\n                                    val remoteTheme = allThemes.find { \n                                        sanitizeFilename(it.author) == sanitizeFilename(author) && \n                                        sanitizeFilename(it.name) == themeName\n                                    }\n                                    \n                                    if (remoteTheme != null) {\n                                        // 从 API 获取到信息，创建 JSON 文件（迁移）\n                                        themeDownloader.saveThemeMetadata(remoteTheme)\n                                        LocalTheme(\n                                            id = remoteTheme.id,\n                                            name = remoteTheme.name,\n                                            author = remoteTheme.author,\n                                            description = remoteTheme.description,\n                                            version = remoteTheme.version,\n                                            previewUrl = remoteTheme.previewUrl,\n                                            downloadUrl = remoteTheme.downloadUrl,\n                                            type = remoteTheme.type,\n                                            source = remoteTheme.source,\n                                            localPath = themeFile.absolutePath,\n                                            previewImagePath = previewFile.absolutePath\n                                        )\n                                    } else {\n                                        // 没有 API 信息，使用基本信息\n                                        createBasicLocalTheme(themeFile, previewFile, author, themeName)\n                                    }\n                                }\n                                \n                                themes.add(localTheme)\n                            }\n                        }\n                    }\n                }\n            }\n            \n            localThemes = themes\n            filteredLocalThemes = localThemes.toMutableList()\n            applyLocalFilter()\n            saveDownloadedThemes(themes)\n        }\n    }\n    \n    /**\n     * 创建基本的 LocalTheme（没有元数据时）\n     */\n    private fun createBasicLocalTheme(\n        themeFile: File,\n        previewFile: File,\n        author: String,\n        themeName: String\n    ): LocalTheme {\n        return LocalTheme(\n            id = \"${author}_$themeName\",\n            name = themeName,\n            author = author,\n            description = \"\",\n            version = \"unknown\",\n            previewUrl = \"\",\n            downloadUrl = \"\",\n            type = \"phone\",\n            source = \"third_party\",\n            localPath = themeFile.absolutePath,\n            previewImagePath = previewFile.absolutePath\n        )\n    }\n\n    /**\n     * 添加本地主题\n     */\n    private fun addLocalTheme(remoteTheme: RemoteTheme) {\n        val localTheme = LocalTheme(\n            id = remoteTheme.id,\n            name = remoteTheme.name,\n            author = remoteTheme.author,\n            description = remoteTheme.description,\n            version = remoteTheme.version,\n            previewUrl = remoteTheme.previewUrl,\n            downloadUrl = remoteTheme.downloadUrl,\n            type = remoteTheme.type,\n            source = remoteTheme.source,\n            localPath = themeDownloader.getThemeFilePath(remoteTheme.author, remoteTheme.name).absolutePath,\n            previewImagePath = themeDownloader.getPreviewImagePath(remoteTheme.author, remoteTheme.name).absolutePath\n        )\n        \n        localThemes = localThemes + localTheme\n        saveDownloadedThemes(localThemes)\n    }\n\n    /**\n     * 检查主题是否已下载\n     */\n    fun isThemeDownloaded(themeId: String): Boolean {\n        return localThemes.any { it.id == themeId }\n    }\n\n    /**\n     * 检查主题是否正在下载\n     */\n    fun isThemeDownloading(themeId: String): Boolean {\n        return downloadJobs.containsKey(themeId)\n    }\n    \n    /**\n     * 本地主题搜索查询变更\n     */\n    fun onLocalSearchQueryChange(query: String) {\n        localSearchQuery = query\n        applyLocalFilter()\n    }\n    \n    /**\n     * 应用本地主题过滤器\n     */\n    private fun applyLocalFilter() {\n        val filtered = filteredLocalThemes.filter { theme ->\n            if (localSearchQuery.isBlank()) {\n                true\n            } else {\n                theme.name.contains(localSearchQuery, ignoreCase = true) ||\n                theme.author.contains(localSearchQuery, ignoreCase = true) ||\n                theme.description.contains(localSearchQuery, ignoreCase = true)\n            }\n        }\n        localThemes = filtered\n    }\n\n    /**\n     * 应用主题\n     */\n    suspend fun applyTheme(localTheme: LocalTheme): Boolean = withContext(Dispatchers.IO) {\n        try {\n            val themeFile = File(localTheme.localPath)\n            if (!themeFile.exists()) {\n                Log.e(TAG, \"Theme file not found: ${localTheme.localPath}\")\n                return@withContext false\n            }\n            \n            val uri = Uri.fromFile(themeFile)\n            val success = ThemeManager.importTheme(context, uri)\n            \n            if (success) {\n                Log.i(TAG, \"Theme applied: ${localTheme.name}\")\n            } else {\n                Log.e(TAG, \"Failed to apply theme: ${localTheme.name}\")\n            }\n            \n            success\n        } catch (e: Exception) {\n            Log.e(TAG, \"Error applying theme\", e)\n            false\n        }\n    }\n\n    /**\n     * 删除主题\n     */\n    suspend fun deleteTheme(localTheme: LocalTheme): Boolean = withContext(Dispatchers.IO) {\n        try {\n            // 删除主题文件\n            val themeFile = File(localTheme.localPath)\n            if (themeFile.exists()) {\n                themeFile.delete()\n            }\n            \n            // 删除预览图\n            val previewFile = File(localTheme.previewImagePath)\n            if (previewFile.exists()) {\n                previewFile.delete()\n            }\n            \n            // 删除主题目录（如果为空）\n            val themeDir = File(localTheme.localPath).parentFile\n            if (themeDir != null && themeDir.isDirectory && (themeDir.listFiles()?.isEmpty() != false)) {\n                themeDir.delete()\n            }\n            \n            // 从列表中移除\n            localThemes = localThemes.filter { it.id != localTheme.id }\n            saveDownloadedThemes(localThemes)\n            \n            Log.i(TAG, \"Theme deleted: ${localTheme.name}\")\n            true\n        } catch (e: Exception) {\n            Log.e(TAG, \"Error deleting theme\", e)\n            false\n        }\n    }\n\n    /**\n     * 获取主题存储目录\n     */\n    fun getThemesDir(): File {\n        return themeDownloader.getThemesDir()\n    }\n\n    // ==================== SharedPreferences 持久化 ====================\n\n    /**\n     * 保存已下载主题列表\n     */\n    private fun saveDownloadedThemes(themes: List<LocalTheme>) {\n        val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)\n        val themeIds = themes.map { it.id }.toSet()\n        prefs.edit().putStringSet(KEY_DOWNLOADED_THEMES, themeIds).apply()\n    }\n\n    /**\n     * 清理文件名\n     */\n    private fun sanitizeFilename(filename: String): String {\n        val illegalChars = \"<>:\\\"/\\\\|?*\"\n        var result = filename\n        for (char in illegalChars) {\n            result = result.replace(char, '_')\n        }\n        return result.trim()\n    }\n\n    /**\n     * ViewModel 工厂类\n     */\n    class Factory(private val context: Context) : ViewModelProvider.Factory {\n        @Suppress(\"UNCHECKED_CAST\")\n        override fun <T : ViewModel> create(modelClass: Class<T>): T {\n            if (modelClass.isAssignableFrom(ThemeStoreViewModel::class.java)) {\n                return ThemeStoreViewModel(context.applicationContext) as T\n            }\n            throw IllegalArgumentException(\"Unknown ViewModel class\")\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/webui/AppIconUtil.kt",
    "content": "package me.bmax.apatch.ui.webui\n\nimport android.content.Context\nimport android.graphics.Bitmap\nimport android.graphics.Canvas\nimport android.graphics.drawable.Drawable\nimport android.util.LruCache\nimport androidx.core.graphics.createBitmap\nimport androidx.core.graphics.scale\nimport me.bmax.apatch.ui.viewmodel.SuperUserViewModel.Companion.getAppIconDrawable\n\nobject AppIconUtil {\n    // Limit cache to 8MB by byte size instead of icon count\n    private const val CACHE_SIZE_BYTES = 8 * 1024 * 1024\n    private val iconCache = object : LruCache<String?, Bitmap?>(CACHE_SIZE_BYTES) {\n        override fun sizeOf(key: String?, value: Bitmap?): Int {\n            return value?.allocationByteCount ?: 0\n        }\n    }\n\n    @Synchronized\n    fun loadAppIconSync(context: Context, packageName: String, sizePx: Int): Bitmap? {\n        val cached = iconCache.get(packageName)\n        if (cached != null) return cached\n\n        try {\n            val drawable = getAppIconDrawable(context, packageName) ?: return null\n            val raw = drawableToBitmap(drawable, sizePx)\n            val icon = raw.scale(sizePx, sizePx)\n            iconCache.put(packageName, icon)\n            return icon\n        } catch (_: Exception) {\n            return null\n        }\n    }\n\n    private fun drawableToBitmap(drawable: Drawable, size: Int): Bitmap {\n        val width = if (drawable.intrinsicWidth > 0) drawable.intrinsicWidth else size\n        val height = if (drawable.intrinsicHeight > 0) drawable.intrinsicHeight else size\n\n        val bmp = createBitmap(width, height)\n        val canvas = Canvas(bmp)\n        drawable.setBounds(0, 0, canvas.width, canvas.height)\n        drawable.draw(canvas)\n        return bmp\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/webui/MimeUtil.java",
    "content": "/*\n * Copyright 2023 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 me.bmax.apatch.ui.webui;\n\nimport java.net.URLConnection;\n\nclass MimeUtil {\n\n    public static String getMimeFromFileName(String fileName) {\n        if (fileName == null) {\n            return null;\n        }\n\n        // Copying the logic and mapping that Chromium follows.\n        // First we check against the OS (this is a limited list by default)\n        // but app developers can extend this.\n        // We then check against a list of hardcoded mime types above if the\n        // OS didn't provide a result.\n        String mimeType = URLConnection.guessContentTypeFromName(fileName);\n\n        if (mimeType != null) {\n            return mimeType;\n        }\n\n        return guessHardcodedMime(fileName);\n    }\n\n    // We should keep this map in sync with the lists under\n    // //net/base/mime_util.cc in Chromium.\n    // A bunch of the mime types don't really apply to Android land\n    // like word docs so feel free to filter out where necessary.\n    private static String guessHardcodedMime(String fileName) {\n        int finalFullStop = fileName.lastIndexOf('.');\n        if (finalFullStop == -1) {\n            return null;\n        }\n\n        final String extension = fileName.substring(finalFullStop + 1).toLowerCase();\n\n        switch (extension) {\n            case \"webm\":\n                return \"video/webm\";\n            case \"mpeg\":\n            case \"mpg\":\n                return \"video/mpeg\";\n            case \"mp3\":\n                return \"audio/mpeg\";\n            case \"wasm\":\n                return \"application/wasm\";\n            case \"xhtml\":\n            case \"xht\":\n            case \"xhtm\":\n                return \"application/xhtml+xml\";\n            case \"flac\":\n                return \"audio/flac\";\n            case \"ogg\":\n            case \"oga\":\n            case \"opus\":\n                return \"audio/ogg\";\n            case \"wav\":\n                return \"audio/wav\";\n            case \"m4a\":\n                return \"audio/x-m4a\";\n            case \"gif\":\n                return \"image/gif\";\n            case \"jpeg\":\n            case \"jpg\":\n            case \"jfif\":\n            case \"pjpeg\":\n            case \"pjp\":\n                return \"image/jpeg\";\n            case \"png\":\n                return \"image/png\";\n            case \"apng\":\n                return \"image/apng\";\n            case \"svg\":\n            case \"svgz\":\n                return \"image/svg+xml\";\n            case \"webp\":\n                return \"image/webp\";\n            case \"mht\":\n            case \"mhtml\":\n                return \"multipart/related\";\n            case \"css\":\n                return \"text/css\";\n            case \"html\":\n            case \"htm\":\n            case \"shtml\":\n            case \"shtm\":\n            case \"ehtml\":\n                return \"text/html\";\n            case \"js\":\n            case \"mjs\":\n                return \"application/javascript\";\n            case \"xml\":\n                return \"text/xml\";\n            case \"mp4\":\n            case \"m4v\":\n                return \"video/mp4\";\n            case \"ogv\":\n            case \"ogm\":\n                return \"video/ogg\";\n            case \"ico\":\n                return \"image/x-icon\";\n            case \"woff\":\n                return \"application/font-woff\";\n            case \"gz\":\n            case \"tgz\":\n                return \"application/gzip\";\n            case \"json\":\n                return \"application/json\";\n            case \"pdf\":\n                return \"application/pdf\";\n            case \"zip\":\n                return \"application/zip\";\n            case \"bmp\":\n                return \"image/bmp\";\n            case \"tiff\":\n            case \"tif\":\n                return \"image/tiff\";\n            default:\n                return null;\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/webui/MonetColorsProvider.kt",
    "content": "package me.bmax.apatch.ui.webui\n\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.surfaceColorAtElevation\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.unit.dp\nimport java.util.concurrent.atomic.AtomicReference\n\n/**\n * @author rifsxd\n * @date 2025/6/2.\n */\nobject MonetColorsProvider {\n\n    private val colorsCss: AtomicReference<String?> = AtomicReference(null)\n\n    fun getColorsCss(): String {\n        return colorsCss.get() ?: \"\"\n    }\n\n    @Composable\n    fun UpdateCss() {\n        val colorScheme = MaterialTheme.colorScheme\n\n        LaunchedEffect(colorScheme) {\n            val monetColors = mapOf(\n                \"primary\" to colorScheme.primary.toCssValue(),\n                \"onPrimary\" to colorScheme.onPrimary.toCssValue(),\n                \"primaryContainer\" to colorScheme.primaryContainer.toCssValue(),\n                \"onPrimaryContainer\" to colorScheme.onPrimaryContainer.toCssValue(),\n                \"inversePrimary\" to colorScheme.inversePrimary.toCssValue(),\n                \"secondary\" to colorScheme.secondary.toCssValue(),\n                \"onSecondary\" to colorScheme.onSecondary.toCssValue(),\n                \"secondaryContainer\" to colorScheme.secondaryContainer.toCssValue(),\n                \"onSecondaryContainer\" to colorScheme.onSecondaryContainer.toCssValue(),\n                \"tertiary\" to colorScheme.tertiary.toCssValue(),\n                \"onTertiary\" to colorScheme.onTertiary.toCssValue(),\n                \"tertiaryContainer\" to colorScheme.tertiaryContainer.toCssValue(),\n                \"onTertiaryContainer\" to colorScheme.onTertiaryContainer.toCssValue(),\n                \"background\" to colorScheme.background.toCssValue(),\n                \"onBackground\" to colorScheme.onBackground.toCssValue(),\n                \"surface\" to colorScheme.surface.toCssValue(),\n                \"tonalSurface\" to colorScheme.surfaceColorAtElevation(1.dp).toCssValue(),\n                \"onSurface\" to colorScheme.onSurface.toCssValue(),\n                \"surfaceVariant\" to colorScheme.surfaceVariant.toCssValue(),\n                \"onSurfaceVariant\" to colorScheme.onSurfaceVariant.toCssValue(),\n                \"surfaceTint\" to colorScheme.surfaceTint.toCssValue(),\n                \"inverseSurface\" to colorScheme.inverseSurface.toCssValue(),\n                \"inverseOnSurface\" to colorScheme.inverseOnSurface.toCssValue(),\n                \"error\" to colorScheme.error.toCssValue(),\n                \"onError\" to colorScheme.onError.toCssValue(),\n                \"errorContainer\" to colorScheme.errorContainer.toCssValue(),\n                \"onErrorContainer\" to colorScheme.onErrorContainer.toCssValue(),\n                \"outline\" to colorScheme.outline.toCssValue(),\n                \"outlineVariant\" to colorScheme.outlineVariant.toCssValue(),\n                \"scrim\" to colorScheme.scrim.toCssValue(),\n                \"surfaceBright\" to colorScheme.surfaceBright.toCssValue(),\n                \"surfaceDim\" to colorScheme.surfaceDim.toCssValue(),\n                \"surfaceContainer\" to colorScheme.surfaceContainer.toCssValue(),\n                \"surfaceContainerHigh\" to colorScheme.surfaceContainerHigh.toCssValue(),\n                \"surfaceContainerHighest\" to colorScheme.surfaceContainerHighest.toCssValue(),\n                \"surfaceContainerLow\" to colorScheme.surfaceContainerLow.toCssValue(),\n                \"surfaceContainerLowest\" to colorScheme.surfaceContainerLowest.toCssValue(),\n                \"filledTonalButtonContentColor\" to colorScheme.onPrimaryContainer.toCssValue(),\n                \"filledTonalButtonContainerColor\" to colorScheme.secondaryContainer.toCssValue(),\n                \"filledTonalButtonDisabledContentColor\" to colorScheme.onSurfaceVariant.toCssValue(),\n                \"filledTonalButtonDisabledContainerColor\" to colorScheme.surfaceVariant.toCssValue(),\n                \"filledCardContentColor\" to colorScheme.onPrimaryContainer.toCssValue(),\n                \"filledCardContainerColor\" to colorScheme.primaryContainer.toCssValue(),\n                \"filledCardDisabledContentColor\" to colorScheme.onSurfaceVariant.toCssValue(),\n                \"filledCardDisabledContainerColor\" to colorScheme.surfaceVariant.toCssValue()\n            )\n\n            colorsCss.set(monetColors.toCssVars())\n        }\n    }\n\n    private fun Map<String, String>.toCssVars(): String {\n        return buildString {\n            append(\":root {\\n\")\n            for ((k, v) in this@toCssVars) {\n                append(\" --$k: $v;\\n\")\n            }\n            append(\"}\\n\")\n        }\n    }\n\n    private fun Color.toCssValue(): String {\n        fun Float.toHex(): String {\n            return (this * 255).toInt().coerceIn(0, 255).toString(16).padStart(2, '0')\n        }\n        // Force opaque for WebUI\n        return \"#${red.toHex()}${green.toHex()}${blue.toHex()}\"\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/webui/SuFilePathHandler.java",
    "content": "package me.bmax.apatch.ui.webui;\n\nimport android.content.Context;\nimport android.util.Log;\nimport android.webkit.WebResourceResponse;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.WorkerThread;\nimport androidx.webkit.WebViewAssetLoader;\n\nimport com.topjohnwu.superuser.Shell;\nimport com.topjohnwu.superuser.io.SuFile;\nimport com.topjohnwu.superuser.io.SuFileInputStream;\n\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.zip.GZIPInputStream;\n\nimport me.bmax.apatch.util.APatchCliKt;\n\n/**\n * Handler class to open files from file system by root access\n * For more information about android storage please refer to\n * <a href=\"https://developer.android.com/guide/topics/data/data-storage\">Android Developers\n * Docs: Data and file storage overview</a>.\n * <p class=\"note\">\n * To avoid leaking user or app data to the web, make sure to choose {@code directory}\n * carefully, and assume any file under this directory could be accessed by any web page subject\n * to same-origin rules.\n * <p>\n * A typical usage would be like:\n * <pre class=\"prettyprint\">\n * File publicDir = new File(context.getFilesDir(), \"public\");\n * // Host \"files/public/\" in app's data directory under:\n * // http://appassets.androidplatform.net/public/...\n * WebViewAssetLoader assetLoader = new WebViewAssetLoader.Builder()\n *          .addPathHandler(\"/public/\", new InternalStoragePathHandler(context, publicDir))\n *          .build();\n * </pre>\n */\npublic final class SuFilePathHandler implements WebViewAssetLoader.PathHandler {\n    private static final String TAG = \"SuFilePathHandler\";\n\n    /**\n     * Default value to be used as MIME type if guessing MIME type failed.\n     */\n    public static final String DEFAULT_MIME_TYPE = \"text/plain\";\n\n    /**\n     * Forbidden subdirectories of {@link Context#getDataDir} that cannot be exposed by this\n     * handler. They are forbidden as they often contain sensitive information.\n     * <p class=\"note\">\n     * Note: Any future addition to this list will be considered breaking changes to the API.\n     */\n    private static final String[] FORBIDDEN_DATA_DIRS =\n            new String[] {\"/data/data\", \"/data/system\"};\n\n    @NonNull\n    private final File mDirectory;\n\n    private final Shell mShell;\n\n    /**\n     * Creates PathHandler for app's internal storage.\n     * The directory to be exposed must be inside either the application's internal data\n     * directory {@link Context#getDataDir} or cache directory {@link Context#getCacheDir}.\n     * External storage is not supported for security reasons, as other apps with\n     * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} may be able to modify the\n     * files.\n     * <p>\n     * Exposing the entire data or cache directory is not permitted, to avoid accidentally\n     * exposing sensitive application files to the web. Certain existing subdirectories of\n     * {@link Context#getDataDir} are also not permitted as they are often sensitive.\n     * These files are ({@code \"app_webview/\"}, {@code \"databases/\"}, {@code \"lib/\"},\n     * {@code \"shared_prefs/\"} and {@code \"code_cache/\"}).\n     * <p>\n     * The application should typically use a dedicated subdirectory for the files it intends to\n     * expose and keep them separate from other files.\n     *\n     * @param context {@link Context} that is used to access app's internal storage.\n     * @param directory the absolute path of the exposed app internal storage directory from\n     *                  which files can be loaded.\n     * @throws IllegalArgumentException if the directory is not allowed.\n     */\n    public SuFilePathHandler(@NonNull Context context, @NonNull File directory) {\n        try {\n            mDirectory = new File(getCanonicalDirPath(directory));\n            if (!isAllowedInternalStorageDir(context)) {\n                throw new IllegalArgumentException(\"The given directory \\\"\" + directory\n                        + \"\\\" doesn't exist under an allowed app internal storage directory\");\n            }\n            mShell = APatchCliKt.createRootShell(true);\n        } catch (IOException e) {\n            throw new IllegalArgumentException(\n                    \"Failed to resolve the canonical path for the given directory: \"\n                            + directory.getPath(), e);\n        }\n    }\n\n    private boolean isAllowedInternalStorageDir(@NonNull Context context) throws IOException {\n        String dir = getCanonicalDirPath(mDirectory);\n\n        for (String forbiddenPath : FORBIDDEN_DATA_DIRS) {\n            if (dir.startsWith(forbiddenPath)) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * Opens the requested file from the exposed data directory.\n     * <p>\n     * The matched prefix path used shouldn't be a prefix of a real web path. Thus, if the\n     * requested file cannot be found or is outside the mounted directory a\n     * {@link WebResourceResponse} object with a {@code null} {@link InputStream} will be\n     * returned instead of {@code null}. This saves the time of falling back to network and\n     * trying to resolve a path that doesn't exist. A {@link WebResourceResponse} with\n     * {@code null} {@link InputStream} will be received as an HTTP response with status code\n     * {@code 404} and no body.\n     * <p class=\"note\">\n     * The MIME type for the file will be determined from the file's extension using\n     * {@link java.net.URLConnection#guessContentTypeFromName}. Developers should ensure that\n     * files are named using standard file extensions. If the file does not have a\n     * recognised extension, {@code \"text/plain\"} will be used by default.\n     *\n     * @param path the suffix path to be handled.\n     * @return {@link WebResourceResponse} for the requested file.\n     */\n    @Override\n    @WorkerThread\n    @NonNull\n    public WebResourceResponse handle(@NonNull String path) {\n        if (\"internal/colors.css\".equals(path)) {\n            String css = MonetColorsProvider.INSTANCE.getColorsCss();\n            return new WebResourceResponse(\n                \"text/css\",\n                \"utf-8\",\n                new java.io.ByteArrayInputStream(css.getBytes(java.nio.charset.StandardCharsets.UTF_8))\n            );\n        }\n        try {\n            File file = getCanonicalFileIfChild(mDirectory, path);\n            if (file != null) {\n                InputStream is = openFile(file, mShell);\n                String mimeType = guessMimeType(path);\n                return new WebResourceResponse(mimeType, null, is);\n            } else {\n                Log.e(TAG, String.format(\n                        \"The requested file: %s is outside the mounted directory: %s\", path,\n                        mDirectory));\n            }\n        } catch (IOException e) {\n            Log.e(TAG, \"Error opening the requested path: \" + path, e);\n        }\n        return new WebResourceResponse(null, null, null);\n    }\n\n    public static String getCanonicalDirPath(@NonNull File file) throws IOException {\n        String canonicalPath = file.getCanonicalPath();\n        if (!canonicalPath.endsWith(\"/\")) canonicalPath += \"/\";\n        return canonicalPath;\n    }\n\n    public static File getCanonicalFileIfChild(@NonNull File parent, @NonNull String child)\n            throws IOException {\n        String parentCanonicalPath = getCanonicalDirPath(parent);\n        String childCanonicalPath = new File(parent, child).getCanonicalPath();\n        if (childCanonicalPath.startsWith(parentCanonicalPath)) {\n            return new File(childCanonicalPath);\n        }\n        return null;\n    }\n\n    @NonNull\n    private static InputStream handleSvgzStream(@NonNull String path,\n                                                @NonNull InputStream stream) throws IOException {\n        return path.endsWith(\".svgz\") ? new GZIPInputStream(stream) : stream;\n    }\n\n    public static InputStream openFile(@NonNull File file, @NonNull Shell shell) throws FileNotFoundException,\n            IOException {\n        SuFile suFile = new SuFile(file.getAbsolutePath());\n        suFile.setShell(shell);\n        InputStream fis = SuFileInputStream.open(suFile);\n        return handleSvgzStream(file.getPath(), fis);\n    }\n\n    /**\n     * Use {@link MimeUtil#getMimeFromFileName} to guess MIME type or return the\n     * {@link #DEFAULT_MIME_TYPE} if it can't guess.\n     *\n     * @param filePath path of the file to guess its MIME type.\n     * @return MIME type guessed from file extension or {@link #DEFAULT_MIME_TYPE}.\n     */\n    @NonNull\n    public static String guessMimeType(@NonNull String filePath) {\n        String mimeType = MimeUtil.getMimeFromFileName(filePath);\n        return mimeType == null ? DEFAULT_MIME_TYPE : mimeType;\n    }\n}"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/webui/WebViewInterface.kt",
    "content": "package me.bmax.apatch.ui.webui\n\nimport android.app.Activity\nimport android.content.Context\nimport android.content.pm.ApplicationInfo\nimport android.os.Handler\nimport android.os.Looper\nimport android.text.TextUtils\nimport android.view.Window\nimport android.webkit.JavascriptInterface\nimport android.webkit.WebView\nimport me.bmax.apatch.util.ui.showToast\nimport androidx.core.content.pm.PackageInfoCompat\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.WindowInsetsControllerCompat\nimport com.topjohnwu.superuser.CallbackList\nimport com.topjohnwu.superuser.ShellUtils\nimport com.topjohnwu.superuser.internal.UiThreadHandler\nimport me.bmax.apatch.ui.viewmodel.SuperUserViewModel\nimport me.bmax.apatch.util.createRootShell\nimport org.json.JSONArray\nimport org.json.JSONObject\nimport java.util.concurrent.CompletableFuture\n\nclass WebViewInterface(val context: Context, private val webView: WebView) {\n    @JavascriptInterface\n    fun exec(cmd: String): String {\n        val shell = createRootShell()\n        return ShellUtils.fastCmd(shell, cmd)\n    }\n\n    @JavascriptInterface\n    fun exec(cmd: String, callbackFunc: String) {\n        exec(cmd, null, callbackFunc)\n    }\n\n    private fun processOptions(sb: StringBuilder, options: String?) {\n        val opts = if (options == null) JSONObject() else {\n            JSONObject(options)\n        }\n\n        val cwd = opts.optString(\"cwd\")\n        if (!TextUtils.isEmpty(cwd)) {\n            sb.append(\"cd ${cwd};\")\n        }\n\n        opts.optJSONObject(\"env\")?.let { env ->\n            env.keys().forEach { key ->\n                sb.append(\"export ${key}=${env.getString(key)};\")\n            }\n        }\n    }\n\n    @JavascriptInterface\n    fun exec(\n        cmd: String, options: String?, callbackFunc: String\n    ) {\n        val finalCommand = StringBuilder()\n        processOptions(finalCommand, options)\n        finalCommand.append(cmd)\n\n        val shell = createRootShell()\n        val result = shell.newJob().add(finalCommand.toString()).to(ArrayList(), ArrayList()).exec()\n        val stdout = result.out.joinToString(separator = \"\\n\")\n        val stderr = result.err.joinToString(separator = \"\\n\")\n\n        val jsCode = \"javascript: (function() { try { ${callbackFunc}(${result.code}, ${\n            JSONObject.quote(\n                stdout\n            )\n        }, ${JSONObject.quote(stderr)}); } catch(e) { console.error(e); } })();\"\n        webView.post {\n            webView.loadUrl(jsCode)\n        }\n    }\n\n    @JavascriptInterface\n    fun spawn(command: String, args: String, options: String?, callbackFunc: String) {\n        val finalCommand = StringBuilder()\n\n        processOptions(finalCommand, options)\n\n        if (!TextUtils.isEmpty(args)) {\n            finalCommand.append(command).append(\" \")\n            JSONArray(args).let { argsArray ->\n                for (i in 0 until argsArray.length()) {\n                    finalCommand.append(argsArray.getString(i))\n                    finalCommand.append(\" \")\n                }\n            }\n        } else {\n            finalCommand.append(command)\n        }\n\n        val shell = createRootShell()\n\n        val emitData = fun(name: String, data: String) {\n            val jsCode = \"javascript: (function() { try { ${callbackFunc}.${name}.emit('data', ${\n                JSONObject.quote(\n                    data\n                )\n            }); } catch(e) { console.error('emitData', e); } })();\"\n            webView.post {\n                webView.loadUrl(jsCode)\n            }\n        }\n\n        val stdout = object : CallbackList<String>(UiThreadHandler::runAndWait) {\n            override fun onAddElement(s: String) {\n                emitData(\"stdout\", s)\n            }\n        }\n\n        val stderr = object : CallbackList<String>(UiThreadHandler::runAndWait) {\n            override fun onAddElement(s: String) {\n                emitData(\"stderr\", s)\n            }\n        }\n\n        val future = shell.newJob().add(finalCommand.toString()).to(stdout, stderr).enqueue()\n        val completableFuture = CompletableFuture.supplyAsync {\n            future.get()\n        }\n\n        completableFuture.thenAccept { result ->\n            val emitExitCode =\n                \"javascript: (function() { try { ${callbackFunc}.emit('exit', ${result.code}); } catch(e) { console.error(`emitExit error: \\${e}`); } })();\"\n            webView.post {\n                webView.loadUrl(emitExitCode)\n            }\n\n            if (result.code != 0) {\n                val emitErrCode =\n                    \"javascript: (function() { try { var err = new Error(); err.exitCode = ${result.code}; err.message = ${\n                        JSONObject.quote(\n                            result.err.joinToString(\n                                \"\\n\"\n                            )\n                        )\n                    };${callbackFunc}.emit('error', err); } catch(e) { console.error('emitErr', e); } })();\"\n                webView.post {\n                    webView.loadUrl(emitErrCode)\n                }\n            }\n        }\n    }\n\n    @JavascriptInterface\n    fun toast(msg: String) {\n        webView.post {\n            showToast(context, msg)\n        }\n    }\n\n    @JavascriptInterface\n    fun fullScreen(enable: Boolean) {\n        if (context is Activity) {\n            Handler(Looper.getMainLooper()).post {\n                if (enable) {\n                    hideSystemUI(context.window)\n                } else {\n                    showSystemUI(context.window)\n                }\n            }\n        }\n    }\n\n    @JavascriptInterface\n    fun listPackages(type: String): String {\n        val packageNames = SuperUserViewModel.apps\n            .filter { appInfo ->\n                val flags = appInfo.packageInfo.applicationInfo?.flags ?: 0\n                when (type.lowercase()) {\n                    \"system\" -> (flags and ApplicationInfo.FLAG_SYSTEM) != 0\n                    \"user\" -> (flags and ApplicationInfo.FLAG_SYSTEM) == 0\n                    else -> true\n                }\n            }\n            .map { it.packageName }\n            .sorted()\n\n        val jsonArray = JSONArray()\n        for (pkgName in packageNames) {\n            jsonArray.put(pkgName)\n        }\n        return jsonArray.toString()\n    }\n\n    @JavascriptInterface\n    fun getPackagesInfo(packageNamesJson: String): String {\n        val packageNames = JSONArray(packageNamesJson)\n        val jsonArray = JSONArray()\n        val appMap = SuperUserViewModel.apps.associateBy { it.packageName }\n        for (i in 0 until packageNames.length()) {\n            val pkgName = packageNames.getString(i)\n            val appInfo = appMap[pkgName]\n            if (appInfo != null) {\n                val pkg = appInfo.packageInfo\n                val app = pkg.applicationInfo\n                val obj = JSONObject()\n                obj.put(\"packageName\", pkg.packageName)\n                obj.put(\"versionName\", pkg.versionName ?: \"\")\n                obj.put(\"versionCode\", PackageInfoCompat.getLongVersionCode(pkg))\n                obj.put(\"appLabel\", appInfo.label)\n                obj.put(\"isSystem\", if (app != null) ((app.flags and ApplicationInfo.FLAG_SYSTEM) != 0) else JSONObject.NULL)\n                obj.put(\"uid\", app?.uid ?: JSONObject.NULL)\n                jsonArray.put(obj)\n            } else {\n                val obj = JSONObject()\n                obj.put(\"packageName\", pkgName)\n                obj.put(\"error\", \"Package not found or inaccessible\")\n                jsonArray.put(obj)\n            }\n        }\n        return jsonArray.toString()\n    }\n\n}\n\nfun hideSystemUI(window: Window) {\n    WindowInsetsControllerCompat(window, window.decorView).let { controller ->\n        controller.hide(WindowInsetsCompat.Type.systemBars())\n        controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE\n    }\n}\n\nfun showSystemUI(window: Window) =\n    WindowInsetsControllerCompat(window, window.decorView).show(WindowInsetsCompat.Type.systemBars())"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/APatchCli.kt",
    "content": "package me.bmax.apatch.util\n\nimport android.content.ContentResolver\nimport android.content.Context\nimport android.content.pm.PackageManager\nimport android.content.pm.Signature\nimport android.database.Cursor\nimport android.net.Uri\nimport android.os.Build\nimport android.os.Handler\nimport android.os.Looper\nimport android.provider.OpenableColumns\nimport android.util.Base64\nimport android.util.Log\nimport android.widget.Toast\nimport com.topjohnwu.superuser.CallbackList\nimport com.topjohnwu.superuser.Shell\nimport com.topjohnwu.superuser.ShellUtils\nimport com.topjohnwu.superuser.io.SuFile\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.APApplication.Companion.SUPERCMD\nimport me.bmax.apatch.BuildConfig\nimport me.bmax.apatch.R\nimport me.bmax.apatch.apApp\nimport me.bmax.apatch.ui.screen.MODULE_TYPE\nimport java.io.File\nimport java.io.IOException\nimport java.security.MessageDigest\nimport java.security.cert.CertificateFactory\nimport java.security.cert.X509Certificate\nimport java.util.zip.ZipFile\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.TimeoutCancellationException\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.runBlocking\nimport kotlinx.coroutines.withContext\nimport kotlinx.coroutines.withTimeout\n\nprivate const val TAG = \"APatchCli\"\nprivate const val SHELL_TIMEOUT_MS = 10_000L\n\nprivate fun getKPatchPath(): String {\n    return apApp.applicationInfo.nativeLibraryDir + File.separator + \"libkpatch.so\"\n}\n\nclass RootShellInitializer : Shell.Initializer() {\n    override fun onInit(context: Context, shell: Shell): Boolean {\n        shell.newJob().add(\n            \"export PATH=\\$PATH:/system_ext/bin:/vendor/bin:${APApplication.APATCH_FOLDER}bin\",\n            \"export BUSYBOX=${APApplication.APATCH_FOLDER}bin/busybox\"\n        ).exec()\n        return true\n    }\n}\n\nprivate fun buildWithTimeout(builder: Shell.Builder, vararg commands: String): Shell {\n    var result: Shell? = null\n    var error: Throwable? = null\n    val t = Thread {\n        try {\n            result = builder.build(*commands)\n        } catch (e: Throwable) {\n            error = e\n        }\n    }\n    t.name = \"shell-build-${commands.firstOrNull() ?: \"unknown\"}\"\n    t.start()\n    t.join(SHELL_TIMEOUT_MS)\n    if (t.isAlive) {\n        t.interrupt()\n        throw IOException(\"Shell creation timed out after ${SHELL_TIMEOUT_MS}ms: ${commands.joinToString(\" \")}\")\n    }\n    return result ?: throw (error ?: IOException(\"Shell creation failed\"))\n}\n\nfun createRootShell(globalMnt: Boolean = false): Shell {\n    Shell.enableVerboseLogging = BuildConfig.DEBUG\n    val builder = Shell.Builder.create().setInitializers(RootShellInitializer::class.java)\n\n    if (android.os.Process.myUid() == 0 && !globalMnt) {\n        try {\n            return buildWithTimeout(builder, \"sh\")\n        } catch (e: Throwable) {\n            Log.e(TAG, \"sh failed for root process\", e)\n        }\n    }\n\n    return try {\n        buildWithTimeout(\n            builder, SUPERCMD, APApplication.superKey, \"-Z\", APApplication.MAGISK_SCONTEXT\n        )\n    } catch (e: Throwable) {\n        Log.e(TAG, \"su failed: \", e)\n        return try {\n            Log.e(TAG, \"retry compat kpatch su\")\n            if (globalMnt) {\n                buildWithTimeout(\n                    builder,\n                    getKPatchPath(), APApplication.superKey, \"su\", \"-Z\", APApplication.MAGISK_SCONTEXT, \"--mount-master\"\n                )\n            }else{\n                buildWithTimeout(\n                    builder,\n                    getKPatchPath(), APApplication.superKey, \"su\", \"-Z\", APApplication.MAGISK_SCONTEXT\n                )\n            }\n        } catch (e: Throwable) {\n            Log.e(TAG, \"retry kpatch su failed: \", e)\n            return try {\n                Log.e(TAG, \"retry su: \", e)\n                if (globalMnt) {\n                    buildWithTimeout(builder, \"su\",\"-mm\")\n                }else{\n                    buildWithTimeout(builder, \"su\")\n                }\n            } catch (e: Throwable) {\n                Log.e(TAG, \"retry su failed: \", e)\n                return buildWithTimeout(builder, \"sh\")\n            }\n        }\n    }\n}\n\nobject APatchCli {\n    var SHELL: Shell = createRootShell()\n    val GLOBAL_MNT_SHELL: Shell = createRootShell(true)\n    fun refresh() {\n        val tmp = SHELL\n        SHELL = createRootShell()\n        tmp.close()\n    }\n}\n\nfun getRootShell(globalMnt: Boolean = false): Shell {\n\n    return if (globalMnt) APatchCli.GLOBAL_MNT_SHELL else {\n        APatchCli.SHELL\n    }\n}\n\ninline fun <T> withNewRootShell(\n    globalMnt: Boolean = false,\n    block: Shell.() -> T\n): T {\n    return createRootShell(globalMnt).use(block)\n}\n\nfun rootAvailable(): Boolean {\n    val shell = getRootShell()\n    return shell.isRoot\n}\n\nfun tryGetRootShell(): Shell {\n    Shell.enableVerboseLogging = BuildConfig.DEBUG\n    val builder = Shell.Builder.create()\n    return try {\n        builder.build(\n            SUPERCMD, APApplication.superKey, \"-Z\", APApplication.MAGISK_SCONTEXT\n        )\n    } catch (e: Throwable) {\n        Log.e(TAG, \"su failed: \", e)\n        return try {\n            Log.e(TAG, \"retry compat kpatch su\")\n            builder.build(\n                getKPatchPath(), APApplication.superKey, \"su\", \"-Z\", APApplication.MAGISK_SCONTEXT\n            )\n        } catch (e: Throwable) {\n            Log.e(TAG, \"retry kpatch su failed: \", e)\n            return try {\n                Log.e(TAG, \"retry su: \", e)\n                builder.build(\"su\")\n            } catch (e: Throwable) {\n                Log.e(TAG, \"retry su failed: \", e)\n                builder.build(\"sh\")\n            }\n        }\n    }\n}\n\nfun shellForResult(shell: Shell, vararg cmds: String): Shell.Result {\n    val out = ArrayList<String>()\n    val err = ArrayList<String>()\n    return shell.newJob().add(*cmds).to(out, err).exec()\n}\n\nfun rootShellForResult(vararg cmds: String): Shell.Result {\n    val out = ArrayList<String>()\n    val err = ArrayList<String>()\n    return getRootShell().newJob().add(*cmds).to(out, err).exec()\n}\n\nfun execApd(args: String, newShell: Boolean = false): Boolean {\n    return if (newShell) {\n        withNewRootShell {\n            ShellUtils.fastCmdResult(this, \"${APApplication.APD_PATH} $args\")\n        }\n    } else {\n        ShellUtils.fastCmdResult(getRootShell(), \"${APApplication.APD_PATH} $args\")\n    }\n}\n\nsuspend fun listModules(): String = withContext(Dispatchers.IO) {\n    val shell = getRootShell()\n    val out = try {\n        withTimeout(30000L) {\n            shell.newJob().add(\"${APApplication.APD_PATH} module list\").to(ArrayList(), null).exec().out\n        }\n    } catch (e: TimeoutCancellationException) {\n        Log.e(TAG, \"listModules timed out after 30 seconds\")\n        ArrayList<String>()\n    } catch (e: Exception) {\n        Log.e(TAG, \"listModules failed: ${e.message}\")\n        ArrayList<String>()\n    }\n    withNewRootShell {\n        newJob().add(\"cp /data/user/*/me.bmax.apatch/patch/ori.img /data/adb/ap/ && rm /data/user/*/me.bmax.apatch/patch/ori.img\")\n            .to(ArrayList(), null).exec()\n    }\n    return@withContext out.joinToString(\"\\n\").ifBlank { \"[]\" }\n}\n\nfun toggleModule(id: String, enable: Boolean): Boolean {\n    val cmd = if (enable) {\n        \"module enable $id\"\n    } else {\n        \"module disable $id\"\n    }\n    val result = execApd(cmd,true)\n    Log.i(TAG, \"$cmd result: $result\")\n    return result\n}\n\nfun uninstallModule(id: String): Boolean {\n    val cmd = \"module uninstall $id\"\n    val result = execApd(cmd,true)\n    Log.i(TAG, \"uninstall module $id result: $result\")\n    return result\n}\n\nfun undoUninstallModule(id: String): Boolean {\n    val cmd = \"module undo-uninstall $id\"\n    val result = execApd(cmd, true)\n    Log.i(TAG, \"undo uninstall module $id result: $result\")\n    return result\n}\n\nfun installModule(\n    uri: Uri, type: MODULE_TYPE, onFinish: (Boolean) -> Unit, onStdout: (String) -> Unit, onStderr: (String) -> Unit\n): Boolean {\n    val permissionMessage = apApp.getString(R.string.file_picker_permission_desc)\n    val inputStream = try {\n        SafeUriResolver.openInputStream(apApp, uri)\n    } catch (e: Exception) {\n        Log.e(TAG, \"Failed to open input stream\", e)\n        Handler(Looper.getMainLooper()).post {\n            me.bmax.apatch.util.ui.showToast(apApp, permissionMessage)\n        }\n        onStderr(\"$permissionMessage\\n\")\n        onFinish(false)\n        return false\n    }\n    inputStream.use { input ->\n        val file = File(apApp.cacheDir, \"module_$type.zip\")\n        file.outputStream().use { output ->\n            input.copyTo(output)\n        }\n\n        // Auto Backup Logic\n        val fileName = getFileNameFromUri(apApp, uri)\n        val backupSubDir = if (type == MODULE_TYPE.APM) \"APM\" else \"KPM\"\n        \n        // Create a temp copy for backup to prevent race condition (ENOENT) when file is deleted after install\n        val backupTempFile = File(apApp.cacheDir, \"backup_${System.currentTimeMillis()}_${file.name}\")\n        try {\n            file.copyTo(backupTempFile, overwrite = true)\n            \n            // Launch backup asynchronously without blocking the main thread\n            CoroutineScope(Dispatchers.IO).launch {\n                try {\n                    val result = ModuleBackupUtils.autoBackupModule(apApp, backupTempFile, fileName, backupSubDir)\n                    withContext(Dispatchers.Main) {\n                        if (result != null && !result.startsWith(\"Duplicate\")) {\n                            onStdout(\"Auto backup failed: $result\\n\")\n                        } else if (result != null && result.startsWith(\"Duplicate\")) {\n                            // onStdout(\"Auto backup skipped: Duplicate found\\n\")\n                        } else {\n                            onStdout(\"Auto backup success\\n\")\n                        }\n                    }\n                } catch (e: Exception) {\n                    withContext(Dispatchers.Main) {\n                        onStdout(\"Auto backup error: ${e.message}\\n\")\n                    }\n                } finally {\n                    // Clean up the temporary backup file\n                    if (backupTempFile.exists()) {\n                        backupTempFile.delete()\n                    }\n                }\n            }\n        } catch (e: Exception) {\n            Log.e(TAG, \"Failed to create temp backup file\", e)\n            onStdout(\"Auto backup failed: Could not create temp file\\n\")\n        }\n\n        val stdoutCallback: CallbackList<String?> = object : CallbackList<String?>() {\n            override fun onAddElement(s: String?) {\n                onStdout(s ?: \"\")\n            }\n        }\n\n        val stderrCallback: CallbackList<String?> = object : CallbackList<String?>() {\n            override fun onAddElement(s: String?) {\n                onStderr(s ?: \"\")\n            }\n        }\n\n        val shell = getRootShell()\n\n        var result = false\n        if(type == MODULE_TYPE.APM) {\n            val cmd = \"${APApplication.APD_PATH} module install ${file.absolutePath}\"\n            // Add timeout to prevent hanging installations\n            result = try {\n                runBlocking {\n                    withTimeout(300000L) { // 5 minute timeout\n                        shell.newJob()\n                            .add(cmd)\n                            .to(stdoutCallback, stderrCallback)\n                            .exec()\n                            .isSuccess\n                    }\n                }\n            } catch (e: TimeoutCancellationException) {\n                Log.e(TAG, \"Module installation timed out after 5 minutes\")\n                onStderr(\"Installation timed out. The module may be incompatible or hanging.\\n\")\n                false\n            } catch (e: Exception) {\n                Log.e(TAG, \"Module installation failed\", e)\n                onStderr(\"Installation failed: ${e.message}\\n\")\n                false\n            }\n        } else {\n//            ZipUtils.\n        }\n\n        Log.i(TAG, \"install $type module $uri result: $result\")\n\n        file.delete()\n\n        onFinish(result)\n        return result\n    }\n}\n\nfun runAPModuleAction(\n    moduleId: String, onStdout: (String) -> Unit, onStderr: (String) -> Unit\n): Boolean {\n    val stdoutCallback: CallbackList<String?> = object : CallbackList<String?>() {\n        override fun onAddElement(s: String?) {\n            onStdout(s ?: \"\")\n        }\n    }\n\n    val stderrCallback: CallbackList<String?> = object : CallbackList<String?>() {\n        override fun onAddElement(s: String?) {\n            onStderr(s ?: \"\")\n        }\n    }\n\n    val result = withNewRootShell{ \n        newJob().add(\"${APApplication.APD_PATH} module action $moduleId\")\n        .to(stdoutCallback, stderrCallback).exec()\n    }\n    Log.i(TAG, \"APModule runAction result: $result\")\n\n    return result.isSuccess\n}\n\nfun reboot(reason: String = \"\") {\n    if (reason == \"recovery\") {\n        // KEYCODE_POWER = 26, hide incorrect \"Factory data reset\" message\n        getRootShell().newJob().add(\"/system/bin/input keyevent 26\").exec()\n    }\n    getRootShell().newJob()\n        .add(\"/system/bin/svc power reboot $reason || /system/bin/reboot $reason\").exec()\n}\n\nfun hasMagisk(): Boolean {\n    val shell = getRootShell()\n    val result = shell.newJob().add(\"nsenter --mount=/proc/1/ns/mnt which magisk\").exec()\n    Log.i(TAG, \"has magisk: ${result.isSuccess}\")\n    return result.isSuccess\n}\n\nfun isGlobalNamespaceEnabled(): Boolean {\n    val shell = getRootShell()\n    val result = ShellUtils.fastCmd(shell, \"cat ${APApplication.GLOBAL_NAMESPACE_FILE}\")\n    Log.i(TAG, \"is global namespace enabled: $result\")\n    return result == \"1\"\n}\n\nfun setGlobalNamespaceEnabled(value: String) {\n    getRootShell().newJob().add(\"echo $value > ${APApplication.GLOBAL_NAMESPACE_FILE}\")\n        .submit { result ->\n            Log.i(TAG, \"setGlobalNamespaceEnabled result: ${result.isSuccess} [${result.out}]\")\n        }\n}\n\nfun isMagicMountEnabled(): Boolean {\n    val magicMount = SuFile(APApplication.MAGIC_MOUNT_FILE)\n    magicMount.shell = getRootShell()\n    return magicMount.exists()\n}\n\nfun setMagicMountEnabled(enable: Boolean) {\n    getRootShell().newJob().add(\"${if (enable) \"touch\" else \"rm -rf\"} ${APApplication.MAGIC_MOUNT_FILE}\")\n        .submit { result ->\n            Log.i(TAG, \"setMagicMountEnabled result: ${result.isSuccess} [${result.out}]\")\n        }\n}\n\nfun isHideServiceEnabled(): Boolean {\n    val hideService = SuFile(APApplication.HIDE_SERVICE_FILE)\n    hideService.shell = getRootShell()\n    return hideService.exists()\n}\n\nfun setHideServiceEnabled(enable: Boolean) {\n    val shell = getRootShell()\n    shell.newJob().add(\"${if (enable) \"touch\" else \"rm -rf\"} ${APApplication.HIDE_SERVICE_FILE}\")\n        .submit { result ->\n            Log.i(TAG, \"setHideServiceEnabled result: ${result.isSuccess} [${result.out}]\")\n        }\n    // 如果启用，异步执行一次 Hide 二进制（避免阻塞 UI 线程）\n    if (enable) {\n        CoroutineScope(Dispatchers.IO).launch {\n            executeHideBinary()\n        }\n    }\n}\n\nfun isUtsSpoofEnabled(): Boolean {\n    val flagFile = SuFile(APApplication.UTS_SPOOF_ENABLE_FILE)\n    flagFile.shell = getRootShell()\n    return flagFile.exists()\n}\n\nfun setUtsSpoofEnabled(enable: Boolean) {\n    val shell = getRootShell()\n    shell.newJob().add(\"${if (enable) \"touch\" else \"rm -f\"} ${APApplication.UTS_SPOOF_ENABLE_FILE}\")\n        .exec()\n}\n\nfun writeUtsSpoofConfig(release: String, version: String) {\n    val shell = getRootShell()\n    val escapedRelease = release.replace(\"\\\\\", \"\\\\\\\\\").replace(\"\\\"\", \"\\\\\\\"\").replace(\"'\", \"'\\\\''\")\n    val escapedVersion = version.replace(\"\\\\\", \"\\\\\\\\\").replace(\"\\\"\", \"\\\\\\\"\").replace(\"'\", \"'\\\\''\")\n    val json = \"{\\\"release\\\":\\\"$escapedRelease\\\",\\\"version\\\":\\\"$escapedVersion\\\"}\"\n    shell.newJob().add(\"echo '$json' > ${APApplication.UTS_SPOOF_CONFIG_FILE}\")\n        .exec()\n}\n\nfun removeUtsSpoofConfig() {\n    val shell = getRootShell()\n    shell.newJob().add(\"rm -f ${APApplication.UTS_SPOOF_CONFIG_FILE}\").exec()\n}\n\nfun isPathHideEnabled(): Boolean {\n    val flagFile = SuFile(APApplication.PATHHIDE_ENABLE_FILE)\n    flagFile.shell = getRootShell()\n    return flagFile.exists()\n}\n\nfun setPathHideEnabled(enable: Boolean) {\n    val shell = getRootShell()\n    shell.newJob().add(\"mkdir -p ${APApplication.PATHHIDE_DIR}\").exec()\n    shell.newJob().add(\"${if (enable) \"touch\" else \"rm -f\"} ${APApplication.PATHHIDE_ENABLE_FILE}\")\n        .exec()\n}\n\nfun writePathHidePaths(paths: String) {\n    val shell = getRootShell()\n    shell.newJob().add(\"mkdir -p ${APApplication.PATHHIDE_DIR}\").exec()\n    val escapedPaths = paths.replace(\"'\", \"'\\\\''\")\n    shell.newJob().add(\"echo -n '$escapedPaths' > ${APApplication.PATHHIDE_PATHS_FILE}\")\n        .exec()\n}\n\nfun readPathHidePaths(): String {\n    val shell = getRootShell()\n    return ShellUtils.fastCmd(shell, \"cat ${APApplication.PATHHIDE_PATHS_FILE} 2>/dev/null\") ?: \"\"\n}\n\nfun writePathHideUids(uids: String) {\n    val shell = getRootShell()\n    shell.newJob().add(\"mkdir -p ${APApplication.PATHHIDE_DIR}\").exec()\n    val escapedUids = uids.replace(\"'\", \"'\\\\''\")\n    shell.newJob().add(\"echo -n '$escapedUids' > ${APApplication.PATHHIDE_UIDS_FILE}\")\n        .exec()\n}\n\nfun readPathHideUids(): String {\n    val shell = getRootShell()\n    return ShellUtils.fastCmd(shell, \"cat ${APApplication.PATHHIDE_UIDS_FILE} 2>/dev/null\") ?: \"\"\n}\n\nfun isPathHideUidModeEnabled(): Boolean {\n    val file = SuFile(APApplication.PATHHIDE_UID_MODE_FILE)\n    file.shell = getRootShell()\n    return file.exists()\n}\n\nfun isPathHideFilterSystemEnabled(): Boolean {\n    val file = SuFile(APApplication.PATHHIDE_FILTER_SYSTEM_FILE)\n    file.shell = getRootShell()\n    return file.exists()\n}\n\nfun setPathHideUidMode(enable: Boolean) {\n    val shell = getRootShell()\n    shell.newJob().add(\"mkdir -p ${APApplication.PATHHIDE_DIR}\").exec()\n    shell.newJob().add(\"${if (enable) \"touch\" else \"rm -f\"} ${APApplication.PATHHIDE_UID_MODE_FILE}\")\n        .exec()\n}\n\nfun setPathHideFilterSystem(enable: Boolean) {\n    val shell = getRootShell()\n    shell.newJob().add(\"mkdir -p ${APApplication.PATHHIDE_DIR}\").exec()\n    shell.newJob().add(\"${if (enable) \"touch\" else \"rm -f\"} ${APApplication.PATHHIDE_FILTER_SYSTEM_FILE}\")\n        .exec()\n}\n\nfun setNetIsolateEnabled(enable: Boolean) {\n    val shell = getRootShell()\n    shell.newJob().add(\"mkdir -p ${APApplication.NETISOLATE_DIR}\").exec()\n    shell.newJob().add(\"${if (enable) \"touch\" else \"rm -f\"} ${APApplication.NETISOLATE_ENABLE_FILE}\")\n        .exec()\n}\n\nfun writeNetIsolateUids(uids: String) {\n    val shell = getRootShell()\n    shell.newJob().add(\"mkdir -p ${APApplication.NETISOLATE_DIR}\").exec()\n    val escapedUids = uids.replace(\"'\", \"'\\\\''\")\n    shell.newJob().add(\"echo -n '$escapedUids' > ${APApplication.NETISOLATE_UIDS_FILE}\").exec()\n}\n\nfun isNetIsolateEnabled(): Boolean {\n    val flagFile = SuFile(APApplication.NETISOLATE_ENABLE_FILE)\n    flagFile.shell = getRootShell()\n    return flagFile.exists()\n}\n\nfun readNetIsolateUids(): String {\n    val shell = getRootShell()\n    return ShellUtils.fastCmd(shell, \"cat ${APApplication.NETISOLATE_UIDS_FILE} 2>/dev/null\") ?: \"\"\n}\n\nfun executeHideBinary(): Boolean {\n    val shell = getRootShell()\n    val context = apApp.applicationContext\n\n    // 确保 fp/bin 目录存在\n    shell.newJob().add(\"mkdir -p /data/adb/fp/bin\").exec()\n\n    // 从 assets 复制 fpd 二进制文件到可执行目录\n    try {\n        val fpdAsset = context.assets.open(\"Service/fpd\")\n        val tempFile = File(context.cacheDir, \"fpd_temp\")\n        tempFile.outputStream().use { output ->\n            fpdAsset.copyTo(output)\n        }\n        fpdAsset.close()\n\n        // 复制到目标目录并设置权限，然后执行\n        val cmds = arrayOf(\n            \"cp ${tempFile.absolutePath} ${APApplication.HIDE_BINARY_PATH}\",\n            \"chmod 755 ${APApplication.HIDE_BINARY_PATH}\",\n            \"restorecon ${APApplication.HIDE_BINARY_PATH}\",\n            \"${APApplication.HIDE_BINARY_PATH} -hide\"\n        )\n\n        val result = shell.newJob().add(*cmds).exec()\n        tempFile.delete()\n\n        Log.i(TAG, \"executeHideBinary result: ${result.isSuccess} [${result.out}]\")\n        return result.isSuccess\n    } catch (e: Exception) {\n        Log.e(TAG, \"executeHideBinary failed: ${e.message}\", e)\n        return false\n    }\n}\n\nfun isUmountServiceEnabled(): Boolean {\n    val umountService = SuFile(APApplication.UMOUNT_SERVICE_FILE)\n    umountService.shell = getRootShell()\n    return umountService.exists()\n}\n\nfun setUmountServiceEnabled(enabled: Boolean): Boolean {\n    val shell = getRootShell()\n    val result = if (enabled) {\n        shell.newJob().add(\"touch ${APApplication.UMOUNT_SERVICE_FILE}\").exec().isSuccess\n    } else {\n        shell.newJob().add(\"rm -rf ${APApplication.UMOUNT_SERVICE_FILE}\").exec().isSuccess\n    }\n\n    // 如果启用，立即执行一次 Umount 二进制复制\n    if (enabled) {\n        executeUmountBinary()\n    }\n\n    return result\n}\n\nfun executeUmountBinary(): Boolean {\n    val shell = getRootShell()\n    val context = apApp.applicationContext\n\n    // 确保 fp/bin 目录存在\n    shell.newJob().add(\"mkdir -p /data/adb/fp/bin\").exec()\n\n    try {\n        val fpdAsset = context.assets.open(\"Service/fpd\")\n        val tempFile = File(context.cacheDir, \"fpd_temp\")\n        tempFile.outputStream().use { output ->\n            fpdAsset.copyTo(output)\n        }\n        fpdAsset.close()\n\n        val cmds = arrayOf(\n            \"cp ${tempFile.absolutePath} ${APApplication.UMOUNT_BINARY_PATH}\",\n            \"chmod 755 ${APApplication.UMOUNT_BINARY_PATH}\",\n            \"restorecon ${APApplication.UMOUNT_BINARY_PATH}\",\n            \"${APApplication.UMOUNT_BINARY_PATH} -umount\"\n        )\n\n        val result = shell.newJob().add(*cmds).exec()\n        tempFile.delete()\n\n        Log.i(TAG, \"executeUmountBinary result: ${result.isSuccess} [${result.out}]\")\n        return result.isSuccess\n    } catch (e: Exception) {\n        Log.e(TAG, \"executeUmountBinary failed: ${e.message}\", e)\n        return false\n    }\n}\n\n/**\n * Get current SELinux mode\n * @return \"Enforcing\" | \"Permissive\" | \"Unknown\"\n */\nfun getSELinuxMode(): String {\n    val shell = getRootShell()\n    val result = ShellUtils.fastCmd(shell, \"getenforce\")\n    Log.i(TAG, \"SELinux mode: $result\")\n    return when (result.uppercase()) {\n        \"ENFORCING\" -> \"Enforcing\"\n        \"PERMISSIVE\" -> \"Permissive\"\n        else -> \"Unknown\"\n    }\n}\n\n/**\n * Set SELinux mode\n * @param enforcing true=Enforcing, false=Permissive\n * @return whether the operation succeeded\n */\nfun setSELinuxMode(enforcing: Boolean): Boolean {\n    val shell = getRootShell()\n    val cmd = \"setenforce ${if (enforcing) \"1\" else \"0\"}\"\n    val result = shell.newJob().add(cmd).exec()\n    Log.i(TAG, \"Set SELinux to ${if (enforcing) \"Enforcing\" else \"Permissive\"}: ${result.isSuccess}\")\n    return result.isSuccess\n}\n\nfun getFileNameFromUri(context: Context, uri: Uri): String? {\n    var fileName: String? = null\n    val contentResolver: ContentResolver = context.contentResolver\n    val cursor: Cursor? = contentResolver.query(uri, null, null, null, null)\n    cursor?.use {\n        if (it.moveToFirst()) {\n            fileName = it.getString(it.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))\n        }\n    }\n    return fileName\n}\n\nfun getZygiskImplement(): String {\n    val zygiskModuleIds = listOf(\n        \"zygisksu\",\n        \"zygisknext\",\n        \"rezygisk\",\n        \"neozygisk\",\n        \"shirokozygisk\"\n    )\n\n    for (moduleId in zygiskModuleIds) {\n        val shell = getRootShell()\n        \n        // 检查是否存在\n        if (!ShellUtils.fastCmdResult(shell, \"test -d /data/adb/modules/$moduleId\")) continue\n\n        // 忽略禁用/即将删除\n        if (ShellUtils.fastCmdResult(shell, \"test -f /data/adb/modules/$moduleId/disable\") || \n            ShellUtils.fastCmdResult(shell, \"test -f /data/adb/modules/$moduleId/remove\")) continue\n\n        // 读取prop\n        val propContent = shell.newJob().add(\"cat /data/adb/modules/$moduleId/module.prop\").to(ArrayList(), null).exec().out\n        if (propContent.isEmpty()) continue\n\n        try {\n            val prop = java.util.Properties()\n            // 将List<String>转换为String Reader，或者手动解析\n            // 为简单起见，这里假设内容不多，合并成字符串处理\n            val propString = propContent.joinToString(\"\\n\")\n            prop.load(java.io.StringReader(propString))\n\n            val name = prop.getProperty(\"name\")\n            if (!name.isNullOrEmpty()) {\n                Log.i(TAG, \"Zygisk implement: $name\")\n                return name\n            }\n        } catch (e: Exception) {\n            Log.e(TAG, \"Failed to parse module.prop for $moduleId\", e)\n        }\n    }\n\n    Log.i(TAG, \"Zygisk implement: None\")\n    return \"None\"\n}\n\nfun getMetaModuleImplement(): String {\n    try {\n        val shell = getRootShell()\n        if (!ShellUtils.fastCmdResult(shell, \"test -f /data/adb/metamodule/module.prop\")) {\n             return \"None\"\n        }\n        val propContent = shell.newJob().add(\"cat /data/adb/metamodule/module.prop\").to(ArrayList(), null).exec().out\n        if (propContent.isEmpty()) return \"None\"\n        \n        val prop = java.util.Properties()\n        val propString = propContent.joinToString(\"\\n\")\n        prop.load(java.io.StringReader(propString))\n        \n        return prop.getProperty(\"name\") ?: \"Unknown\"\n    } catch (e: Exception) {\n        Log.e(TAG, \"getMetaModuleImplement failed\", e)\n        return \"None\"\n    }\n}\n\nfun getMountImplement(): String {\n    if (isMagicMountEnabled()) {\n        return \"Folk Mount API\"\n    }\n    val metaModule = getMetaModuleImplement()\n    if (metaModule != \"None\") {\n        return metaModule\n    }\n    return \"None\"\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/APatchKeyHelper.java",
    "content": "package me.bmax.apatch.util;\n\nimport android.content.SharedPreferences;\nimport android.security.keystore.KeyGenParameterSpec;\nimport android.security.keystore.KeyProperties;\nimport android.util.Base64;\nimport android.util.Log;\n\nimport java.nio.charset.StandardCharsets;\nimport java.security.KeyStore;\nimport java.security.SecureRandom;\nimport java.security.spec.AlgorithmParameterSpec;\n\nimport javax.crypto.Cipher;\nimport javax.crypto.KeyGenerator;\nimport javax.crypto.SecretKey;\nimport javax.crypto.spec.GCMParameterSpec;\n\n\npublic class APatchKeyHelper {\n    protected static final String SUPER_KEY = \"super_key\";\n    protected static final String SUPER_KEY_ENC = \"super_key_enc\";\n    private static final String TAG = \"APatchSecurityHelper\";\n    private static final String ANDROID_KEYSTORE = \"AndroidKeyStore\";\n    private static final String SKIP_STORE_SUPER_KEY = \"skip_store_super_key\";\n    private static final String SUPER_KEY_IV = \"super_key_iv\";\n    private static final String KEY_ALIAS = \"APatchSecurityKey\";\n    private static final String ENCRYPT_MODE = \"AES/GCM/NoPadding\";\n    private static SharedPreferences prefs = null;\n\n    static {\n        try {\n            KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);\n            keyStore.load(null);\n            if (!keyStore.containsAlias(KEY_ALIAS)) {\n                generateSecretKey();\n            }\n        } catch (Exception e) {\n            Log.e(TAG, \"Failed to checkAndGenerateSecretKey\", e);\n        }\n    }\n\n    public static void setSharedPreferences(SharedPreferences sp) {\n        prefs = sp;\n    }\n\n    private static void generateSecretKey() {\n        try {\n            KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);\n            keyStore.load(null);\n\n            if (!keyStore.containsAlias(KEY_ALIAS)) {\n                KeyGenerator keyGenerator = KeyGenerator.getInstance(\n                        KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE);\n\n                AlgorithmParameterSpec spec = new KeyGenParameterSpec.Builder(\n                        KEY_ALIAS,\n                        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)\n                        .setBlockModes(KeyProperties.BLOCK_MODE_GCM)\n                        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)\n                        .setRandomizedEncryptionRequired(false)\n                        .build();\n\n                keyGenerator.init(spec);\n                keyGenerator.generateKey();\n            }\n        } catch (Exception e) {\n            Log.e(TAG, \"Failed to generateSecretKey\", e);\n        }\n    }\n\n    private static String getRandomIV() {\n        String randIV = prefs.getString(SUPER_KEY_IV, null);\n        if (randIV == null) {\n            SecureRandom secureRandom = new SecureRandom();\n            byte[] generated = secureRandom.generateSeed(12);\n            randIV = Base64.encodeToString(generated, Base64.DEFAULT);\n            prefs.edit().putString(SUPER_KEY_IV, randIV).apply();\n        }\n        return randIV;\n    }\n\n    private static String encrypt(String orig) {\n        try {\n            KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);\n            keyStore.load(null);\n            SecretKey secretKey = (SecretKey) keyStore.getKey(KEY_ALIAS, null);\n\n            Cipher cipher = Cipher.getInstance(ENCRYPT_MODE);\n            cipher.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(128, Base64.decode(getRandomIV(), Base64.DEFAULT)));\n\n            return Base64.encodeToString(cipher.doFinal(orig.getBytes(StandardCharsets.UTF_8)), Base64.DEFAULT);\n        } catch (Exception e) {\n            Log.e(TAG, \"Failed to encrypt: \", e);\n            return null;\n        }\n    }\n\n    private static String decrypt(String encryptedData) {\n        try {\n            KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);\n            keyStore.load(null);\n            SecretKey secretKey = (SecretKey) keyStore.getKey(KEY_ALIAS, null);\n\n            Cipher cipher = Cipher.getInstance(ENCRYPT_MODE);\n            cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(128, Base64.decode(getRandomIV(), Base64.DEFAULT)));\n\n            return new String(cipher.doFinal(Base64.decode(encryptedData, Base64.DEFAULT)), StandardCharsets.UTF_8);\n        } catch (Exception e) {\n            Log.e(TAG, \"Failed to decrypt\", e);\n            return null;\n        }\n    }\n\n    public static boolean shouldSkipStoreSuperKey() {\n        return prefs.getInt(SKIP_STORE_SUPER_KEY, 0) != 0;\n    }\n\n    public static void clearConfigKey() {\n        prefs.edit().remove(SUPER_KEY).apply();\n        prefs.edit().remove(SUPER_KEY_ENC).apply();\n        prefs.edit().remove(SUPER_KEY_IV).apply();\n    }\n\n    public static void setShouldSkipStoreSuperKey(boolean should) {\n        clearConfigKey();\n        prefs.edit().putInt(SKIP_STORE_SUPER_KEY, should ? 1 : 0).apply();\n    }\n\n    public static String readSPSuperKey() {\n        Log.d(TAG, \"Reading superKey from SharedPreferences\");\n        String encKey = prefs.getString(SUPER_KEY_ENC, \"\");\n        Log.d(TAG, \"Encrypted key length: \" + encKey.length());\n        \n        if (!encKey.isEmpty()) {\n            Log.d(TAG, \"Decrypting superKey\");\n            String decryptedKey = decrypt(encKey);\n            if (decryptedKey != null) {\n                Log.d(TAG, \"SuperKey decrypted successfully, length: \" + decryptedKey.length());\n                return decryptedKey;\n            } else {\n                Log.w(TAG, \"SuperKey decryption failed, clearing corrupted encrypted key from SharedPreferences\");\n                prefs.edit()\n                    .remove(SUPER_KEY_ENC)\n                    .remove(SUPER_KEY_IV)\n                    .apply();\n            }\n        }\n\n        Log.d(TAG, \"Trying deprecated plaintext key\");\n        @Deprecated()\n        String key = prefs.getString(SUPER_KEY, \"\");\n        if (!key.isEmpty()) {\n            Log.d(TAG, \"Found deprecated key, migrating to encrypted storage\");\n            writeSPSuperKey(key);\n            prefs.edit().remove(SUPER_KEY).apply();\n            Log.d(TAG, \"Key migration completed\");\n        } else {\n            Log.d(TAG, \"No superKey found in SharedPreferences\");\n        }\n        return key;\n    }\n\n    public static void writeSPSuperKey(String key) {\n        if (shouldSkipStoreSuperKey()) return;\n        key = APatchKeyHelper.encrypt(key);\n        prefs.edit().putString(SUPER_KEY_ENC, key).apply();\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/AppData.kt",
    "content": "package me.bmax.apatch.util\n\nimport android.os.SystemClock\nimport android.util.Log\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.Natives\nimport org.json.JSONArray\n\n/**\n * AppData - Data management center for badge counts\n * Manages counts for superuser and APM modules\n */\nobject AppData {\n    private const val TAG = \"AppData\"\n    private const val NATIVE_CALL_TIMEOUT_MS = 5_000L\n\n    /**\n     * Run a potentially blocking native call with a timeout fallback.\n     * Since JNI syscalls cannot be cancelled by coroutines, we use a thread + join(timeout).\n     */\n    private fun <T> runNativeWithTimeout(timeoutMs: Long, defaultValue: T, block: () -> T): T {\n        var result: T? = null\n        val t = Thread { result = block() }\n        t.name = \"native-call-timeout\"\n        t.start()\n        t.join(timeoutMs)\n        return if (t.isAlive) {\n            Log.w(TAG, \"Native call timed out after ${timeoutMs}ms\")\n            defaultValue\n        } else {\n            result ?: defaultValue\n        }\n    }\n\n    object DataRefreshManager {\n        // Private state flows for counts\n        private val _superuserCount = MutableStateFlow(0)\n        private val _apmModuleCount = MutableStateFlow(0)\n        private val _kernelModuleCount = MutableStateFlow(0)\n\n        private var lastRefreshAt = 0L\n\n        // Public read-only state flows\n        val superuserCount: StateFlow<Int> = _superuserCount.asStateFlow()\n        val apmModuleCount: StateFlow<Int> = _apmModuleCount.asStateFlow()\n        val kernelModuleCount: StateFlow<Int> = _kernelModuleCount.asStateFlow()\n\n        /**\n         * Refresh all data counts\n         */\n        suspend fun refreshData(\n            enableSuperUser: Boolean,\n            enableApm: Boolean,\n            enableKernel: Boolean,\n            minIntervalMs: Long = 15000L,\n            force: Boolean = false\n        ) = withContext(Dispatchers.IO) {\n            if (!enableSuperUser && !enableApm && !enableKernel) {\n                return@withContext\n            }\n\n            val now = SystemClock.elapsedRealtime()\n            if (!force && now - lastRefreshAt < minIntervalMs) {\n                return@withContext\n            }\n            lastRefreshAt = now\n\n            if (enableSuperUser) {\n                val count = getSuperuserCount()\n                if (_superuserCount.value != count) {\n                    _superuserCount.value = count\n                }\n            }\n\n            if (enableApm) {\n                val count = getApmModuleCount()\n                if (_apmModuleCount.value != count) {\n                    _apmModuleCount.value = count\n                }\n            }\n\n            if (enableKernel) {\n                val count = getKernelModuleCount()\n                if (_kernelModuleCount.value != count) {\n                    _kernelModuleCount.value = count\n                }\n            }\n        }\n\n \n        suspend fun ensureCountsLoaded(force: Boolean = false) = withContext(Dispatchers.IO) {\n\n            try {\n                val suCount = getSuperuserCount()\n                if (_superuserCount.value != suCount) {\n                    _superuserCount.value = suCount\n                }\n                \n                val apmCount = getApmModuleCount()\n                if (_apmModuleCount.value != apmCount) {\n                    _apmModuleCount.value = apmCount\n                }\n                \n                val kpmCount = getKernelModuleCount()\n                if (_kernelModuleCount.value != kpmCount) {\n                    _kernelModuleCount.value = kpmCount\n                }\n            } catch (e: Exception) {\n                Log.e(TAG, \"Failed to ensure counts loaded\", e)\n            }\n        }\n    }\n\n    /**\n     * Get superuser count\n     * Note: Minus 1 to exclude the APatch manager itself from the count\n     */\n    private fun getSuperuserCount(): Int {\n        return runNativeWithTimeout(NATIVE_CALL_TIMEOUT_MS, 0) {\n            try {\n                val uids = Natives.suUids()\n                (uids.size - 1).coerceAtLeast(0)\n            } catch (e: Exception) {\n                Log.e(TAG, \"Failed to get superuser count\", e)\n                0\n            }\n        }\n    }\n\n    /**\n     * Get APM module count\n     */\n    private suspend fun getApmModuleCount(): Int {\n        return try {\n            val result = listModules()\n            val array = JSONArray(result)\n            array.length()\n        } catch (e: Exception) {\n            Log.e(TAG, \"Failed to get APM module count\", e)\n            0\n        }\n    }\n\n    /**\n     * Get kernel module count\n     */\n    private fun getKernelModuleCount(): Int {\n        return runNativeWithTimeout(NATIVE_CALL_TIMEOUT_MS, 0) {\n            try {\n                Natives.kernelPatchModuleNum().toInt()\n            } catch (e: Exception) {\n                Log.e(TAG, \"Failed to get kernel module count\", e)\n                0\n            }\n        }\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/BackupLogManager.kt",
    "content": "package me.bmax.apatch.util\n\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.apApp\nimport java.io.File\nimport java.text.SimpleDateFormat\nimport java.util.Date\nimport java.util.Locale\n\nobject BackupLogManager {\n    private const val LOG_FILE_NAME = \"backup_log.log\"\n    private val logFile: File by lazy { File(apApp.filesDir, LOG_FILE_NAME) }\n    private val dateFormat = SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\", Locale.getDefault())\n\n    suspend fun log(message: String) {\n        withContext(Dispatchers.IO) {\n            try {\n                val timestamp = dateFormat.format(Date())\n                val logEntry = \"[$timestamp] $message\\n\"\n                logFile.appendText(logEntry)\n            } catch (e: Exception) {\n                e.printStackTrace()\n            }\n        }\n    }\n\n    suspend fun readLogs(): String {\n        return withContext(Dispatchers.IO) {\n            try {\n                if (logFile.exists()) {\n                    logFile.readText()\n                } else {\n                    \"\"\n                }\n            } catch (e: Exception) {\n                \"Error reading logs: ${e.message}\"\n            }\n        }\n    }\n\n    suspend fun clearLogs() {\n        withContext(Dispatchers.IO) {\n            try {\n                if (logFile.exists()) {\n                    logFile.writeText(\"\")\n                }\n            } catch (e: Exception) {\n                e.printStackTrace()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/BiometricUtils.kt",
    "content": "package me.bmax.apatch.util\n\nimport android.content.Context\nimport androidx.biometric.BiometricManager\nimport androidx.biometric.BiometricPrompt\nimport androidx.core.content.ContextCompat\nimport androidx.fragment.app.FragmentActivity\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.suspendCancellableCoroutine\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.R\nimport kotlin.coroutines.resume\n\nobject BiometricUtils {\n\n    suspend fun authenticate(activity: FragmentActivity): Boolean {\n        return withContext(Dispatchers.Main) {\n            suspendCancellableCoroutine { cont ->\n                val executor = ContextCompat.getMainExecutor(activity)\n                val biometricPrompt = BiometricPrompt(activity, executor,\n                    object : BiometricPrompt.AuthenticationCallback() {\n                        override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {\n                            super.onAuthenticationSucceeded(result)\n                            if (cont.isActive) {\n                                cont.resume(true)\n                            }\n                        }\n\n                        override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {\n                            super.onAuthenticationError(errorCode, errString)\n                            if (cont.isActive) {\n                                cont.resume(false)\n                            }\n                        }\n\n                        override fun onAuthenticationFailed() {\n                            super.onAuthenticationFailed()\n                            // Don't resume here, let the user try again\n                        }\n                    })\n\n                val promptInfo = BiometricPrompt.PromptInfo.Builder()\n                    .setTitle(activity.getString(R.string.action_biometric))\n                    .setSubtitle(activity.getString(R.string.msg_biometric))\n                    .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG or BiometricManager.Authenticators.DEVICE_CREDENTIAL)\n                    .build()\n\n                biometricPrompt.authenticate(promptInfo)\n\n                cont.invokeOnCancellation {\n                    biometricPrompt.cancelAuthentication()\n                }\n            }\n        }\n    }\n\n    fun isBiometricAvailable(context: Context): Boolean {\n        val biometricManager = BiometricManager.from(context)\n        return biometricManager.canAuthenticate(\n            BiometricManager.Authenticators.BIOMETRIC_STRONG or\n                    BiometricManager.Authenticators.DEVICE_CREDENTIAL\n        ) == BiometricManager.BIOMETRIC_SUCCESS\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/BulkInstallManager.kt",
    "content": "package me.bmax.apatch.util\n\nimport android.net.Uri\n\nobject BulkInstallManager {\n    private val queue = ArrayList<Uri>()\n\n    fun setQueue(uris: List<Uri>) {\n        queue.clear()\n        queue.addAll(uris)\n    }\n\n    fun hasNext(): Boolean {\n        return queue.isNotEmpty()\n    }\n\n    fun popNext(): Uri? {\n        return if (queue.isNotEmpty()) {\n            queue.removeAt(0)\n        } else {\n            null\n        }\n    }\n\n    fun clear() {\n        queue.clear()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/ComposePrefs.kt",
    "content": "package me.bmax.apatch.util\n\nimport android.content.SharedPreferences\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.DisposableEffect\nimport androidx.compose.runtime.State\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport me.bmax.apatch.APApplication\n\n/**\n * Reactive SharedPreferences reading utilities for Compose.\n * Automatically updates when the preference value changes externally.\n */\n\n@Composable\nfun rememberBoolPref(\n    key: String,\n    defaultValue: Boolean = false\n): State<Boolean> {\n    val prefs = APApplication.sharedPreferences\n    val state = remember { mutableStateOf(prefs.getBoolean(key, defaultValue)) }\n\n    DisposableEffect(key) {\n        val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, changedKey ->\n            if (changedKey == key) {\n                state.value = prefs.getBoolean(key, defaultValue)\n            }\n        }\n        prefs.registerOnSharedPreferenceChangeListener(listener)\n        onDispose { prefs.unregisterOnSharedPreferenceChangeListener(listener) }\n    }\n\n    return state\n}\n\n@Composable\nfun rememberStringPref(\n    key: String,\n    defaultValue: String = \"\"\n): State<String> {\n    val prefs = APApplication.sharedPreferences\n    val state = remember { mutableStateOf(prefs.getString(key, defaultValue) ?: defaultValue) }\n\n    DisposableEffect(key) {\n        val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, changedKey ->\n            if (changedKey == key) {\n                state.value = prefs.getString(key, defaultValue) ?: defaultValue\n            }\n        }\n        prefs.registerOnSharedPreferenceChangeListener(listener)\n        onDispose { prefs.unregisterOnSharedPreferenceChangeListener(listener) }\n    }\n\n    return state\n}\n\n@Composable\nfun rememberFloatPref(\n    key: String,\n    defaultValue: Float = 0f\n): State<Float> {\n    val prefs = APApplication.sharedPreferences\n    val state = remember { mutableStateOf(prefs.getFloat(key, defaultValue)) }\n\n    DisposableEffect(key) {\n        val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, changedKey ->\n            if (changedKey == key) {\n                state.value = prefs.getFloat(key, defaultValue)\n            }\n        }\n        prefs.registerOnSharedPreferenceChangeListener(listener)\n        onDispose { prefs.unregisterOnSharedPreferenceChangeListener(listener) }\n    }\n\n    return state\n}\n\n@Composable\nfun rememberIntPref(\n    key: String,\n    defaultValue: Int = 0\n): State<Int> {\n    val prefs = APApplication.sharedPreferences\n    val state = remember { mutableStateOf(prefs.getInt(key, defaultValue)) }\n\n    DisposableEffect(key) {\n        val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, changedKey ->\n            if (changedKey == key) {\n                state.value = prefs.getInt(key, defaultValue)\n            }\n        }\n        prefs.registerOnSharedPreferenceChangeListener(listener)\n        onDispose { prefs.unregisterOnSharedPreferenceChangeListener(listener) }\n    }\n\n    return state\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/DPIUtils.kt",
    "content": "package me.bmax.apatch.util\n\nimport android.app.Activity\nimport android.content.Context\nimport android.content.res.Configuration\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.core.content.edit\n\nobject DPIUtils {\n    private const val KEY_APP_DPI = \"app_dpi\"\n    const val DEFAULT_DPI = -1 // Follow System\n    const val DPI_MIN = 160\n    const val DPI_MAX = 600\n\n    var currentDpi: Int by mutableIntStateOf(DEFAULT_DPI)\n        private set\n\n    val systemDpi: Int\n        get() = android.content.res.Resources.getSystem().displayMetrics.densityDpi\n\n    fun load(context: Context) {\n        val prefs = context.getSharedPreferences(\"settings\", Context.MODE_PRIVATE)\n        currentDpi = prefs.getInt(KEY_APP_DPI, DEFAULT_DPI)\n    }\n\n    fun setDpi(context: Context, dpi: Int) {\n        val prefs = context.getSharedPreferences(\"settings\", Context.MODE_PRIVATE)\n        if (dpi == DEFAULT_DPI) {\n            prefs.edit { putInt(KEY_APP_DPI, DEFAULT_DPI) }\n            currentDpi = DEFAULT_DPI\n        } else {\n            val clamped = dpi.coerceIn(DPI_MIN, DPI_MAX)\n            prefs.edit { putInt(KEY_APP_DPI, clamped) }\n            currentDpi = clamped\n        }\n    }\n\n    fun applyDpi(context: Context) {\n        if (currentDpi == DEFAULT_DPI) return\n\n        val res = context.resources\n        val config = res.configuration\n        val metrics = res.displayMetrics\n\n        if (config.densityDpi != currentDpi) {\n            config.densityDpi = currentDpi\n            metrics.densityDpi = currentDpi\n            @Suppress(\"DEPRECATION\")\n            res.updateConfiguration(config, metrics)\n        }\n    }\n\n    fun updateContext(context: Context): Context {\n        val prefs = context.getSharedPreferences(\"settings\", Context.MODE_PRIVATE)\n        val dpi = prefs.getInt(KEY_APP_DPI, DEFAULT_DPI)\n\n        if (dpi == DEFAULT_DPI) return context\n\n        val config = Configuration(context.resources.configuration)\n        config.densityDpi = dpi\n        return context.createConfigurationContext(config)\n    }\n\n    fun getDpiFriendlyName(dpi: Int): String {\n        return when {\n            dpi == DEFAULT_DPI -> \"System Default\"\n            dpi <= 240 -> \"Small\"\n            dpi <= 360 -> \"Medium\"\n            dpi <= 480 -> \"Large\"\n            else -> \"XLarge\"\n        }\n    }\n\n    data class DpiPreset(val name: String, val value: Int)\n\n    val presets = listOf(\n        DpiPreset(\"Small\", 240),\n        DpiPreset(\"Medium\", 360),\n        DpiPreset(\"Large\", 480),\n        DpiPreset(\"XLarge\", 560),\n    )\n}"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/DeviceInfoUtils.kt",
    "content": "package me.bmax.apatch.util\n\nimport android.os.Build\nimport android.util.Log\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.res.stringResource\nimport com.topjohnwu.superuser.Shell\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.R\n\n@Composable\nfun getSELinuxStatus(): String {\n    var status by remember { mutableStateOf(\"\") }\n    val enforcing = stringResource(R.string.home_selinux_status_enforcing)\n    val permissive = stringResource(R.string.home_selinux_status_permissive)\n    val disabled = stringResource(R.string.home_selinux_status_disabled)\n    val unknown = stringResource(R.string.home_selinux_status_unknown)\n\n    LaunchedEffect(Unit) {\n        withContext(Dispatchers.IO) {\n            val shell = Shell.Builder.create().build(\"sh\")\n            val list = ArrayList<String>()\n            val result = shell.newJob().add(\"getenforce\").to(list, list).exec()\n            val output = result.out.joinToString(\"\\n\").trim()\n            shell.close()\n\n            status = if (result.isSuccess) {\n                when (output) {\n                    \"Enforcing\" -> enforcing\n                    \"Permissive\" -> permissive\n                    \"Disabled\" -> disabled\n                    else -> unknown\n                }\n            } else if (output.endsWith(\"Permission denied\")) {\n                enforcing\n            } else {\n                unknown\n            }\n        }\n    }\n\n    return if (status.isEmpty()) unknown else status\n}\n\nprivate fun getSystemProperty(key: String): Boolean {\n    try {\n        val c = Class.forName(\"android.os.SystemProperties\")\n        val get = c.getMethod(\n            \"getBoolean\",\n            String::class.java,\n            Boolean::class.javaPrimitiveType\n        )\n        return get.invoke(c, key, false) as Boolean\n    } catch (e: Exception) {\n        Log.e(\"APatch\", \"[DeviceUtils] Failed to get system property: \", e)\n    }\n    return false\n}\n\n// Check to see if device supports A/B (seamless) system updates\nfun isABDevice(): Boolean {\n    return getSystemProperty(\"ro.build.ab_update\")\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/Downloader.kt",
    "content": "package me.bmax.apatch.util\n\nimport android.annotation.SuppressLint\nimport android.app.DownloadManager\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.net.Uri\nimport android.os.Build\nimport android.os.Environment\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.DisposableEffect\nimport me.bmax.apatch.apApp\nimport androidx.core.content.ContextCompat\nimport java.io.File\n\nfun getSafeDownloadsDir(context: Context): File {\n    return try {\n        Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)\n    } catch (_: SecurityException) {\n        context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)\n            ?: File(context.filesDir, \"Download\")\n    }\n}\n\n@SuppressLint(\"Range\")\nfun download(\n    context: Context,\n    url: String,\n    fileName: String,\n    description: String,\n    onDownloaded: (Uri) -> Unit = {},\n    onDownloading: () -> Unit = {}\n) {\n    val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager\n\n    val query = DownloadManager.Query()\n    query.setFilterByStatus(DownloadManager.STATUS_RUNNING or DownloadManager.STATUS_PAUSED or DownloadManager.STATUS_PENDING)\n    downloadManager.query(query).use { cursor ->\n        while (cursor.moveToNext()) {\n            val uri = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_URI))\n            val localUri = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))\n            val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))\n            val columnTitle = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_TITLE))\n            if (url == uri || fileName == columnTitle) {\n                if (status == DownloadManager.STATUS_RUNNING || status == DownloadManager.STATUS_PENDING) {\n                    onDownloading()\n                    return\n                } else if (status == DownloadManager.STATUS_SUCCESSFUL) {\n                    onDownloaded(Uri.parse(localUri))\n                    return\n                }\n            }\n        }\n    }\n\n    val request = DownloadManager.Request(Uri.parse(url))\n        .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)\n        .setMimeType(\"application/zip\").setTitle(fileName).setDescription(description)\n\n    try {\n        request.setDestinationInExternalPublicDir(\n            Environment.DIRECTORY_DOWNLOADS, fileName\n        )\n    } catch (_: SecurityException) {\n        val dir = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)\n        if (dir != null) {\n            request.setDestinationUri(Uri.fromFile(java.io.File(dir, fileName)))\n        } else {\n            request.setDestinationInExternalFilesDir(\n                context, Environment.DIRECTORY_DOWNLOADS, fileName\n            )\n        }\n    }\n\n    downloadManager.enqueue(request)\n}\n\n@Composable\nfun DownloadListener(context: Context, onDownloaded: (Uri) -> Unit) {\n    DisposableEffect(context) {\n        val receiver = object : BroadcastReceiver() {\n            @SuppressLint(\"Range\")\n            override fun onReceive(context: Context?, intent: Intent?) {\n                if (intent?.action == DownloadManager.ACTION_DOWNLOAD_COMPLETE) {\n                    val id = intent.getLongExtra(\n                        DownloadManager.EXTRA_DOWNLOAD_ID, -1\n                    )\n                    val query = DownloadManager.Query().setFilterById(id)\n                    val downloadManager =\n                        context?.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager\n                    val cursor = downloadManager.query(query)\n                    if (cursor.moveToFirst()) {\n                        val status = cursor.getInt(\n                            cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)\n                        )\n                        if (status == DownloadManager.STATUS_SUCCESSFUL) {\n                            val uri = cursor.getString(\n                                cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)\n                            )\n                            onDownloaded(Uri.parse(uri))\n                        }\n                    }\n                }\n            }\n        }\n        val intentFilter = IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)\n        ContextCompat.registerReceiver(\n            context,\n            receiver,\n            intentFilter,\n            ContextCompat.RECEIVER_EXPORTED\n        )\n        onDispose {\n            context.unregisterReceiver(receiver)\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/FolkApiClient.kt",
    "content": "package me.bmax.apatch.util\n\nimport android.util.Log\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Deferred\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.SupervisorJob\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\nimport me.bmax.apatch.apApp\nimport okhttp3.Request\nimport java.io.IOException\nimport java.net.SocketException\nimport java.net.SocketTimeoutException\nimport java.net.UnknownHostException\nimport java.util.concurrent.ConcurrentHashMap\n\nobject FolkApiClient {\n    private const val TAG = \"FolkApiClient\"\n    private const val DEFAULT_TTL_MS = 5 * 60 * 1000L\n    private const val DEFAULT_MAX_RETRIES = 2\n    private const val INITIAL_BACKOFF_MS = 1000L\n\n    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)\n\n    private class CacheEntry(\n        val data: String,\n        val timestamp: Long\n    )\n\n    private val memoryCache = ConcurrentHashMap<String, CacheEntry>()\n    private val inFlightRequests = ConcurrentHashMap<String, Deferred<String>>()\n\n    suspend fun fetchJson(\n        url: String,\n        ttlMs: Long = DEFAULT_TTL_MS,\n        maxRetries: Int = DEFAULT_MAX_RETRIES,\n        forceRefresh: Boolean = false\n    ): Result<String> {\n        if (!forceRefresh) {\n            val cached = memoryCache[url]\n            if (cached != null && System.currentTimeMillis() - cached.timestamp < ttlMs) {\n                Log.d(TAG, \"Cache hit: $url\")\n                return Result.success(cached.data)\n            }\n        }\n\n        val existing = inFlightRequests[url]\n        if (existing != null) {\n            Log.d(TAG, \"Deduplicating in-flight request: $url\")\n            return try {\n                Result.success(existing.await())\n            } catch (e: Exception) {\n                Result.failure(e)\n            }\n        }\n\n        val deferred = scope.async {\n            fetchWithRetry(url, maxRetries)\n        }\n\n        inFlightRequests[url] = deferred\n\n        return try {\n            val result = deferred.await()\n            memoryCache[url] = CacheEntry(result, System.currentTimeMillis())\n            Result.success(result)\n        } catch (e: Exception) {\n            Result.failure(e)\n        } finally {\n            inFlightRequests.remove(url)\n        }\n    }\n\n    private suspend fun fetchWithRetry(url: String, maxRetries: Int): String {\n        var lastException: Exception? = null\n        repeat(maxRetries + 1) { attempt ->\n            try {\n                val request = Request.Builder().url(url).build()\n                val response = apApp.okhttpClient.newCall(request).execute()\n                if (response.isSuccessful) {\n                    return response.body?.string() ?: \"\"\n                }\n                if (response.code in 400..499) {\n                    throw IOException(\"HTTP ${response.code}\")\n                }\n                lastException = IOException(\"HTTP ${response.code}\")\n            } catch (e: UnknownHostException) {\n                throw e\n            } catch (e: SocketTimeoutException) {\n                lastException = e\n            } catch (e: SocketException) {\n                lastException = e\n            } catch (e: IOException) {\n                lastException = e\n            }\n\n            if (attempt < maxRetries) {\n                val backoff = INITIAL_BACKOFF_MS * (1L shl attempt)\n                Log.d(TAG, \"Retry $attempt/$maxRetries for $url, waiting ${backoff}ms\")\n                delay(backoff)\n            }\n        }\n        throw lastException ?: IOException(\"Unknown error\")\n    }\n\n    fun clearCache() {\n        memoryCache.clear()\n    }\n\n    fun clearCacheForUrl(url: String) {\n        memoryCache.remove(url)\n    }\n\n    fun prefetch(vararg urls: String) {\n        urls.forEach { url ->\n            scope.launch {\n                try {\n                    fetchJson(url, forceRefresh = false)\n                    Log.d(TAG, \"Prefetch success: $url\")\n                } catch (_: Exception) {\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/HanziToPinyin.java",
    "content": "package me.bmax.apatch.util;\n/*\n * Copyright (C) 2009 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\nimport android.text.TextUtils;\nimport android.util.Log;\n\nimport java.text.Collator;\nimport java.util.ArrayList;\nimport java.util.Locale;\n\n/**\n * An object to convert Chinese character to its corresponding pinyin string. For characters with\n * multiple possible pinyin string, only one is selected according to collator. Polyphone is not\n * supported in this implementation. This class is implemented to achieve the best runtime\n * performance and minimum runtime resources with tolerable sacrifice of accuracy. This\n * implementation highly depends on zh_CN ICU collation data and must be always synchronized with\n * ICU.\n * <p>\n * Currently this file is aligned to zh.txt in ICU 4.6\n */\npublic class HanziToPinyin {\n    /**\n     * Unihans array.\n     * <p>\n     * Each unihans is the first one within same pinyin when collator is zh_CN.\n     */\n    public static final char[] UNIHANS = {\n            '\\u963f', '\\u54ce', '\\u5b89', '\\u80ae', '\\u51f9', '\\u516b',\n            '\\u6300', '\\u6273', '\\u90a6', '\\u52f9', '\\u9642', '\\u5954',\n            '\\u4f3b', '\\u5c44', '\\u8fb9', '\\u706c', '\\u618b', '\\u6c43',\n            '\\u51ab', '\\u7676', '\\u5cec', '\\u5693', '\\u5072', '\\u53c2',\n            '\\u4ed3', '\\u64a1', '\\u518a', '\\u5d7e', '\\u66fd', '\\u66fe',\n            '\\u5c64', '\\u53c9', '\\u8286', '\\u8fbf', '\\u4f25', '\\u6284',\n            '\\u8f66', '\\u62bb', '\\u6c88', '\\u6c89', '\\u9637', '\\u5403',\n            '\\u5145', '\\u62bd', '\\u51fa', '\\u6b3b', '\\u63e3', '\\u5ddb',\n            '\\u5205', '\\u5439', '\\u65fe', '\\u9034', '\\u5472', '\\u5306',\n            '\\u51d1', '\\u7c97', '\\u6c46', '\\u5d14', '\\u90a8', '\\u6413',\n            '\\u5491', '\\u5446', '\\u4e39', '\\u5f53', '\\u5200', '\\u561a',\n            '\\u6265', '\\u706f', '\\u6c10', '\\u55f2', '\\u7538', '\\u5201',\n            '\\u7239', '\\u4e01', '\\u4e1f', '\\u4e1c', '\\u543a', '\\u53be',\n            '\\u8011', '\\u8968', '\\u5428', '\\u591a', '\\u59b8', '\\u8bf6',\n            '\\u5940', '\\u97a5', '\\u513f', '\\u53d1', '\\u5e06', '\\u531a',\n            '\\u98de', '\\u5206', '\\u4e30', '\\u8985', '\\u4ecf', '\\u7d11',\n            '\\u4f15', '\\u65ee', '\\u4f85', '\\u7518', '\\u5188', '\\u768b',\n            '\\u6208', '\\u7ed9', '\\u6839', '\\u522f', '\\u5de5', '\\u52fe',\n            '\\u4f30', '\\u74dc', '\\u4e56', '\\u5173', '\\u5149', '\\u5f52',\n            '\\u4e28', '\\u5459', '\\u54c8', '\\u548d', '\\u4f44', '\\u592f',\n            '\\u8320', '\\u8bc3', '\\u9ed2', '\\u62eb', '\\u4ea8', '\\u5677',\n            '\\u53ff', '\\u9f41', '\\u4e6f', '\\u82b1', '\\u6000', '\\u72bf',\n            '\\u5ddf', '\\u7070', '\\u660f', '\\u5419', '\\u4e0c', '\\u52a0',\n            '\\u620b', '\\u6c5f', '\\u827d', '\\u9636', '\\u5dfe', '\\u5755',\n            '\\u5182', '\\u4e29', '\\u51e5', '\\u59e2', '\\u5658', '\\u519b',\n            '\\u5494', '\\u5f00', '\\u520a', '\\u5ffc', '\\u5c3b', '\\u533c',\n            '\\u808e', '\\u52a5', '\\u7a7a', '\\u62a0', '\\u625d', '\\u5938',\n            '\\u84af', '\\u5bbd', '\\u5321', '\\u4e8f', '\\u5764', '\\u6269',\n            '\\u5783', '\\u6765', '\\u5170', '\\u5577', '\\u635e', '\\u808b',\n            '\\u52d2', '\\u5d1a', '\\u5215', '\\u4fe9', '\\u5941', '\\u826f',\n            '\\u64a9', '\\u5217', '\\u62ce', '\\u5222', '\\u6e9c', '\\u56d6',\n            '\\u9f99', '\\u779c', '\\u565c', '\\u5a08', '\\u7567', '\\u62a1',\n            '\\u7f57', '\\u5463', '\\u5988', '\\u57cb', '\\u5ada', '\\u7264',\n            '\\u732b', '\\u4e48', '\\u5445', '\\u95e8', '\\u753f', '\\u54aa',\n            '\\u5b80', '\\u55b5', '\\u4e5c', '\\u6c11', '\\u540d', '\\u8c2c',\n            '\\u6478', '\\u54de', '\\u6bea', '\\u55ef', '\\u62cf', '\\u8149',\n            '\\u56e1', '\\u56d4', '\\u5b6c', '\\u7592', '\\u5a1e', '\\u6041',\n            '\\u80fd', '\\u59ae', '\\u62c8', '\\u5b22', '\\u9e1f', '\\u634f',\n            '\\u56dc', '\\u5b81', '\\u599e', '\\u519c', '\\u7fba', '\\u5974',\n            '\\u597b', '\\u759f', '\\u9ec1', '\\u90cd', '\\u5594', '\\u8bb4',\n            '\\u5991', '\\u62cd', '\\u7705', '\\u4e53', '\\u629b', '\\u5478',\n            '\\u55b7', '\\u5309', '\\u4e15', '\\u56e8', '\\u527d', '\\u6c15',\n            '\\u59d8', '\\u4e52', '\\u948b', '\\u5256', '\\u4ec6', '\\u4e03',\n            '\\u6390', '\\u5343', '\\u545b', '\\u6084', '\\u767f', '\\u4eb2',\n            '\\u72c5', '\\u828e', '\\u4e18', '\\u533a', '\\u5cd1', '\\u7f3a',\n            '\\u590b', '\\u5465', '\\u7a63', '\\u5a06', '\\u60f9', '\\u4eba',\n            '\\u6254', '\\u65e5', '\\u8338', '\\u53b9', '\\u909a', '\\u633c',\n            '\\u5827', '\\u5a51', '\\u77a4', '\\u637c', '\\u4ee8', '\\u6be2',\n            '\\u4e09', '\\u6852', '\\u63bb', '\\u95aa', '\\u68ee', '\\u50e7',\n            '\\u6740', '\\u7b5b', '\\u5c71', '\\u4f24', '\\u5f30', '\\u5962',\n            '\\u7533', '\\u8398', '\\u6552', '\\u5347', '\\u5c38', '\\u53ce',\n            '\\u4e66', '\\u5237', '\\u8870', '\\u95e9', '\\u53cc', '\\u8c01',\n            '\\u542e', '\\u8bf4', '\\u53b6', '\\u5fea', '\\u635c', '\\u82cf',\n            '\\u72fb', '\\u590a', '\\u5b59', '\\u5506', '\\u4ed6', '\\u56fc',\n            '\\u574d', '\\u6c64', '\\u5932', '\\u5fd1', '\\u71a5', '\\u5254',\n            '\\u5929', '\\u65eb', '\\u5e16', '\\u5385', '\\u56f2', '\\u5077',\n            '\\u51f8', '\\u6e4d', '\\u63a8', '\\u541e', '\\u4e47', '\\u7a75',\n            '\\u6b6a', '\\u5f2f', '\\u5c23', '\\u5371', '\\u6637', '\\u7fc1',\n            '\\u631d', '\\u4e4c', '\\u5915', '\\u8672', '\\u4eda', '\\u4e61',\n            '\\u7071', '\\u4e9b', '\\u5fc3', '\\u661f', '\\u51f6', '\\u4f11',\n            '\\u5401', '\\u5405', '\\u524a', '\\u5743', '\\u4e2b', '\\u6079',\n            '\\u592e', '\\u5e7a', '\\u503b', '\\u4e00', '\\u56d9', '\\u5e94',\n            '\\u54df', '\\u4f63', '\\u4f18', '\\u625c', '\\u56e6', '\\u66f0',\n            '\\u6655', '\\u7b60', '\\u7b7c', '\\u5e00', '\\u707d', '\\u5142',\n            '\\u5328', '\\u50ae', '\\u5219', '\\u8d3c', '\\u600e', '\\u5897',\n            '\\u624e', '\\u635a', '\\u6cbe', '\\u5f20', '\\u957f', '\\u9577',\n            '\\u4f4b', '\\u8707', '\\u8d1e', '\\u4e89', '\\u4e4b', '\\u5cd9',\n            '\\u5ea2', '\\u4e2d', '\\u5dde', '\\u6731', '\\u6293', '\\u62fd',\n            '\\u4e13', '\\u5986', '\\u96b9', '\\u5b92', '\\u5353', '\\u4e72',\n            '\\u5b97', '\\u90b9', '\\u79df', '\\u94bb', '\\u539c', '\\u5c0a',\n            '\\u6628', '\\u5159', '\\u9fc3', '\\u9fc4',};\n    /**\n     * Pinyin array.\n     * <p>\n     * Each pinyin is corresponding to unihans of same\n     * offset in the unihans array.\n     */\n    public static final byte[][] PINYINS = {\n            {65, 0, 0, 0, 0, 0}, {65, 73, 0, 0, 0, 0},\n            {65, 78, 0, 0, 0, 0}, {65, 78, 71, 0, 0, 0},\n            {65, 79, 0, 0, 0, 0}, {66, 65, 0, 0, 0, 0},\n            {66, 65, 73, 0, 0, 0}, {66, 65, 78, 0, 0, 0},\n            {66, 65, 78, 71, 0, 0}, {66, 65, 79, 0, 0, 0},\n            {66, 69, 73, 0, 0, 0}, {66, 69, 78, 0, 0, 0},\n            {66, 69, 78, 71, 0, 0}, {66, 73, 0, 0, 0, 0},\n            {66, 73, 65, 78, 0, 0}, {66, 73, 65, 79, 0, 0},\n            {66, 73, 69, 0, 0, 0}, {66, 73, 78, 0, 0, 0},\n            {66, 73, 78, 71, 0, 0}, {66, 79, 0, 0, 0, 0},\n            {66, 85, 0, 0, 0, 0}, {67, 65, 0, 0, 0, 0},\n            {67, 65, 73, 0, 0, 0}, {67, 65, 78, 0, 0, 0},\n            {67, 65, 78, 71, 0, 0}, {67, 65, 79, 0, 0, 0},\n            {67, 69, 0, 0, 0, 0}, {67, 69, 78, 0, 0, 0},\n            {67, 69, 78, 71, 0, 0}, {90, 69, 78, 71, 0, 0},\n            {67, 69, 78, 71, 0, 0}, {67, 72, 65, 0, 0, 0},\n            {67, 72, 65, 73, 0, 0}, {67, 72, 65, 78, 0, 0},\n            {67, 72, 65, 78, 71, 0}, {67, 72, 65, 79, 0, 0},\n            {67, 72, 69, 0, 0, 0}, {67, 72, 69, 78, 0, 0},\n            {83, 72, 69, 78, 0, 0}, {67, 72, 69, 78, 0, 0},\n            {67, 72, 69, 78, 71, 0}, {67, 72, 73, 0, 0, 0},\n            {67, 72, 79, 78, 71, 0}, {67, 72, 79, 85, 0, 0},\n            {67, 72, 85, 0, 0, 0}, {67, 72, 85, 65, 0, 0},\n            {67, 72, 85, 65, 73, 0}, {67, 72, 85, 65, 78, 0},\n            {67, 72, 85, 65, 78, 71}, {67, 72, 85, 73, 0, 0},\n            {67, 72, 85, 78, 0, 0}, {67, 72, 85, 79, 0, 0},\n            {67, 73, 0, 0, 0, 0}, {67, 79, 78, 71, 0, 0},\n            {67, 79, 85, 0, 0, 0}, {67, 85, 0, 0, 0, 0},\n            {67, 85, 65, 78, 0, 0}, {67, 85, 73, 0, 0, 0},\n            {67, 85, 78, 0, 0, 0}, {67, 85, 79, 0, 0, 0},\n            {68, 65, 0, 0, 0, 0}, {68, 65, 73, 0, 0, 0},\n            {68, 65, 78, 0, 0, 0}, {68, 65, 78, 71, 0, 0},\n            {68, 65, 79, 0, 0, 0}, {68, 69, 0, 0, 0, 0},\n            {68, 69, 78, 0, 0, 0}, {68, 69, 78, 71, 0, 0},\n            {68, 73, 0, 0, 0, 0}, {68, 73, 65, 0, 0, 0},\n            {68, 73, 65, 78, 0, 0}, {68, 73, 65, 79, 0, 0},\n            {68, 73, 69, 0, 0, 0}, {68, 73, 78, 71, 0, 0},\n            {68, 73, 85, 0, 0, 0}, {68, 79, 78, 71, 0, 0},\n            {68, 79, 85, 0, 0, 0}, {68, 85, 0, 0, 0, 0},\n            {68, 85, 65, 78, 0, 0}, {68, 85, 73, 0, 0, 0},\n            {68, 85, 78, 0, 0, 0}, {68, 85, 79, 0, 0, 0},\n            {69, 0, 0, 0, 0, 0}, {69, 73, 0, 0, 0, 0},\n            {69, 78, 0, 0, 0, 0}, {69, 78, 71, 0, 0, 0},\n            {69, 82, 0, 0, 0, 0}, {70, 65, 0, 0, 0, 0},\n            {70, 65, 78, 0, 0, 0}, {70, 65, 78, 71, 0, 0},\n            {70, 69, 73, 0, 0, 0}, {70, 69, 78, 0, 0, 0},\n            {70, 69, 78, 71, 0, 0}, {70, 73, 65, 79, 0, 0},\n            {70, 79, 0, 0, 0, 0}, {70, 79, 85, 0, 0, 0},\n            {70, 85, 0, 0, 0, 0}, {71, 65, 0, 0, 0, 0},\n            {71, 65, 73, 0, 0, 0}, {71, 65, 78, 0, 0, 0},\n            {71, 65, 78, 71, 0, 0}, {71, 65, 79, 0, 0, 0},\n            {71, 69, 0, 0, 0, 0}, {71, 69, 73, 0, 0, 0},\n            {71, 69, 78, 0, 0, 0}, {71, 69, 78, 71, 0, 0},\n            {71, 79, 78, 71, 0, 0}, {71, 79, 85, 0, 0, 0},\n            {71, 85, 0, 0, 0, 0}, {71, 85, 65, 0, 0, 0},\n            {71, 85, 65, 73, 0, 0}, {71, 85, 65, 78, 0, 0},\n            {71, 85, 65, 78, 71, 0}, {71, 85, 73, 0, 0, 0},\n            {71, 85, 78, 0, 0, 0}, {71, 85, 79, 0, 0, 0},\n            {72, 65, 0, 0, 0, 0}, {72, 65, 73, 0, 0, 0},\n            {72, 65, 78, 0, 0, 0}, {72, 65, 78, 71, 0, 0},\n            {72, 65, 79, 0, 0, 0}, {72, 69, 0, 0, 0, 0},\n            {72, 69, 73, 0, 0, 0}, {72, 69, 78, 0, 0, 0},\n            {72, 69, 78, 71, 0, 0}, {72, 77, 0, 0, 0, 0},\n            {72, 79, 78, 71, 0, 0}, {72, 79, 85, 0, 0, 0},\n            {72, 85, 0, 0, 0, 0}, {72, 85, 65, 0, 0, 0},\n            {72, 85, 65, 73, 0, 0}, {72, 85, 65, 78, 0, 0},\n            {72, 85, 65, 78, 71, 0}, {72, 85, 73, 0, 0, 0},\n            {72, 85, 78, 0, 0, 0}, {72, 85, 79, 0, 0, 0},\n            {74, 73, 0, 0, 0, 0}, {74, 73, 65, 0, 0, 0},\n            {74, 73, 65, 78, 0, 0}, {74, 73, 65, 78, 71, 0},\n            {74, 73, 65, 79, 0, 0}, {74, 73, 69, 0, 0, 0},\n            {74, 73, 78, 0, 0, 0}, {74, 73, 78, 71, 0, 0},\n            {74, 73, 79, 78, 71, 0}, {74, 73, 85, 0, 0, 0},\n            {74, 85, 0, 0, 0, 0}, {74, 85, 65, 78, 0, 0},\n            {74, 85, 69, 0, 0, 0}, {74, 85, 78, 0, 0, 0},\n            {75, 65, 0, 0, 0, 0}, {75, 65, 73, 0, 0, 0},\n            {75, 65, 78, 0, 0, 0}, {75, 65, 78, 71, 0, 0},\n            {75, 65, 79, 0, 0, 0}, {75, 69, 0, 0, 0, 0},\n            {75, 69, 78, 0, 0, 0}, {75, 69, 78, 71, 0, 0},\n            {75, 79, 78, 71, 0, 0}, {75, 79, 85, 0, 0, 0},\n            {75, 85, 0, 0, 0, 0}, {75, 85, 65, 0, 0, 0},\n            {75, 85, 65, 73, 0, 0}, {75, 85, 65, 78, 0, 0},\n            {75, 85, 65, 78, 71, 0}, {75, 85, 73, 0, 0, 0},\n            {75, 85, 78, 0, 0, 0}, {75, 85, 79, 0, 0, 0},\n            {76, 65, 0, 0, 0, 0}, {76, 65, 73, 0, 0, 0},\n            {76, 65, 78, 0, 0, 0}, {76, 65, 78, 71, 0, 0},\n            {76, 65, 79, 0, 0, 0}, {76, 69, 0, 0, 0, 0},\n            {76, 69, 73, 0, 0, 0}, {76, 69, 78, 71, 0, 0},\n            {76, 73, 0, 0, 0, 0}, {76, 73, 65, 0, 0, 0},\n            {76, 73, 65, 78, 0, 0}, {76, 73, 65, 78, 71, 0},\n            {76, 73, 65, 79, 0, 0}, {76, 73, 69, 0, 0, 0},\n            {76, 73, 78, 0, 0, 0}, {76, 73, 78, 71, 0, 0},\n            {76, 73, 85, 0, 0, 0}, {76, 79, 0, 0, 0, 0},\n            {76, 79, 78, 71, 0, 0}, {76, 79, 85, 0, 0, 0},\n            {76, 85, 0, 0, 0, 0}, {76, 85, 65, 78, 0, 0},\n            {76, 85, 69, 0, 0, 0}, {76, 85, 78, 0, 0, 0},\n            {76, 85, 79, 0, 0, 0}, {77, 0, 0, 0, 0, 0},\n            {77, 65, 0, 0, 0, 0}, {77, 65, 73, 0, 0, 0},\n            {77, 65, 78, 0, 0, 0}, {77, 65, 78, 71, 0, 0},\n            {77, 65, 79, 0, 0, 0}, {77, 69, 0, 0, 0, 0},\n            {77, 69, 73, 0, 0, 0}, {77, 69, 78, 0, 0, 0},\n            {77, 69, 78, 71, 0, 0}, {77, 73, 0, 0, 0, 0},\n            {77, 73, 65, 78, 0, 0}, {77, 73, 65, 79, 0, 0},\n            {77, 73, 69, 0, 0, 0}, {77, 73, 78, 0, 0, 0},\n            {77, 73, 78, 71, 0, 0}, {77, 73, 85, 0, 0, 0},\n            {77, 79, 0, 0, 0, 0}, {77, 79, 85, 0, 0, 0},\n            {77, 85, 0, 0, 0, 0}, {78, 0, 0, 0, 0, 0},\n            {78, 65, 0, 0, 0, 0}, {78, 65, 73, 0, 0, 0},\n            {78, 65, 78, 0, 0, 0}, {78, 65, 78, 71, 0, 0},\n            {78, 65, 79, 0, 0, 0}, {78, 69, 0, 0, 0, 0},\n            {78, 69, 73, 0, 0, 0}, {78, 69, 78, 0, 0, 0},\n            {78, 69, 78, 71, 0, 0}, {78, 73, 0, 0, 0, 0},\n            {78, 73, 65, 78, 0, 0}, {78, 73, 65, 78, 71, 0},\n            {78, 73, 65, 79, 0, 0}, {78, 73, 69, 0, 0, 0},\n            {78, 73, 78, 0, 0, 0}, {78, 73, 78, 71, 0, 0},\n            {78, 73, 85, 0, 0, 0}, {78, 79, 78, 71, 0, 0},\n            {78, 79, 85, 0, 0, 0}, {78, 85, 0, 0, 0, 0},\n            {78, 85, 65, 78, 0, 0}, {78, 85, 69, 0, 0, 0},\n            {78, 85, 78, 0, 0, 0}, {78, 85, 79, 0, 0, 0},\n            {79, 0, 0, 0, 0, 0}, {79, 85, 0, 0, 0, 0},\n            {80, 65, 0, 0, 0, 0}, {80, 65, 73, 0, 0, 0},\n            {80, 65, 78, 0, 0, 0}, {80, 65, 78, 71, 0, 0},\n            {80, 65, 79, 0, 0, 0}, {80, 69, 73, 0, 0, 0},\n            {80, 69, 78, 0, 0, 0}, {80, 69, 78, 71, 0, 0},\n            {80, 73, 0, 0, 0, 0}, {80, 73, 65, 78, 0, 0},\n            {80, 73, 65, 79, 0, 0}, {80, 73, 69, 0, 0, 0},\n            {80, 73, 78, 0, 0, 0}, {80, 73, 78, 71, 0, 0},\n            {80, 79, 0, 0, 0, 0}, {80, 79, 85, 0, 0, 0},\n            {80, 85, 0, 0, 0, 0}, {81, 73, 0, 0, 0, 0},\n            {81, 73, 65, 0, 0, 0}, {81, 73, 65, 78, 0, 0},\n            {81, 73, 65, 78, 71, 0}, {81, 73, 65, 79, 0, 0},\n            {81, 73, 69, 0, 0, 0}, {81, 73, 78, 0, 0, 0},\n            {81, 73, 78, 71, 0, 0}, {81, 73, 79, 78, 71, 0},\n            {81, 73, 85, 0, 0, 0}, {81, 85, 0, 0, 0, 0},\n            {81, 85, 65, 78, 0, 0}, {81, 85, 69, 0, 0, 0},\n            {81, 85, 78, 0, 0, 0}, {82, 65, 78, 0, 0, 0},\n            {82, 65, 78, 71, 0, 0}, {82, 65, 79, 0, 0, 0},\n            {82, 69, 0, 0, 0, 0}, {82, 69, 78, 0, 0, 0},\n            {82, 69, 78, 71, 0, 0}, {82, 73, 0, 0, 0, 0},\n            {82, 79, 78, 71, 0, 0}, {82, 79, 85, 0, 0, 0},\n            {82, 85, 0, 0, 0, 0}, {82, 85, 65, 0, 0, 0},\n            {82, 85, 65, 78, 0, 0}, {82, 85, 73, 0, 0, 0},\n            {82, 85, 78, 0, 0, 0}, {82, 85, 79, 0, 0, 0},\n            {83, 65, 0, 0, 0, 0}, {83, 65, 73, 0, 0, 0},\n            {83, 65, 78, 0, 0, 0}, {83, 65, 78, 71, 0, 0},\n            {83, 65, 79, 0, 0, 0}, {83, 69, 0, 0, 0, 0},\n            {83, 69, 78, 0, 0, 0}, {83, 69, 78, 71, 0, 0},\n            {83, 72, 65, 0, 0, 0}, {83, 72, 65, 73, 0, 0},\n            {83, 72, 65, 78, 0, 0}, {83, 72, 65, 78, 71, 0},\n            {83, 72, 65, 79, 0, 0}, {83, 72, 69, 0, 0, 0},\n            {83, 72, 69, 78, 0, 0}, {88, 73, 78, 0, 0, 0},\n            {83, 72, 69, 78, 0, 0}, {83, 72, 69, 78, 71, 0},\n            {83, 72, 73, 0, 0, 0}, {83, 72, 79, 85, 0, 0},\n            {83, 72, 85, 0, 0, 0}, {83, 72, 85, 65, 0, 0},\n            {83, 72, 85, 65, 73, 0}, {83, 72, 85, 65, 78, 0},\n            {83, 72, 85, 65, 78, 71}, {83, 72, 85, 73, 0, 0},\n            {83, 72, 85, 78, 0, 0}, {83, 72, 85, 79, 0, 0},\n            {83, 73, 0, 0, 0, 0}, {83, 79, 78, 71, 0, 0},\n            {83, 79, 85, 0, 0, 0}, {83, 85, 0, 0, 0, 0},\n            {83, 85, 65, 78, 0, 0}, {83, 85, 73, 0, 0, 0},\n            {83, 85, 78, 0, 0, 0}, {83, 85, 79, 0, 0, 0},\n            {84, 65, 0, 0, 0, 0}, {84, 65, 73, 0, 0, 0},\n            {84, 65, 78, 0, 0, 0}, {84, 65, 78, 71, 0, 0},\n            {84, 65, 79, 0, 0, 0}, {84, 69, 0, 0, 0, 0},\n            {84, 69, 78, 71, 0, 0}, {84, 73, 0, 0, 0, 0},\n            {84, 73, 65, 78, 0, 0}, {84, 73, 65, 79, 0, 0},\n            {84, 73, 69, 0, 0, 0}, {84, 73, 78, 71, 0, 0},\n            {84, 79, 78, 71, 0, 0}, {84, 79, 85, 0, 0, 0},\n            {84, 85, 0, 0, 0, 0}, {84, 85, 65, 78, 0, 0},\n            {84, 85, 73, 0, 0, 0}, {84, 85, 78, 0, 0, 0},\n            {84, 85, 79, 0, 0, 0}, {87, 65, 0, 0, 0, 0},\n            {87, 65, 73, 0, 0, 0}, {87, 65, 78, 0, 0, 0},\n            {87, 65, 78, 71, 0, 0}, {87, 69, 73, 0, 0, 0},\n            {87, 69, 78, 0, 0, 0}, {87, 69, 78, 71, 0, 0},\n            {87, 79, 0, 0, 0, 0}, {87, 85, 0, 0, 0, 0},\n            {88, 73, 0, 0, 0, 0}, {88, 73, 65, 0, 0, 0},\n            {88, 73, 65, 78, 0, 0}, {88, 73, 65, 78, 71, 0},\n            {88, 73, 65, 79, 0, 0}, {88, 73, 69, 0, 0, 0},\n            {88, 73, 78, 0, 0, 0}, {88, 73, 78, 71, 0, 0},\n            {88, 73, 79, 78, 71, 0}, {88, 73, 85, 0, 0, 0},\n            {88, 85, 0, 0, 0, 0}, {88, 85, 65, 78, 0, 0},\n            {88, 85, 69, 0, 0, 0}, {88, 85, 78, 0, 0, 0},\n            {89, 65, 0, 0, 0, 0}, {89, 65, 78, 0, 0, 0},\n            {89, 65, 78, 71, 0, 0}, {89, 65, 79, 0, 0, 0},\n            {89, 69, 0, 0, 0, 0}, {89, 73, 0, 0, 0, 0},\n            {89, 73, 78, 0, 0, 0}, {89, 73, 78, 71, 0, 0},\n            {89, 79, 0, 0, 0, 0}, {89, 79, 78, 71, 0, 0},\n            {89, 79, 85, 0, 0, 0}, {89, 85, 0, 0, 0, 0},\n            {89, 85, 65, 78, 0, 0}, {89, 85, 69, 0, 0, 0},\n            {89, 85, 78, 0, 0, 0}, {74, 85, 78, 0, 0, 0},\n            {89, 85, 78, 0, 0, 0}, {90, 65, 0, 0, 0, 0},\n            {90, 65, 73, 0, 0, 0}, {90, 65, 78, 0, 0, 0},\n            {90, 65, 78, 71, 0, 0}, {90, 65, 79, 0, 0, 0},\n            {90, 69, 0, 0, 0, 0}, {90, 69, 73, 0, 0, 0},\n            {90, 69, 78, 0, 0, 0}, {90, 69, 78, 71, 0, 0},\n            {90, 72, 65, 0, 0, 0}, {90, 72, 65, 73, 0, 0},\n            {90, 72, 65, 78, 0, 0}, {90, 72, 65, 78, 71, 0},\n            {67, 72, 65, 78, 71, 0}, {90, 72, 65, 78, 71, 0},\n            {90, 72, 65, 79, 0, 0}, {90, 72, 69, 0, 0, 0},\n            {90, 72, 69, 78, 0, 0}, {90, 72, 69, 78, 71, 0},\n            {90, 72, 73, 0, 0, 0}, {83, 72, 73, 0, 0, 0},\n            {90, 72, 73, 0, 0, 0}, {90, 72, 79, 78, 71, 0},\n            {90, 72, 79, 85, 0, 0}, {90, 72, 85, 0, 0, 0},\n            {90, 72, 85, 65, 0, 0}, {90, 72, 85, 65, 73, 0},\n            {90, 72, 85, 65, 78, 0}, {90, 72, 85, 65, 78, 71},\n            {90, 72, 85, 73, 0, 0}, {90, 72, 85, 78, 0, 0},\n            {90, 72, 85, 79, 0, 0}, {90, 73, 0, 0, 0, 0},\n            {90, 79, 78, 71, 0, 0}, {90, 79, 85, 0, 0, 0},\n            {90, 85, 0, 0, 0, 0}, {90, 85, 65, 78, 0, 0},\n            {90, 85, 73, 0, 0, 0}, {90, 85, 78, 0, 0, 0},\n            {90, 85, 79, 0, 0, 0}, {0, 0, 0, 0, 0, 0},\n            {83, 72, 65, 78, 0, 0}, {0, 0, 0, 0, 0, 0},};\n    private static final String TAG = \"HanziToPinyin\";\n    // Turn on this flag when we want to check internal data structure.\n    private static final boolean DEBUG = false;\n    /**\n     * First and last Chinese character with known Pinyin according to zh collation\n     */\n    private static final String FIRST_PINYIN_UNIHAN = \"\\u963F\";\n    private static final String LAST_PINYIN_UNIHAN = \"\\u9FFF\";\n\n    private static final Collator COLLATOR = Collator.getInstance(Locale.CHINA);\n\n    private static HanziToPinyin sInstance;\n    private final boolean mHasChinaCollator;\n\n    protected HanziToPinyin(boolean hasChinaCollator) {\n        mHasChinaCollator = hasChinaCollator;\n    }\n\n    public static HanziToPinyin getInstance() {\n        synchronized (HanziToPinyin.class) {\n            if (sInstance != null) {\n                return sInstance;\n            }\n            // Check if zh_CN collation data is available\n            final Locale[] locale = Collator.getAvailableLocales();\n            for (int i = 0; i < locale.length; i++) {\n                if (locale[i].equals(Locale.CHINA) || locale[i].getLanguage().contains(\"zh\")) {\n                    // Do self validation just once.\n                    if (DEBUG) {\n                        Log.d(TAG, \"Self validation. Result: \" + doSelfValidation());\n                    }\n                    sInstance = new HanziToPinyin(true);\n                    return sInstance;\n                }\n            }\n            if (sInstance == null) {//这个判断是用于处理国产ROM的兼容性问题\n                if (Locale.CHINA.equals(Locale.getDefault())) {\n                    sInstance = new HanziToPinyin(true);\n                    return sInstance;\n                }\n            }\n            Log.w(TAG, \"There is no Chinese collator, HanziToPinyin is disabled\");\n            sInstance = new HanziToPinyin(false);\n            return sInstance;\n        }\n    }\n\n    /**\n     * Validate if our internal table has some wrong value.\n     *\n     * @return true when the table looks correct.\n     */\n    private static boolean doSelfValidation() {\n        char lastChar = UNIHANS[0];\n        String lastString = Character.toString(lastChar);\n        for (char c : UNIHANS) {\n            if (lastChar == c) {\n                continue;\n            }\n            final String curString = Character.toString(c);\n            int cmp = COLLATOR.compare(lastString, curString);\n            if (cmp >= 0) {\n                Log.e(TAG, \"Internal error in Unihan table. \" + \"The last string \\\"\" + lastString\n                        + \"\\\" is greater than current string \\\"\" + curString + \"\\\".\");\n                return false;\n            }\n            lastString = curString;\n        }\n        return true;\n    }\n\n    private Token getToken(char character) {\n        Token token = new Token();\n        final String letter = Character.toString(character);\n        token.source = letter;\n        int offset = -1;\n        int cmp;\n        if (character < 256) {\n            token.type = Token.LATIN;\n            token.target = letter;\n            return token;\n        } else {\n            cmp = COLLATOR.compare(letter, FIRST_PINYIN_UNIHAN);\n            if (cmp < 0) {\n                token.type = Token.UNKNOWN;\n                token.target = letter;\n                return token;\n            } else if (cmp == 0) {\n                token.type = Token.PINYIN;\n                offset = 0;\n            } else {\n                cmp = COLLATOR.compare(letter, LAST_PINYIN_UNIHAN);\n                if (cmp > 0) {\n                    token.type = Token.UNKNOWN;\n                    token.target = letter;\n                    return token;\n                } else if (cmp == 0) {\n                    token.type = Token.PINYIN;\n                    offset = UNIHANS.length - 1;\n                }\n            }\n        }\n\n        token.type = Token.PINYIN;\n        if (offset < 0) {\n            int begin = 0;\n            int end = UNIHANS.length - 1;\n            while (begin <= end) {\n                offset = (begin + end) / 2;\n                final String unihan = Character.toString(UNIHANS[offset]);\n                cmp = COLLATOR.compare(letter, unihan);\n                if (cmp == 0) {\n                    break;\n                } else if (cmp > 0) {\n                    begin = offset + 1;\n                } else {\n                    end = offset - 1;\n                }\n            }\n        }\n        if (cmp < 0) {\n            offset--;\n        }\n        StringBuilder pinyin = new StringBuilder();\n        for (int j = 0; j < PINYINS[offset].length && PINYINS[offset][j] != 0; j++) {\n            pinyin.append((char) PINYINS[offset][j]);\n        }\n        token.target = pinyin.toString();\n        if (TextUtils.isEmpty(token.target)) {\n            token.type = Token.UNKNOWN;\n            token.target = token.source;\n        }\n        return token;\n    }\n\n    /**\n     * Convert the input to a array of tokens. The sequence of ASCII or Unknown characters without\n     * space will be put into a Token, One Hanzi character which has pinyin will be treated as a\n     * Token. If these is no China collator, the empty token array is returned.\n     */\n    public ArrayList<Token> get(final String input) {\n        ArrayList<Token> tokens = new ArrayList<Token>();\n        if (!mHasChinaCollator || TextUtils.isEmpty(input)) {\n            // return empty tokens.\n            return tokens;\n        }\n        final int inputLength = input.length();\n        final StringBuilder sb = new StringBuilder();\n        int tokenType = Token.LATIN;\n        // Go through the input, create a new token when\n        // a. Token type changed\n        // b. Get the Pinyin of current charater.\n        // c. current character is space.\n        for (int i = 0; i < inputLength; i++) {\n            final char character = input.charAt(i);\n            if (character == ' ') {\n                if (sb.length() > 0) {\n                    addToken(sb, tokens, tokenType);\n                }\n            } else if (character < 256) {\n                if (tokenType != Token.LATIN && sb.length() > 0) {\n                    addToken(sb, tokens, tokenType);\n                }\n                tokenType = Token.LATIN;\n                sb.append(character);\n            } else {\n                Token t = getToken(character);\n                if (t.type == Token.PINYIN) {\n                    if (sb.length() > 0) {\n                        addToken(sb, tokens, tokenType);\n                    }\n                    tokens.add(t);\n                    tokenType = Token.PINYIN;\n                } else {\n                    if (tokenType != t.type && sb.length() > 0) {\n                        addToken(sb, tokens, tokenType);\n                    }\n                    tokenType = t.type;\n                    sb.append(character);\n                }\n            }\n        }\n        if (sb.length() > 0) {\n            addToken(sb, tokens, tokenType);\n        }\n        return tokens;\n    }\n\n    private void addToken(\n            final StringBuilder sb, final ArrayList<Token> tokens, final int tokenType) {\n        String str = sb.toString();\n        tokens.add(new Token(tokenType, str, str));\n        sb.setLength(0);\n    }\n\n    public String toPinyinString(String string) {\n        if (string == null) {\n            return null;\n        }\n        StringBuilder sb = new StringBuilder();\n        ArrayList<Token> tokens = get(string);\n        for (Token token : tokens) {\n            sb.append(token.target);\n        }\n        return sb.toString().toLowerCase();\n    }\n\n    public static class Token {\n        /**\n         * Separator between target string for each source char\n         */\n        public static final String SEPARATOR = \" \";\n\n        public static final int LATIN = 1;\n        public static final int PINYIN = 2;\n        public static final int UNKNOWN = 3;\n        /**\n         * Type of this token, ASCII, PINYIN or UNKNOWN.\n         */\n        public int type;\n        /**\n         * Original string before translation.\n         */\n        public String source;\n        /**\n         * Translated string of source. For Han, target is corresponding Pinyin. Otherwise target is\n         * original string in source.\n         */\n        public String target;\n        public Token() {\n        }\n        public Token(int type, String source, String target) {\n            this.type = type;\n            this.source = source;\n            this.target = target;\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/HardwareMonitor.kt",
    "content": "package me.bmax.apatch.util\n\nimport android.util.Log\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport java.io.File\n\n/**\n * Hardware Monitor for CPU, GPU, Memory, and Swap/ZRAM.\n * Implements stateful calculations for differential metrics (like Adreno GPU load).\n */\nobject HardwareMonitor {\n    private const val TAG = \"HardwareMonitor\"\n\n    // CPU State\n    private var prevTotalCpu: Long = 0\n    private var prevIdleCpu: Long = 0\n\n    // GPU State\n    private var prevGpuUsage: Long = 0\n    private var prevGpuTotal: Long = 0\n    private var lastGpuUpdateTime: Long = 0\n    private var smoothedGpuUsage: Float = -1f\n    private const val GPU_EMA_ALPHA = 0.3f\n\n    // Adreno path\n    private const val ADRENO_PATH_NEW = \"/sys/class/kgsl/kgsl-3d0/gpu_busy_percentage\"\n    private const val ADRENO_PATH = \"/sys/class/kgsl/kgsl-3d0/gpubusy\"\n    // Mali path\n    private const val MALI_PATH = \"/sys/class/misc/mali0/device/utilization\"\n    // Generic path\n    private const val GENERIC_PATH = \"/sys/kernel/gpu/gpu_busy\"\n\n    data class MemoryInfo(\n        val ramTotal: Long,\n        val ramUsed: Long,\n        val swapTotal: Long,\n        val swapUsed: Long,\n        val zramTotal: Long,\n        val zramUsed: Long\n    )\n\n    data class CpuFreqInfo(\n        val coreIndex: Int,\n        val currentFreqKhz: Long,\n        val maxFreqKhz: Long\n    )\n\n    data class StoragePartitionInfo(\n        val label: String,\n        val totalBytes: Long,\n        val usedBytes: Long\n    )\n\n    /**\n     * Get CPU Usage percentage (0-100) using differential /proc/stat.\n     */\n    suspend fun getCpuUsage(): Int {\n        return withContext(Dispatchers.IO) {\n            try {\n                val result = rootShellForResult(\"cat /proc/stat\")\n                if (result.isSuccess && result.out.isNotEmpty()) {\n                    val line = result.out.firstOrNull { it.startsWith(\"cpu \") } ?: return@withContext 0\n                    val parts = line.split(Regex(\"\\\\s+\"))\n                    if (parts.size >= 8) {\n                        // user, nice, system, idle, iowait, irq, softirq\n                        val user = parts[1].toLong()\n                        val nice = parts[2].toLong()\n                        val system = parts[3].toLong()\n                        val idle = parts[4].toLong()\n                        val iowait = parts[5].toLong()\n                        val irq = parts[6].toLong()\n                        val softirq = parts[7].toLong()\n\n                        val currentTotal = user + nice + system + idle + iowait + irq + softirq\n                        val currentIdle = idle + iowait\n\n                        if (prevTotalCpu == 0L || currentTotal < prevTotalCpu) {\n                            prevTotalCpu = currentTotal\n                            prevIdleCpu = currentIdle\n                            return@withContext 0\n                        }\n\n                        val deltaTotal = currentTotal - prevTotalCpu\n                        val deltaIdle = currentIdle - prevIdleCpu\n\n                        prevTotalCpu = currentTotal\n                        prevIdleCpu = currentIdle\n\n                        if (deltaTotal > 0) {\n                            val usage = (deltaTotal - deltaIdle) * 100 / deltaTotal\n                            return@withContext usage.toInt().coerceIn(0, 100)\n                        }\n                    }\n                }\n                0\n            } catch (e: Exception) {\n                Log.e(TAG, \"Error reading CPU usage\", e)\n                0\n            }\n        }\n    }\n\n    /**\n     * Get GPU Usage percentage (0-100).\n     */\n    suspend fun getGpuUsage(): Int {\n        return withContext(Dispatchers.IO) {\n            try {\n                var rawGpu = -1\n\n                // 1. Try Direct Percentage Path (New Adreno)\n                val adrenoPercentResult = rootShellForResult(\"cat $ADRENO_PATH_NEW\")\n                if (adrenoPercentResult.isSuccess && adrenoPercentResult.out.isNotEmpty()) {\n                    val content = adrenoPercentResult.out[0].trim().replace(\"%\", \"\")\n                    val value = content.toIntOrNull()\n                    if (value != null) rawGpu = value.coerceIn(0, 100)\n                }\n\n                // 2. Try Adreno (Cumulative Differential)\n                if (rawGpu < 0) {\n                    val adrenoResult = rootShellForResult(\"cat $ADRENO_PATH\")\n                    if (adrenoResult.isSuccess && adrenoResult.out.isNotEmpty()) {\n                        val content = adrenoResult.out[0].trim()\n                        val parts = content.split(Regex(\"\\\\s+\"))\n                        if (parts.size >= 2) {\n                            val currUsage = parts[0].toLongOrNull() ?: 0L\n                            val currTotal = parts[1].toLongOrNull() ?: 0L\n\n                            if (lastGpuUpdateTime == 0L || currTotal < prevGpuTotal) {\n                                prevGpuUsage = currUsage\n                                prevGpuTotal = currTotal\n                                lastGpuUpdateTime = System.currentTimeMillis()\n                                rawGpu = 0\n                            } else {\n                                val deltaUsage = currUsage - prevGpuUsage\n                                val deltaTotal = currTotal - prevGpuTotal\n\n                                prevGpuUsage = currUsage\n                                prevGpuTotal = currTotal\n                                lastGpuUpdateTime = System.currentTimeMillis()\n\n                                rawGpu = if (deltaTotal > 0) {\n                                    (deltaUsage.toDouble() / deltaTotal.toDouble() * 100.0).toInt().coerceIn(0, 100)\n                                } else {\n                                    0\n                                }\n                            }\n                        }\n                    }\n                }\n\n                // 3. Try Mali (Direct Value)\n                if (rawGpu < 0) {\n                    val maliResult = rootShellForResult(\"cat $MALI_PATH\")\n                    if (maliResult.isSuccess && maliResult.out.isNotEmpty()) {\n                        val value = maliResult.out[0].trim().toIntOrNull() ?: 0\n                        rawGpu = if (value > 100) {\n                            (value * 100 / 255).coerceIn(0, 100)\n                        } else {\n                            value.coerceIn(0, 100)\n                        }\n                    }\n                }\n                \n                // 4. Try Generic\n                if (rawGpu < 0) {\n                    val genericResult = rootShellForResult(\"cat $GENERIC_PATH\")\n                    if (genericResult.isSuccess && genericResult.out.isNotEmpty()) {\n                        val content = genericResult.out[0].trim().replace(\"%\", \"\")\n                        rawGpu = content.toIntOrNull()?.coerceIn(0, 100) ?: 0\n                    }\n                }\n\n                if (rawGpu < 0) rawGpu = 0\n\n                // Apply EMA smoothing\n                smoothedGpuUsage = if (smoothedGpuUsage < 0) {\n                    rawGpu.toFloat()\n                } else {\n                    smoothedGpuUsage * (1f - GPU_EMA_ALPHA) + rawGpu.toFloat() * GPU_EMA_ALPHA\n                }\n\n                smoothedGpuUsage.toInt().coerceIn(0, 100)\n            } catch (e: Exception) {\n                Log.e(TAG, \"Error reading GPU usage\", e)\n                0\n            }\n        }\n    }\n\n    /**\n     * Get Memory and Swap info using `free` command and `/proc/swaps`.\n     */\n    suspend fun getMemoryInfo(): MemoryInfo {\n        return withContext(Dispatchers.IO) {\n            var ramTotal = 0L\n            var ramUsed = 0L\n            var swapTotal = 0L\n            var swapUsed = 0L\n            var zramTotal = 0L\n            var zramUsed = 0L\n\n            try {\n                // Use /proc/meminfo for more reliable parsing\n                val memInfoResult = rootShellForResult(\"cat /proc/meminfo\")\n                if (memInfoResult.isSuccess) {\n                    var memTotal = 0L\n                    var memFree = 0L\n                    var memAvailable = 0L\n                    var buffers = 0L\n                    var cached = 0L\n                    \n                    memInfoResult.out.forEach { line ->\n                        val parts = line.split(Regex(\":\\\\s+\"))\n                        if (parts.size >= 2) {\n                            val key = parts[0].trim()\n                            val valueStr = parts[1].trim().split(Regex(\"\\\\s+\"))[0]\n                            val valueKb = valueStr.toLongOrNull() ?: 0L\n                            val valueBytes = valueKb * 1024\n                            \n                            when (key) {\n                                \"MemTotal\" -> memTotal = valueBytes\n                                \"MemFree\" -> memFree = valueBytes\n                                \"MemAvailable\" -> memAvailable = valueBytes\n                                \"Buffers\" -> buffers = valueBytes\n                                \"Cached\" -> cached = valueBytes\n                            }\n                        }\n                    }\n                    \n                    ramTotal = memTotal\n                    \n                    // Calculate Available if MemAvailable is missing (older kernels)\n                    if (memAvailable == 0L && memFree > 0) {\n                         // Rough estimation: Free + Cached + Buffers\n                         // Note: This is an over-estimation as not all cached is reclaimable,\n                         // but better than nothing for old kernels.\n                         memAvailable = memFree + cached + buffers\n                    }\n                    \n                    // Calculate Used\n                    // Used = Total - Available\n                    if (ramTotal > 0) {\n                        ramUsed = (ramTotal - memAvailable).coerceAtLeast(0)\n                    }\n                }\n                \n                // Parse /proc/swaps to distinguish ZRAM vs Swap File\n                val swapsResult = rootShellForResult(\"cat /proc/swaps\")\n                if (swapsResult.isSuccess) {\n                    swapsResult.out.forEach { line ->\n                        if (line.trim().startsWith(\"Filename\")) return@forEach\n                        val parts = line.split(Regex(\"\\\\s+\"))\n                        if (parts.size >= 4) {\n                            val filename = parts[0]\n                            val sizeKb = parts[2].toLongOrNull() ?: 0L\n                            val usedKb = parts[3].toLongOrNull() ?: 0L\n                            \n                            val sizeBytes = sizeKb * 1024\n                            val usedBytes = usedKb * 1024\n                            \n                            if (filename.contains(\"zram\")) {\n                                zramTotal += sizeBytes\n                                zramUsed += usedBytes\n                            } else {\n                                // Assume everything else is \"Swap File\" (e.g., /data/swapfile)\n                                swapTotal += sizeBytes\n                                swapUsed += usedBytes\n                            }\n                        }\n                    }\n                }\n            } catch (e: Exception) {\n                Log.e(TAG, \"Error reading Memory info\", e)\n            }\n\n            MemoryInfo(ramTotal, ramUsed, swapTotal, swapUsed, zramTotal, zramUsed)\n        }\n    }\n\n    suspend fun getCpuTemperature(): Float {\n        return withContext(Dispatchers.IO) {\n            try {\n                val result = rootShellForResult(\n                    \"grep -rl 'cpu' /sys/class/thermal/thermal_zone*/type 2>/dev/null | \" +\n                    \"sed 's|/type$|/temp|' | xargs cat 2>/dev/null | head -1\"\n                )\n                if (result.isSuccess && result.out.isNotEmpty()) {\n                    val temp = result.out[0].trim().toFloatOrNull()\n                    if (temp != null && temp > 0) {\n                        return@withContext if (temp > 1000) temp / 1000f else temp\n                    }\n                }\n\n                val allZones = rootShellForResult(\"ls /sys/class/thermal/ 2>/dev/null\")\n                if (allZones.isSuccess) {\n                    for (zone in allZones.out) {\n                        val zoneName = zone.trim()\n                        if (!zoneName.startsWith(\"thermal_zone\")) continue\n                        val typeResult = rootShellForResult(\"cat /sys/class/thermal/$zoneName/type 2>/dev/null\")\n                        if (!typeResult.isSuccess || typeResult.out.isEmpty()) continue\n                        val type = typeResult.out[0].trim().lowercase()\n                        if (!type.contains(\"cpu\")) continue\n\n                        val tempResult = rootShellForResult(\"cat /sys/class/thermal/$zoneName/temp 2>/dev/null\")\n                        if (tempResult.isSuccess && tempResult.out.isNotEmpty()) {\n                            val temp = tempResult.out[0].trim().toFloatOrNull()\n                            if (temp != null && temp > 0) {\n                                return@withContext if (temp > 1000) temp / 1000f else temp\n                            }\n                        }\n                    }\n                }\n\n                0f\n            } catch (e: Exception) {\n                Log.e(TAG, \"Error reading CPU temperature\", e)\n                0f\n            }\n        }\n    }\n\n    suspend fun getCpuFrequencies(): List<CpuFreqInfo> {\n        return withContext(Dispatchers.IO) {\n            try {\n                val result = rootShellForResult(\"ls /sys/devices/system/cpu/ 2>/dev/null | grep -E '^cpu[0-9]+$'\")\n                if (!result.isSuccess) return@withContext emptyList()\n\n                val infos = mutableListOf<CpuFreqInfo>()\n                for (line in result.out) {\n                    val coreName = line.trim()\n                    val match = Regex(\"cpu(\\\\d+)\").find(coreName) ?: continue\n                    val coreIndex = match.groupValues[1].toIntOrNull() ?: continue\n\n                    val onlineResult = rootShellForResult(\"cat /sys/devices/system/cpu/$coreName/online 2>/dev/null\")\n                    if (onlineResult.isSuccess && onlineResult.out.isNotEmpty()) {\n                        if (onlineResult.out[0].trim() == \"0\") continue\n                    }\n\n                    val curFreqResult = rootShellForResult(\"cat /sys/devices/system/cpu/$coreName/cpufreq/scaling_cur_freq 2>/dev/null\")\n                    val maxFreqResult = rootShellForResult(\"cat /sys/devices/system/cpu/$coreName/cpufreq/cpuinfo_max_freq 2>/dev/null\")\n\n                    val curFreq = if (curFreqResult.isSuccess && curFreqResult.out.isNotEmpty())\n                        curFreqResult.out[0].trim().toLongOrNull() ?: 0L else 0L\n                    val maxFreq = if (maxFreqResult.isSuccess && maxFreqResult.out.isNotEmpty())\n                        maxFreqResult.out[0].trim().toLongOrNull() ?: 0L else 0L\n\n                    infos.add(CpuFreqInfo(coreIndex, curFreq, maxFreq))\n                }\n\n                infos\n            } catch (e: Exception) {\n                Log.e(TAG, \"Error reading CPU frequencies\", e)\n                emptyList()\n            }\n        }\n    }\n\n    suspend fun getStoragePartitions(): List<StoragePartitionInfo> {\n        return withContext(Dispatchers.IO) {\n            try {\n                val partitions = listOf(\n                    Pair(\"/data\", \"Data\"),\n                    Pair(\"/system\", \"System\"),\n                    Pair(\"/cache\", \"Cache\"),\n                    Pair(\"/vendor\", \"Vendor\"),\n                    Pair(\"/product\", \"Product\")\n                )\n\n                val infos = mutableListOf<StoragePartitionInfo>()\n                for ((path, label) in partitions) {\n                    val result = rootShellForResult(\"df -k $path 2>/dev/null\")\n                    if (result.isSuccess && result.out.size >= 2) {\n                        val dataLine = result.out.lastOrNull()?.trim() ?: continue\n                        val parts = dataLine.split(Regex(\"\\\\s+\"))\n                        if (parts.size >= 5) {\n                            val totalKb = parts[1].toLongOrNull() ?: continue\n                            val usedKb = parts[2].toLongOrNull() ?: continue\n                            val totalBytes = totalKb * 1024\n                            val usedBytes = usedKb * 1024\n                            if (totalBytes > 0) {\n                                infos.add(StoragePartitionInfo(label, totalBytes, usedBytes))\n                            }\n                        }\n                    }\n                }\n\n                infos\n            } catch (e: Exception) {\n                Log.e(TAG, \"Error reading storage partitions\", e)\n                emptyList()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/IOStreamUtils.kt",
    "content": "package me.bmax.apatch.util\n\nimport android.content.ContentResolver\nimport android.net.Uri\nimport me.bmax.apatch.apApp\nimport java.io.File\nimport java.io.FileNotFoundException\nimport java.io.InputStream\nimport java.io.OutputStream\n\n\nval cr: ContentResolver get() = apApp.contentResolver\n\nfun Uri.inputStream() = SafeUriResolver.openInputStream(apApp, this)\n\nfun Uri.outputStream() = cr.openOutputStream(this, \"rwt\") ?: throw FileNotFoundException()\n\nfun Uri.fileDescriptor(mode: String) =\n    cr.openFileDescriptor(this, mode) ?: throw FileNotFoundException()\n\ninline fun <In : InputStream, Out : OutputStream> withStreams(\n    inStream: In,\n    outStream: Out,\n    withBoth: (In, Out) -> Unit\n) {\n    inStream.use { reader ->\n        outStream.use { writer ->\n            withBoth(reader, writer)\n        }\n    }\n}\n\nfun InputStream.copyAndClose(out: OutputStream) = withStreams(this, out) { i, o -> i.copyTo(o) }\nfun InputStream.writeTo(file: File) = copyAndClose(file.outputStream())\n\nfun InputStream.copyAndCloseOut(out: OutputStream) = out.use { copyTo(it) }\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/LauncherIconUtils.kt",
    "content": "package me.bmax.apatch.util\n\nimport android.content.ComponentName\nimport android.content.Context\nimport android.content.pm.PackageManager\nimport me.bmax.apatch.APApplication\n\nobject LauncherIconUtils {\n    private const val MAIN_ACTIVITY = \".ui.MainActivityDefault\"\n    private const val ALIAS_ACTIVITY = \".ui.MainActivityAlias\"\n    private const val ALIAS_ACTIVITY_SU = \".ui.MainActivityAliasSu\"\n    private const val ALIAS_ACTIVITY_ALT_SU = \".ui.MainActivityAliasAltSu\"\n\n    fun updateLauncherState(context: Context) {\n        val prefs = APApplication.sharedPreferences\n        val useAlt = prefs.getBoolean(\"use_alt_icon\", false)\n        val appName = prefs.getString(\"desktop_app_name\", \"FolkPatch\")\n        val isSu = appName == \"FPatch\"\n\n        val pm = context.packageManager\n        val basePackage = APApplication::class.java.`package`?.name ?: \"me.bmax.apatch\"\n        \n        val mainComponent = ComponentName(context.packageName, basePackage + MAIN_ACTIVITY)\n        val aliasComponent = ComponentName(context.packageName, basePackage + ALIAS_ACTIVITY)\n        val aliasSuComponent = ComponentName(context.packageName, basePackage + ALIAS_ACTIVITY_SU)\n        val aliasAltSuComponent = ComponentName(context.packageName, basePackage + ALIAS_ACTIVITY_ALT_SU)\n\n\n        val targetComponent = when {\n            useAlt && isSu -> aliasAltSuComponent\n            useAlt && !isSu -> aliasComponent\n            !useAlt && isSu -> aliasSuComponent\n            else -> mainComponent\n        }\n\n        val allComponents = listOf(mainComponent, aliasComponent, aliasSuComponent, aliasAltSuComponent)\n\n        try {\n            // Enable target\n            pm.setComponentEnabledSetting(\n                targetComponent,\n                PackageManager.COMPONENT_ENABLED_STATE_ENABLED,\n                PackageManager.DONT_KILL_APP\n            )\n\n            // Disable others\n            allComponents.filter { it != targetComponent }.forEach {\n                pm.setComponentEnabledSetting(\n                    it,\n                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED,\n                    PackageManager.DONT_KILL_APP\n                )\n            }\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n    }\n\n    // Deprecated but kept for compatibility if needed, redirects to updateLauncherState\n    fun toggleLauncherIcon(context: Context, useAlt: Boolean) {\n        updateLauncherState(context)\n    }\n\n    fun applySaved(context: Context) {\n        updateLauncherState(context)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/LogEvent.kt",
    "content": "package me.bmax.apatch.util\n\nimport android.content.Context\nimport android.os.Build\nimport android.system.Os\nimport com.topjohnwu.superuser.ShellUtils\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport java.io.File\nimport java.io.FileWriter\nimport java.io.PrintWriter\nimport java.time.LocalDateTime\nimport java.time.format.DateTimeFormatter\n\nsuspend fun getBugreportFile(context: Context): File = withContext(Dispatchers.IO) {\n\n    val bugreportDir = File(context.cacheDir, \"bugreport\")\n    bugreportDir.mkdirs()\n\n    val dmesgFile = File(bugreportDir, \"dmesg.txt\")\n    val logcatFile = File(bugreportDir, \"logcat.txt\")\n    val tombstonesFile = File(bugreportDir, \"tombstones.tar.gz\")\n    val dropboxFile = File(bugreportDir, \"dropbox.tar.gz\")\n    val pstoreFile = File(bugreportDir, \"pstore.tar.gz\")\n    val diagFile = File(bugreportDir, \"diag.tar.gz\")\n    val bootlogFile = File(bugreportDir, \"bootlog.tar.gz\")\n    val mountsFile = File(bugreportDir, \"mounts.txt\")\n    val fileSystemsFile = File(bugreportDir, \"filesystems.txt\")\n    val apFileTree = File(bugreportDir, \"ap_tree.txt\")\n    val appListFile = File(bugreportDir, \"packages.txt\")\n    val propFile = File(bugreportDir, \"props.txt\")\n    val packageConfigFile = File(bugreportDir, \"package_config\")\n\n    val shell = tryGetRootShell()\n\n    shell.newJob().add(\"dmesg > ${dmesgFile.absolutePath}\").exec()\n    shell.newJob().add(\"logcat -d > ${logcatFile.absolutePath}\").exec()\n    shell.newJob().add(\"tar -czf ${tombstonesFile.absolutePath} -C /data/tombstones .\").exec()\n    shell.newJob().add(\"tar -czf ${dropboxFile.absolutePath} -C /data/system/dropbox .\").exec()\n    shell.newJob().add(\"tar -czf ${pstoreFile.absolutePath} -C /sys/fs/pstore .\").exec()\n    shell.newJob().add(\"tar -czf ${diagFile.absolutePath} -C /data/vendor/diag .\").exec()\n    shell.newJob().add(\"tar -czf ${bootlogFile.absolutePath} -C /data/adb/ap/log .\").exec()\n\n    shell.newJob().add(\"cat /proc/1/mountinfo > ${mountsFile.absolutePath}\").exec()\n    shell.newJob().add(\"cat /proc/filesystems > ${fileSystemsFile.absolutePath}\").exec()\n    shell.newJob().add(\"ls -alRZ /data/adb > ${apFileTree.absolutePath}\").exec()\n    shell.newJob().add(\"cp /data/system/packages.list ${appListFile.absolutePath}\").exec()\n    shell.newJob().add(\"getprop > ${propFile.absolutePath}\").exec()\n    shell.newJob().add(\"cp /data/adb/ap/package_config ${packageConfigFile.absolutePath}\").exec()\n\n    val selinux = ShellUtils.fastCmd(shell, \"getenforce\")\n\n    // basic information\n    val buildInfo = File(bugreportDir, \"basic.txt\")\n    PrintWriter(FileWriter(buildInfo)).use { pw ->\n        pw.println(\"Kernel: ${System.getProperty(\"os.version\")}\")\n        pw.println(\"BRAND: \" + Build.BRAND)\n        pw.println(\"MODEL: \" + Build.MODEL)\n        pw.println(\"PRODUCT: \" + Build.PRODUCT)\n        pw.println(\"MANUFACTURER: \" + Build.MANUFACTURER)\n        pw.println(\"SDK: \" + Build.VERSION.SDK_INT)\n        pw.println(\"PREVIEW_SDK: \" + Build.VERSION.PREVIEW_SDK_INT)\n        pw.println(\"FINGERPRINT: \" + Build.FINGERPRINT)\n        pw.println(\"DEVICE: \" + Build.DEVICE)\n        pw.println(\"Manager: \" + Version.getManagerVersion())\n        pw.println(\"SELinux: $selinux\")\n\n        val uname = Os.uname()\n        pw.println(\"KernelRelease: ${uname.release}\")\n        pw.println(\"KernelVersion: ${uname.version}\")\n        pw.println(\"Mahcine: ${uname.machine}\")\n        pw.println(\"Nodename: ${uname.nodename}\")\n        pw.println(\"Sysname: ${uname.sysname}\")\n\n        pw.println(\"KPatch: ${Version.installedKPVString()}\")\n        pw.println(\"APatch: ${Version.installedApdHash}\")\n        val safeMode = false\n        pw.println(\"SafeMode: $safeMode\")\n    }\n\n    // modules\n    val modulesFile = File(bugreportDir, \"modules.json\")\n    modulesFile.writeText(listModules())\n\n    val formatter = DateTimeFormatter.ofPattern(\"yyyy-MM-dd_HH_mm\")\n    val current = LocalDateTime.now().format(formatter)\n\n    val targetFile = File(context.cacheDir, \"FolkPatch_bugreport_${current}.tar.gz\")\n\n    shell.newJob().add(\"tar czf ${targetFile.absolutePath} -C ${bugreportDir.absolutePath} .\")\n        .exec()\n    shell.newJob().add(\"rm -rf ${bugreportDir.absolutePath}\").exec()\n    val uid = android.os.Process.myUid()\n    shell.newJob().add(\"chown $uid:$uid ${targetFile.absolutePath}\").exec()\n    shell.newJob().add(\"chmod 0644 ${targetFile.absolutePath}\").exec()\n\n    return@withContext targetFile\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/ModuleBackupUtils.kt",
    "content": "package me.bmax.apatch.util\n\nimport android.content.Context\nimport android.net.Uri\nimport androidx.compose.material3.SnackbarHostState\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.R\nimport java.io.BufferedInputStream\nimport java.io.BufferedOutputStream\nimport java.io.File\nimport java.security.MessageDigest\n\nimport android.os.Environment\n\nimport kotlinx.coroutines.async\n\nobject ModuleBackupUtils {\n\n    private const val MODULE_DIR = \"/data/adb/modules\"\n\n    suspend fun autoBackupModule(context: Context, file: File, originalFileName: String?, subDir: String): String? {\n        return withContext(Dispatchers.IO) {\n            val errors = StringBuilder()\n            \n            val webdavJob = async {\n                if (me.bmax.apatch.ui.theme.BackupConfig.isBackupEnabled) {\n                     try {\n                         val basePath = me.bmax.apatch.ui.theme.BackupConfig.webdavPath\n                         // Construct full subDir: basePath + subDir (e.g. \"/Backup\" + \"APM\")\n                         val fullSubDir = if (basePath.endsWith(\"/\")) \"$basePath$subDir\" else \"$basePath/$subDir\"\n                         val cleanSubDir = if (fullSubDir.startsWith(\"/\")) fullSubDir.substring(1) else fullSubDir\n                         \n                         val webDavResult = WebDavUtils.uploadFile(\n                            me.bmax.apatch.ui.theme.BackupConfig.webdavUrl,\n                            me.bmax.apatch.ui.theme.BackupConfig.webdavUsername,\n                            me.bmax.apatch.ui.theme.BackupConfig.webdavPassword,\n                            file,\n                            cleanSubDir,\n                            originalFileName\n                         )\n                         if (webDavResult.isFailure) {\n                             \"WebDAV: ${webDavResult.exceptionOrNull()?.message}\"\n                         } else {\n                             null\n                         }\n                     } catch (e: Exception) {\n                         \"WebDAV Error: ${e.message}\"\n                     }\n                } else {\n                    null\n                }\n            }\n\n            val localJob = async {\n                if (APApplication.sharedPreferences.getBoolean(\"auto_backup_module\", false)) {\n                    try {\n                        val baseBackupDir = File(getSafeDownloadsDir(me.bmax.apatch.apApp), \"FolkPatch/ModuleBackups\")\n                        // Use subDir (APM/KPM) to separate backups\n                        val backupDir = File(baseBackupDir, subDir)\n                        \n                        if (!backupDir.exists()) backupDir.mkdirs()\n\n                        // Calculate hash of the incoming file\n                        val digest = MessageDigest.getInstance(\"SHA-256\")\n                        val buffer = ByteArray(8192)\n                        var bytesRead: Int\n                        file.inputStream().use { input ->\n                            while (input.read(buffer).also { bytesRead = it } != -1) {\n                                digest.update(buffer, 0, bytesRead)\n                            }\n                        }\n                        val fileHash = digest.digest().joinToString(\"\") { \"%02x\".format(it) }\n\n                        val baseName = originalFileName ?: file.name\n                        val nameWithoutExt = baseName.substringBeforeLast(\".\")\n                        val ext = baseName.substringAfterLast(\".\", \"\")\n                        val extWithDot = if (ext.isNotEmpty()) \".$ext\" else \"\"\n\n                        var counter = 0\n                        while (true) {\n                            val candidateName = if (counter == 0) baseName else \"$nameWithoutExt ($counter)$extWithDot\"\n                            val candidateFile = File(backupDir, candidateName)\n\n                            if (candidateFile.exists()) {\n                                // Check hash\n                                val existingDigest = MessageDigest.getInstance(\"SHA-256\")\n                                candidateFile.inputStream().use { input ->\n                                    while (input.read(buffer).also { bytesRead = it } != -1) {\n                                        existingDigest.update(buffer, 0, bytesRead)\n                                    }\n                                }\n                                val existingHash = existingDigest.digest().joinToString(\"\") { \"%02x\".format(it) }\n\n                                if (fileHash == existingHash) {\n                                    // Duplicate found\n                                    break\n                                }\n                                // Hash mismatch, try next name\n                                counter++\n                            } else {\n                                // File doesn't exist, save here\n                                file.copyTo(candidateFile)\n                                break\n                            }\n                        }\n                        null\n                    } catch (e: Exception) {\n                        \"Local Error: ${e.message}\"\n                    }\n                } else {\n                    null\n                }\n            }\n\n            val webdavError = webdavJob.await()\n            val localError = localJob.await()\n            \n            if (webdavError != null) errors.append(\"$webdavError; \")\n            if (localError != null) errors.append(\"$localError; \")\n            \n            if (errors.isNotEmpty()) errors.toString() else null\n        }\n    }\n\n    suspend fun backupModules(context: Context, snackBarHost: SnackbarHostState, uri: Uri) {\n        withContext(Dispatchers.IO) {\n            try {\n                // Use the busybox bundled with APatch\n                val busyboxPath = \"/data/adb/ap/bin/busybox\"\n                val tempFile = File(context.cacheDir, \"backup_tmp.tar.gz\")\n                val tempPath = tempFile.absolutePath\n\n                if (tempFile.exists()) tempFile.delete()\n\n                // Construct command to tar the modules directory to temp file\n                // And chmod it so the app can read it\n                val command = \"cd \\\"$MODULE_DIR\\\" && $busyboxPath tar -czf \\\"$tempPath\\\" ./* && chmod 666 \\\"$tempPath\\\"\"\n\n                val result = getRootShell().newJob().add(command).exec()\n\n                if (result.isSuccess) {\n                    context.contentResolver.openOutputStream(uri)?.use { output ->\n                        tempFile.inputStream().use { input ->\n                            input.copyTo(output)\n                        }\n                    }\n                    tempFile.delete()\n                    withContext(Dispatchers.Main) {\n                        snackBarHost.showSnackbar(context.getString(R.string.apm_backup_success))\n                    }\n                } else {\n                    val error = result.err.joinToString(\"\\n\")\n                    withContext(Dispatchers.Main) {\n                        snackBarHost.showSnackbar(context.getString(R.string.apm_backup_failed_msg, error))\n                    }\n                }\n\n            } catch (e: Exception) {\n                withContext(Dispatchers.Main) {\n                    snackBarHost.showSnackbar(context.getString(R.string.apm_backup_failed_msg, e.message))\n                }\n            }\n        }\n    }\n\n    suspend fun restoreModules(context: Context, snackBarHost: SnackbarHostState, uri: Uri) {\n        withContext(Dispatchers.IO) {\n            try {\n                val busyboxPath = \"/data/adb/ap/bin/busybox\"\n                val tempFile = File(context.cacheDir, \"restore_tmp.tar.gz\")\n                val tempPath = tempFile.absolutePath\n\n                if (tempFile.exists()) tempFile.delete()\n\n                SafeUriResolver.openInputStream(context, uri)?.use { input ->\n                    tempFile.outputStream().use { output ->\n                        input.copyTo(output)\n                    }\n                }\n\n                // Make sure root can read it\n                tempFile.setReadable(true, false)\n\n                val command = \"cd \\\"$MODULE_DIR\\\" && $busyboxPath tar -xzf \\\"$tempPath\\\"\"\n                val result = getRootShell().newJob().add(command).exec()\n\n                tempFile.delete()\n\n                if (result.isSuccess) {\n                    // Refresh module list\n                    // APatchCli.refresh() // Wait, this refreshes shell, not module list. \n                    // Module list is refreshed by viewModel.fetchModuleList() in UI\n                    \n                    withContext(Dispatchers.Main) {\n                        snackBarHost.showSnackbar(context.getString(R.string.apm_restore_success))\n                    }\n                } else {\n                    val error = result.err.joinToString(\"\\n\")\n                    withContext(Dispatchers.Main) {\n                        snackBarHost.showSnackbar(context.getString(R.string.apm_restore_failed_msg, error))\n                    }\n                }\n\n            } catch (e: Exception) {\n                withContext(Dispatchers.Main) {\n                    snackBarHost.showSnackbar(context.getString(R.string.apm_restore_failed_msg, e.message))\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/ModuleShortcut.kt",
    "content": "package me.bmax.apatch.util\n\nimport android.app.AppOpsManager\nimport android.content.ComponentName\nimport android.content.Context\nimport android.content.Intent\nimport android.graphics.Bitmap\nimport android.graphics.BitmapFactory\nimport android.graphics.Canvas\nimport android.net.Uri\nimport android.os.Build\nimport android.provider.Settings\nimport android.util.Log\nimport me.bmax.apatch.util.ui.showToast\nimport androidx.core.content.ContextCompat\nimport androidx.core.content.pm.ShortcutInfoCompat\nimport androidx.core.content.pm.ShortcutManagerCompat\nimport androidx.core.graphics.createBitmap\nimport androidx.core.graphics.drawable.IconCompat\nimport androidx.core.graphics.scale\nimport androidx.core.net.toUri\nimport com.topjohnwu.superuser.io.SuFile\nimport com.topjohnwu.superuser.io.SuFileInputStream\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.R\nimport me.bmax.apatch.ui.MainActivity\nimport me.bmax.apatch.ui.WebUIActivity\nimport java.util.Locale\n\nobject ModuleShortcut {\n    private const val TAG = \"ModuleShortcut\"\n\n    fun createModuleWebUiShortcut(\n        context: Context,\n        moduleId: String,\n        name: String,\n        iconUri: String?\n    ) {\n        val shortcutId = \"module_webui_$moduleId\"\n        val shortcutIntent = Intent(context, WebUIActivity::class.java).apply {\n            action = Intent.ACTION_VIEW\n            data = \"apatch://webui/$moduleId\".toUri()\n            putExtra(\"id\", moduleId)\n            putExtra(\"name\", name)\n            putExtra(\"from_webui_shortcut\", true)\n            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)\n        }\n        createModuleShortcut(\n            context = context,\n            moduleId = moduleId,\n            name = name,\n            iconUri = iconUri,\n            shortcutId = shortcutId,\n            shortcutIntent = shortcutIntent,\n            logPrefix = \"createModuleWebUiShortcut\"\n        )\n    }\n\n    fun hasModuleWebUiShortcut(context: Context, moduleId: String): Boolean {\n        val id = \"module_webui_$moduleId\"\n        return hasPinnedShortcut(context, id)\n    }\n\n    fun deleteModuleWebUiShortcut(context: Context, moduleId: String) {\n        deleteShortcut(context, \"module_webui_$moduleId\")\n    }\n\n    private fun getLauncherComponent(context: Context): ComponentName {\n        val prefs = APApplication.sharedPreferences\n        val useAlt = prefs.getBoolean(\"use_alt_icon\", false)\n        val pkg = context.packageName\n        return if (useAlt) {\n            ComponentName(pkg, \"me.bmax.apatch.ui.MainActivityAlias\")\n        } else {\n            ComponentName(pkg, \"me.bmax.apatch.ui.MainActivity\")\n        }\n    }\n\n    fun createModuleActionShortcut(\n        context: Context,\n        moduleId: String,\n        name: String,\n        iconUri: String?\n    ) {\n        val shortcutId = \"module_action_$moduleId\"\n        val shortcutIntent = Intent().apply {\n            component = getLauncherComponent(context)\n            action = Intent.ACTION_VIEW\n            putExtra(\"apm_action_module_id\", moduleId)\n            putExtra(\"from_action_shortcut\", true)\n            addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)\n        }\n        createModuleShortcut(\n            context = context,\n            moduleId = moduleId,\n            name = name,\n            iconUri = iconUri,\n            shortcutId = shortcutId,\n            shortcutIntent = shortcutIntent,\n            logPrefix = \"createModuleActionShortcut\"\n        )\n    }\n\n    fun hasModuleActionShortcut(context: Context, moduleId: String): Boolean {\n        val id = \"module_action_$moduleId\"\n        return hasPinnedShortcut(context, id)\n    }\n\n    fun deleteModuleActionShortcut(context: Context, moduleId: String) {\n        deleteShortcut(context, \"module_action_$moduleId\")\n    }\n\n    fun createScriptShortcut(\n        context: Context,\n        scriptId: String,\n        name: String,\n        iconUri: String?\n    ) {\n        val shortcutId = \"script_$scriptId\"\n        val shortcutIntent = Intent().apply {\n            component = getLauncherComponent(context)\n            action = Intent.ACTION_VIEW\n            putExtra(\"script_id\", scriptId)\n            putExtra(\"from_script_shortcut\", true)\n            addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)\n        }\n        createModuleShortcut(\n            context = context,\n            moduleId = scriptId,\n            name = name,\n            iconUri = iconUri,\n            shortcutId = shortcutId,\n            shortcutIntent = shortcutIntent,\n            logPrefix = \"createScriptShortcut\"\n        )\n    }\n\n    fun hasScriptShortcut(context: Context, scriptId: String): Boolean {\n        return hasPinnedShortcut(context, \"script_$scriptId\")\n    }\n\n    fun deleteScriptShortcut(context: Context, scriptId: String) {\n        deleteShortcut(context, \"script_$scriptId\")\n    }\n\n    private fun createModuleShortcut(\n        context: Context,\n        moduleId: String,\n        name: String,\n        iconUri: String?,\n        shortcutId: String,\n        shortcutIntent: Intent,\n        logPrefix: String\n    ) {\n        val hasPinned = hasPinnedShortcut(context, shortcutId)\n        Log.d(TAG, \"$logPrefix: shortcutId=$shortcutId, hasPinned=$hasPinned\")\n\n        val iconCompat = createShortcutIcon(context, iconUri)\n        val finalIcon = iconCompat ?: IconCompat.createWithResource(context, R.mipmap.ic_launcher)\n\n        val shortcut = ShortcutInfoCompat.Builder(context, shortcutId)\n            .setShortLabel(name)\n            .setIntent(shortcutIntent)\n            .setIcon(finalIcon)\n            .build()\n\n        try {\n            Log.d(TAG, \"$logPrefix: pushDynamicShortcut() called for moduleId=$moduleId\")\n            ShortcutManagerCompat.pushDynamicShortcut(context, shortcut)\n        } catch (t: Throwable) {\n            Log.w(TAG, \"$logPrefix: pushDynamicShortcut() threw exception for moduleId=$moduleId: ${t.message}\", t)\n        }\n\n        if (hasPinned) {\n            Log.d(TAG, \"$logPrefix: detected existing pinned shortcut, updating only\")\n            showToast(context, context.getString(R.string.module_shortcut_updated))\n            return\n        }\n\n        val manufacturer = Build.MANUFACTURER.lowercase(Locale.ROOT)\n        val initialState = getShortcutPermissionState(context)\n        Log.d(TAG, \"$logPrefix: initial permission state=$initialState\")\n        if (manufacturer.contains(\"xiaomi\") && initialState != ShortcutPermissionState.Granted) {\n            Log.d(TAG, \"$logPrefix: device is Xiaomi, trying to grant via root shell\")\n            val rootSuccess = tryGrantMiuiShortcutPermissionByRoot(context)\n            Log.d(TAG, \"$logPrefix: root grant attempt success=$rootSuccess\")\n            val afterState = getShortcutPermissionState(context)\n            Log.d(TAG, \"$logPrefix: state after root attempt=$afterState\")\n            if (afterState != ShortcutPermissionState.Granted) {\n                Log.d(TAG, \"$logPrefix: still not Granted after root, showing hint\")\n                showShortcutPermissionHint(context)\n                return\n            }\n        } else if (initialState == ShortcutPermissionState.Denied || initialState == ShortcutPermissionState.Ask) {\n            Log.d(TAG, \"$logPrefix: permission not granted (state=$initialState), showing hint first\")\n            showShortcutPermissionHint(context)\n            return\n        }\n        if (!ShortcutManagerCompat.isRequestPinShortcutSupported(context)) {\n            Log.w(TAG, \"$logPrefix: requestPinShortcut not supported on this launcher\")\n            showToast(context, context.getString(R.string.module_shortcut_not_supported))\n            return\n        }\n\n        val pinned = try {\n            Log.d(TAG, \"$logPrefix: requestPinShortcut() called for moduleId=$moduleId\")\n            val result = ShortcutManagerCompat.requestPinShortcut(context, shortcut, null)\n            Log.d(TAG, \"$logPrefix: requestPinShortcut() result=$result\")\n            result\n        } catch (t: Throwable) {\n            Log.w(TAG, \"$logPrefix: requestPinShortcut() threw exception for moduleId=$moduleId: ${t.message}\", t)\n            false\n        }\n\n        if (pinned) {\n            Log.d(TAG, \"$logPrefix: pinned shortcut created successfully for moduleId=$moduleId\")\n            showToast(context, context.getString(R.string.module_shortcut_created))\n        } else {\n            Log.w(TAG, \"$logPrefix: pinned shortcut not created, showing permission hint for moduleId=$moduleId\")\n            showShortcutPermissionHint(context)\n        }\n    }\n\n    private fun createShortcutIcon(context: Context, iconUri: String?): IconCompat? {\n        val bitmap = loadShortcutBitmap(context, iconUri) ?: return null\n        return IconCompat.createWithBitmap(bitmap)\n    }\n\n    private fun hasPinnedShortcut(context: Context, id: String): Boolean {\n        return try {\n            val shortcuts = ShortcutManagerCompat.getShortcuts(\n                context,\n                ShortcutManagerCompat.FLAG_MATCH_PINNED\n            )\n            val exists = shortcuts.any { it.id == id && it.isEnabled }\n            Log.d(TAG, \"hasPinnedShortcut: id=$id, exists=$exists\")\n            exists\n        } catch (t: Throwable) {\n            Log.w(TAG, \"hasPinnedShortcut: exception for id=$id: ${t.message}\", t)\n            false\n        }\n    }\n\n    private fun deleteShortcut(context: Context, id: String) {\n        try {\n            ShortcutManagerCompat.removeDynamicShortcuts(context, listOf(id))\n            Log.d(TAG, \"deleteShortcut: removed dynamic shortcut id=$id\")\n        } catch (t: Throwable) {\n            Log.w(TAG, \"deleteShortcut: removeDynamicShortcuts exception for id=$id: ${t.message}\", t)\n        }\n        try {\n            ShortcutManagerCompat.disableShortcuts(context, listOf(id), \"\")\n            Log.d(TAG, \"deleteShortcut: disabled shortcut id=$id\")\n        } catch (t: Throwable) {\n            Log.w(TAG, \"deleteShortcut: disableShortcuts exception for id=$id: ${t.message}\", t)\n        }\n    }\n\n    fun loadShortcutBitmap(context: Context, iconUri: String?): Bitmap? {\n        if (iconUri.isNullOrBlank()) {\n            return null\n        }\n        return try {\n            val uri = iconUri.toUri()\n            Log.d(TAG, \"loadShortcutBitmap: loading bitmap from uri=$uri\")\n            val rawBitmap = if (uri.scheme.equals(\"su\", ignoreCase = true)) {\n                val path = uri.path ?: \"\"\n                if (path.isNotBlank()) {\n                    val shell = getRootShell(true)\n                    val suFile = SuFile(path)\n                    suFile.shell = shell\n                    SuFileInputStream.open(suFile).use { input ->\n                        BitmapFactory.decodeStream(input)\n                    }\n                } else null\n            } else {\n                SafeUriResolver.openInputStream(context, uri)?.use { input ->\n                    BitmapFactory.decodeStream(input)\n                }\n            }\n            if (rawBitmap != null) {\n                Log.d(TAG, \"loadShortcutBitmap: decoded bitmap successfully\")\n                val w = rawBitmap.width\n                val h = rawBitmap.height\n                val side = minOf(w, h)\n                val x = (w - side) / 2\n                val y = (h - side) / 2\n                val square = try {\n                    Bitmap.createBitmap(rawBitmap, x, y, side, side)\n                } catch (_: Throwable) {\n                    rawBitmap\n                }\n                if (square !== rawBitmap && !rawBitmap.isRecycled) {\n                    rawBitmap.recycle()\n                }\n                if (side > 512) {\n                    try {\n                        val scaled = square.scale(512, 512)\n                        if (scaled !== square && !square.isRecycled) {\n                            square.recycle()\n                        }\n                        scaled\n                    } catch (_: Throwable) {\n                        square\n                    }\n                } else {\n                    square\n                }\n            } else {\n                Log.w(TAG, \"loadShortcutBitmap: bitmap decode returned null\")\n                null\n            }\n        } catch (t: Throwable) {\n            Log.w(TAG, \"loadShortcutBitmap: exception when loading icon from uri=$iconUri: ${t.message}\", t)\n            null\n        }\n    }\n\n    private enum class ShortcutPermissionState {\n        Granted,\n        Denied,\n        Ask,\n        Unknown\n    }\n\n    private fun checkMiuiShortcutPermission(context: Context): ShortcutPermissionState {\n        return try {\n            val appOps = context.getSystemService(Context.APP_OPS_SERVICE) as? AppOpsManager\n                ?: return ShortcutPermissionState.Unknown\n            val pkg = context.applicationContext.packageName\n            val uid = context.applicationInfo.uid\n            Log.d(TAG, \"checkMiuiShortcutPermission: pkg=$pkg, uid=$uid\")\n            val appOpsClass = Class.forName(AppOpsManager::class.java.name)\n            val method = appOpsClass.getDeclaredMethod(\n                \"checkOpNoThrow\",\n                Integer.TYPE,\n                Integer.TYPE,\n                String::class.java\n            )\n            val result = method.invoke(appOps, 10017, uid, pkg)?.toString()\n            if (result == null) {\n                Log.w(TAG, \"checkMiuiShortcutPermission: checkOpNoThrow returned null\")\n                return ShortcutPermissionState.Unknown\n            }\n            Log.d(TAG, \"checkMiuiShortcutPermission: raw result=$result\")\n            val state = when (result) {\n                \"0\" -> ShortcutPermissionState.Granted\n                \"1\" -> ShortcutPermissionState.Denied\n                \"5\" -> ShortcutPermissionState.Ask\n                else -> ShortcutPermissionState.Unknown\n            }\n            Log.d(TAG, \"checkMiuiShortcutPermission: mapped state=$state\")\n            state\n        } catch (t: Throwable) {\n            Log.w(TAG, \"checkMiuiShortcutPermission: exception=${t.message}\", t)\n            ShortcutPermissionState.Unknown\n        }\n    }\n\n    private fun checkOppoShortcutPermission(context: Context): ShortcutPermissionState {\n        val resolver = context.contentResolver ?: run {\n            Log.w(TAG, \"checkOppoShortcutPermission: contentResolver is null\")\n            return ShortcutPermissionState.Unknown\n        }\n        val uri = \"content://settings/secure/launcher_shortcut_permission_settings\".toUri()\n        val cursor = resolver.query(uri, null, null, null, null) ?: run {\n            Log.w(TAG, \"checkOppoShortcutPermission: query returned null cursor, uri=$uri\")\n            return ShortcutPermissionState.Unknown\n        }\n        cursor.use { c ->\n            val pkg = context.applicationContext.packageName\n            val index = c.getColumnIndex(\"value\")\n            if (index == -1) {\n                Log.w(TAG, \"checkOppoShortcutPermission: 'value' column not found\")\n                return ShortcutPermissionState.Unknown\n            }\n            Log.d(TAG, \"checkOppoShortcutPermission: pkg=$pkg\")\n            while (c.moveToNext()) {\n                val value = c.getString(index)\n                if (!value.isNullOrEmpty()) {\n                    Log.d(TAG, \"checkOppoShortcutPermission: row value=$value\")\n                    if (value.contains(\"$pkg, 1\")) {\n                        Log.d(TAG, \"checkOppoShortcutPermission: detected Granted\")\n                        return ShortcutPermissionState.Granted\n                    }\n                    if (value.contains(\"$pkg, 0\")) {\n                        Log.d(TAG, \"checkOppoShortcutPermission: detected Denied\")\n                        return ShortcutPermissionState.Denied\n                    }\n                }\n            }\n        }\n        return ShortcutPermissionState.Unknown\n    }\n\n    private fun tryGrantMiuiShortcutPermissionByRoot(context: Context): Boolean {\n        val pkg = context.applicationContext.packageName\n        val cmd = \"appops set $pkg 10017 allow\"\n        return try {\n            val shell = getRootShell()\n            val result = shell.newJob().add(cmd).exec()\n            Log.d(TAG, \"tryGrantMiuiShortcutPermissionByRoot: cmd=$cmd, code=${result.code}, isSuccess=${result.isSuccess}\")\n            result.isSuccess\n        } catch (t: Throwable) {\n            Log.w(TAG, \"tryGrantMiuiShortcutPermissionByRoot: exception=${t.message}\", t)\n            false\n        }\n    }\n\n    private fun getShortcutPermissionState(context: Context): ShortcutPermissionState {\n        val manufacturer = Build.MANUFACTURER.lowercase(Locale.ROOT)\n        return when {\n            manufacturer.contains(\"xiaomi\") -> checkMiuiShortcutPermission(context)\n            manufacturer.contains(\"oppo\") -> checkOppoShortcutPermission(context)\n            else -> ShortcutPermissionState.Unknown\n        }\n    }\n\n    private fun showShortcutPermissionHint(context: Context) {\n        val manufacturer = Build.MANUFACTURER.lowercase(Locale.ROOT)\n        Log.d(TAG, \"showShortcutPermissionHint: manufacturer=$manufacturer\")\n        val state = getShortcutPermissionState(context)\n        val messageRes = when {\n            manufacturer.contains(\"xiaomi\") -> R.string.module_shortcut_permission_tip_xiaomi\n            manufacturer.contains(\"oppo\") -> R.string.module_shortcut_permission_tip_oppo\n            else -> R.string.module_shortcut_permission_tip_default\n        }\n        Log.d(TAG, \"showShortcutPermissionHint: state=$state, messageRes=$messageRes\")\n        showToast(context, context.getString(messageRes))\n        if (state != ShortcutPermissionState.Granted) {\n            Log.d(TAG, \"showShortcutPermissionHint: state is not Granted, opening app details settings\")\n            openAppDetailsSettings(context)\n        }\n    }\n\n    private fun openAppDetailsSettings(context: Context) {\n        val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {\n            data = Uri.fromParts(\"package\", context.packageName, null)\n            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)\n        }\n        try {\n            Log.d(TAG, \"openAppDetailsSettings: launching settings for package=${context.packageName}\")\n            context.startActivity(intent)\n        } catch (t: Throwable) {\n            Log.w(TAG, \"openAppDetailsSettings: failed to launch settings: ${t.message}\", t)\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/MusicManager.kt",
    "content": "package me.bmax.apatch.util\n\nimport android.content.Context\nimport android.media.MediaPlayer\nimport android.net.Uri\nimport android.util.Log\nimport androidx.lifecycle.DefaultLifecycleObserver\nimport androidx.lifecycle.LifecycleOwner\nimport androidx.lifecycle.ProcessLifecycleOwner\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.launch\nimport me.bmax.apatch.ui.theme.MusicConfig\nimport java.io.File\n\nobject MusicManager : DefaultLifecycleObserver {\n    private const val TAG = \"MusicManager\"\n    private var mediaPlayer: MediaPlayer? = null\n    private var context: Context? = null\n    \n    private val scope = CoroutineScope(Dispatchers.Main + Job())\n    private var progressJob: Job? = null\n\n    private val _isPlaying = MutableStateFlow(false)\n    val isPlaying: StateFlow<Boolean> = _isPlaying.asStateFlow()\n\n    private val _currentPosition = MutableStateFlow(0)\n    val currentPosition: StateFlow<Int> = _currentPosition.asStateFlow()\n\n    private val _duration = MutableStateFlow(0)\n    val duration: StateFlow<Int> = _duration.asStateFlow()\n\n    fun init(ctx: Context) {\n        context = ctx.applicationContext\n        ProcessLifecycleOwner.get().lifecycle.addObserver(this)\n        \n        // Initial setup if enabled and auto-play is on\n        if (MusicConfig.isMusicEnabled && MusicConfig.isAutoPlayEnabled) {\n            prepareAndPlay()\n        }\n    }\n\n    private fun startProgressUpdater() {\n        progressJob?.cancel()\n        progressJob = scope.launch {\n            while (isActive) {\n                if (mediaPlayer?.isPlaying == true) {\n                    _currentPosition.value = mediaPlayer?.currentPosition ?: 0\n                }\n                delay(1000)\n            }\n        }\n    }\n\n    private fun prepareAndPlay() {\n        if (context == null) return\n        val file = MusicConfig.getMusicFile(context!!) ?: return\n\n        try {\n            if (mediaPlayer == null) {\n                mediaPlayer = MediaPlayer()\n            } else {\n                mediaPlayer?.reset()\n            }\n\n            mediaPlayer?.apply {\n                setDataSource(context!!, Uri.fromFile(file))\n                setVolume(MusicConfig.volume, MusicConfig.volume)\n                isLooping = MusicConfig.isLoopingEnabled\n                setOnPreparedListener { mp ->\n                    mp.start()\n                    _duration.value = mp.duration\n                    _isPlaying.value = true\n                    startProgressUpdater()\n                }\n                setOnCompletionListener {\n                    if (!isLooping) {\n                        _isPlaying.value = false\n                        _currentPosition.value = 0\n                    }\n                }\n                setOnErrorListener { _, what, extra ->\n                    Log.e(TAG, \"MediaPlayer error: what=$what extra=$extra\")\n                    _isPlaying.value = false\n                    true\n                }\n                prepareAsync()\n            }\n        } catch (e: Exception) {\n            Log.e(TAG, \"Failed to play music\", e)\n            _isPlaying.value = false\n        }\n    }\n\n    fun play() {\n        if (mediaPlayer == null) {\n            prepareAndPlay()\n        } else {\n            try {\n                if (!mediaPlayer!!.isPlaying) {\n                    mediaPlayer?.start()\n                    _isPlaying.value = true\n                    startProgressUpdater()\n                }\n            } catch (e: Exception) {\n                 Log.e(TAG, \"Error in play()\", e)\n                 // Try to recover\n                 prepareAndPlay()\n            }\n        }\n    }\n\n    fun seekTo(position: Int) {\n        try {\n            mediaPlayer?.seekTo(position)\n            _currentPosition.value = position\n        } catch (e: Exception) {\n            Log.e(TAG, \"Error in seekTo()\", e)\n        }\n    }\n\n    fun pause() {\n        try {\n            if (mediaPlayer?.isPlaying == true) {\n                mediaPlayer?.pause()\n                _isPlaying.value = false\n            }\n        } catch (e: Exception) {\n            Log.e(TAG, \"Error in pause()\", e)\n        }\n    }\n\n    fun toggle() {\n        if (_isPlaying.value) {\n            pause()\n        } else {\n            play()\n        }\n    }\n\n    fun stop() {\n        try {\n            progressJob?.cancel()\n            mediaPlayer?.stop()\n            mediaPlayer?.release()\n            mediaPlayer = null\n            _isPlaying.value = false\n            _currentPosition.value = 0\n            _duration.value = 0\n        } catch (e: Exception) {\n            Log.e(TAG, \"Error in stop()\", e)\n        }\n    }\n\n    fun updateVolume(volume: Float) {\n        try {\n            mediaPlayer?.setVolume(volume, volume)\n        } catch (e: Exception) {\n            Log.e(TAG, \"Error setting volume\", e)\n        }\n    }\n\n    fun updateLooping(looping: Boolean) {\n        try {\n            mediaPlayer?.isLooping = looping\n        } catch (e: Exception) {\n            Log.e(TAG, \"Error setting looping\", e)\n        }\n    }\n    \n    fun reload() {\n        // Called when settings change (e.g. new file selected)\n        stop()\n        if (MusicConfig.isMusicEnabled) {\n             if (MusicConfig.isAutoPlayEnabled || _isPlaying.value) {\n                 prepareAndPlay()\n             }\n        }\n    }\n\n    override fun onStart(owner: LifecycleOwner) {\n        super.onStart(owner)\n        // App comes to foreground\n        if (MusicConfig.isMusicEnabled && MusicConfig.isAutoPlayEnabled) {\n             if (mediaPlayer == null) {\n                 prepareAndPlay()\n             } else if (!mediaPlayer!!.isPlaying) {\n                 // Resume playback from where it paused\n                 play()\n             }\n        }\n    }\n\n    override fun onStop(owner: LifecycleOwner) {\n        super.onStop(owner)\n        // App goes to background\n        pause()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/PermissionUtils.kt",
    "content": "package me.bmax.apatch.util\n\nimport android.Manifest\nimport android.content.Context\nimport android.content.pm.PackageManager\nimport android.os.Build\nimport androidx.activity.ComponentActivity\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.core.content.ContextCompat\n\n/**\n * 权限管理工具类\n */\nobject PermissionUtils {\n    \n    /**\n     * 检查是否有外部存储权限\n     */\n    fun hasExternalStoragePermission(context: Context): Boolean {\n        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n            // Android 13及以上版本使用READ_MEDIA_IMAGES权限\n            ContextCompat.checkSelfPermission(\n                context,\n                Manifest.permission.READ_MEDIA_IMAGES\n            ) == PackageManager.PERMISSION_GRANTED\n        } else {\n            // Android 12及以下版本使用READ_EXTERNAL_STORAGE权限\n            ContextCompat.checkSelfPermission(\n                context,\n                Manifest.permission.READ_EXTERNAL_STORAGE\n            ) == PackageManager.PERMISSION_GRANTED\n        }\n    }\n    \n    /**\n     * 检查是否有写入外部存储权限\n     */\n    fun hasWriteExternalStoragePermission(context: Context): Boolean {\n        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n            // Android 10及以上版本不需要WRITE_EXTERNAL_STORAGE权限来写入应用专有目录\n            true\n        } else {\n            ContextCompat.checkSelfPermission(\n                context,\n                Manifest.permission.WRITE_EXTERNAL_STORAGE\n            ) == PackageManager.PERMISSION_GRANTED\n        }\n    }\n    \n    /**\n     * 获取所需权限列表\n     */\n    fun getRequiredPermissions(): Array<String> {\n        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n            arrayOf(Manifest.permission.READ_MEDIA_IMAGES)\n        } else {\n            arrayOf(\n                Manifest.permission.READ_EXTERNAL_STORAGE,\n                Manifest.permission.WRITE_EXTERNAL_STORAGE\n            )\n        }\n    }\n}\n\n/**\n * 权限请求处理器\n */\nclass PermissionRequestHandler(private val activity: ComponentActivity) {\n    \n    private var onPermissionGranted: (() -> Unit)? = null\n    private var onPermissionDenied: (() -> Unit)? = null\n    \n    private val permissionLauncher = activity.registerForActivityResult(\n        ActivityResultContracts.RequestMultiplePermissions()\n    ) { permissions ->\n        val allGranted = permissions.values.all { it }\n        if (allGranted) {\n            onPermissionGranted?.invoke()\n        } else {\n            onPermissionDenied?.invoke()\n        }\n    }\n    \n    /**\n     * 请求权限\n     */\n    fun requestPermissions(\n        onGranted: () -> Unit,\n        onDenied: () -> Unit\n    ) {\n        onPermissionGranted = onGranted\n        onPermissionDenied = onDenied\n        \n        val permissions = PermissionUtils.getRequiredPermissions()\n        permissionLauncher.launch(permissions)\n    }\n}"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/PkgConfig.kt",
    "content": "package me.bmax.apatch.util\n\nimport android.os.Parcelable\nimport android.util.Log\nimport androidx.annotation.Keep\nimport androidx.compose.runtime.Immutable\nimport kotlinx.parcelize.Parcelize\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.Natives\nimport java.io.File\nimport java.io.FileWriter\nimport kotlin.concurrent.thread\n\nobject PkgConfig {\n    private const val TAG = \"PkgConfig\"\n\n    private const val CSV_HEADER = \"pkg,exclude,allow,uid,to_uid,sctx\"\n\n    @Immutable\n    @Parcelize\n    @Keep\n    data class Config(\n        var pkg: String = \"\", var exclude: Int = 0, var allow: Int = 0, var profile: Natives.Profile\n    ) : Parcelable {\n        companion object {\n            fun fromLine(line: String): Config {\n                val sp = line.split(\",\")\n                val profile = Natives.Profile(sp[3].toInt(), sp[4].toInt(), sp[5])\n                return Config(sp[0], sp[1].toInt(), sp[2].toInt(), profile)\n            }\n        }\n\n        fun isDefault(): Boolean {\n            return allow == 0 && exclude == 0\n        }\n\n        fun toLine(): String {\n            return \"${pkg},${exclude},${allow},${profile.uid},${profile.toUid},${profile.scontext}\"\n        }\n    }\n\n    fun readConfigs(): HashMap<Int, Config> {\n        val configs = HashMap<Int, Config>()\n        val file = File(APApplication.PACKAGE_CONFIG_FILE)\n        if (file.exists()) {\n            file.readLines().drop(1).filter { it.isNotEmpty() }.forEach {\n                Log.d(TAG, it)\n                val p = Config.fromLine(it)\n                if (!p.isDefault()) {\n                    configs[p.profile.uid] = p\n                }\n            }\n        }\n        return configs\n    }\n\n    private fun writeConfigs(configs: HashMap<Int, Config>) {\n        val file = File(APApplication.PACKAGE_CONFIG_FILE)\n        if (!file.parentFile?.exists()!!) file.parentFile?.mkdirs()\n        val tmpFile = File.createTempFile(\"package_config\", \".tmp\", file.parentFile)\n        try {\n            val writer = FileWriter(tmpFile, false)\n            writer.use { w ->\n                w.write(CSV_HEADER + '\\n')\n                configs.values.forEach {\n                    if (!it.isDefault()) {\n                        w.write(it.toLine() + '\\n')\n                    }\n                }\n            }\n            if (!tmpFile.renameTo(file)) {\n                throw IllegalStateException(\"Failed to rename temp file to ${file.absolutePath}\")\n            }\n        } catch (e: Exception) {\n            tmpFile.delete()\n            throw e\n        }\n    }\n\n    fun changeConfig(config: Config) {\n        synchronized(PkgConfig.javaClass) {\n            Natives.su()\n            val configs = readConfigs()\n            val uid = config.profile.uid\n            if (config.allow == 1) {\n                config.exclude = 0\n            }\n            if (config.isDefault() && configs[uid] != null) {\n                configs.remove(uid)\n            } else {\n                Log.d(TAG, \"change config: $config\")\n                configs[uid] = config\n            }\n            writeConfigs(configs)\n        }\n    }\n\n    fun batchChangeConfigs(newConfigs: List<Config>) {\n        thread {\n            updateConfigs(newConfigs)\n        }\n    }\n\n    fun updateConfigs(newConfigs: List<Config>) {\n        synchronized(PkgConfig.javaClass) {\n            Natives.su()\n            val configs = readConfigs()\n\n            newConfigs.forEach { config ->\n                val uid = config.profile.uid\n                // Root App should not be excluded\n                if (config.allow == 1) {\n                    config.exclude = 0\n                }\n\n                if (config.isDefault() && configs[uid] != null) {\n                    configs.remove(uid)\n                } else {\n                    configs[uid] = config\n                }\n            }\n            writeConfigs(configs)\n        }\n    }\n\n    fun overwriteConfigs(newConfigs: List<Config>) {\n        synchronized(PkgConfig.javaClass) {\n            val configMap = HashMap<Int, Config>()\n            newConfigs.forEach { config ->\n                // Root App should not be excluded\n                if (config.allow == 1) {\n                    config.exclude = 0\n                }\n                if (!config.isDefault()) {\n                    configMap[config.profile.uid] = config\n                }\n            }\n            writeConfigs(configMap)\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/SafeFileProvider.kt",
    "content": "package me.bmax.apatch.util\n\nimport android.app.Application\nimport android.database.Cursor\nimport android.database.MatrixCursor\nimport android.net.Uri\nimport android.os.ParcelFileDescriptor\nimport androidx.core.content.FileProvider\n\nclass SafeFileProvider : FileProvider() {\n    private var shouldInit = false\n\n    override fun onCreate(): Boolean {\n        val processName = Application.getProcessName()\n        shouldInit = !processName.endsWith(\":root\") && !processName.endsWith(\":webui\")\n        if (!shouldInit) return false\n        return try {\n            super.onCreate()\n        } catch (e: Exception) {\n            false\n        }\n    }\n\n    override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor {\n        if (!shouldInit) return MatrixCursor(emptyArray())\n        return try {\n            super.query(uri, projection, selection, selectionArgs, sortOrder)\n        } catch (e: Exception) {\n            MatrixCursor(emptyArray())\n        }\n    }\n\n    override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor {\n        if (!shouldInit) throw java.io.FileNotFoundException(\"Not available in this process\")\n        return try {\n            super.openFile(uri, mode) ?: throw java.io.FileNotFoundException(\"Null result\")\n        } catch (e: java.io.FileNotFoundException) {\n            throw e\n        } catch (e: Exception) {\n            throw java.io.FileNotFoundException(e.message)\n        }\n    }\n\n    override fun getType(uri: Uri): String? {\n        if (!shouldInit) return null\n        return try {\n            super.getType(uri)\n        } catch (e: Exception) {\n            null\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/SafeUriResolver.kt",
    "content": "package me.bmax.apatch.util\n\nimport android.content.Context\nimport android.net.Uri\nimport android.util.Log\nimport com.topjohnwu.superuser.Shell\nimport java.io.File\nimport java.io.FileInputStream\nimport java.io.FileNotFoundException\nimport java.io.InputStream\n\nobject SafeUriResolver {\n\n    private const val TAG = \"SafeUriResolver\"\n\n    @Throws(FileNotFoundException::class)\n    fun openInputStream(context: Context, uri: Uri): InputStream {\n        try {\n            val stream = context.contentResolver.openInputStream(uri)\n            if (stream != null) return stream\n        } catch (_: SecurityException) {\n            Log.w(TAG, \"SecurityException for $uri, trying fallback strategies\")\n        } catch (_: Exception) {\n        }\n\n        val filePath = extractFilePath(uri)\n        if (filePath != null) {\n            try {\n                val file = File(filePath)\n                if (file.exists()) {\n                    Log.i(TAG, \"Fallback: direct file access for $filePath\")\n                    return FileInputStream(file)\n                }\n            } catch (_: Exception) {\n            }\n\n            try {\n                val stream = copyViaRoot(context, filePath)\n                if (stream != null) {\n                    Log.i(TAG, \"Fallback: root shell copy for $filePath\")\n                    return stream\n                }\n            } catch (_: Exception) {\n            }\n        }\n\n        if (uri.scheme == \"file\") {\n            try {\n                val path = uri.path\n                if (path != null) {\n                    val file = File(path)\n                    if (file.exists()) return FileInputStream(file)\n                }\n            } catch (_: Exception) {\n            }\n        }\n\n        throw FileNotFoundException(\"Cannot open input stream for URI: $uri\")\n    }\n\n    fun extractFilePath(uri: Uri): String? {\n        val path = uri.path ?: return null\n        if (uri.scheme == \"file\") return path\n        if (uri.scheme != \"content\") return null\n\n        if (path.startsWith(\"/storage/\") || path.startsWith(\"/sdcard/\") ||\n            path.startsWith(\"/data/\") || path.startsWith(\"/system/\")\n        ) {\n            return path\n        }\n\n        for (prefix in listOf(\n            \"storage/emulated/\",\n            \"sdcard/\",\n            \"data/\",\n            \"mnt/\"\n        )) {\n            val idx = path.indexOf(prefix)\n            if (idx >= 0) {\n                return \"/\" + path.substring(idx)\n            }\n        }\n\n        return null\n    }\n\n    private fun copyViaRoot(context: Context, sourcePath: String): InputStream? {\n        val cacheDir = context.cacheDir\n        val tempFile = File(cacheDir, \"safe_uri_${System.currentTimeMillis()}\")\n        try {\n            val result = Shell.cmd(\"cp \\\"$sourcePath\\\" \\\"${tempFile.absolutePath}\\\"\").exec()\n            if (!result.isSuccess) {\n                Shell.cmd(\"cat \\\"$sourcePath\\\" > \\\"${tempFile.absolutePath}\\\"\").exec()\n            }\n            if (tempFile.exists() && tempFile.length() > 0) {\n                tempFile.deleteOnExit()\n                return tempFile.inputStream()\n            }\n            tempFile.delete()\n        } catch (_: Exception) {\n            tempFile.delete()\n        }\n        return null\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/ScriptLibraryManager.kt",
    "content": "package me.bmax.apatch.util\n\nimport com.google.gson.Gson\nimport com.google.gson.reflect.TypeToken\nimport com.topjohnwu.superuser.CallbackList\nimport com.topjohnwu.superuser.Shell\nimport com.topjohnwu.superuser.internal.UiThreadHandler\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.apApp\nimport me.bmax.apatch.data.ScriptInfo\nimport java.io.File\n\n object ScriptLibraryManager {\n\n    private const val LEGACY_BASE_DIR = \"/storage/emulated/0/Download/FolkPatch\"\n    private const val LEGACY_SCRIPTS_DIR = \"$LEGACY_BASE_DIR/script\"\n    private const val LEGACY_CONFIG_FILE = \"$LEGACY_BASE_DIR/scripts_library.json\"\n\n    private val gson = Gson()\n\n    private fun appBaseDir(): File {\n        val external = apApp.getExternalFilesDir(null)\n        val base = external ?: apApp.filesDir\n        return File(base, \"FolkPatch\")\n    }\n\n    private fun appScriptsDir(): File = File(appBaseDir(), \"script\")\n\n    private fun appConfigFile(): File = File(appBaseDir(), \"scripts_library.json\")\n\n    private fun legacyBaseDir(): File = File(LEGACY_BASE_DIR)\n\n    private fun legacyScriptsDir(): File = File(LEGACY_SCRIPTS_DIR)\n\n    private fun legacyConfigFile(): File = File(LEGACY_CONFIG_FILE)\n\n    private fun ensureDir(dir: File) {\n        if (!dir.exists()) {\n            dir.mkdirs()\n        }\n    }\n\n    private fun isLegacyWritable(): Boolean {\n        val legacyDir = legacyBaseDir()\n        ensureDir(legacyDir)\n        return legacyDir.exists() && legacyDir.canWrite()\n    }\n\n    private fun writableScriptsDir(): File {\n        val dir = if (isLegacyWritable()) legacyScriptsDir() else appScriptsDir()\n        ensureDir(dir)\n        return dir\n    }\n\n    private fun readConfig(file: File): List<ScriptInfo> {\n        return try {\n            if (!file.exists()) {\n                emptyList()\n            } else {\n                val json = file.readText()\n                val type = object : TypeToken<List<ScriptInfo>>() {}.type\n                gson.fromJson<List<ScriptInfo>>(json, type) ?: emptyList()\n            }\n        } catch (_: Exception) {\n            emptyList()\n        }\n    }\n\n    private fun buildScriptsFromDirectories(directories: List<File>): List<ScriptInfo> {\n        val files = directories\n            .filter { it.exists() && it.isDirectory }\n            .flatMap { dir -> dir.listFiles()?.filter { it.isFile } ?: emptyList() }\n            .sortedBy { it.name.lowercase() }\n        return files.map { file ->\n            val name = if (file.name.endsWith(\".sh\", ignoreCase = true)) {\n                file.name.substring(0, file.name.length - 3)\n            } else {\n                file.name\n            }\n            ScriptInfo(path = file.absolutePath, alias = name)\n        }\n    }\n\n    data class ScriptExecutionResult(\n        val success: Boolean,\n        val exitCode: Int,\n        val output: String,\n        val error: String\n    )\n\n    suspend fun loadScripts(): List<ScriptInfo> = withContext(Dispatchers.IO) {\n        try {\n            val parsedLegacy = if (isLegacyWritable()) readConfig(legacyConfigFile()) else emptyList()\n            val parsedApp = readConfig(appConfigFile())\n            val merged = linkedMapOf<String, ScriptInfo>()\n            parsedLegacy.forEach { merged[it.path] = it }\n            parsedApp.forEach { merged[it.path] = it }\n            val combined = merged.values.filter { File(it.path).exists() }\n            if (combined.isNotEmpty()) {\n                saveScripts(combined)\n                return@withContext combined\n            }\n\n            val rebuilt = buildScriptsFromDirectories(listOf(legacyScriptsDir(), appScriptsDir()))\n            if (rebuilt.isNotEmpty()) {\n                saveScripts(rebuilt)\n            }\n            rebuilt\n        } catch (e: Exception) {\n            e.printStackTrace()\n            emptyList()\n        }\n    }\n\n    suspend fun saveScripts(scripts: List<ScriptInfo>): Boolean = withContext(Dispatchers.IO) {\n        try {\n            val json = gson.toJson(scripts)\n            val appBase = appBaseDir()\n            ensureDir(appBase)\n            val appScripts = appScriptsDir()\n            ensureDir(appScripts)\n            appConfigFile().writeText(json)\n            if (isLegacyWritable()) {\n                ensureDir(legacyScriptsDir())\n                legacyConfigFile().writeText(json)\n            }\n            true\n        } catch (e: Exception) {\n            e.printStackTrace()\n            false\n        }\n    }\n\n    suspend fun addScript(sourceFile: File, alias: String): ScriptInfo? = withContext(Dispatchers.IO) {\n        try {\n            val scriptsDir = writableScriptsDir()\n\n            val scriptId = java.util.UUID.randomUUID().toString()\n            \n            val originalName = sourceFile.name\n            var scriptFileName = originalName\n            \n            if (!originalName.endsWith(\".sh\", ignoreCase = true)) {\n                scriptFileName = \"$originalName.sh\"\n            }\n            \n            var scriptFile = File(scriptsDir, scriptFileName)\n            var counter = 1\n            \n            while (scriptFile.exists()) {\n                val nameWithoutExt = if (scriptFileName.endsWith(\".sh\", ignoreCase = true)) {\n                    scriptFileName.substring(0, scriptFileName.length - 3)\n                } else {\n                    scriptFileName\n                }\n                scriptFileName = \"${nameWithoutExt}_$counter.sh\"\n                scriptFile = File(scriptsDir, scriptFileName)\n                counter++\n            }\n            \n            val scriptPath = scriptFile.absolutePath\n\n            sourceFile.inputStream().use { input ->\n                scriptFile.outputStream().use { output ->\n                    input.copyTo(output)\n                }\n            }\n            \n            scriptFile.setExecutable(true)\n\n            val finalAlias = alias.ifEmpty { \n                if (scriptFileName.endsWith(\".sh\", ignoreCase = true)) {\n                    scriptFileName.substring(0, scriptFileName.length - 3)\n                } else {\n                    scriptFileName\n                }\n            }\n            \n            val scriptInfo = ScriptInfo(\n                id = scriptId,\n                path = scriptPath,\n                alias = finalAlias\n            )\n\n            scriptInfo\n        } catch (e: Exception) {\n            e.printStackTrace()\n            null\n        }\n    }\n\n    suspend fun removeScript(scriptInfo: ScriptInfo): Boolean = withContext(Dispatchers.IO) {\n        try {\n            val scriptFile = File(scriptInfo.path)\n            if (scriptFile.exists()) {\n                scriptFile.delete()\n            }\n            true\n        } catch (e: Exception) {\n            e.printStackTrace()\n            false\n        }\n    }\n\n    suspend fun executeScript(scriptInfo: ScriptInfo): ScriptExecutionResult = withContext(Dispatchers.IO) {\n        try {\n            val outList = ArrayList<String>()\n            val errList = ArrayList<String>()\n\n            val result = getRootShell().newJob()\n                .add(\"sh \\\"${scriptInfo.path}\\\"\")\n                .to(outList, errList)\n                .exec()\n\n            ScriptExecutionResult(\n                success = result.isSuccess,\n                exitCode = result.code,\n                output = outList.joinToString(\"\\n\"),\n                error = errList.joinToString(\"\\n\")\n            )\n        } catch (e: Exception) {\n            e.printStackTrace()\n            ScriptExecutionResult(\n                success = false,\n                exitCode = -1,\n                output = \"\",\n                error = e.message ?: \"执行失败\"\n            )\n        }\n    }\n\n    suspend fun executeScriptWithCallbacks(\n        scriptInfo: ScriptInfo,\n        onStdout: (String) -> Unit,\n        onStderr: (String) -> Unit\n    ): Boolean = withContext(Dispatchers.IO) {\n        try {\n            val shell = getRootShell()\n            var emittedOut = 0\n            var emittedErr = 0\n            val stdoutCallback = object : CallbackList<String>(UiThreadHandler::runAndWait) {\n                override fun onAddElement(s: String) {\n                    emittedOut++\n                    onStdout(s)\n                }\n            }\n\n            val stderrCallback = object : CallbackList<String>(UiThreadHandler::runAndWait) {\n                override fun onAddElement(s: String) {\n                    emittedErr++\n                    onStderr(s)\n                }\n            }\n            val result = shell.newJob()\n                .add(\"sh \\\"${scriptInfo.path}\\\"\")\n                .to(stdoutCallback, stderrCallback)\n                .exec()\n            if (emittedOut < stdoutCallback.size) {\n                for (i in emittedOut until stdoutCallback.size) {\n                    onStdout(stdoutCallback[i])\n                }\n            }\n            if (emittedErr < stderrCallback.size) {\n                for (i in emittedErr until stderrCallback.size) {\n                    onStderr(stderrCallback[i])\n                }\n            }\n            result.isSuccess\n        } catch (e: Exception) {\n            e.printStackTrace()\n            onStderr(\"Error: ${e.message}\\n\")\n            false\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/SoundEffectManager.kt",
    "content": "package me.bmax.apatch.util\n\nimport android.content.Context\nimport android.media.MediaPlayer\nimport android.net.Uri\nimport android.util.Log\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.launch\nimport me.bmax.apatch.ui.theme.SoundEffectConfig\nimport java.io.File\n\nobject SoundEffectManager {\n    private const val TAG = \"SoundEffectManager\"\n    private var mediaPlayer: MediaPlayer? = null\n    \n    // Use Main dispatcher as MediaPlayer must be created/accessed on same thread or handle synch\n    // But prepare can be async.\n    private val scope = CoroutineScope(Dispatchers.Main + Job())\n\n    fun play(context: Context) {\n        if (!SoundEffectConfig.isSoundEffectEnabled) return\n        \n        // Scope check is done by the caller (onClick listener)\n        \n        val sourceType = SoundEffectConfig.sourceType\n        \n        scope.launch {\n            playSound(context, sourceType, SoundEffectConfig.presetName, SoundEffectConfig.soundEffectFilename, \"sound\")\n        }\n    }\n\n    fun playStartup(context: Context) {\n        if (!SoundEffectConfig.isStartupSoundEnabled) return\n        \n        val sourceType = SoundEffectConfig.startupSourceType\n        \n        scope.launch {\n            playSound(context, sourceType, SoundEffectConfig.startupPresetName, SoundEffectConfig.startupSoundFilename, \"start\")\n        }\n    }\n\n    private fun playSound(context: Context, sourceType: String, presetName: String, filename: String?, assetDir: String) {\n        try {\n            if (mediaPlayer == null) {\n                mediaPlayer = MediaPlayer()\n            } else {\n                mediaPlayer?.reset()\n            }\n\n            mediaPlayer?.apply {\n                if (sourceType == SoundEffectConfig.SOURCE_TYPE_PRESET) {\n                    val afd = context.assets.openFd(\"$assetDir/$presetName.wav\")\n                    setDataSource(afd.fileDescriptor, afd.startOffset, afd.length)\n                    afd.close()\n                } else {\n                    if (filename == null) return\n                    val file = File(SoundEffectConfig.getSoundEffectDir(context), filename)\n                    if (!file.exists()) return\n                    setDataSource(context, Uri.fromFile(file))\n                }\n                \n                setOnPreparedListener { mp ->\n                    mp.start()\n                }\n                setOnErrorListener { mp, what, extra ->\n                    Log.e(TAG, \"MediaPlayer error: $what, $extra\")\n                    mp.reset()\n                    true\n                }\n                prepareAsync() // Use async to avoid blocking UI\n            }\n        } catch (e: Exception) {\n            Log.e(TAG, \"Failed to play sound effect\", e)\n            mediaPlayer = null // Reset on hard failure\n        }\n    }\n\n    fun release() {\n        mediaPlayer?.release()\n        mediaPlayer = null\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/SuAuditLog.kt",
    "content": "package me.bmax.apatch.util\n\nimport android.util.Log\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.Natives\nimport org.json.JSONArray\nimport org.json.JSONObject\nimport org.json.JSONException\n\nobject SuAuditLog {\n    private const val TAG = \"SuAuditLog\"\n    private const val PREF_KEY = \"su_audit_log\"\n    private const val MAX_ENTRIES = 200\n\n    sealed class AuditEntry {\n        abstract val timestamp: Long\n        abstract val uid: Int\n\n        data class KernelEntry(\n            override val timestamp: Long,\n            override val uid: Int,\n            val pid: Int,\n            val tgid: Int,\n            val toUid: Int,\n            val scontext: String,\n            val comm: String,\n        ) : AuditEntry()\n\n        data class AppEntry(\n            override val timestamp: Long,\n            override val uid: Int,\n            val packageName: String,\n            val action: String,\n        ) : AuditEntry()\n    }\n\n    private fun addEntry(entry: AuditEntry.AppEntry) {\n        synchronized(this) {\n            val prefs = APApplication.sharedPreferences\n            val jsonStr = prefs.getString(PREF_KEY, \"[]\") ?: \"[]\"\n            val jsonArray = JSONArray(jsonStr)\n\n            val jsonObj = JSONObject().apply {\n                put(\"ts\", entry.timestamp)\n                put(\"pkg\", entry.packageName)\n                put(\"uid\", entry.uid)\n                put(\"act\", entry.action)\n            }\n            jsonArray.put(jsonObj)\n\n            while (jsonArray.length() > MAX_ENTRIES) {\n                jsonArray.remove(0)\n            }\n\n            prefs.edit().putString(PREF_KEY, jsonArray.toString()).apply()\n            Log.d(TAG, \"Logged ${entry.action} for ${entry.packageName} uid=${entry.uid}\")\n        }\n    }\n\n    fun logGrant(packageName: String, uid: Int) {\n        addEntry(AuditEntry.AppEntry(System.currentTimeMillis(), uid, packageName, \"GRANT\"))\n    }\n\n    fun logRevoke(packageName: String, uid: Int) {\n        addEntry(AuditEntry.AppEntry(System.currentTimeMillis(), uid, packageName, \"REVOKE\"))\n    }\n\n    fun logExclude(packageName: String, uid: Int) {\n        addEntry(AuditEntry.AppEntry(System.currentTimeMillis(), uid, packageName, \"EXCLUDE\"))\n    }\n\n    fun getKernelEntries(): List<AuditEntry.KernelEntry> {\n        val jsonStr = try {\n            Natives.suAuditList()\n        } catch (e: Exception) {\n            Log.e(TAG, \"Failed to read kernel audit log\", e)\n            return emptyList()\n        }\n\n        if (jsonStr.isEmpty() || jsonStr == \"[]\") return emptyList()\n\n        val entries = mutableListOf<AuditEntry.KernelEntry>()\n        try {\n            val jsonArray = JSONArray(jsonStr)\n            for (i in 0 until jsonArray.length()) {\n                val obj = jsonArray.getJSONObject(i)\n                entries.add(\n                    AuditEntry.KernelEntry(\n                        timestamp = obj.optLong(\"ts\", 0), // sequence number, used for ordering\n                        uid = obj.getInt(\"uid\"),\n                        pid = obj.getInt(\"pid\"),\n                        tgid = obj.getInt(\"tgid\"),\n                        toUid = obj.getInt(\"to_uid\"),\n                        scontext = obj.optString(\"sctx\", \"\"),\n                        comm = obj.optString(\"comm\", \"\"),\n                    )\n                )\n            }\n        } catch (e: JSONException) {\n            Log.e(TAG, \"Failed to parse kernel audit log\", e)\n        }\n\n        return entries.filter { it.uid != 0 }.reversed()\n    }\n\n    fun getAppEntries(): List<AuditEntry.AppEntry> {\n        synchronized(this) {\n            val prefs = APApplication.sharedPreferences\n            val jsonStr = prefs.getString(PREF_KEY, \"[]\") ?: \"[]\"\n            val jsonArray = JSONArray(jsonStr)\n\n            val entries = mutableListOf<AuditEntry.AppEntry>()\n            for (i in 0 until jsonArray.length()) {\n                val obj = jsonArray.getJSONObject(i)\n                entries.add(\n                    AuditEntry.AppEntry(\n                        timestamp = obj.getLong(\"ts\"),\n                        uid = obj.getInt(\"uid\"),\n                        packageName = obj.getString(\"pkg\"),\n                        action = obj.getString(\"act\"),\n                    )\n                )\n            }\n            return entries.reversed()\n        }\n    }\n\n    fun getAllEntries(): List<AuditEntry> {\n        val kernel = getKernelEntries()\n        val app = getAppEntries()\n        return (kernel + app).sortedByDescending { it.timestamp }\n    }\n\n    fun clearEntries() {\n        synchronized(this) {\n            APApplication.sharedPreferences.edit().remove(PREF_KEY).apply()\n        }\n        try {\n            Natives.suAuditClear()\n        } catch (e: Exception) {\n            Log.e(TAG, \"Failed to clear kernel audit log\", e)\n        }\n        Log.d(TAG, \"Audit log cleared\")\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/ThemeDownloader.kt",
    "content": "package me.bmax.apatch.util\n\nimport android.content.Context\nimport android.util.Log\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport kotlinx.coroutines.withContext\nimport me.bmax.apatch.apApp\nimport me.bmax.apatch.ui.viewmodel.ThemeStoreViewModel\nimport okhttp3.OkHttpClient\nimport okhttp3.Request\nimport org.json.JSONObject\nimport java.io.File\nimport java.io.FileOutputStream\nimport java.io.IOException\nimport java.util.concurrent.ConcurrentHashMap\nimport java.util.concurrent.TimeUnit\n\n/**\n * 主题下载器 - 支持断点续传、并发下载、后台下载\n */\nclass ThemeDownloader(private val context: Context) {\n    companion object {\n        private const val TAG = \"ThemeDownloader\"\n        private const val MAX_RETRIES = 3\n        private const val RETRY_DELAY_MS = 2000L\n        private const val DOWNLOAD_TIMEOUT_SEC = 300L\n        private const val BUFFER_SIZE = 8192\n        \n        // 下载目录\n        private const val THEMES_DIR_NAME = \"themes\"\n        \n        // 并发控制 - 最多 3 个同时下载\n        private val downloadSemaphore = kotlinx.coroutines.sync.Semaphore(3)\n    }\n\n    // 下载任务状态\n    private val downloadTasks = ConcurrentHashMap<String, DownloadTask>()\n    \n    // 下载进度状态\n    private val _downloadProgress = MutableStateFlow<Map<String, DownloadProgress>>(emptyMap())\n    val downloadProgress: StateFlow<Map<String, DownloadProgress>> = _downloadProgress.asStateFlow()\n    \n    // 进度更新锁\n    private val progressMutex = Mutex()\n    \n    // OkHttp 客户端\n    private val client = OkHttpClient.Builder()\n        .connectTimeout(DOWNLOAD_TIMEOUT_SEC, TimeUnit.SECONDS)\n        .readTimeout(DOWNLOAD_TIMEOUT_SEC, TimeUnit.SECONDS)\n        .build()\n\n    /**\n     * 获取主题存储目录\n     */\n    fun getThemesDir(): File {\n        val themesDir = File(context.filesDir, THEMES_DIR_NAME)\n        if (!themesDir.exists()) {\n            themesDir.mkdirs()\n        }\n        return themesDir\n    }\n\n    /**\n     * 获取主题的本地路径\n     */\n    fun getThemePath(author: String, themeName: String): File {\n        val safeAuthor = sanitizeFilename(author)\n        val safeThemeName = sanitizeFilename(themeName)\n        val themeDir = File(getThemesDir(), \"$safeAuthor/$safeThemeName\")\n        if (!themeDir.exists()) {\n            themeDir.mkdirs()\n        }\n        return themeDir\n    }\n\n    /**\n     * 获取主题文件路径\n     */\n    fun getThemeFilePath(author: String, themeName: String): File {\n        return File(getThemePath(author, themeName), \"${sanitizeFilename(themeName)}.fpt\")\n    }\n\n    /**\n     * 获取预览图路径\n     */\n    fun getPreviewImagePath(author: String, themeName: String): File {\n        return File(getThemePath(author, themeName), \"Theme.webp\")\n    }\n\n    /**\n     * 获取外部存储主题目录 (/storage/emulated/0/Download/FolkPatch/Themes/)\n     */\n    private fun getExternalThemesDir(): File {\n        val externalDir = File(\n            getSafeDownloadsDir(context),\n            \"FolkPatch/Themes\"\n        )\n        if (!externalDir.exists()) {\n            externalDir.mkdirs()\n        }\n        return externalDir\n    }\n\n    /**\n     * 获取外部存储主题文件路径\n     */\n    private fun getExternalThemeFilePath(author: String, themeName: String): File {\n        val safeAuthor = sanitizeFilename(author)\n        val safeThemeName = sanitizeFilename(themeName)\n        val themeDir = File(getExternalThemesDir(), \"$safeAuthor/$safeThemeName\")\n        if (!themeDir.exists()) {\n            themeDir.mkdirs()\n        }\n        return File(themeDir, \"${sanitizeFilename(themeName)}.fpt\")\n    }\n\n    /**\n     * 备份主题 FPT 文件到外部存储\n     */\n    private fun backupThemeFileToExternal(theme: ThemeStoreViewModel.RemoteTheme) {\n        try {\n            val internalThemeFile = getThemeFilePath(theme.author, theme.name)\n            val externalThemeFile = getExternalThemeFilePath(theme.author, theme.name)\n            \n            // 复制 FPT 文件到外部存储\n            if (internalThemeFile.exists()) {\n                internalThemeFile.copyTo(externalThemeFile, overwrite = true)\n                Log.d(TAG, \"Backed up theme FPT to: ${externalThemeFile.absolutePath}\")\n            }\n        } catch (e: Exception) {\n            Log.e(TAG, \"Failed to backup theme FPT to external storage\", e)\n        }\n    }\n\n    /**\n     * 开始下载主题\n     */\n    fun downloadTheme(theme: ThemeStoreViewModel.RemoteTheme): Flow<DownloadProgress> = flow {\n        val taskId = theme.id\n        \n        // 检查是否已在下载\n        if (downloadTasks.containsKey(taskId)) {\n            emit(DownloadProgress(\n                themeId = taskId,\n                fileProgress = 0f,\n                imageProgress = 0f,\n                overallProgress = 0f,\n                status = DownloadStatus.DOWNLOADING,\n                errorMessage = \"Already downloading\"\n            ))\n            return@flow\n        }\n\n        // 获取信号量（限制并发数）\n        downloadSemaphore.acquire()\n        \n        try {\n            val task = DownloadTask(theme)\n            downloadTasks[taskId] = task\n            \n            // 初始状态\n            emit(DownloadProgress(\n                themeId = taskId,\n                fileProgress = 0f,\n                imageProgress = 0f,\n                overallProgress = 0f,\n                status = DownloadStatus.DOWNLOADING,\n                errorMessage = null\n            ))\n            \n            // 1. 下载主题文件\n            val themeFile = getThemeFilePath(theme.author, theme.name)\n            emit(DownloadProgress(\n                themeId = taskId,\n                fileProgress = 0f,\n                imageProgress = 0f,\n                overallProgress = 0f,\n                status = DownloadStatus.DOWNLOADING,\n                errorMessage = null\n            ))\n            \n            val themeSuccess = downloadFileWithRetry(\n                url = theme.downloadUrl,\n                file = themeFile,\n                taskId = taskId,\n                isThemeFile = true\n            ) { fileProgress ->\n                // 主题文件进度占 70%\n                val overall = fileProgress * 0.7f\n                updateProgress(taskId, fileProgress, 0f, overall, DownloadStatus.DOWNLOADING, null)\n            }\n            \n            if (!themeSuccess) {\n                updateProgress(taskId, 0f, 0f, 0f, DownloadStatus.FAILED, \"Failed to download theme file\")\n                return@flow\n            }\n            \n            // 2. 下载预览图\n            val previewFile = getPreviewImagePath(theme.author, theme.name)\n            emit(DownloadProgress(\n                themeId = taskId,\n                fileProgress = 1f,\n                imageProgress = 0f,\n                overallProgress = 0.7f,\n                status = DownloadStatus.DOWNLOADING,\n                errorMessage = null\n            ))\n            \n            val imageSuccess = downloadFileWithRetry(\n                url = theme.previewUrl,\n                file = previewFile,\n                taskId = taskId,\n                isThemeFile = false\n            ) { imageProgress ->\n                // 预览图进度占 30%\n                val overall = 0.7f + (imageProgress * 0.3f)\n                updateProgress(taskId, 1f, imageProgress, overall, DownloadStatus.DOWNLOADING, null)\n            }\n            \n            if (!imageSuccess) {\n                updateProgress(taskId, 1f, 0f, 0.7f, DownloadStatus.FAILED, \"Failed to download preview image\")\n                return@flow\n            }\n            \n            // 3. 下载完成\n            updateProgress(taskId, 1f, 1f, 1f, DownloadStatus.COMPLETED, null)\n            \n            // 4. 保存主题元数据 JSON\n            saveThemeMetadata(theme)\n            \n            // 5. 备份 FPT 文件到外部存储\n            backupThemeFileToExternal(theme)\n            \n            // 从任务列表中移除\n            downloadTasks.remove(taskId)\n            \n        } catch (e: Exception) {\n            Log.e(TAG, \"Download failed for theme ${theme.id}\", e)\n            updateProgress(taskId, 0f, 0f, 0f, DownloadStatus.FAILED, e.message ?: \"Unknown error\")\n            downloadTasks.remove(taskId)\n        } finally {\n            downloadSemaphore.release()\n        }\n    }.flowOn(Dispatchers.IO)\n\n    /**\n     * 下载文件（支持断点续传）\n     */\n    private suspend fun downloadFileWithRetry(\n        url: String,\n        file: File,\n        taskId: String,\n        isThemeFile: Boolean,\n        onProgress: (Float) -> Unit\n    ): Boolean = withContext(Dispatchers.IO) {\n        var retryCount = 0\n        var lastError: String? = null\n        \n        while (retryCount < MAX_RETRIES) {\n            try {\n                // 获取已下载的字节数（断点续传）\n                val downloadedBytes = if (file.exists()) file.length() else 0L\n                \n                // 构建请求\n                val request = Request.Builder()\n                    .url(url)\n                    .apply {\n                        if (downloadedBytes > 0) {\n                            addHeader(\"Range\", \"bytes=$downloadedBytes-\")\n                        }\n                    }\n                    .build()\n                \n                val response = client.newCall(request).execute()\n                \n                // 处理响应\n                if (response.code !in 200..299) {\n                    throw IOException(\"HTTP error: ${response.code}\")\n                }\n                \n                val totalBytes = if (response.body?.contentLength() ?: -1L > 0) {\n                    downloadedBytes + (response.body?.contentLength() ?: 0L)\n                } else {\n                    -1L\n                }\n                \n                // 写入文件\n                FileOutputStream(file, downloadedBytes > 0).use { output ->\n                    response.body?.byteStream()?.use { input ->\n                        val buffer = ByteArray(BUFFER_SIZE)\n                        var bytesRead: Long = 0\n                        \n                        var read: Int\n                        while (input.read(buffer).also { read = it } != -1) {\n                            output.write(buffer, 0, read)\n                            bytesRead += read\n                            \n                            // 计算进度\n                            val progress = if (totalBytes > 0) {\n                                (downloadedBytes + bytesRead).toFloat() / totalBytes\n                            } else {\n                                // 未知总大小时，使用已下载字节数估算\n                                0.9f // 暂时显示 90%\n                            }\n                            \n                            onProgress(progress.coerceIn(0f, 1f))\n                            \n                            // 检查是否被取消\n                            if (downloadTasks[taskId]?.isCancelled == true) {\n                                throw IOException(\"Download cancelled\")\n                            }\n                        }\n                    }\n                }\n                \n                Log.d(TAG, \"Download completed: ${file.absolutePath}\")\n                return@withContext true\n                \n            } catch (e: Exception) {\n                lastError = e.message ?: \"Unknown error\"\n                Log.w(TAG, \"Download attempt ${retryCount + 1} failed: $lastError\")\n                retryCount++\n                \n                if (retryCount < MAX_RETRIES) {\n                    // 等待后重试\n                    kotlinx.coroutines.delay(RETRY_DELAY_MS)\n                }\n            }\n        }\n        \n        Log.e(TAG, \"Download failed after $MAX_RETRIES retries: $lastError\")\n        false\n    }\n\n    /**\n     * 更新进度状态\n     */\n    private fun updateProgress(\n        themeId: String,\n        fileProgress: Float,\n        imageProgress: Float,\n        overallProgress: Float,\n        status: DownloadStatus,\n        errorMessage: String?\n    ) {\n        val currentMap = _downloadProgress.value.toMutableMap()\n        currentMap[themeId] = DownloadProgress(\n            themeId = themeId,\n            fileProgress = fileProgress,\n            imageProgress = imageProgress,\n            overallProgress = overallProgress,\n            status = status,\n            errorMessage = errorMessage\n        )\n        _downloadProgress.value = currentMap\n    }\n\n    /**\n     * 获取主题元数据 JSON 文件路径\n     */\n    fun getMetaJsonFile(author: String, themeName: String): File {\n        return File(getThemePath(author, themeName), \"theme_meta.json\")\n    }\n\n    /**\n     * 保存主题元数据到 JSON 文件\n     */\n    fun saveThemeMetadata(theme: ThemeStoreViewModel.RemoteTheme) {\n        try {\n            val metaFile = getMetaJsonFile(theme.author, theme.name)\n            val json = JSONObject().apply {\n                put(\"id\", theme.id)\n                put(\"name\", theme.name)\n                put(\"author\", theme.author)\n                put(\"description\", theme.description)\n                put(\"version\", theme.version)\n                put(\"type\", theme.type)\n                put(\"source\", theme.source)\n                put(\"previewUrl\", theme.previewUrl)\n                put(\"downloadUrl\", theme.downloadUrl)\n            }\n            metaFile.writeText(json.toString(2)) // 格式化输出\n            Log.d(TAG, \"Theme metadata saved: ${metaFile.absolutePath}\")\n        } catch (e: Exception) {\n            Log.e(TAG, \"Failed to save theme metadata\", e)\n        }\n    }\n\n    /**\n     * 取消下载\n     */\n    suspend fun cancelDownload(themeId: String) {\n        val task = downloadTasks[themeId]\n        if (task != null) {\n            task.isCancelled = true\n            \n            // 删除已下载的文件和目录\n            withContext(Dispatchers.IO) {\n                val themeDir = getThemePath(task.theme.author, task.theme.name)\n                if (themeDir.exists()) {\n                    themeDir.deleteRecursively()\n                    Log.d(TAG, \"Cancelled download, deleted theme directory: ${themeDir.absolutePath}\")\n                }\n            }\n        }\n        \n        progressMutex.withLock {\n            val currentMap = _downloadProgress.value.toMutableMap()\n            currentMap.remove(themeId)\n            _downloadProgress.value = currentMap\n        }\n    }\n\n    /**\n     * 获取下载进度\n     */\n    fun getProgress(themeId: String): DownloadProgress? {\n        return _downloadProgress.value[themeId]\n    }\n\n    /**\n     * 清理文件名中的非法字符\n     */\n    private fun sanitizeFilename(filename: String): String {\n        val illegalChars = \"<>:\\\"/\\\\|?*\"\n        var result = filename\n        for (char in illegalChars) {\n            result = result.replace(char, '_')\n        }\n        return result.trim()\n    }\n\n    /**\n     * 下载任务\n     */\n    private class DownloadTask(val theme: ThemeStoreViewModel.RemoteTheme) {\n        @Volatile\n        var isCancelled = false\n    }\n}\n\n/**\n * 下载进度数据类\n */\ndata class DownloadProgress(\n    val themeId: String,\n    val fileProgress: Float,      // 主题文件进度 0.0-1.0\n    val imageProgress: Float,     // 预览图进度 0.0-1.0\n    val overallProgress: Float,   // 总体进度 0.0-1.0\n    val status: DownloadStatus,\n    val errorMessage: String? = null\n)\n\n/**\n * 下载状态枚举\n */\nenum class DownloadStatus {\n    PENDING,      // 等待下载\n    DOWNLOADING,  // 下载中\n    PAUSED,       // 已暂停\n    COMPLETED,    // 已完成\n    FAILED,       // 下载失败\n    RETRYING      // 重试中\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/UpdateChecker.kt",
    "content": "package me.bmax.apatch.util\n\nimport android.content.Context\nimport android.content.Intent\nimport android.net.Uri\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport android.util.Log\nimport me.bmax.apatch.BuildConfig\n\nobject UpdateChecker {\n    private const val TAG = \"UpdateChecker\"\n    private const val UPDATE_API_URL = \"https://folk.mysqil.com/api/version\"\n    private const val UPDATE_URL = \"https://github.com/LyraVoid/FolkPatch/releases\"\n\n    suspend fun checkUpdate(): Boolean {\n        return withContext(Dispatchers.IO) {\n            try {\n                val result = FolkApiClient.fetchJson(\n                    UPDATE_API_URL,\n                    ttlMs = 30 * 60 * 1000L,\n                    maxRetries = 1\n                )\n                val rawResponse = result.getOrNull() ?: return@withContext false\n\n                val remoteVersionCodeStr = rawResponse.replace(\"\\uFEFF\", \"\").trim()\n                Log.d(TAG, \"Parsed version string: '$remoteVersionCodeStr'\")\n\n                val remoteVersionCode = remoteVersionCodeStr.toIntOrNull()\n                if (remoteVersionCode != null) {\n                    Log.d(TAG, \"Remote: $remoteVersionCode, Local: ${BuildConfig.VERSION_CODE}\")\n                    return@withContext remoteVersionCode > BuildConfig.VERSION_CODE\n                } else {\n                    Log.e(TAG, \"Failed to parse version code\")\n                }\n                false\n            } catch (e: Exception) {\n                Log.e(TAG, \"Check update failed\", e)\n                false\n            }\n        }\n    }\n\n    fun openUpdateUrl(context: Context) {\n        try {\n            val intent = Intent(Intent.ACTION_VIEW, Uri.parse(UPDATE_URL))\n            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)\n            context.startActivity(intent)\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/Version.kt",
    "content": "package me.bmax.apatch.util\n\nimport java.security.MessageDigest\nimport android.util.Log\nimport androidx.core.content.pm.PackageInfoCompat\nimport me.bmax.apatch.APApplication\nimport me.bmax.apatch.BuildConfig\nimport me.bmax.apatch.Natives\nimport me.bmax.apatch.apApp\nimport me.bmax.apatch.util.shellForResult\nimport org.ini4j.Ini\nimport java.io.StringReader\nimport me.bmax.apatch.ui.viewmodel.KPModel\nimport com.topjohnwu.superuser.nio.ExtendedFile\nimport com.topjohnwu.superuser.nio.FileSystemManager\nimport com.topjohnwu.superuser.Shell\nimport androidx.compose.runtime.mutableStateOf\nimport java.io.File\nimport android.system.Os\n\n\n/**\n * version string is like 0.9.0 or 0.9.0-dev\n * version uint is hex number like: 0x000900\n */\nobject Version {\n\n    private fun string2UInt(ver: String): UInt {\n        val v = ver.trim().split(\"-\")[0]\n        val vn = v.split('.')\n        val vi = vn[0].toInt().shl(16) + vn[1].toInt().shl(8) + vn[2].toInt()\n        return vi.toUInt()\n    }\n\n    fun getKpImg(): String {\n        if (BuildConfig.DEBUG_FAKE_ROOT) {\n            return \"2024-05-20 12:00:00\"\n        }\n        var shell: Shell = createRootShell()\n        var kimgInfo = mutableStateOf(KPModel.KImgInfo(\"\", false))\n        var kpimgInfo = mutableStateOf(KPModel.KPImgInfo(\"\", \"\", \"\", \"\", \"\"))\n        val patchDir: ExtendedFile = FileSystemManager.getLocal().getFile(apApp.filesDir.parent, \"check\")\n        patchDir.deleteRecursively()\n        patchDir.mkdirs()\n        val execs = listOf(\n            \"libkptools.so\",  \"libbusybox.so\"\n        )\n\n        val info = apApp.applicationInfo\n        val libs = File(info.nativeLibraryDir).listFiles { _, name ->\n            execs.contains(name)\n        } ?: emptyArray()\n\n        for (lib in libs) {\n            val name = lib.name.substring(3, lib.name.length - 3)\n            Os.symlink(lib.path, \"$patchDir/$name\")\n        }\n\n        for (script in listOf(\n            \"boot_patch.sh\", \"boot_unpatch.sh\", \"boot_extract.sh\", \"util_functions.sh\", \"kpimg\"\n        )) {\n            val dest = File(patchDir, script)\n            apApp.assets.open(script).writeTo(dest)\n        }\n        val result = shellForResult(\n            shell, \"cd $patchDir\", \"./kptools -l -k kpimg\"\n        )\n\n        if (result.isSuccess) {\n            try {\n                val ini = Ini(StringReader(result.out.joinToString(\"\\n\")))\n                val kpimg = ini[\"kpimg\"]\n                if (kpimg != null) {\n                    kpimgInfo.value = KPModel.KPImgInfo(\n                        kpimg[\"version\"].toString(),\n                        kpimg[\"compile_time\"].toString(),\n                        kpimg[\"config\"].toString(),\n                        APApplication.superKey,     // current key\n                        kpimg[\"root_superkey\"].toString()      // possibly empty\n                    )\n                    return kpimg[\"compile_time\"].toString()\n                }\n            } catch (e: Exception) {\n                Log.e(\"Version\", \"getKpImg INI error: ${e.message}\")\n            }\n        } \n\n        return \"unknown\"\n    }\n\n    fun uInt2String(ver: UInt): String {\n        return \"%d.%d.%d\".format(\n            ver.and(0xff0000u).shr(16).toInt(),\n            ver.and(0xff00u).shr(8).toInt(),\n            ver.and(0xffu).toInt()\n        )\n    }\n    \n    fun installedKPTime(): String {\n        if (BuildConfig.DEBUG_FAKE_ROOT) {\n            return \"2024-05-20 12:00:00\"\n        }\n        val time = Natives.kernelPatchBuildTime()\n        return if (time.startsWith(\"ERROR_\")) \"读取失败\" else time\n    }\n\n    fun buildKPVUInt(): UInt {\n        val buildVS = BuildConfig.buildKPV\n        return string2UInt(buildVS)\n    }\n\n    fun buildKPVString(): String {\n        return BuildConfig.buildKPV\n    }\n\n    /**\n     * installed KernelPatch version (installed kpimg)\n     */\n    fun installedKPVUInt(): UInt {\n        if (BuildConfig.DEBUG_FAKE_ROOT) {\n            return string2UInt(\"0.12.2\")\n        }\n        return Natives.kernelPatchVersion().toUInt()\n    }\n\n    fun installedKPVString(): String {\n        if (BuildConfig.DEBUG_FAKE_ROOT) {\n            return \"0.12.2\"\n        }\n        return uInt2String(installedKPVUInt())\n    }\n\n\n    fun getBundledApdSha256(): String {\n        val nativeDir = apApp.applicationInfo.nativeLibraryDir\n        val libapd = File(nativeDir, \"libapd.so\")\n        return computeSHA256(libapd)\n    }\n\n    fun getInstalledApdSha256(): String {\n        val resultShell = rootShellForResult(\"sha256sum ${APApplication.APD_PATH}\")\n        installedApdHash = if (resultShell.isSuccess) {\n            resultShell.out.firstOrNull()?.split(\"\\\\s+\".toRegex())?.first() ?: \"\"\n        } else {\n            \"\"\n        }\n        return installedApdHash\n    }\n\n    private fun computeSHA256(file: File): String {\n        val digest = MessageDigest.getInstance(\"SHA-256\")\n        file.inputStream().use { fis ->\n            val buffer = ByteArray(8192)\n            var read = fis.read(buffer)\n            while (read != -1) {\n                digest.update(buffer, 0, read)\n                read = fis.read(buffer)\n            }\n        }\n        return digest.digest().joinToString(\"\") { \"%02x\".format(it) }\n    }\n\n\n    fun getManagerVersion(): Pair<String, Long> {\n        val packageInfo = apApp.packageManager.getPackageInfo(apApp.packageName, 0)!!\n        val versionCode = PackageInfoCompat.getLongVersionCode(packageInfo)\n        return Pair(packageInfo.versionName!!, versionCode)\n    }\n\n    var installedApdHash: String = \"\"\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/VibrationManager.kt",
    "content": "package me.bmax.apatch.util\n\nimport android.content.Context\nimport android.os.Build\nimport android.os.VibrationEffect\nimport android.os.Vibrator\nimport android.os.VibratorManager\nimport me.bmax.apatch.ui.theme.VibrationConfig\n\nobject VibrationManager {\n\n    fun vibrate(context: Context) {\n        if (!VibrationConfig.isVibrationEnabled) return\n\n        try {\n            val vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n                val vibratorManager = context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager\n                vibratorManager.defaultVibrator\n            } else {\n                @Suppress(\"DEPRECATION\")\n                context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator\n            }\n\n            if (vibrator.hasVibrator()) {\n                // Map 0.0-1.0 to 1-255\n                val intensity = (VibrationConfig.vibrationIntensity * 255).toInt().coerceIn(1, 255)\n                // A short duration for a \"tick\" or \"click\" feel\n                val duration = 30L\n\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                    try {\n                        vibrator.vibrate(VibrationEffect.createOneShot(duration, intensity))\n                    } catch (e: Exception) {\n                        // Fallback if amplitude control is not supported or other errors\n                        @Suppress(\"DEPRECATION\")\n                        vibrator.vibrate(duration)\n                    }\n                } else {\n                    @Suppress(\"DEPRECATION\")\n                    vibrator.vibrate(duration)\n                }\n            }\n        } catch (e: Exception) {\n            // Catch all exceptions including SecurityException to prevent crash\n            android.util.Log.e(\"VibrationManager\", \"Failed to vibrate\", e)\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/WebDavUtils.kt",
    "content": "package me.bmax.apatch.util\n\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport okhttp3.Credentials\nimport okhttp3.MediaType.Companion.toMediaTypeOrNull\nimport okhttp3.OkHttpClient\nimport okhttp3.Request\nimport okhttp3.RequestBody.Companion.asRequestBody\nimport java.io.File\nimport java.io.IOException\n\nobject WebDavUtils {\n    private val client = OkHttpClient()\n\n    suspend fun testConnection(url: String, user: String, pass: String): Result<Boolean> {\n        return withContext(Dispatchers.IO) {\n            try {\n                val credential = Credentials.basic(user, pass)\n                val request = Request.Builder()\n                    .url(url)\n                    .header(\"Authorization\", credential)\n                    .method(\"PROPFIND\", null) // WebDAV check\n                    .header(\"Depth\", \"0\")\n                    .build()\n\n                client.newCall(request).execute().use { response ->\n                    if (response.isSuccessful || response.code == 207) { // 207 Multi-Status is typical for PROPFIND\n                         Result.success(true)\n                    } else {\n                         Result.failure(IOException(\"HTTP ${response.code}\"))\n                    }\n                }\n            } catch (e: Exception) {\n                Result.failure(e)\n            }\n        }\n    }\n\n    suspend fun uploadFile(baseUrl: String, user: String, pass: String, file: File, subDir: String, remoteFileName: String? = null): Result<Boolean> {\n        return withContext(Dispatchers.IO) {\n            try {\n                var cleanBaseUrl = if (baseUrl.endsWith(\"/\")) baseUrl.dropLast(1) else baseUrl\n            \n                \n                val parts = subDir.split(\"/\").filter { it.isNotEmpty() }\n                var currentPath = \"\"\n                for (part in parts) {\n                    currentPath = if (currentPath.isEmpty()) part else \"$currentPath/$part\"\n                    createDir(cleanBaseUrl, user, pass, currentPath)\n                }\n                \n                val fileName = remoteFileName ?: file.name\n                val fullUrl = \"$cleanBaseUrl/$currentPath/$fileName\"\n                BackupLogManager.log(\"Uploading to: $fullUrl\")\n                \n                val credential = Credentials.basic(user, pass)\n                val request = Request.Builder()\n                    .url(fullUrl)\n                    .header(\"Authorization\", credential)\n                    .put(file.asRequestBody(\"application/octet-stream\".toMediaTypeOrNull()))\n                    .build()\n\n                client.newCall(request).execute().use { response ->\n                    if (response.isSuccessful || response.code == 201 || response.code == 204) {\n                        BackupLogManager.log(\"Upload success: $fileName\")\n                        Result.success(true)\n                    } else {\n                        val msg = \"Upload failed: ${response.code} - ${response.message}\"\n                        BackupLogManager.log(msg)\n                        Result.failure(IOException(msg))\n                    }\n                }\n            } catch (e: Exception) {\n                BackupLogManager.log(\"Upload exception: ${e.message}\")\n                Result.failure(e)\n            }\n        }\n    }\n    \n    private fun createDir(baseUrl: String, user: String, pass: String, path: String) {\n        val fullUrl = \"$baseUrl/$path\"\n        val credential = Credentials.basic(user, pass)\n        \n        // Check if exists\n        val checkRequest = Request.Builder()\n            .url(fullUrl)\n            .header(\"Authorization\", credential)\n            .method(\"HEAD\", null)\n            .build()\n            \n        try {\n            client.newCall(checkRequest).execute().use { response ->\n                if (response.code == 404) {\n                    // Create it\n                    val mkcolRequest = Request.Builder()\n                        .url(fullUrl)\n                        .header(\"Authorization\", credential)\n                        .method(\"MKCOL\", null)\n                        .build()\n                    client.newCall(mkcolRequest).execute().close()\n                }\n            }\n        } catch (e: Exception) {\n            // Ignore errors in directory creation, maybe it exists or we can't create it\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/ui/APDialogBlurBehindUtils.kt",
    "content": "package me.bmax.apatch.util.ui\n\nimport android.animation.ValueAnimator\nimport android.annotation.SuppressLint\nimport android.os.Build\nimport android.util.Log\nimport android.view.SurfaceControl\nimport android.view.View\nimport android.view.Window\nimport android.view.WindowManager\nimport android.view.animation.DecelerateInterpolator\nimport java.lang.reflect.Method\n\nopen class APDialogBlurBehindUtils {\n    companion object {\n        private val bIsBlurSupport =\n            getSystemProperty(\"ro.surface_flinger.supports_background_blur\") && !getSystemProperty(\"persist.sys.sf.disable_blurs\")\n\n        private fun getSystemProperty(key: String?): Boolean {\n            var value = false\n            try {\n                val c = Class.forName(\"android.os.SystemProperties\")\n                val get = c.getMethod(\n                    \"getBoolean\", String::class.java, Boolean::class.javaPrimitiveType\n                )\n                value = get.invoke(c, key, false) as Boolean\n            } catch (e: Exception) {\n                Log.e(\"APatchUI\", \"[APDialogBlurBehindUtils] Failed to getSystemProperty: \", e)\n            }\n            return value\n        }\n\n        private fun updateWindowForBlurs(window: Window, blursEnabled: Boolean) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n                window.setDimAmount(0.27f)\n                window.attributes.blurBehindRadius = 20\n            } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {\n                if (blursEnabled) {\n                    val view = window.decorView\n                    val animator = ValueAnimator.ofInt(1, 53)\n                    animator.duration = 667\n                    animator.interpolator = DecelerateInterpolator()\n                    try {\n                        val viewRootImpl =\n                            view.javaClass.getMethod(\"getViewRootImpl\").invoke(view) ?: return\n                        val surfaceControl = viewRootImpl.javaClass.getMethod(\"getSurfaceControl\")\n                            .invoke(viewRootImpl) as SurfaceControl\n                        @SuppressLint(\"BlockedPrivateApi\") val setBackgroundBlurRadius: Method =\n                            SurfaceControl.Transaction::class.java.getDeclaredMethod(\n                                \"setBackgroundBlurRadius\",\n                                SurfaceControl::class.java,\n                                Int::class.javaPrimitiveType\n                            )\n                        animator.addUpdateListener { animation: ValueAnimator ->\n                            try {\n                                val transaction = SurfaceControl.Transaction()\n                                try {\n                                    val animatedValue = animation.animatedValue\n                                    if (animatedValue != null) {\n                                        setBackgroundBlurRadius.invoke(\n                                            transaction, surfaceControl, animatedValue as Int\n                                        )\n                                    }\n                                    transaction.apply()\n                                } finally {\n                                    transaction.close()\n                                }\n                            } catch (t: Throwable) {\n                                Log.e(\n                                    \"APatchUI\",\n                                    \"[APDialogBlurBehindUtils] Blur behind dialog builder: \" + t.toString()\n                                )\n                            }\n                        }\n                    } catch (t: Throwable) {\n                        Log.e(\n                            \"APatchUI\",\n                            \"[APDialogBlurBehindUtils] Blur behind dialog builder: \" + t.toString()\n                        )\n                    }\n                    view.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {\n                        override fun onViewAttachedToWindow(v: View) {}\n                        override fun onViewDetachedFromWindow(v: View) {\n                            animator.cancel()\n                        }\n                    })\n                    animator.start()\n                }\n            }\n        }\n\n        fun setupWindowBlurListener(window: Window) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n                window.setFlags(\n                    WindowManager.LayoutParams.FLAG_BLUR_BEHIND,\n                    WindowManager.LayoutParams.FLAG_BLUR_BEHIND\n                )\n                updateWindowForBlurs(window, true)\n            } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {\n                updateWindowForBlurs(\n                    window, bIsBlurSupport\n                )\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/ui/AnsiUtils.kt",
    "content": "package me.bmax.apatch.util.ui\n\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.text.AnnotatedString\nimport androidx.compose.ui.text.SpanStyle\nimport androidx.compose.ui.text.buildAnnotatedString\nimport androidx.compose.ui.text.font.FontWeight\n\nobject AnsiUtils {\n    private val ANSI_ESCAPE = Regex(\"\\u001B\\\\[[;\\\\d?]*[A-Za-z]\")\n\n    fun parseAnsi(text: String): AnnotatedString {\n        return buildAnnotatedString {\n            var currentIndex = 0\n            var currentColor = Color.Unspecified\n            var isBold = false\n            \n            ANSI_ESCAPE.findAll(text).forEach { match ->\n                if (match.range.first > currentIndex) {\n                    append(text.substring(currentIndex, match.range.first))\n                    addStyle(\n                        SpanStyle(color = currentColor, fontWeight = if (isBold) FontWeight.Bold else FontWeight.Normal),\n                        length - (match.range.first - currentIndex),\n                        length\n                    )\n                }\n                \n                // Parse codes\n                // Only process color codes (ending in 'm')\n                if (match.value.endsWith(\"m\")) {\n                    val content = match.value.drop(2).dropLast(1)\n                    val codes = if (content.isEmpty()) emptyList() else content.split(\";\").mapNotNull { it.toIntOrNull() }\n                    \n                    if (codes.isEmpty()) {\n                        // Reset\n                        currentColor = Color.Unspecified\n                        isBold = false\n                    } else {\n                        for (code in codes) {\n                            when (code) {\n                                0 -> { currentColor = Color.Unspecified; isBold = false }\n                                1 -> isBold = true\n                                in 30..37 -> currentColor = getAnsiColor(code - 30)\n                                in 90..97 -> currentColor = getAnsiColor(code - 90, bright = true)\n                                39 -> currentColor = Color.Unspecified\n                                // Background colors (40-47, 100-107) are ignored for now to keep it simple\n                            }\n                        }\n                    }\n                }\n                // Non-color codes (like [2J, [H) are effectively stripped by skipping them\n                \n                currentIndex = match.range.last + 1\n            }\n            if (currentIndex < text.length) {\n                append(text.substring(currentIndex))\n                val start = length - (text.length - currentIndex)\n                if (start >= 0) {\n                    addStyle(\n                         SpanStyle(color = currentColor, fontWeight = if (isBold) FontWeight.Bold else FontWeight.Normal),\n                         start,\n                         length\n                    )\n                }\n            }\n        }\n    }\n\n    private fun getAnsiColor(index: Int, bright: Boolean = false): Color {\n        return when (index) {\n            0 -> if (bright) Color.Gray else Color.Black\n            1 -> if (bright) Color(0xFFFF5555) else Color(0xFFAA0000) // Red\n            2 -> if (bright) Color(0xFF55FF55) else Color(0xFF00AA00) // Green\n            3 -> if (bright) Color(0xFFFFFF55) else Color(0xFFAA5500) // Yellow\n            4 -> if (bright) Color(0xFF5555FF) else Color(0xFF0000AA) // Blue\n            5 -> if (bright) Color(0xFFFF55FF) else Color(0xFFAA00AA) // Magenta\n            6 -> if (bright) Color(0xFF55FFFF) else Color(0xFF00AAAA) // Cyan\n            7 -> if (bright) Color.White else Color.LightGray\n            else -> Color.Unspecified\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/ui/CompositionProvider.kt",
    "content": "package me.bmax.apatch.util.ui\n\nimport androidx.compose.material3.SnackbarHostState\nimport androidx.compose.runtime.compositionLocalOf\n\nval LocalSnackbarHost = compositionLocalOf<SnackbarHostState> {\n    error(\"CompositionLocal LocalSnackbarController not present\")\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/ui/GlassEffectHelper.kt",
    "content": "package me.bmax.apatch.util.ui\n\nimport android.os.Build\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.drawBehind\nimport androidx.compose.ui.geometry.CornerRadius\nimport androidx.compose.ui.geometry.Offset\nimport androidx.compose.ui.geometry.Size\nimport androidx.compose.ui.graphics.Brush\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.Shape\nimport androidx.compose.ui.graphics.drawscope.Stroke\nimport androidx.compose.ui.graphics.luminance\nimport androidx.compose.ui.platform.LocalDensity\nimport androidx.compose.ui.unit.dp\nimport io.github.fletchmckee.liquid.liquid\nimport io.github.fletchmckee.liquid.liquefiable\nimport io.github.fletchmckee.liquid.rememberLiquidState\nimport me.bmax.apatch.ui.theme.BackgroundConfig\n\nfun isRealTimeBlurAvailable(): Boolean = Build.VERSION.SDK_INT >= 33\n\n@Composable\nfun rememberNavBarGlassLiquidState() = rememberLiquidState()\n\nfun Modifier.navBarLiquefiable(liquidState: io.github.fletchmckee.liquid.LiquidState?): Modifier {\n    return if (isRealTimeBlurAvailable() && liquidState != null) {\n        this.liquefiable(liquidState)\n    } else {\n        this\n    }\n}\n\n@Composable\nfun Modifier.navBarGlassEffect(\n    shape: Shape = CircleShape,\n    blurStrength: Float = BackgroundConfig.navBarGlassBlurStrength,\n    transparency: Float = BackgroundConfig.navBarGlassTransparency,\n    highlightStrength: Float = BackgroundConfig.navBarGlassHighlightStrength,\n    enableSpecular: Boolean = BackgroundConfig.isNavBarGlassSpecularEnabled,\n    enableInnerGlow: Boolean = BackgroundConfig.isNavBarGlassInnerGlowEnabled,\n    enableBorder: Boolean = BackgroundConfig.isNavBarGlassBorderEnabled,\n    liquidState: io.github.fletchmckee.liquid.LiquidState? = null,\n): Modifier {\n    val isDarkTheme = !MaterialTheme.colorScheme.background.luminance().let { it > 0.5f }\n    val density = LocalDensity.current\n    val halfDp = with(density) { 0.5.dp.toPx() }\n    val oneDp = with(density) { 1.dp.toPx() }\n    val twoDp = with(density) { 2.dp.toPx() }\n    val oneAndHalfDp = with(density) { 1.5.dp.toPx() }\n    val fourDp = with(density) { 4.dp.toPx() }\n    val eightDp = with(density) { 8.dp.toPx() }\n\n    val glassBaseColor = if (isDarkTheme) {\n        MaterialTheme.colorScheme.surfaceContainerHighest.copy(alpha = 0.14f + (1f - transparency) * 0.10f)\n    } else {\n        MaterialTheme.colorScheme.surfaceContainerHighest.copy(alpha = 0.10f + (1f - transparency) * 0.08f)\n    }\n\n    val mod = this\n        .then(\n            Modifier.drawBehind {\n            val w = size.width\n            val h = size.height\n            val radius = h / 2f\n            val cornerRadius = CornerRadius(radius, radius)\n            val strokeRadius = CornerRadius((radius - halfDp).coerceAtLeast(0f))\n\n            if (enableBorder) {\n                val borderColor = if (isDarkTheme) {\n                    Color.White.copy(alpha = 0.08f * highlightStrength)\n                } else {\n                    Color.White.copy(alpha = 0.25f * highlightStrength)\n                }\n                drawRoundRect(\n                    color = borderColor,\n                    topLeft = Offset(halfDp, halfDp),\n                    size = Size(w - oneDp, h - oneDp),\n                    cornerRadius = strokeRadius,\n                    style = Stroke(width = oneDp),\n                )\n            }\n\n            val glassHighAlpha = if (isDarkTheme) 0.05f + 0.10f * highlightStrength else 0.08f + 0.18f * highlightStrength\n            val glassLowAlpha = if (isDarkTheme) 0.01f + 0.04f * highlightStrength else 0.03f + 0.08f * highlightStrength\n            val baseAlpha = 0.45f + (1f - transparency) * 0.35f\n            drawRoundRect(\n                brush = Brush.verticalGradient(\n                    colors = listOf(\n                        Color.White.copy(alpha = glassHighAlpha * baseAlpha),\n                        Color.White.copy(alpha = glassLowAlpha * baseAlpha),\n                    ),\n                ),\n                topLeft = Offset.Zero,\n                size = Size(w, h),\n                cornerRadius = cornerRadius,\n            )\n\n            if (enableSpecular) {\n                val specularAlpha = if (isDarkTheme) 0.10f + 0.12f * highlightStrength else 0.25f + 0.25f * highlightStrength\n                drawRoundRect(\n                    brush = Brush.horizontalGradient(\n                        colors = listOf(\n                            Color.Transparent,\n                            Color.White.copy(alpha = specularAlpha),\n                            Color.Transparent,\n                        ),\n                        startX = w * 0.15f,\n                        endX = w * 0.85f,\n                    ),\n                    topLeft = Offset(w * 0.15f, twoDp),\n                    size = Size(w * 0.7f, oneAndHalfDp),\n                    cornerRadius = CornerRadius(oneDp),\n                )\n            }\n\n            if (enableInnerGlow) {\n                val glowAlpha = if (isDarkTheme) 0.02f + 0.04f * highlightStrength else 0.04f + 0.08f * highlightStrength\n                drawRoundRect(\n                    brush = Brush.verticalGradient(\n                        colors = listOf(Color.Transparent, Color.White.copy(alpha = glowAlpha)),\n                    ),\n                    topLeft = Offset(fourDp, h - eightDp),\n                    size = Size(w - eightDp, fourDp),\n                    cornerRadius = CornerRadius(twoDp),\n                )\n            }\n            }\n        )\n        .then(if (isRealTimeBlurAvailable() && liquidState != null) Modifier else Modifier)\n\n    if (isRealTimeBlurAvailable() && liquidState != null) {\n        val blurAmount = (blurStrength * 20f).dp\n        val curve = if (isDarkTheme) 0.34f + blurStrength * 0.10f else 0.40f + blurStrength * 0.10f\n        val refraction = if (isDarkTheme) 0.07f + blurStrength * 0.06f else 0.10f + blurStrength * 0.08f\n        return mod.then(\n            Modifier\n                .drawBehind {\n                    val radius = size.height / 2f\n                    drawRoundRect(\n                        color = glassBaseColor,\n                        cornerRadius = CornerRadius(radius, radius),\n                    )\n                }\n                .liquid(liquidState) {\n                this.shape = shape\n                this.frost = blurAmount.coerceIn(8.dp, 20.dp)\n                this.curve = curve\n                this.refraction = refraction\n                this.dispersion = if (isDarkTheme) 0.16f + blurStrength * 0.08f else 0.20f + blurStrength * 0.10f\n                this.saturation = if (isDarkTheme) 0.38f + blurStrength * 0.12f else 0.48f + blurStrength * 0.14f\n                this.contrast = if (isDarkTheme) 1.7f + blurStrength * 0.2f else 1.55f + blurStrength * 0.2f\n            }\n        )\n    }\n\n    return mod.then(\n        Modifier.drawBehind {\n            val radius = size.height / 2f\n            drawRoundRect(\n                color = glassBaseColor,\n                cornerRadius = CornerRadius(radius, radius),\n            )\n        }\n    )\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/ui/HomeBottomSpacer.kt",
    "content": "package me.bmax.apatch.util.ui\n\nimport androidx.compose.animation.core.animateDpAsState\nimport androidx.compose.animation.core.tween\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.asPaddingValues\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.navigationBars\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.unit.dp\nimport me.bmax.apatch.ui.LocalBottomBarVisible\nimport me.bmax.apatch.ui.LocalIsFloatingNavMode\n\n@Composable\nfun HomeBottomSpacer(modifier: Modifier = Modifier) {\n    val isFloatingMode = LocalIsFloatingNavMode.current\n    val bottomBarVisible = LocalBottomBarVisible.current.value\n    val navBarBottom = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()\n\n    val targetHeight by animateDpAsState(\n        targetValue = when {\n            isFloatingMode && bottomBarVisible -> 80.dp + navBarBottom\n            isFloatingMode -> navBarBottom\n            else -> 16.dp\n        },\n        animationSpec = tween(durationMillis = 300),\n        label = \"homeBottomSpacer\"\n    )\n\n    Spacer(modifier.height(targetHeight))\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/ui/HyperlinkText.kt",
    "content": "package me.bmax.apatch.util.ui\n\nimport androidx.compose.foundation.gestures.detectTapGestures\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.input.pointer.pointerInput\nimport androidx.compose.ui.platform.LocalUriHandler\nimport androidx.compose.ui.text.SpanStyle\nimport androidx.compose.ui.text.TextLayoutResult\nimport androidx.compose.ui.text.buildAnnotatedString\nimport androidx.compose.ui.text.style.TextDecoration\nimport java.util.regex.Pattern\n\n@Composable\nfun LinkifyText(\n    text: String,\n    modifier: Modifier = Modifier\n) {\n    val uriHandler = LocalUriHandler.current\n    val layoutResult = remember {\n        mutableStateOf<TextLayoutResult?>(null)\n    }\n    val linksList = extractUrls(text)\n    val annotatedString = buildAnnotatedString {\n        append(text)\n        linksList.forEach {\n            addStyle(\n                style = SpanStyle(\n                    color = MaterialTheme.colorScheme.primary,\n                    textDecoration = TextDecoration.Underline\n                ),\n                start = it.start,\n                end = it.end\n            )\n            addStringAnnotation(\n                tag = \"URL\",\n                annotation = it.url,\n                start = it.start,\n                end = it.end\n            )\n        }\n    }\n    Text(\n        text = annotatedString,\n        modifier = modifier.pointerInput(Unit) {\n            detectTapGestures { offsetPosition ->\n                layoutResult.value?.let {\n                    val position = it.getOffsetForPosition(offsetPosition)\n                    annotatedString.getStringAnnotations(position, position).firstOrNull()\n                        ?.let { result ->\n                            if (result.tag == \"URL\") {\n                                uriHandler.openUri(result.item)\n                            }\n                        }\n                }\n            }\n        },\n        onTextLayout = { layoutResult.value = it }\n    )\n}\n\nprivate val urlPattern: Pattern = Pattern.compile(\n    \"(?:^|[\\\\W])((ht|f)tp(s?):\\\\/\\\\/|www\\\\.)\"\n            + \"(([\\\\w\\\\-]+\\\\.){1,}?([\\\\w\\\\-.~]+\\\\/?)*\"\n            + \"[\\\\p{Alnum}.,%_=?&#\\\\-+()\\\\[\\\\]\\\\*$~@!:/{};']*)\",\n    Pattern.CASE_INSENSITIVE or Pattern.MULTILINE or Pattern.DOTALL\n)\n\nprivate data class LinkInfo(\n    val url: String,\n    val start: Int,\n    val end: Int\n)\n\nprivate fun extractUrls(text: String): List<LinkInfo> = buildList {\n    val matcher = urlPattern.matcher(text)\n    while (matcher.find()) {\n        val matchStart = matcher.start(1)\n        val matchEnd = matcher.end()\n        val url = text.substring(matchStart, matchEnd).replaceFirst(\"http://\", \"https://\")\n        add(LinkInfo(url, matchStart, matchEnd))\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/ui/NavigationBarsSpacer.kt",
    "content": "package me.bmax.apatch.util.ui\n\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.asPaddingValues\nimport androidx.compose.foundation.layout.navigationBars\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\n\n@Composable\nfun NavigationBarsSpacer(\n    modifier: Modifier = Modifier\n) {\n    val paddingValues = WindowInsets.navigationBars.asPaddingValues()\n\n    Box(\n        modifier = Modifier.padding(paddingValues)\n    ) {\n        Spacer(modifier = modifier)\n    }\n}"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/util/ui/ToastExt.kt",
    "content": "package me.bmax.apatch.util.ui\n\nimport android.content.Context\nimport android.graphics.PixelFormat\nimport android.os.Handler\nimport android.os.Looper\nimport android.util.Log\nimport android.view.Gravity\nimport android.view.WindowManager\nimport android.widget.TextView\nimport android.widget.Toast\nimport android.graphics.Color\n\nprivate const val TAG = \"SafeToast\"\n\nfun showToast(context: Context, message: String) {\n    try {\n        Toast.makeText(context, message, Toast.LENGTH_SHORT).show()\n    } catch (e: Exception) {\n        Log.w(TAG, \"System toast unavailable, using fallback: $message\", e)\n        showFallbackToast(context, message)\n    }\n}\n\nfun showToast(context: Context, resId: Int) {\n    showToast(context, context.getString(resId))\n}\n\nfun showToast(context: Context, resId: Int, vararg formatArgs: Any) {\n    showToast(context, context.getString(resId, *formatArgs))\n}\n\nfun Toast.safeShow() {\n    try {\n        show()\n    } catch (e: SecurityException) {\n        Log.w(TAG, \"System toast unavailable, safeShow suppressed\", e)\n    }\n}\n\nprivate fun showFallbackToast(context: Context, message: String) {\n    val handler = Handler(Looper.getMainLooper())\n    handler.post {\n        try {\n            val windowManager =\n                context.getSystemService(Context.WINDOW_SERVICE) as WindowManager\n            val params = WindowManager.LayoutParams(\n                WindowManager.LayoutParams.WRAP_CONTENT,\n                WindowManager.LayoutParams.WRAP_CONTENT,\n                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,\n                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE\n                        or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,\n                PixelFormat.TRANSLUCENT\n            )\n            params.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL\n            params.y = 100\n\n            val textView = TextView(context).apply {\n                text = message\n                setPadding(48, 24, 48, 24)\n                setTextColor(Color.WHITE)\n                textSize = 14f\n            }\n\n            windowManager.addView(textView, params)\n            handler.postDelayed({\n                try {\n                    windowManager.removeView(textView)\n                } catch (_: Exception) {\n                }\n            }, 2500)\n        } catch (e: Exception) {\n            Log.e(TAG, \"Fallback toast failed too: $message\", e)\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/res/drawable/device_mobile_down.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:pathData=\"M12.5,21h-4.5a2,2 0,0 1,-2 -2v-14a2,2 0,0 1,2 -2h8a2,2 0,0 1,2 2v7\"\n        android:strokeLineJoin=\"round\"\n        android:strokeWidth=\"2\"\n        android:strokeColor=\"#ffff\"\n        android:strokeLineCap=\"round\"/>\n    <path\n        android:pathData=\"M11,4h2\"\n        android:strokeLineJoin=\"round\"\n        android:strokeWidth=\"2\"\n        android:strokeColor=\"#ffff\"\n        android:strokeLineCap=\"round\"/>\n    <path\n        android:pathData=\"M12,17v0.01\"\n        android:strokeLineJoin=\"round\"\n        android:strokeWidth=\"2\"\n        android:strokeColor=\"#ffff\"\n        android:strokeLineCap=\"round\"/>\n    <path\n        android:pathData=\"M19,16v6\"\n        android:strokeLineJoin=\"round\"\n        android:strokeWidth=\"2\"\n        android:strokeColor=\"#ffff\"\n        android:strokeLineCap=\"round\"/>\n    <path\n        android:pathData=\"M22,19l-3,3l-3,-3\"\n        android:strokeLineJoin=\"round\"\n        android:strokeWidth=\"2\"\n        android:strokeColor=\"#ffff\"\n        android:strokeLineCap=\"round\"/>\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/github.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24.0dip\"\n    android:height=\"24.0dip\"\n    android:viewportHeight=\"24.0\"\n    android:viewportWidth=\"24.0\">\n    <path\n        android:fillColor=\"#ffff\"\n        android:pathData=\"M12,2A10,10 0 0,0 2,12C2,16.42 4.87,20.17 8.84,21.5C9.34,21.58 9.5,21.27 9.5,21C9.5,20.77 9.5,20.14 9.5,19.31C6.73,19.91 6.14,17.97 6.14,17.97C5.68,16.81 5.03,16.5 5.03,16.5C4.12,15.88 5.1,15.9 5.1,15.9C6.1,15.97 6.63,16.93 6.63,16.93C7.5,18.45 8.97,18 9.54,17.76C9.63,17.11 9.89,16.67 10.17,16.42C7.95,16.17 5.62,15.31 5.62,11.5C5.62,10.39 6,9.5 6.65,8.79C6.55,8.54 6.2,7.5 6.75,6.15C6.75,6.15 7.59,5.88 9.5,7.17C10.29,6.95 11.15,6.84 12,6.84C12.85,6.84 13.71,6.95 14.5,7.17C16.41,5.88 17.25,6.15 17.25,6.15C17.8,7.5 17.45,8.54 17.35,8.79C18,9.5 18.38,10.39 18.38,11.5C18.38,15.32 16.04,16.16 13.81,16.41C14.17,16.72 14.5,17.33 14.5,18.26C14.5,19.6 14.5,20.68 14.5,21C14.5,21.27 14.66,21.59 15.17,21.5C19.14,20.16 22,16.42 22,12A10,10 0 0,0 12,2Z\"/>\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_clear_background.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\"\n    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6V19zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z\"/>\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_custom_background.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\"\n    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M19,3H5C3.89,3 3,3.89 3,5v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5C21,3.89 20.1,3 19,3zM19,19H5V5h14V19zM13.96,12.29l-1.41,-1.41L11,12.46V7h2v5.46l1.54,-1.54l1.41,1.41L12,15.37L8.04,11.41z\"/>\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher_foreground_alt.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"512\"\n    android:viewportHeight=\"512\">\n  <group android:scaleX=\"0.7135135\"\n      android:scaleY=\"0.7135135\"\n      android:translateX=\"73.34054\"\n      android:translateY=\"73.34054\">\n    <path\n        android:pathData=\"M304.42,123.08Q319,99.08 322,92.18a7.08,7.08 0,0 0,-1.31 -7.8,3.79 3.79,0 0,0 -3.18,-1.44c-3.51,0.12 -5.65,-0.63 -7.93,2.69a60.66,60.66 0,0 0,-5.37 9.64c-3.33,7.48 -7.67,14.36 -11.54,21.59a0.86,0.86 0,0 1,-1.07 0.39c-14.4,-5.52 -30.47,-7.17 -45.76,-6a94.62,94.62 0,0 0,-26 6,1.15 1.15,0 0,1 -1.41,-0.5c-3.84,-6.48 -7.19,-12.44 -10.77,-20 -2.1,-4.42 -4.6,-9.64 -8.21,-13.08a1.81,1.81 0,0 0,-1 -0.49c-4.23,-0.57 -9.27,-0.79 -9.95,4.78a12.35,12.35 0,0 0,1.87 7.43q7.75,14 15.7,27.43a1.21,1.21 0,0 1,-0.43 1.64c-26.65,15.7 -44,42.33 -43.91,73.42a0.67,0.67 0,0 0,0.67 0.67l186.39,-0.1a1.39,1.39 0,0 0,1.39 -1.4v-0.08c-0.76,-11.39 -2.94,-22.68 -8,-33.18q-12,-25.2 -37.52,-40a0.45,0.45 0,0 1,-0.18 -0.69\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#C62828\"/>\n    <path\n        android:pathData=\"M136.32,330.27A21.63,21.63 0,0 0,156 309.12q0,-43.94 0,-86c0,-5.5 -1.13,-10.51 -4.75,-14.87a22.21,22.21 0,0 0,-35.45 1.63,9.93 9.93,0 0,0 -1.81,5.86q0,49.15 0,98.85a10.64,10.64 0,0 0,1.8 6q7.55,10.76 20.5,9.7\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#C62828\"/>\n    <path\n        android:pathData=\"M398.87,221.61a21.3,21.3 0,0 0,-21.33 -21.26h-1.48a21.3,21.3 0,0 0,-21.27 21.34l0.16,87.22a21.3,21.3 0,0 0,21.33 21.26h1.48A21.3,21.3 0,0 0,399 308.83l-0.16,-87.22\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#C62828\"/>\n    <path\n        android:pathData=\"M243.08,363l24.62,-0.05a0.45,0.45 0,0 1,0.52 0.51q0.13,17.21 -0.06,34.75 -0.14,11 1.41,16 2.76,8.84 12.66,13.73c3,1.5 7.25,1.17 10.62,1.18a15,15 0,0 0,9.09 -3q10,-7.66 10.08,-20.67 0.14,-19.67 -0.07,-41.42c0,-0.76 0.36,-1.12 1.13,-1.09a115.4,115.4 0,0 0,11.91 0c16.94,-0.9 25.13,-12.07 25.11,-28.21q-0.06,-64.95 0,-129.87a0.8,0.8 0,0 0,-0.8 -0.81h-187a0.83,0.83 0,0 0,-0.82 0.83h0q0.35,61.24 0.08,128c0,9.74 0.26,15.67 6.6,22.33 8.58,9 19.77,8 31.25,7.75a0.42,0.42 0,0 1,0.42 0.41q0,21.4 -0.08,43.79a22.1,22.1 0,0 0,1.47 8.58c2.18,5.27 8.63,12.65 15.17,13.15 4.89,0.37 10.12,0.7 14.42,-1.79 7.15,-4.16 12,-10.91 11.91,-19.12q-0.18,-22.17 -0.09,-44.46a0.48,0.48 0,0 1,0.54 -0.54\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#C62828\"/>\n    <path\n        android:pathData=\"M202.24,142.7a73.29,73.29 0,0 0,-26 41.57,0.6 0.6,0 0,0 0.46,0.71h0.12q78.3,0 156.93,0a3,3 0,0 0,1 -0.16,0.52 0.52,0 0,0 0.39,-0.7c-2.83,-12 -8.39,-23.5 -16.83,-32.58a93.87,93.87 0,0 0,-21.1 -17.25,63.89 63.89,0 0,0 -13.06,-5.64 98.42,98.42 0,0 0,-13.78 -3.44,89.93 89.93,0 0,0 -45.77,4.37 61,61 0,0 0,-13.44 6.6q-4.54,3.09 -8.86,6.55\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#C62828\"/>\n    <path\n        android:pathData=\"M143,222.08a8.63,8.63 0,0 0,-8.63 -8.63H134a8.63,8.63 0,0 0,-8.63 8.63h0V308.7a8.63,8.63 0,0 0,8.63 8.63h0.3A8.63,8.63 0,0 0,143 308.7V222.08\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#C62828\"/>\n    <path\n        android:pathData=\"M385.51,221.45a7.87,7.87 0,0 0,-7.84 -7.9h-1a7.87,7.87 0,0 0,-7.9 7.84l-0.3,88a7.87,7.87 0,0 0,7.84 7.9h1a7.87,7.87 0,0 0,7.9 -7.84l0.3,-88\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#C62828\"/>\n    <path\n        android:pathData=\"M204.46,350.63c5.26,1.85 7.52,8.11 7.51,13.3q0,18.68 0,40.92a18.92,18.92 0,0 0,0.66 5A8.41,8.41 0,0 0,225 415q5,-2.91 5,-10.15 -0.07,-20.33 0,-42.76 0,-7.64 6.29,-11.18a17.67,17.67 0,0 1,7.48 -1.69q12.3,0 23.73,0a5.94,5.94 0,0 1,1.77 0.27c1.44,0.45 3,0.49 4.37,1.13q7.94,3.69 7.85,12.49 -0.25,23.72 0.25,43.09a10.52,10.52 0,0 0,3.45 7.89,8.43 8.43,0 0,0 13.05,-2.88 13.45,13.45 0,0 0,1 -5.33q-0.1,-20.39 0,-39.88c0,-6.48 2.17,-13.84 9,-15.7a24,24 0,0 1,6 -1.08q5.76,0 11.39,0.07a10.06,10.06 0,0 0,8.7 -3.85c2.38,-3.09 2.06,-7.7 2,-11.44q-0.24,-50.9 0,-100.2 0,-8.13 0.52,-16.54a0.48,0.48 0,0 0,-0.45 -0.51h-161a0.48,0.48 0,0 0,-0.49 0.48h0q0,59.14 -0.07,118.94c0,5.08 0.71,9.07 5.52,11.79a12.21,12.21 0,0 0,6.63 1.36c5.86,-0.24 12,-0.69 17.6,1.26\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#C62828\"/>\n    <path\n        android:pathData=\"M256.14,241.06c2.47,1.21 4.51,5.41 5.77,7.73a1.87,1.87 0,0 0,2.66 0.93l5.18,-2.23a1.7,1.7 0,0 0,1 -2.45,28.37 28.37,0 0,0 -8.27,-11.56c-9.1,-7.71 -17.26,1.13 -21.57,9.18a14.06,14.06 0,0 0,-1.4 3.83,1 1,0 0,0 0.29,0.94c1.51,1.49 4.46,2.26 6.39,3a2.41,2.41 0,0 0,3.13 -1.35l0.05,-0.14a19.08,19.08 0,0 1,4.66 -7.56,1.83 1.83,0 0,1 2.08,-0.34\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#C62828\"/>\n    <path\n        android:pathData=\"M244,262.69a0.45,0.45 0,0 1,0.2 0.6,0.4 0.4,0 0,1 -0.16,0.18c-8.73,5.77 -16.12,12.75 -23.42,20.14a77.72,77.72 0,0 0,-9.36 11.74c-5.51,8.31 -10.38,26.49 5.94,27.34 11,0.56 23.33,-4.52 33.21,-9.79 5.46,-2.91 10.75,-6.8 16,-10.29C279.23,294 293.2,281.16 301.47,267a24.68,24.68 0,0 0,2.5 -16.87c-0.84,-4.13 -6.55,-6.3 -10.17,-6.6a40.62,40.62 0,0 0,-15.52 2.07,98.9 98.9,0 0,0 -22.7,10.19 0.79,0.79 0,0 1,-0.84 0,136.77 136.77,0 0,0 -18.21,-8.14c-8.95,-3.25 -29.8,-8.43 -30.71,7.3 -0.2,3.48 1,6.81 2.12,10.08a8,8 0,0 1,0.25 4.07c-0.89,4.78 0.63,9.48 5.19,11.62q6.41,3 11.39,-1.84c6.58,-6.38 3.13,-16.56 -5.94,-17.65A3.7,3.7 0,0 1,215.6 257a2.54,2.54 0,0 1,2.53 -2.4c8,-0.67 18.37,4.28 25.87,8.09\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#C62828\"/>\n    <path\n        android:pathData=\"M295.13,307.57c0,0.63 0,1.25 0,1.88a2.06,2.06 0,0 1,-2.13 2.28c-8.24,0.66 -17.51,-3.69 -24.95,-7.42a2.45,2.45 0,0 0,-2.4 0.11l-6.94,4.3a1.72,1.72 0,0 0,-0.56 2.37,1.7 1.7,0 0,0 0.64,0.61 89.73,89.73 0,0 0,28.29 10.15c4.74,0.81 13.83,0.63 16.4,-4.11 3,-5.59 1.52,-10.36 0,-17 -0.66,-2.82 0.13,-5.78 -0.93,-8.44 -1.8,-4.51 -6.79,-8.14 -11.84,-6.81 -5.57,1.47 -10.46,7.66 -7.77,13.69q3.13,7 10.69,7a1.32,1.32 0,0 1,1.51 1.43\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#C62828\"/>\n    <path\n        android:pathData=\"M258.74,323c-4.24,6 -8,-2.7 -9.08,-5.69a1.92,1.92 0,0 0,-2.24 -1.23,21.29 21.29,0 0,0 -6.63,2.68 1.86,1.86 0,0 0,-0.8 2.55c3.17,7.55 12,19.16 21.61,12.47q5.94,-4.15 9.42,-12.37a2.53,2.53 0,0 0,-1.44 -3.69l-4.16,-1.88a2.34,2.34 0,0 0,-3.54 1.39,30.07 30.07,0 0,1 -3.14,5.77\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#C62828\"/>\n    <path\n        android:pathData=\"M252.39,295.58a1.36,1.36 0,0 1,-0.44 1.57,65 65,0 0,1 -6.55,4.29 2.69,2.69 0,0 1,-3.07 -0.08,124 124,0 0,1 -12.82,-10.07 0.93,0.93 0,0 0,-1.29 0c-4,4 -12.35,13 -12.18,18.91a2.06,2.06 0,0 0,1.92 2,26.58 26.58,0 0,0 11.29,-1.58 121.66,121.66 0,0 0,24.36 -12.43,212.63 212.63,0 0,0 25.07,-19.73c6,-5.5 14,-13.41 16,-21.4 1.65,-6.65 -13.58,-1.58 -16.06,-0.51q-6,2.61 -12.34,5.71a0.54,0.54 0,0 0,-0.25 0.71,0.64 0.64,0 0,0 0.19,0.21 137.63,137.63 0,0 1,12.46 9.29,2 2,0 0,1 0.3,3.15 21.39,21.39 0,0 1,-6 5.14,2.06 2.06,0 0,1 -2.34,-0.21 134.52,134.52 0,0 0,-14.92 -10.77,0.78 0.78,0 0,0 -0.9,0 157.9,157.9 0,0 0,-17.7 13.43,0.33 0.33,0 0,0 0,0.45l0.06,0.05q7.53,5.38 14.56,10.95a2.16,2.16 0,0 1,0.68 0.9\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#C62828\"/>\n    <path\n        android:pathData=\"M261.87,275a8.83,8.83 0,0 0,-12.49 0l-2,2a8.82,8.82 0,0 0,0 12.48h0l2,2a8.83,8.83 0,0 0,12.49 0l2,-2a8.82,8.82 0,0 0,0 -12.48h0l-2,-2\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#C62828\"/>\n    <path\n        android:pathData=\"M304.6,123.77q25.48,14.78 37.52,40c5,10.5 7.2,21.79 8,33.18a1.4,1.4 0,0 1,-1.31 1.48h-0.08l-186.39,0.1a0.67,0.67 0,0 1,-0.67 -0.67c-0.09,-31.09 17.26,-57.72 43.91,-73.42a1.21,1.21 0,0 0,0.43 -1.64q-7.93,-13.45 -15.7,-27.43a12.35,12.35 0,0 1,-1.87 -7.43c0.68,-5.57 5.72,-5.35 9.95,-4.78a1.81,1.81 0,0 1,1 0.49c3.61,3.44 6.11,8.66 8.21,13.08 3.58,7.57 6.93,13.53 10.77,20a1.15,1.15 0,0 0,1.41 0.5,94.62 94.62,0 0,1 26,-6c15.29,-1.15 31.36,0.5 45.76,6a0.86,0.86 0,0 0,1.07 -0.39c3.87,-7.23 8.21,-14.11 11.54,-21.59a60.66,60.66 0,0 1,5.37 -9.64c2.28,-3.32 4.42,-2.57 7.93,-2.69a3.79,3.79 0,0 1,3.18 1.44,7.08 7.08,0 0,1 1.31,7.8q-3,6.9 -17.56,30.9A0.45,0.45 0,0 0,304.6 123.77ZM202.24,142.7a73.29,73.29 0,0 0,-26 41.57,0.6 0.6,0 0,0 0.46,0.71h0.12q78.3,0 156.93,0a3,3 0,0 0,1 -0.16,0.52 0.52,0 0,0 0.39,-0.7c-2.83,-12 -8.39,-23.5 -16.83,-32.58a93.87,93.87 0,0 0,-21.1 -17.25,63.89 63.89,0 0,0 -13.06,-5.64 98.42,98.42 0,0 0,-13.78 -3.44,89.93 89.93,0 0,0 -45.77,4.37 61,61 0,0 0,-13.44 6.6Q206.56,139.24 202.24,142.7Z\"\n        android:fillColor=\"#FF8A80\"/>\n    <path\n        android:pathData=\"M156,309.12a21.63,21.63 0,0 1,-19.68 21.15q-13,1.07 -20.5,-9.7a10.64,10.64 0,0 1,-1.8 -6q0,-49.69 0,-98.85a9.93,9.93 0,0 1,1.81 -5.86,22.21 22.21,0 0,1 35.45,-1.63c3.62,4.36 4.75,9.37 4.75,14.87Q156,265.18 156,309.12ZM142.95,222.12a8.63,8.63 0,0 0,-8.63 -8.63L134,213.49a8.63,8.63 0,0 0,-8.63 8.63h0L125.37,308.7a8.63,8.63 0,0 0,8.63 8.63h0.3A8.63,8.63 0,0 0,143 308.7Z\"\n        android:fillColor=\"#FF8A80\"/>\n    <path\n        android:pathData=\"M399,308.83a21.3,21.3 0,0 1,-21.27 21.34h-1.48A21.3,21.3 0,0 1,355 308.91h0l-0.16,-87.22a21.3,21.3 0,0 1,21.27 -21.34h1.48a21.3,21.3 0,0 1,21.33 21.26h0ZM385.48,221.45a7.87,7.87 0,0 0,-7.84 -7.9h-1a7.87,7.87 0,0 0,-7.9 7.84l-0.3,88a7.87,7.87 0,0 0,7.84 7.9h1a7.87,7.87 0,0 0,7.9 -7.84Z\"\n        android:fillColor=\"#FF8A80\"/>\n    <path\n        android:pathData=\"M242.54,363.52q-0.09,22.29 0.09,44.46c0.07,8.21 -4.76,15 -11.91,19.12 -4.3,2.49 -9.53,2.16 -14.42,1.79 -6.54,-0.5 -13,-7.88 -15.17,-13.15a22.1,22.1 0,0 1,-1.47 -8.58q0.12,-22.4 0.08,-43.79a0.42,0.42 0,0 0,-0.42 -0.41c-11.48,0.22 -22.67,1.25 -31.25,-7.75 -6.34,-6.66 -6.64,-12.59 -6.6,-22.33q0.27,-66.81 -0.08,-128a0.83,0.83 0,0 1,0.82 -0.83h187a0.8,0.8 0,0 1,0.8 0.81q0,64.92 0,129.87c0,16.14 -8.17,27.31 -25.11,28.21a115.4,115.4 0,0 1,-11.91 0c-0.77,0 -1.14,0.33 -1.13,1.09q0.19,21.76 0.07,41.42 -0.09,13 -10.08,20.67a15,15 0,0 1,-9.09 3c-3.37,0 -7.6,0.32 -10.62,-1.18q-9.9,-4.9 -12.66,-13.73 -1.54,-4.92 -1.41,-16 0.19,-17.55 0.06,-34.75a0.45,0.45 0,0 0,-0.52 -0.51l-24.62,0.05A0.48,0.48 0,0 0,242.54 363.52ZM204.46,350.63c5.26,1.85 7.52,8.11 7.51,13.3q0,18.68 0,40.92a18.92,18.92 0,0 0,0.66 5A8.41,8.41 0,0 0,225 415q5,-2.91 5,-10.15 -0.07,-20.33 0,-42.76 0,-7.64 6.29,-11.18a17.67,17.67 0,0 1,7.48 -1.69q12.3,0 23.73,0a5.94,5.94 0,0 1,1.77 0.27c1.44,0.45 3,0.49 4.37,1.13q7.94,3.69 7.85,12.49 -0.25,23.72 0.25,43.09a10.52,10.52 0,0 0,3.45 7.89,8.43 8.43,0 0,0 13.05,-2.88 13.45,13.45 0,0 0,1 -5.33q-0.1,-20.39 0,-39.88c0,-6.48 2.17,-13.84 9,-15.7a24,24 0,0 1,6 -1.08q5.76,0 11.39,0.07a10.06,10.06 0,0 0,8.7 -3.85c2.38,-3.09 2.06,-7.7 2,-11.44q-0.24,-50.9 0,-100.2 0,-8.13 0.52,-16.54a0.48,0.48 0,0 0,-0.45 -0.51h-161a0.48,0.48 0,0 0,-0.49 0.48h0q0,59.14 -0.07,118.94c0,5.08 0.71,9.07 5.52,11.79a12.21,12.21 0,0 0,6.63 1.36C192.72,349.13 198.89,348.68 204.46,350.63Z\"\n        android:fillColor=\"#FF8A80\"/>\n    <path\n        android:pathData=\"M256.14,241.06a1.83,1.83 0,0 0,-2.08 0.34A19.08,19.08 0,0 0,249.4 249a2.42,2.42 0,0 1,-3 1.54l-0.14,-0.05c-1.93,-0.76 -4.88,-1.53 -6.39,-3a1,1 0,0 1,-0.29 -0.94,14.06 14.06,0 0,1 1.4,-3.83c4.31,-8 12.47,-16.89 21.57,-9.18A28.37,28.37 0,0 1,270.78 245a1.7,1.7 0,0 1,-1 2.45l-5.18,2.23a1.87,1.87 0,0 1,-2.66 -0.93C260.65,246.47 258.61,242.27 256.14,241.06Z\"\n        android:fillColor=\"#FF8A80\"/>\n    <path\n        android:pathData=\"M244,262.69c-7.5,-3.81 -17.85,-8.76 -25.87,-8.09a2.54,2.54 0,0 0,-2.53 2.4,3.7 3.7,0 0,0 3.23,4.21c9.07,1.09 12.52,11.27 5.94,17.65q-5,4.83 -11.39,1.84c-4.56,-2.14 -6.08,-6.84 -5.19,-11.62a8,8 0,0 0,-0.25 -4.07c-1.1,-3.27 -2.32,-6.6 -2.12,-10.08 0.91,-15.73 21.76,-10.55 30.71,-7.3a136.77,136.77 0,0 1,18.21 8.14,0.79 0.79,0 0,0 0.84,0 98.9,98.9 0,0 1,22.7 -10.19,40.62 40.62,0 0,1 15.52,-2.07c3.62,0.3 9.33,2.47 10.17,6.6a24.68,24.68 0,0 1,-2.5 16.87c-8.27,14.21 -22.24,27.08 -35.09,35.66 -5.22,3.49 -10.51,7.38 -16,10.29 -9.88,5.27 -22.24,10.35 -33.21,9.79 -16.32,-0.85 -11.45,-19 -5.94,-27.34a77.72,77.72 0,0 1,9.36 -11.74c7.3,-7.39 14.69,-14.37 23.42,-20.14a0.45,0.45 0,0 0,0.14 -0.62A0.55,0.55 0,0 0,244 262.69ZM252.39,295.58a1.36,1.36 0,0 1,-0.44 1.57,65 65,0 0,1 -6.55,4.29 2.69,2.69 0,0 1,-3.07 -0.08,124 124,0 0,1 -12.82,-10.07 0.93,0.93 0,0 0,-1.29 0c-4,4 -12.35,13 -12.18,18.91a2.06,2.06 0,0 0,1.92 2,26.58 26.58,0 0,0 11.29,-1.58 121.66,121.66 0,0 0,24.36 -12.43,212.63 212.63,0 0,0 25.07,-19.73c6,-5.5 14,-13.41 16,-21.4 1.65,-6.65 -13.58,-1.58 -16.06,-0.51q-6,2.61 -12.34,5.71a0.54,0.54 0,0 0,-0.25 0.71,0.64 0.64,0 0,0 0.19,0.21 137.63,137.63 0,0 1,12.46 9.29,2 2,0 0,1 0.3,3.15 21.39,21.39 0,0 1,-6 5.14,2.06 2.06,0 0,1 -2.34,-0.21 134.52,134.52 0,0 0,-14.92 -10.77,0.78 0.78,0 0,0 -0.9,0 157.9,157.9 0,0 0,-17.7 13.43,0.33 0.33,0 0,0 0,0.45l0.06,0.05q7.53,5.38 14.56,10.95A2.16,2.16 0,0 1,252.39 295.58Z\"\n        android:fillColor=\"#FF8A80\"/>\n    <path\n        android:pathData=\"M247.42,276.94L249.39,274.98A8.83,8.83 90.2,0 1,261.88 275.02L263.88,277.04A8.83,8.83 90.2,0 1,263.83 289.53L261.86,291.49A8.83,8.83 90.2,0 1,249.37 291.44L247.37,289.43A8.83,8.83 90.2,0 1,247.42 276.94z\"\n        android:fillColor=\"#FF8A80\"/>\n    <path\n        android:pathData=\"M293.62,306.14q-7.56,0 -10.69,-7c-2.69,-6 2.2,-12.22 7.77,-13.69 5.05,-1.33 10,2.3 11.84,6.81 1.06,2.66 0.27,5.62 0.93,8.44 1.56,6.67 3.08,11.44 0,17 -2.57,4.74 -11.66,4.92 -16.4,4.11a89.73,89.73 0,0 1,-28.29 -10.15,1.73 1.73,0 0,1 -0.69,-2.34 1.78,1.78 0,0 1,0.61 -0.64l6.94,-4.3a2.45,2.45 0,0 1,2.4 -0.11c7.44,3.73 16.71,8.08 24.95,7.42a2.06,2.06 0,0 0,2.13 -2.28c0,-0.63 0,-1.25 0,-1.88A1.32,1.32 0,0 0,293.62 306.14Z\"\n        android:fillColor=\"#FF8A80\"/>\n    <path\n        android:pathData=\"M249.66,317.34c1.05,3 4.84,11.72 9.08,5.69a30.07,30.07 0,0 0,3.14 -5.77,2.34 2.34,0 0,1 3.54,-1.39l4.16,1.88a2.53,2.53 0,0 1,1.44 3.69q-3.48,8.22 -9.42,12.37c-9.59,6.69 -18.44,-4.92 -21.61,-12.47a1.86,1.86 0,0 1,0.8 -2.55,21.29 21.29,0 0,1 6.63,-2.68A1.92,1.92 0,0 1,249.66 317.34Z\"\n        android:fillColor=\"#FF8A80\"/>\n  </group>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher_monochrome.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\n    <group\n        android:scaleX=\"0.5\"\n        android:scaleY=\"0.5\"\n        android:translateX=\"27\"\n        android:translateY=\"27\">\n\n        <path\n            android:pathData=\"M 20 20 H 92 V 92 H 20 V 20 Z\"\n            android:strokeWidth=\"8\"\n            android:strokeColor=\"#000000\" />\n        \n        <path\n            android:fillColor=\"#000000\"\n            android:pathData=\"M 28 28 h 12 v 56 h -12 z\" />\n        \n        <path\n            android:fillColor=\"#000000\"\n            android:pathData=\"M 28 28 h 48 v 12 h -48 z\" />\n        \n        <path\n            android:fillColor=\"#000000\"\n            android:pathData=\"M 28 50 h 36 v 12 h -36 z\" />\n\n    </group>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher_monochrome_alt.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"512\"\n    android:viewportHeight=\"512\">\n  <group android:scaleX=\"0.7135135\"\n      android:scaleY=\"0.7135135\"\n      android:translateX=\"73.34054\"\n      android:translateY=\"73.34054\">\n    <path\n        android:pathData=\"M304.42,123.08Q319,99.08 322,92.18a7.08,7.08 0,0 0,-1.31 -7.8,3.79 3.79,0 0,0 -3.18,-1.44c-3.51,0.12 -5.65,-0.63 -7.93,2.69a60.66,60.66 0,0 0,-5.37 9.64c-3.33,7.48 -7.67,14.36 -11.54,21.59a0.86,0.86 0,0 1,-1.07 0.39c-14.4,-5.52 -30.47,-7.17 -45.76,-6a94.62,94.62 0,0 0,-26 6,1.15 1.15,0 0,1 -1.41,-0.5c-3.84,-6.48 -7.19,-12.44 -10.77,-20 -2.1,-4.42 -4.6,-9.64 -8.21,-13.08a1.81,1.81 0,0 0,-1 -0.49c-4.23,-0.57 -9.27,-0.79 -9.95,4.78a12.35,12.35 0,0 0,1.87 7.43q7.75,14 15.7,27.43a1.21,1.21 0,0 1,-0.43 1.64c-26.65,15.7 -44,42.33 -43.91,73.42a0.67,0.67 0,0 0,0.67 0.67l186.39,-0.1a1.39,1.39 0,0 0,1.39 -1.4v-0.08c-0.76,-11.39 -2.94,-22.68 -8,-33.18q-12,-25.2 -37.52,-40a0.45,0.45 0,0 1,-0.18 -0.69\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#FF000000\"/>\n    <path\n        android:pathData=\"M136.32,330.27A21.63,21.63 0,0 0,156 309.12q0,-43.94 0,-86c0,-5.5 -1.13,-10.51 -4.75,-14.87a22.21,22.21 0,0 0,-35.45 1.63,9.93 9.93,0 0,0 -1.81,5.86q0,49.15 0,98.85a10.64,10.64 0,0 0,1.8 6q7.55,10.76 20.5,9.7\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#FF000000\"/>\n    <path\n        android:pathData=\"M398.87,221.61a21.3,21.3 0,0 0,-21.33 -21.26h-1.48a21.3,21.3 0,0 0,-21.27 21.34l0.16,87.22a21.3,21.3 0,0 0,21.33 21.26h1.48A21.3,21.3 0,0 0,399 308.83l-0.16,-87.22\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#FF000000\"/>\n    <path\n        android:pathData=\"M243.08,363l24.62,-0.05a0.45,0.45 0,0 1,0.52 0.51q0.13,17.21 -0.06,34.75 -0.14,11 1.41,16 2.76,8.84 12.66,13.73c3,1.5 7.25,1.17 10.62,1.18a15,15 0,0 0,9.09 -3q10,-7.66 10.08,-20.67 0.14,-19.67 -0.07,-41.42c0,-0.76 0.36,-1.12 1.13,-1.09a115.4,115.4 0,0 0,11.91 0c16.94,-0.9 25.13,-12.07 25.11,-28.21q-0.06,-64.95 0,-129.87a0.8,0.8 0,0 0,-0.8 -0.81h-187a0.83,0.83 0,0 0,-0.82 0.83h0q0.35,61.24 0.08,128c0,9.74 0.26,15.67 6.6,22.33 8.58,9 19.77,8 31.25,7.75a0.42,0.42 0,0 1,0.42 0.41q0,21.4 -0.08,43.79a22.1,22.1 0,0 0,1.47 8.58c2.18,5.27 8.63,12.65 15.17,13.15 4.89,0.37 10.12,0.7 14.42,-1.79 7.15,-4.16 12,-10.91 11.91,-19.12q-0.18,-22.17 -0.09,-44.46a0.48,0.48 0,0 1,0.54 -0.54\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#FF000000\"/>\n    <path\n        android:pathData=\"M202.24,142.7a73.29,73.29 0,0 0,-26 41.57,0.6 0.6,0 0,0 0.46,0.71h0.12q78.3,0 156.93,0a3,3 0,0 0,1 -0.16,0.52 0.52,0 0,0 0.39,-0.7c-2.83,-12 -8.39,-23.5 -16.83,-32.58a93.87,93.87 0,0 0,-21.1 -17.25,63.89 63.89,0 0,0 -13.06,-5.64 98.42,98.42 0,0 0,-13.78 -3.44,89.93 89.93,0 0,0 -45.77,4.37 61,61 0,0 0,-13.44 6.6q-4.54,3.09 -8.86,6.55\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#FF000000\"/>\n    <path\n        android:pathData=\"M143,222.08a8.63,8.63 0,0 0,-8.63 -8.63H134a8.63,8.63 0,0 0,-8.63 8.63h0V308.7a8.63,8.63 0,0 0,8.63 8.63h0.3A8.63,8.63 0,0 0,143 308.7V222.08\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#FF000000\"/>\n    <path\n        android:pathData=\"M385.51,221.45a7.87,7.87 0,0 0,-7.84 -7.9h-1a7.87,7.87 0,0 0,-7.9 7.84l-0.3,88a7.87,7.87 0,0 0,7.84 7.9h1a7.87,7.87 0,0 0,7.9 -7.84l0.3,-88\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#FF000000\"/>\n    <path\n        android:pathData=\"M204.46,350.63c5.26,1.85 7.52,8.11 7.51,13.3q0,18.68 0,40.92a18.92,18.92 0,0 0,0.66 5A8.41,8.41 0,0 0,225 415q5,-2.91 5,-10.15 -0.07,-20.33 0,-42.76 0,-7.64 6.29,-11.18a17.67,17.67 0,0 1,7.48 -1.69q12.3,0 23.73,0a5.94,5.94 0,0 1,1.77 0.27c1.44,0.45 3,0.49 4.37,1.13q7.94,3.69 7.85,12.49 -0.25,23.72 0.25,43.09a10.52,10.52 0,0 0,3.45 7.89,8.43 8.43,0 0,0 13.05,-2.88 13.45,13.45 0,0 0,1 -5.33q-0.1,-20.39 0,-39.88c0,-6.48 2.17,-13.84 9,-15.7a24,24 0,0 1,6 -1.08q5.76,0 11.39,0.07a10.06,10.06 0,0 0,8.7 -3.85c2.38,-3.09 2.06,-7.7 2,-11.44q-0.24,-50.9 0,-100.2 0,-8.13 0.52,-16.54a0.48,0.48 0,0 0,-0.45 -0.51h-161a0.48,0.48 0,0 0,-0.49 0.48h0q0,59.14 -0.07,118.94c0,5.08 0.71,9.07 5.52,11.79a12.21,12.21 0,0 0,6.63 1.36c5.86,-0.24 12,-0.69 17.6,1.26\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#FF000000\"/>\n    <path\n        android:pathData=\"M256.14,241.06c2.47,1.21 4.51,5.41 5.77,7.73a1.87,1.87 0,0 0,2.66 0.93l5.18,-2.23a1.7,1.7 0,0 0,1 -2.45,28.37 28.37,0 0,0 -8.27,-11.56c-9.1,-7.71 -17.26,1.13 -21.57,9.18a14.06,14.06 0,0 0,-1.4 3.83,1 1,0 0,0 0.29,0.94c1.51,1.49 4.46,2.26 6.39,3a2.41,2.41 0,0 0,3.13 -1.35l0.05,-0.14a19.08,19.08 0,0 1,4.66 -7.56,1.83 1.83,0 0,1 2.08,-0.34\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#FF000000\"/>\n    <path\n        android:pathData=\"M244,262.69a0.45,0.45 0,0 1,0.2 0.6,0.4 0.4,0 0,1 -0.16,0.18c-8.73,5.77 -16.12,12.75 -23.42,20.14a77.72,77.72 0,0 0,-9.36 11.74c-5.51,8.31 -10.38,26.49 5.94,27.34 11,0.56 23.33,-4.52 33.21,-9.79 5.46,-2.91 10.75,-6.8 16,-10.29C279.23,294 293.2,281.16 301.47,267a24.68,24.68 0,0 0,2.5 -16.87c-0.84,-4.13 -6.55,-6.3 -10.17,-6.6a40.62,40.62 0,0 0,-15.52 2.07,98.9 98.9,0 0,0 -22.7,10.19 0.79,0.79 0,0 1,-0.84 0,136.77 136.77,0 0,0 -18.21,-8.14c-8.95,-3.25 -29.8,-8.43 -30.71,7.3 -0.2,3.48 1,6.81 2.12,10.08a8,8 0,0 1,0.25 4.07c-0.89,4.78 0.63,9.48 5.19,11.62q6.41,3 11.39,-1.84c6.58,-6.38 3.13,-16.56 -5.94,-17.65A3.7,3.7 0,0 1,215.6 257a2.54,2.54 0,0 1,2.53 -2.4c8,-0.67 18.37,4.28 25.87,8.09\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#FF000000\"/>\n    <path\n        android:pathData=\"M295.13,307.57c0,0.63 0,1.25 0,1.88a2.06,2.06 0,0 1,-2.13 2.28c-8.24,0.66 -17.51,-3.69 -24.95,-7.42a2.45,2.45 0,0 0,-2.4 0.11l-6.94,4.3a1.72,1.72 0,0 0,-0.56 2.37,1.7 1.7,0 0,0 0.64,0.61 89.73,89.73 0,0 0,28.29 10.15c4.74,0.81 13.83,0.63 16.4,-4.11 3,-5.59 1.52,-10.36 0,-17 -0.66,-2.82 0.13,-5.78 -0.93,-8.44 -1.8,-4.51 -6.79,-8.14 -11.84,-6.81 -5.57,1.47 -10.46,7.66 -7.77,13.69q3.13,7 10.69,7a1.32,1.32 0,0 1,1.51 1.43\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#FF000000\"/>\n    <path\n        android:pathData=\"M258.74,323c-4.24,6 -8,-2.7 -9.08,-5.69a1.92,1.92 0,0 0,-2.24 -1.23,21.29 21.29,0 0,0 -6.63,2.68 1.86,1.86 0,0 0,-0.8 2.55c3.17,7.55 12,19.16 21.61,12.47q5.94,-4.15 9.42,-12.37a2.53,2.53 0,0 0,-1.44 -3.69l-4.16,-1.88a2.34,2.34 0,0 0,-3.54 1.39,30.07 30.07,0 0,1 -3.14,5.77\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#FF000000\"/>\n    <path\n        android:pathData=\"M252.39,295.58a1.36,1.36 0,0 1,-0.44 1.57,65 65,0 0,1 -6.55,4.29 2.69,2.69 0,0 1,-3.07 -0.08,124 124,0 0,1 -12.82,-10.07 0.93,0.93 0,0 0,-1.29 0c-4,4 -12.35,13 -12.18,18.91a2.06,2.06 0,0 0,1.92 2,26.58 26.58,0 0,0 11.29,-1.58 121.66,121.66 0,0 0,24.36 -12.43,212.63 212.63,0 0,0 25.07,-19.73c6,-5.5 14,-13.41 16,-21.4 1.65,-6.65 -13.58,-1.58 -16.06,-0.51q-6,2.61 -12.34,5.71a0.54,0.54 0,0 0,-0.25 0.71,0.64 0.64,0 0,0 0.19,0.21 137.63,137.63 0,0 1,12.46 9.29,2 2,0 0,1 0.3,3.15 21.39,21.39 0,0 1,-6 5.14,2.06 2.06,0 0,1 -2.34,-0.21 134.52,134.52 0,0 0,-14.92 -10.77,0.78 0.78,0 0,0 -0.9,0 157.9,157.9 0,0 0,-17.7 13.43,0.33 0.33,0 0,0 0,0.45l0.06,0.05q7.53,5.38 14.56,10.95a2.16,2.16 0,0 1,0.68 0.9\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#FF000000\"/>\n    <path\n        android:pathData=\"M261.87,275a8.83,8.83 0,0 0,-12.49 0l-2,2a8.82,8.82 0,0 0,0 12.48h0l2,2a8.83,8.83 0,0 0,12.49 0l2,-2a8.82,8.82 0,0 0,0 -12.48h0l-2,-2\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#FF000000\"/>\n    <path\n        android:pathData=\"M304.6,123.77q25.48,14.78 37.52,40c5,10.5 7.2,21.79 8,33.18a1.4,1.4 0,0 1,-1.31 1.48h-0.08l-186.39,0.1a0.67,0.67 0,0 1,-0.67 -0.67c-0.09,-31.09 17.26,-57.72 43.91,-73.42a1.21,1.21 0,0 0,0.43 -1.64q-7.93,-13.45 -15.7,-27.43a12.35,12.35 0,0 1,-1.87 -7.43c0.68,-5.57 5.72,-5.35 9.95,-4.78a1.81,1.81 0,0 1,1 0.49c3.61,3.44 6.11,8.66 8.21,13.08 3.58,7.57 6.93,13.53 10.77,20a1.15,1.15 0,0 0,1.41 0.5,94.62 94.62,0 0,1 26,-6c15.29,-1.15 31.36,0.5 45.76,6a0.86,0.86 0,0 0,1.07 -0.39c3.87,-7.23 8.21,-14.11 11.54,-21.59a60.66,60.66 0,0 1,5.37 -9.64c2.28,-3.32 4.42,-2.57 7.93,-2.69a3.79,3.79 0,0 1,3.18 1.44,7.08 7.08,0 0,1 1.31,7.8q-3,6.9 -17.56,30.9A0.45,0.45 0,0 0,304.6 123.77ZM202.24,142.7a73.29,73.29 0,0 0,-26 41.57,0.6 0.6,0 0,0 0.46,0.71h0.12q78.3,0 156.93,0a3,3 0,0 0,1 -0.16,0.52 0.52,0 0,0 0.39,-0.7c-2.83,-12 -8.39,-23.5 -16.83,-32.58a93.87,93.87 0,0 0,-21.1 -17.25,63.89 63.89,0 0,0 -13.06,-5.64 98.42,98.42 0,0 0,-13.78 -3.44,89.93 89.93,0 0,0 -45.77,4.37 61,61 0,0 0,-13.44 6.6Q206.56,139.24 202.24,142.7Z\"\n        android:fillColor=\"#F44336\"/>\n    <path\n        android:pathData=\"M156,309.12a21.63,21.63 0,0 1,-19.68 21.15q-13,1.07 -20.5,-9.7a10.64,10.64 0,0 1,-1.8 -6q0,-49.69 0,-98.85a9.93,9.93 0,0 1,1.81 -5.86,22.21 22.21,0 0,1 35.45,-1.63c3.62,4.36 4.75,9.37 4.75,14.87Q156,265.18 156,309.12ZM142.95,222.12a8.63,8.63 0,0 0,-8.63 -8.63L134,213.49a8.63,8.63 0,0 0,-8.63 8.63h0L125.37,308.7a8.63,8.63 0,0 0,8.63 8.63h0.3A8.63,8.63 0,0 0,143 308.7Z\"\n        android:fillColor=\"#F44336\"/>\n    <path\n        android:pathData=\"M399,308.83a21.3,21.3 0,0 1,-21.27 21.34h-1.48A21.3,21.3 0,0 1,355 308.91h0l-0.16,-87.22a21.3,21.3 0,0 1,21.27 -21.34h1.48a21.3,21.3 0,0 1,21.33 21.26h0ZM385.48,221.45a7.87,7.87 0,0 0,-7.84 -7.9h-1a7.87,7.87 0,0 0,-7.9 7.84l-0.3,88a7.87,7.87 0,0 0,7.84 7.9h1a7.87,7.87 0,0 0,7.9 -7.84Z\"\n        android:fillColor=\"#F44336\"/>\n    <path\n        android:pathData=\"M242.54,363.52q-0.09,22.29 0.09,44.46c0.07,8.21 -4.76,15 -11.91,19.12 -4.3,2.49 -9.53,2.16 -14.42,1.79 -6.54,-0.5 -13,-7.88 -15.17,-13.15a22.1,22.1 0,0 1,-1.47 -8.58q0.12,-22.4 0.08,-43.79a0.42,0.42 0,0 0,-0.42 -0.41c-11.48,0.22 -22.67,1.25 -31.25,-7.75 -6.34,-6.66 -6.64,-12.59 -6.6,-22.33q0.27,-66.81 -0.08,-128a0.83,0.83 0,0 1,0.82 -0.83h187a0.8,0.8 0,0 1,0.8 0.81q0,64.92 0,129.87c0,16.14 -8.17,27.31 -25.11,28.21a115.4,115.4 0,0 1,-11.91 0c-0.77,0 -1.14,0.33 -1.13,1.09q0.19,21.76 0.07,41.42 -0.09,13 -10.08,20.67a15,15 0,0 1,-9.09 3c-3.37,0 -7.6,0.32 -10.62,-1.18q-9.9,-4.9 -12.66,-13.73 -1.54,-4.92 -1.41,-16 0.19,-17.55 0.06,-34.75a0.45,0.45 0,0 0,-0.52 -0.51l-24.62,0.05A0.48,0.48 0,0 0,242.54 363.52ZM204.46,350.63c5.26,1.85 7.52,8.11 7.51,13.3q0,18.68 0,40.92a18.92,18.92 0,0 0,0.66 5A8.41,8.41 0,0 0,225 415q5,-2.91 5,-10.15 -0.07,-20.33 0,-42.76 0,-7.64 6.29,-11.18a17.67,17.67 0,0 1,7.48 -1.69q12.3,0 23.73,0a5.94,5.94 0,0 1,1.77 0.27c1.44,0.45 3,0.49 4.37,1.13q7.94,3.69 7.85,12.49 -0.25,23.72 0.25,43.09a10.52,10.52 0,0 0,3.45 7.89,8.43 8.43,0 0,0 13.05,-2.88 13.45,13.45 0,0 0,1 -5.33q-0.1,-20.39 0,-39.88c0,-6.48 2.17,-13.84 9,-15.7a24,24 0,0 1,6 -1.08q5.76,0 11.39,0.07a10.06,10.06 0,0 0,8.7 -3.85c2.38,-3.09 2.06,-7.7 2,-11.44q-0.24,-50.9 0,-100.2 0,-8.13 0.52,-16.54a0.48,0.48 0,0 0,-0.45 -0.51h-161a0.48,0.48 0,0 0,-0.49 0.48h0q0,59.14 -0.07,118.94c0,5.08 0.71,9.07 5.52,11.79a12.21,12.21 0,0 0,6.63 1.36C192.72,349.13 198.89,348.68 204.46,350.63Z\"\n        android:fillColor=\"#F44336\"/>\n    <path\n        android:pathData=\"M256.14,241.06a1.83,1.83 0,0 0,-2.08 0.34A19.08,19.08 0,0 0,249.4 249a2.42,2.42 0,0 1,-3 1.54l-0.14,-0.05c-1.93,-0.76 -4.88,-1.53 -6.39,-3a1,1 0,0 1,-0.29 -0.94,14.06 14.06,0 0,1 1.4,-3.83c4.31,-8 12.47,-16.89 21.57,-9.18A28.37,28.37 0,0 1,270.78 245a1.7,1.7 0,0 1,-1 2.45l-5.18,2.23a1.87,1.87 0,0 1,-2.66 -0.93C260.65,246.47 258.61,242.27 256.14,241.06Z\"\n        android:fillColor=\"#F44336\"/>\n    <path\n        android:pathData=\"M244,262.69c-7.5,-3.81 -17.85,-8.76 -25.87,-8.09a2.54,2.54 0,0 0,-2.53 2.4,3.7 3.7,0 0,0 3.23,4.21c9.07,1.09 12.52,11.27 5.94,17.65q-5,4.83 -11.39,1.84c-4.56,-2.14 -6.08,-6.84 -5.19,-11.62a8,8 0,0 0,-0.25 -4.07c-1.1,-3.27 -2.32,-6.6 -2.12,-10.08 0.91,-15.73 21.76,-10.55 30.71,-7.3a136.77,136.77 0,0 1,18.21 8.14,0.79 0.79,0 0,0 0.84,0 98.9,98.9 0,0 1,22.7 -10.19,40.62 40.62,0 0,1 15.52,-2.07c3.62,0.3 9.33,2.47 10.17,6.6a24.68,24.68 0,0 1,-2.5 16.87c-8.27,14.21 -22.24,27.08 -35.09,35.66 -5.22,3.49 -10.51,7.38 -16,10.29 -9.88,5.27 -22.24,10.35 -33.21,9.79 -16.32,-0.85 -11.45,-19 -5.94,-27.34a77.72,77.72 0,0 1,9.36 -11.74c7.3,-7.39 14.69,-14.37 23.42,-20.14a0.45,0.45 0,0 0,0.14 -0.62A0.55,0.55 0,0 0,244 262.69ZM252.39,295.58a1.36,1.36 0,0 1,-0.44 1.57,65 65,0 0,1 -6.55,4.29 2.69,2.69 0,0 1,-3.07 -0.08,124 124,0 0,1 -12.82,-10.07 0.93,0.93 0,0 0,-1.29 0c-4,4 -12.35,13 -12.18,18.91a2.06,2.06 0,0 0,1.92 2,26.58 26.58,0 0,0 11.29,-1.58 121.66,121.66 0,0 0,24.36 -12.43,212.63 212.63,0 0,0 25.07,-19.73c6,-5.5 14,-13.41 16,-21.4 1.65,-6.65 -13.58,-1.58 -16.06,-0.51q-6,2.61 -12.34,5.71a0.54,0.54 0,0 0,-0.25 0.71,0.64 0.64,0 0,0 0.19,0.21 137.63,137.63 0,0 1,12.46 9.29,2 2,0 0,1 0.3,3.15 21.39,21.39 0,0 1,-6 5.14,2.06 2.06,0 0,1 -2.34,-0.21 134.52,134.52 0,0 0,-14.92 -10.77,0.78 0.78,0 0,0 -0.9,0 157.9,157.9 0,0 0,-17.7 13.43,0.33 0.33,0 0,0 0,0.45l0.06,0.05q7.53,5.38 14.56,10.95A2.16,2.16 0,0 1,252.39 295.58Z\"\n        android:fillColor=\"#F44336\"/>\n    <path\n        android:pathData=\"M247.42,276.94L249.39,274.98A8.83,8.83 90.2,0 1,261.88 275.02L263.88,277.04A8.83,8.83 90.2,0 1,263.83 289.53L261.86,291.49A8.83,8.83 90.2,0 1,249.37 291.44L247.37,289.43A8.83,8.83 90.2,0 1,247.42 276.94z\"\n        android:fillColor=\"#F44336\"/>\n    <path\n        android:pathData=\"M293.62,306.14q-7.56,0 -10.69,-7c-2.69,-6 2.2,-12.22 7.77,-13.69 5.05,-1.33 10,2.3 11.84,6.81 1.06,2.66 0.27,5.62 0.93,8.44 1.56,6.67 3.08,11.44 0,17 -2.57,4.74 -11.66,4.92 -16.4,4.11a89.73,89.73 0,0 1,-28.29 -10.15,1.73 1.73,0 0,1 -0.69,-2.34 1.78,1.78 0,0 1,0.61 -0.64l6.94,-4.3a2.45,2.45 0,0 1,2.4 -0.11c7.44,3.73 16.71,8.08 24.95,7.42a2.06,2.06 0,0 0,2.13 -2.28c0,-0.63 0,-1.25 0,-1.88A1.32,1.32 0,0 0,293.62 306.14Z\"\n        android:fillColor=\"#F44336\"/>\n    <path\n        android:pathData=\"M249.66,317.34c1.05,3 4.84,11.72 9.08,5.69a30.07,30.07 0,0 0,3.14 -5.77,2.34 2.34,0 0,1 3.54,-1.39l4.16,1.88a2.53,2.53 0,0 1,1.44 3.69q-3.48,8.22 -9.42,12.37c-9.59,6.69 -18.44,-4.92 -21.61,-12.47a1.86,1.86 0,0 1,0.8 -2.55,21.29 21.29,0 0,1 6.63,-2.68A1.92,1.92 0,0 1,249.66 317.34Z\"\n        android:fillColor=\"#F44336\"/>\n  </group>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/info_circle_filled.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:pathData=\"M12,2c5.523,0 10,4.477 10,10a10,10 0,0 1,-19.995 0.324l-0.005,-0.324l0.004,-0.28c0.148,-5.393 4.566,-9.72 9.996,-9.72zM12,11h-1l-0.117,0.007a1,1 0,0 0,0 1.986l0.117,0.007v3l0.007,0.117a1,1 0,0 0,0.876 0.876l0.117,0.007h1l0.117,-0.007a1,1 0,0 0,0.876 -0.876l0.007,-0.117l-0.007,-0.117a1,1 0,0 0,-0.764 -0.857l-0.112,-0.02l-0.117,-0.006v-3l-0.007,-0.117a1,1 0,0 0,-0.876 -0.876l-0.117,-0.007zM12.01,8l-0.127,0.007a1,1 0,0 0,0 1.986l0.117,0.007l0.127,-0.007a1,1 0,0 0,0 -1.986l-0.117,-0.007z\"\n        android:strokeLineJoin=\"round\"\n        android:strokeWidth=\"0\"\n        android:fillColor=\"#ffff\"\n        android:strokeLineCap=\"round\"/>\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/package_import.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:pathData=\"M12,21l-8,-4.5v-9l8,-4.5l8,4.5v4.5\"\n        android:strokeLineJoin=\"round\"\n        android:strokeWidth=\"2\"\n        android:strokeColor=\"#ffff\"\n        android:strokeLineCap=\"round\"/>\n    <path\n        android:pathData=\"M12,12l8,-4.5\"\n        android:strokeLineJoin=\"round\"\n        android:strokeWidth=\"2\"\n        android:strokeColor=\"#ffff\"\n        android:strokeLineCap=\"round\"/>\n    <path\n        android:pathData=\"M12,12v9\"\n        android:strokeLineJoin=\"round\"\n        android:strokeWidth=\"2\"\n        android:strokeColor=\"#ffff\"\n        android:strokeLineCap=\"round\"/>\n    <path\n        android:pathData=\"M12,12l-8,-4.5\"\n        android:strokeLineJoin=\"round\"\n        android:strokeWidth=\"2\"\n        android:strokeColor=\"#ffff\"\n        android:strokeLineCap=\"round\"/>\n    <path\n        android:pathData=\"M22,18h-7\"\n        android:strokeLineJoin=\"round\"\n        android:strokeWidth=\"2\"\n        android:strokeColor=\"#ffff\"\n        android:strokeLineCap=\"round\"/>\n    <path\n        android:pathData=\"M18,15l-3,3l3,3\"\n        android:strokeLineJoin=\"round\"\n        android:strokeWidth=\"2\"\n        android:strokeColor=\"#ffff\"\n        android:strokeLineCap=\"round\"/>\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/play_circle.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  <path\n      android:pathData=\"m380,660 l280,-180 -280,-180v360ZM480,880q-83,0 -156,-31.5T197,763q-54,-54 -85.5,-127T80,480q0,-83 31.5,-156T197,197q54,-54 127,-85.5T480,80q83,0 156,31.5T763,197q54,54 85.5,127T880,480q0,83 -31.5,156T763,763q-54,54 -127,85.5T480,880ZM480,800q134,0 227,-93t93,-227q0,-134 -93,-227t-227,-93q-134,0 -227,93t-93,227q0,134 93,227t227,93ZM480,480Z\"\n      android:fillColor=\"@android:color/transparent\"\n      android:strokeColor=\"#ffffffff\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/settings.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:pathData=\"M10.325,4.317c0.426,-1.756 2.924,-1.756 3.35,0a1.724,1.724 0,0 0,2.573 1.066c1.543,-0.94 3.31,0.826 2.37,2.37a1.724,1.724 0,0 0,1.065 2.572c1.756,0.426 1.756,2.924 0,3.35a1.724,1.724 0,0 0,-1.066 2.573c0.94,1.543 -0.826,3.31 -2.37,2.37a1.724,1.724 0,0 0,-2.572 1.065c-0.426,1.756 -2.924,1.756 -3.35,0a1.724,1.724 0,0 0,-2.573 -1.066c-1.543,0.94 -3.31,-0.826 -2.37,-2.37a1.724,1.724 0,0 0,-1.065 -2.572c-1.756,-0.426 -1.756,-2.924 0,-3.35a1.724,1.724 0,0 0,1.066 -2.573c-0.94,-1.543 0.826,-3.31 2.37,-2.37c1,0.608 2.296,0.07 2.572,-1.065z\"\n        android:strokeLineJoin=\"round\"\n        android:strokeWidth=\"2\"\n        android:strokeColor=\"#ffff\"\n        android:strokeLineCap=\"round\"/>\n    <path\n        android:pathData=\"M9,12a3,3 0,1 0,6 0a3,3 0,0 0,-6 0\"\n        android:strokeLineJoin=\"round\"\n        android:strokeWidth=\"2\"\n        android:strokeColor=\"#ffff\"\n        android:strokeLineCap=\"round\"/>\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/telegram.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"1024\"\n    android:viewportHeight=\"1024\">\n    <path\n        android:fillColor=\"#ffff\"\n        android:pathData=\"M769.45,245.76c21.08,-7.47 42.28,-15.36 65.19,-15.36 27.05,0.04 39.94,11.73 40.32,38.83 0.68,44.03 -8.66,87.17 -13.99,130.69 -2.99,24.32 -45.27,285.95 -64.77,392.19 -4.05,22.06 -7.04,44.37 -16.38,65.15 -15.15,33.66 -36.1,43.14 -71.21,31.7 -15.96,-5.21 -29.99,-13.99 -43.78,-23.34 -66.99,-45.27 -211.67,-142.98 -216.7,-146.99 -31.15,-24.87 -36.95,-45.4 -5.42,-78.59 34.86,-36.69 132.44,-124.37 143.15,-134.83a956.84,956.84 0,0 0,84.91 -83.03c5.85,-6.4 12.76,-13.4 5.21,-22.27 -7.34,-8.62 -15.79,-3.84 -23.17,0.73a2884.91,2884.91 0,0 0,-135.42 89.3c-53.72,35.71 -108.46,70.02 -160.77,107.65 -42.67,30.68 -86.74,41.26 -137.47,26.07 -37.89,-11.31 -76.12,-21.5 -113.15,-35.41 -13.87,-5.16 -30.29,-10.45 -30.98,-28.5 -0.6,-16.85 14.12,-24.45 26.79,-31.15 44.16,-23.34 310.61,-136.83 383.62,-166.91 94.55,-38.95 187.61,-81.71 284.03,-115.93z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/trash.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:pathData=\"M4,7l16,0\"\n        android:strokeLineJoin=\"round\"\n        android:strokeWidth=\"2\"\n        android:strokeColor=\"#ffff\"\n        android:strokeLineCap=\"round\"/>\n    <path\n        android:pathData=\"M10,11l0,6\"\n        android:strokeLineJoin=\"round\"\n        android:strokeWidth=\"2\"\n        android:strokeColor=\"#ffff\"\n        android:strokeLineCap=\"round\"/>\n    <path\n        android:pathData=\"M14,11l0,6\"\n        android:strokeLineJoin=\"round\"\n        android:strokeWidth=\"2\"\n        android:strokeColor=\"#ffff\"\n        android:strokeLineCap=\"round\"/>\n    <path\n        android:pathData=\"M5,7l1,12a2,2 0,0 0,2 2h8a2,2 0,0 0,2 -2l1,-12\"\n        android:strokeLineJoin=\"round\"\n        android:strokeWidth=\"2\"\n        android:strokeColor=\"#ffff\"\n        android:strokeLineCap=\"round\"/>\n    <path\n        android:pathData=\"M9,7v-3a1,1 0,0 1,1 -1h4a1,1 0,0 1,1 1v3\"\n        android:strokeLineJoin=\"round\"\n        android:strokeWidth=\"2\"\n        android:strokeColor=\"#ffff\"\n        android:strokeLineCap=\"round\"/>\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/webui.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:pathData=\"M3,12a9,9 0,1 0,18 0a9,9 0,0 0,-18 0\"\n        android:strokeLineJoin=\"round\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#ffff\"\n        android:strokeLineCap=\"round\"/>\n    <path\n        android:pathData=\"M3.6,9h16.8\"\n        android:strokeLineJoin=\"round\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#ffff\"\n        android:strokeLineCap=\"round\"/>\n    <path\n        android:pathData=\"M3.6,15h16.8\"\n        android:strokeLineJoin=\"round\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#ffff\"\n        android:strokeLineCap=\"round\"/>\n    <path\n        android:pathData=\"M11.5,3a17,17 0,0 0,0 18\"\n        android:strokeLineJoin=\"round\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#ffff\"\n        android:strokeLineCap=\"round\"/>\n    <path\n        android:pathData=\"M12.5,3a17,17 0,0 1,0 18\"\n        android:strokeLineJoin=\"round\"\n        android:strokeWidth=\"2\"\n        android:fillColor=\"#00000000\"\n        android:strokeColor=\"#ffff\"\n        android:strokeLineCap=\"round\"/>\n</vector>\n"
  },
  {
    "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_white\"/>\n    <foreground android:drawable=\"@mipmap/ic_launcher_foreground\"/>\n    <monochrome android:drawable=\"@drawable/ic_launcher_monochrome\" />\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher_alt.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=\"@drawable/ic_launcher_foreground_alt\"/>\n    <monochrome android:drawable=\"@drawable/ic_launcher_monochrome_alt\" />\n</adaptive-icon>\n"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher_alt_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=\"@drawable/ic_launcher_foreground_alt\"/>\n    <monochrome android:drawable=\"@drawable/ic_launcher_monochrome_alt\" />\n</adaptive-icon>\n"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@color/ic_launcher_background_white\"/>\n    <foreground android:drawable=\"@mipmap/ic_launcher_foreground\"/>\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/resources.properties",
    "content": "unqualifiedResLocale=en"
  },
  {
    "path": "app/src/main/res/values/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string-array name=\"languages\">\n        <item>@string/system_default</item>\n        <item>English</item>\n        <item>Polski</item>\n        <item>Bahasa Indonesia</item>\n        <item>Русский</item>\n        <item>Tiếng Việt</item>\n        <item>Português (Brasil)</item>\n        <item>Español</item>\n        <item>العربية</item>\n        <item>Türkçe</item>\n        <item>日本語</item>\n        <item>한국어</item>\n        <item>简体中文</item>\n        <item>繁體中文</item>\n        <item>猫娘</item>\n        <item>天道仙文</item>\n        <item>魔法少女</item>\n        <item>天使语</item>\n        <item>饭庄通语</item>\n        <item>方块世界</item>\n    </string-array>\n\n    <string-array name=\"languages_values\">\n        <item>default</item>\n        <item>en</item>\n        <item>pl</item>\n        <item>in</item>\n        <item>ru</item>\n        <item>vi</item>\n        <item>pt-BR</item>\n        <item>es</item>\n        <item>ar</item>\n        <item>tr-TR</item>\n        <item>ja</item>\n        <item>ko</item>\n        <item>zh-CN</item>\n        <item>zh-TW</item>\n        <item>zh-AG</item>\n        <item>zh-WC</item>\n        <item>mgl</item>\n        <item>zh-AT</item>\n        <item>zh-CK</item>\n        <item>zh-MC</item>\n    </string-array>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"ic_launcher_superroot_background\">#019486</color>\n    <color name=\"about_icon_background\">#ffffff</color>\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\">#2B0A0A</color>\n    <color name=\"ic_launcher_background_white\">#FFFFFF</color>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\" translatable=\"false\">FolkPatch</string>\n    <string name=\"app_name_fpatch\">FPatch</string>\n\n    <string name=\"desktop_app_name\">Desktop App Name</string>\n\n    <string name=\"home\">Home</string>\n\n    <string name=\"success\">Success</string>\n    <string name=\"failure\">Failure</string>\n\n    <string name=\"patch_warnning\">Installation comes with risks. Please ensure your data is backed up.</string>\n    <string name=\"patch\">Patch</string>\n\n    <string name=\"kernel_patch\">KernelPatch</string>\n    <string name=\"android_patch\">AndroidPatch</string>\n\n    <string name=\"settings_nav_layout_title\">Navigation Layout Settings</string>\n    <string name=\"settings_nav_layout_summary\">Hide or show some navigation components</string>\n    <string name=\"settings_nav_scheme\">Navigation Scheme</string>\n    <string name=\"settings_nav_mode\">Navigation Bar Mode</string>\n    <string name=\"settings_nav_mode_summary\">Choose how the navigation bar is displayed</string>\n    <string name=\"settings_nav_mode_auto\">Auto Traditional</string>\n    <string name=\"settings_nav_mode_bottom\">Always Bottom Bar</string>\n    <string name=\"settings_nav_mode_rail\">Always Side Rail</string>\n    <string name=\"settings_nav_mode_floating\">Floating Bottom Bar</string>\n    <string name=\"settings_show_apm\">Show System Modules</string>\n    <string name=\"settings_show_kpm\">Show KPM</string>\n    <string name=\"settings_show_superuser\">Show SuperUser</string>\n    <string name=\"settings_floating_auto_hide\">Auto-hide Floating Bar</string>\n    <string name=\"settings_floating_auto_hide_summary\">Automatically hide the floating bar after 3 seconds of inactivity</string>\n    <string name=\"settings_floating_swipe_hide\">Swipe to Hide Floating Bar</string>\n    <string name=\"settings_floating_swipe_hide_summary\">Hide the floating bar when scrolling down, show when scrolling up</string>\n    <string name=\"settings_navbar_glass_effect\">Glass Effect</string>\n    <string name=\"settings_navbar_glass_effect_summary\">Apply frosted glass effect to the floating navigation bar</string>\n    <string name=\"settings_navbar_glass_blur_strength\">Blur Strength</string>\n    <string name=\"settings_navbar_glass_transparency\">Background Transparency</string>\n    <string name=\"settings_navbar_glass_highlight_strength\">Highlight Intensity</string>\n    <string name=\"settings_navbar_glass_specular\">Specular Reflection</string>\n    <string name=\"settings_navbar_glass_specular_summary\">Enable mirror-like highlight at the top of the navigation bar</string>\n    <string name=\"settings_navbar_glass_inner_glow\">Inner Glow</string>\n    <string name=\"settings_navbar_glass_inner_glow_summary\">Enable subtle glow effect at the bottom of the navigation bar</string>\n    <string name=\"settings_navbar_glass_border\">Glass Border</string>\n    <string name=\"settings_navbar_glass_border_summary\">Enable subtle border outline around the navigation bar</string>\n    <string name=\"settings_stats_top_layout\">Top Layout</string>\n    <string name=\"settings_stats_top_layout_summary\">Choose the top info card style</string>\n    <string name=\"settings_stats_top_layout_list\">ListUI</string>\n    <string name=\"settings_stats_top_layout_grid\">GridUI</string>\n    <string name=\"settings_block_kernelpatch_update\">Block KernelPatch Update</string>\n    <string name=\"settings_block_kernelpatch_update_summary\">Do not show KernelPatch update notifications</string>\n    <string name=\"settings_block_androidpatch_update\">Block System Patch Update</string>\n    <string name=\"settings_block_androidpatch_update_summary\">Do not show system patch (APD) update notifications</string>\n\n    <string name=\"reboot\">Reboot</string>\n    <string name=\"settings\">Settings</string>\n    <string name=\"reboot_recovery\">Reboot to Recovery</string>\n    <string name=\"reboot_bootloader\">Reboot to Bootloader</string>\n    <string name=\"reboot_download\">Reboot to Download</string>\n    <string name=\"reboot_edl\">Reboot to EDL</string>\n    <string name=\"reboot_fastbootd\">Reboot to FastbootD</string>\n    <string name=\"about\">About</string>\n    <string name=\"developer_and_maintainer\">Developer | Maintainer</string>\n    <string name=\"settings_app_language\">Language</string>\n    <string name=\"system_default\">System default</string>\n    <string name=\"settings_global_namespace_mode\">Global namespace mode</string>\n    <string name=\"settings_global_namespace_mode_summary\">All root sessions use the global mount namespace</string>\n    <string name=\"settings_clear_super_key_dialog\">Do you really want to proceed?</string>\n    <string name=\"settings_grid_working_card_hide_check\">Hide status icon</string>\n    <string name=\"settings_grid_working_card_hide_check_summary\">Hide the checkmark or warning icon on the working card</string>\n    <string name=\"settings_grid_working_card_hide_text\">Hide status text</string>\n    <string name=\"settings_grid_working_card_hide_text_summary\">Hide the \\'Working\\' or \\'Not installed\\' text on the working card</string>\n    <string name=\"settings_grid_working_card_hide_mode\">Hide working mode</string>\n    <string name=\"settings_grid_working_card_hide_mode_summary\">Hide the \\'Full\\' or \\'Half\\' text on the working card</string>\n\n\n    <string name=\"settings_folkx_engine_title\">FolkX Animation Engine</string>\n    <string name=\"settings_folkx_engine_summary\">Use smooth physics-based spring animation when switching between top-level pages</string>\n    <string name=\"settings_folkx_animation_type\">Animation Type</string>\n    <string name=\"settings_folkx_animation_speed\">Animation Speed</string>\n    <string name=\"settings_folkx_animation_linear\">Linear Motion</string>\n    <string name=\"settings_folkx_animation_spatial\">Spatial Motion</string>\n    <string name=\"settings_folkx_animation_fade\">Fade In/Out</string>\n    <string name=\"settings_folkx_animation_vertical\">Vertical Slide</string>\n    <string name=\"settings_folkx_animation_diagonal\">Diagonal Slide</string>\n\n    <string name=\"su_exclude_all_title\">Exclude All</string>\n    <string name=\"su_exclude_all_confirm\">Are you sure you want to exclude all non-root apps?</string>\n    <string name=\"su_batch_exclude_title\">Batch Exclude</string>\n    <string name=\"su_batch_exclude_content\">Exclude injection for all non-ROOT apps, please select an action</string>\n    <string name=\"su_exclude_btn\">Exclude</string>\n    <string name=\"su_exclude_reverse_btn\">Reverse</string>\n\n    <string name=\"home_learn_apatch\">Learn FolkPatch</string>\n    <string name=\"home_click_to_learn_apatch\">Learn about the features of FolkPatch and how to use</string>\n    <string name=\"settings_hide_apatch_card\">Hide Learn FolkPatch card</string>\n    <string name=\"settings_hide_apatch_card_summary\">Hide the Learn FolkPatch card on the home screen</string>\n\n    <string name=\"about_source_code\"><![CDATA[<p>View source code at %1$s<p/>Join our %2$s channel<p/>Join our %3$s group]]></string>\n    <string name=\"send_log\">Send logs</string>\n    <string name=\"save_log\">Save logs</string>\n    <string name=\"log_saved\">Logs saved</string>\n    <string name=\"safe_mode\">Safe mode</string>\n    <string name=\"support_or_donate\">Support / Donate</string>\n\n    <string name=\"super_key\">SuperKey</string>\n    <string name=\"clear_super_key\">Clear SuperKey</string>\n    <string name=\"patch_set_superkey\">Set SuperKey</string>\n    <string name=\"home_patch_set_key_desc\">Only credential for KernelPatch</string>\n    <string name=\"home_patch_next_step\">Next step</string>\n\n    <string name=\"home_not_installed\">Not installed</string>\n    <string name=\"home_install_unknown\">KernelPatch not installed</string>\n    <string name=\"home_install_unknown_summary\">Root is unavailable. The kernel may not be patched or flashed yet.</string>\n    <string name=\"home_click_to_install\">Click to install</string>\n    <string name=\"home_working\">Working</string>\n    <string name=\"home_version\">Version: %s</string>\n    <string name=\"home_kp_need_update\">New version available</string>\n    <string name=\"home_kp_cando_update\">Update</string>\n\n    <string name=\"home_installing\">Installing</string>\n    <string name=\"kpatch_version\">Version: %s</string>\n    <string name=\"apatch_version\">Version: %s</string>\n\n    <string name=\"kpatch_version_update\" formatted=\"false\">Version: %s -&gt; %s</string>\n    <string name=\"kpatch_shadow_path_title\">kpatch</string>\n    <string name=\"home_auth_key_title\">Enter SuperKey</string>\n    <string name=\"home_auth_key_desc\">Start after certification</string>\n    <string name=\"home_kpatch_info_title\">Info</string>\n\n    <string name=\"home_ap_cando_install\">Install</string>\n\n    <string name=\"home_ap_cando_uninstall\">Uninstall</string>\n    <string name=\"home_ap_cando_reboot\">Reboot</string>\n\n    <string name=\"patch_title\">Patch</string>\n\n    <string name=\"patch_config_title\">Patches</string>\n    <string name=\"patch_mode_bootimg_patch\">Mode: Patch</string>\n    <string name=\"patch_mode_patch_and_install\">Mode: Patch and install</string>\n    <string name=\"patch_mode_install_to_next_slot\">Mode: Install to inactive slot (After OTA)</string>\n    <string name=\"patch_mode_restore\">Mode: Restore</string>\n    <string name=\"patch_mode_uninstall_patch\">Mode: Uninstall KPatch</string>\n    <string name=\"patch_select_bootimg_btn\">Select boot</string>\n    <string name=\"patch_embed_kpm_btn\">Embed KPM</string>\n    <string name=\"patch_start_patch_btn\">Start</string>\n    <string name=\"patch_start_unpatch_btn\">UnPatch</string>\n    <string name=\"patch_item_error\">!!ERROR!!</string>\n    <string name=\"patch_item_bootimg\">bootimg</string>\n    <string name=\"patch_item_bootimg_slot\">Slot:</string>\n    <string name=\"patch_item_bootimg_dev\">Device:</string>\n    <string name=\"patch_item_kernel\">Kernel</string>\n    <string name=\"patch_item_kpimg\">kpimg</string>\n    <string name=\"patch_item_kpimg_version\">Version:</string>\n    <string name=\"patch_item_kpimg_comile_time\">Time:</string>\n    <string name=\"patch_item_kpimg_config\">Config:</string>\n    <string name=\"patch_item_new_extra_kpm\">Embed new</string>\n    <string name=\"patch_item_existed_extra_kpm\">Existed</string>\n    <string name=\"patch_item_extra_name\">Name:</string>\n    <string name=\"patch_item_extra_version\">Version:</string>\n    <string name=\"patch_item_extra_author\">Author:</string>\n    <string name=\"patch_item_extra_kpm_license\">License:</string>\n    <string name=\"patch_item_extra_kpm_desciption\">Description:</string>\n    <string name=\"patch_item_extra_args\">Args:</string>\n    <string name=\"patch_item_extra_event\">Event:</string>\n    <string name=\"patch_item_skey\">SuperKey</string>\n    <string name=\"patch_custom_superkey\">Custom SuperKey</string>\n    <string name=\"patch_custom_superkey_summary\">still can use superkey manager root, now we are verify manager signature by kernel</string>\n    <string name=\"patch_item_set_skey_label\">The SuperKey should be 8-63 characters long and include numbers and letters, but no special characters.</string>\n    <string name=\"patch_confirm_superkey\">Confirm SuperKey</string>\n    <string name=\"patch_skey_mismatch\">SuperKey does not match</string>\n\n    <string name=\"home_kernel\">Kernel version</string>\n    <string name=\"home_manager_version\">Manager version</string>\n    <string name=\"home_fingerprint\">Fingerprint</string>\n\n    <string name=\"home_selinux_status\">SELinux status</string>\n    <string name=\"home_selinux_status_disabled\">Disabled</string>\n    <string name=\"home_selinux_status_enforcing\">Enforcing</string>\n    <string name=\"home_selinux_status_permissive\">Permissive</string>\n    <string name=\"home_selinux_status_unknown\">Unknown</string>\n\n    <string name=\"settings_selinux_mode\">SELinux Mode</string>\n    <string name=\"settings_selinux_mode_summary\">Select SELinux enforcement mode</string>\n    <string name=\"settings_selinux_mode_enforcing\">Enforcing (Strict)</string>\n    <string name=\"settings_selinux_mode_permissive\">Permissive (Lenient)</string>\n    <string name=\"settings_selinux_current_mode\">Current: %s</string>\n    <string name=\"settings_selinux_mode_enforcing_summary\">SELinux fully enforces access rules</string>\n    <string name=\"settings_selinux_mode_permissive_summary\">SELinux only logs violations, does not deny</string>\n\n    <string name=\"home_device_info\">Device</string>\n    <string name=\"home_system_version\">System version</string>\n    <string name=\"home_kpatch_version\">KernelPatch version</string>\n    <string name=\"home_su_path\">Executable su</string>\n    <string name=\"home_apatch_version\">FolkPatch version</string>\n\n    <string name=\"kpm\">KPModule</string>\n    <string name=\"kpm_kp_not_installed\">KernelPatch not installed</string>\n    <string name=\"kpm_add_kpm\">Add KPM</string>\n    <string name=\"kpm_load\">Load</string>\n    <string name=\"kpm_install\">Install</string>\n    <string name=\"kpm_embed\">Embed</string>\n    <string name=\"kpm_load_toast_succ\">Load succeed</string>\n    <string name=\"kpm_load_toast_failed\">Load failed</string>\n    <string name=\"kpm_unload_confirm\">Unload %s module?</string>\n    <string name=\"kpm_unload\">Unload</string>\n    <string name=\"kpm_control\">Control</string>\n    <string name=\"kpm_apm_empty\">No module loaded</string>\n    <string name=\"kpm_version\">Version</string>\n    <string name=\"kpm_license\">License</string>\n    <string name=\"kpm_author\">Author</string>\n    <string name=\"kpm_desc\">Desc</string>\n    <string name=\"kpm_args\">Args</string>\n\n    <string name=\"su_title\">Superuser</string>\n    <string name=\"su_selinux_via_hook\">Bypass via hook</string>\n    <string name=\"su_pkg_excluded_label\">Exclude</string>\n    <string name=\"su_pkg_excluded_setting_title\">Exclude modifications</string>\n    <string name=\"su_pkg_excluded_setting_summary\">Enabling this option will allow FolkPatch to restore any files modified by the modules of this app.</string>\n    <string name=\"su_pkg_root_setting_title\">Superuser</string>\n    <string name=\"su_pkg_root_setting_summary\">Enabling this option grants superuser access to your app, allowing it to use SU commands.</string>\n    <string name=\"su_pkg_normal_setting_title\">Normal Mode</string>\n    <string name=\"su_pkg_normal_setting_summary\">Default state, no special handling.</string>\n    <string name=\"su_show_system_apps\">Show system apps</string>\n    <string name=\"su_hide_system_apps\">Hide system apps</string>\n    <string name=\"su_refresh\">Refresh</string>\n    <string name=\"apm\">System Module</string>\n\n\n    <string name=\"apm_not_installed\">AndroidPatch not installed</string>\n    <string name=\"apm_failed_to_enable\">Failed to enable module: %s</string>\n    <string name=\"apm_failed_to_disable\">Failed to disable module: %s</string>\n    <string name=\"apm_empty\">No module installed</string>\n    <string name=\"apm_remove\">Remove</string>\n    <string name=\"apm_undo\">Undo</string>\n    <string name=\"apm_install\">Install</string>\n    <string name=\"apm_uninstall_confirm\">Uninstall the %s module?</string>\n    <string name=\"apm_uninstall_success\">%s uninstalled</string>\n    <string name=\"apm_uninstall_failed\">Failed to uninstall: %s</string>\n    <string name=\"apm_undo_uninstall_success\">%s restored successfully</string>\n    <string name=\"apm_undo_uninstall_failed\">Failed to restore: %s</string>\n    <string name=\"apm_version\">Version</string>\n    <string name=\"apm_author\">Author</string>\n    <string name=\"apm_desc\">Desc</string>\n    <string name=\"apm_overlay_fs_not_available\">Modules are unavailable as OverlayFS is disabled by kernel!</string>\n    <string name=\"apm_magisk_conflict\">Modules are unavailable due to a conflict with Magisk!</string>\n    <string name=\"apm_mount_warning_title\">Important Notice</string>\n    <string name=\"apm_mount_warning_message\">Modules are not mounted by default. Please use built-in mounting system or metamodule.</string>\n    <string name=\"apm_mount_warning_button\">I Understand</string>\n    <string name=\"apm_reboot_to_apply\">Reboot to take effect</string>\n    <string name=\"apm_changelog\">Changelog</string>\n    <string name=\"apm_update\">Update</string>\n    <string name=\"apm_downloading\">Downloading module: %s</string>\n    <string name=\"apm_start_downloading\">Start downloading: %s</string>\n    <string name=\"apm_new_version_available\">New version %s is available, click to upgrade.</string>\n\n    <string name=\"hide_apatch_manager\">Hide APatch Manager</string>\n    <string name=\"hide_apatch_manager_summary\">Install a proxy app with a random package ID and custom app label</string>\n    <string name=\"hide_apatch_dialog_new_manager_name\">New manager name</string>\n    <string name=\"hide_apatch_dialog_summary\">It will be used as the new app label shown in launcher</string>\n    <string name=\"hide_apatch_manager_failure\">Failed to hide. Please report the bug!</string>\n\n    <string name=\"setting_reset_su_path\">Reset su path</string>\n    <string name=\"setting_reset_su_new_path\">New full path</string>\n\n\n    <string name=\"apm_webui_open\">Open</string>\n    <string name=\"apm_action\">Action</string>\n    <string name=\"module_shortcut_add\">Shortcut</string>\n    <string name=\"module_shortcut_not_supported\">Launcher does not support desktop shortcuts.</string>\n    <string name=\"module_shortcut_created\">Shortcut created on desktop.</string>\n    <string name=\"module_shortcut_updated\">Shortcut updated.</string>\n    <string name=\"module_shortcut_permission_tip_xiaomi\">Please enable \"Create desktop shortcuts\" permission for this app in Xiaomi settings.</string>\n    <string name=\"module_shortcut_permission_tip_oppo\">Please enable \"Desktop shortcut\" permission for this app in OPPO settings.</string>\n    <string name=\"module_shortcut_permission_tip_default\">If shortcut creation fails, please enable desktop shortcut permission for this app in system settings.</string>\n    <string name=\"enable_web_debugging\">Enable WebView debugging</string>\n    <string name=\"enable_web_debugging_summary\">Can be used to debug WebUI. Please enable only when needed.</string>\n    <string name=\"settings_apm_install_confirm\">Confirm before install</string>\n    <string name=\"settings_apm_install_confirm_summary\">Show a confirmation dialog before installing modules</string>\n    <string name=\"settings_show_more_module_info\">Show module details</string>\n    <string name=\"settings_show_more_module_info_summary\">Show module ID and size in the module list</string>\n    <string name=\"settings_apm_stay_on_page\">Stay on operation page</string>\n    <string name=\"settings_apm_stay_on_page_summary\">Do not automatically return after module action execution</string>\n    <string name=\"settings_enable_module_shortcut_add\">Enable shortcut add button</string>\n    <string name=\"settings_enable_module_shortcut_add_summary\">Show WebUI shortcut add button</string>\n    <string name=\"settings_disable_module_update_check\">Disable module update check</string>\n    <string name=\"settings_disable_module_update_check_summary\">Disable automatic update checks for system modules</string>\n    <string name=\"settings_module_sort_optimization\">Module Sorting Optimization</string>\n    <string name=\"settings_module_sort_optimization_summary\">Make system modules with WebUI and Action appear at the top</string>\n    <string name=\"settings_fold_system_module\">Fold System Modules</string>\n    <string name=\"settings_fold_system_module_summary\">Click module card to expand/collapse actions</string>\n    <string name=\"settings_simple_list_bottom_bar\">Simple List Bottom Bar</string>\n    <string name=\"settings_simple_list_bottom_bar_summary\">Use icon-only buttons style for module actions, inspired by APatch</string>\n    <string name=\"settings_spliced_card_group\">Spliced Card Group</string>\n    <string name=\"settings_spliced_card_group_summary\">Use M3E continuous card group style for module list</string>\n    <string name=\"apm_install_confirm_title\">Install Module</string>\n    <string name=\"apm_install_confirm_content\">Are you sure you want to install %s?</string>\n    <string name=\"apm_disable_all_title\">Disable All Modules</string>\n    <string name=\"module_shortcut_name\">Shortcut name</string>\n    <string name=\"module_shortcut_icon\">Shortcut icon</string>\n    <string name=\"module_shortcut_type\">Shortcut type</string>\n    <string name=\"module_shortcut_type_webui\">WebUI</string>\n    <string name=\"module_shortcut_type_action\">Action</string>\n    <string name=\"module_shortcut_icon_default\">Use default icon</string>\n    <string name=\"module_shortcut_icon_select\">Select icon</string>\n    <string name=\"apm_disable_all_confirm\">Are you sure you want to disable all modules? This will deactivate all installed modules.</string>\n    <string name=\"apm_enable_module_banner\">Enable Module Banner</string>\n    <string name=\"apm_enable_module_banner_summary\">Display banner image for modules if available</string>\n    <string name=\"apm_enable_folk_banner\">Customize Module Banner</string>\n    <string name=\"apm_enable_folk_banner_summary\">Long press the module card to select the banner image</string>\n    <string name=\"apm_folk_banner_title\">FolkBanner</string>\n    <string name=\"apm_folk_banner_select\">Select image</string>\n    <string name=\"apm_folk_banner_clear\">Clear FolkBanner</string>\n    <string name=\"apm_folk_banner_saved\">FolkBanner injected for %s.</string>\n    <string name=\"apm_folk_banner_cleared\">FolkBanner cleared for %s.</string>\n    <string name=\"apm_folk_banner_failed\">Failed to update FolkBanner for %s.</string>\n    <string name=\"apm_banner_api_mode\">API Mode</string>\n    <string name=\"apm_banner_api_mode_summary\">Use random image API or local directory for module banners</string>\n    <string name=\"apm_banner_api_source\">Configure API Source</string>\n    <string name=\"apm_banner_api_source_hint\">Enter API URL or local path</string>\n    <string name=\"apm_banner_api_source_saved\">API source saved</string>\n    <string name=\"apm_banner_api_source_not_configured\">Not configured</string>\n    <string name=\"apm_banner_api_url_configured\">API URL configured</string>\n    <string name=\"apm_banner_local_dir_configured\">Local directory configured</string>\n    <string name=\"apm_banner_clear_cache\">Clear Cache</string>\n    <string name=\"apm_banner_cache_cleared\">Banner cache cleared</string>\n    <string name=\"apm_banner_api_config_title\">Configure API Source</string>\n    <string name=\"apm_banner_api_config_desc\">Enter a random image API URL or a local directory path. Each module will get a unique banner image.</string>\n    <string name=\"apm_banner_api_examples_title\">Examples:</string>\n    <string name=\"apm_banner_api_examples\">API: https://picsum.photos/800/400\\nLocal: /sdcard/Pictures/Banners</string>\n    <!-- API Marketplace -->\n    <string name=\"apm_api_marketplace_title\">API Marketplace</string>\n    <string name=\"apm_api_preview\">Preview</string>\n    <string name=\"apm_api_apply\">Apply</string>\n    <string name=\"apm_api_preview_title\">API Preview</string>\n    <string name=\"apm_api_preview_failed\">Failed to load preview</string>\n    <string name=\"apm_api_url_label\">API URL:</string>\n    <string name=\"apm_api_apply_success\">API source applied successfully</string>\n    <string name=\"apm_api_verifying\">Verifying…</string>\n    <string name=\"apm_api_retry\">Retry</string>\n    <string name=\"apm_api_empty\">No API sources available</string>\n    <string name=\"settings_banner_custom_opacity\">Banner Opacity</string>\n    <string name=\"settings_banner_custom_opacity_summary\">Use custom opacity for banners instead of following wallpaper mode</string>\n    <string name=\"settings_banner_opacity\">Banner transparency</string>\n\n    <string name=\"settings_donot_store_superkey\">Don\\'t store SuperKey in local</string>\n    <string name=\"settings_donot_store_superkey_summary\">Authenticate the SuperKey every time the manager starts</string>\n\n\n\n    <string name=\"mode_select_page_title\">Install</string>\n    <string name=\"mode_select_page_patch_and_install\">Patch and install</string>\n    <string name=\"mode_select_page_select_file\">Select a boot image to patch</string>\n    <string name=\"restore_select_file\">Select a partition boot file to restore</string>\n    <string name=\"mode_select_page_install_inactive_slot\">Install to inactive slot (After OTA)</string>\n    <string name=\"mode_select_page_install_inactive_slot_warning\">Your device will be **FORCED** to boot to the current inactive slot after a reboot!\\nOnly use this option after OTA is done.\\nContinue?</string>\n    <string name=\"mode_select_page_select_kpimg\">Use local patch file (KPimg)</string>\n    <string name=\"patch_custom_kpimg_label\">Custom KPimg</string>\n    <string name=\"patch_custom_kpimg_file\">File: %s</string>\n    <string name=\"patch_select_kpimg_btn\">Select KPimg</string>\n\n    <string name=\"home_dialog_auth_fail_title\">Authentication failed</string>\n    <string name=\"home_dialog_auth_fail_content\">Unable to authenticate the SuperKey, causing FolkPatch activation to fail.\nHere are some possible reasons for authentication failure—please check which one applies to you:\n\n\\n1. You didn\\'t use KernelPatch to patch boot.img at all, or forgot what you actually did.\n\\n2. The patched boot.img is still sitting on your computer sleeping, never flashed to the device.\n\\n3. The SuperKey was entered incorrectly, or contains mysterious symbols, like characters from alien languages.\n\\n4. Your device may not be compatible with FolkPatch and KernelPatch,强行折腾 is futile.\n\\n5. You may have done some mysterious operations—like using certain modules that exclude package names that blocked FolkPatch, resulting in putting yourself out of the game.\n\n</string>\n\n    <string name=\"home_more_menu_feedback_or_suggestion\">Feedback or suggestion</string>\n    <string name=\"home_more_menu_about\">About</string>\n    <string name=\"home_more_menu_document\">Document</string>\n\n    <string name=\"home_dialog_uninstall_title\">Uninstall</string>\n    \n    <string name=\"home_dialog_uninstall_ap_only\">Uninstall patch only</string>\n    <string name=\"home_dialog_uninstall_all\">Uninstall fully</string>\n    <string name=\"home_dialog_uninstall_ap_only_desc\">Remove AndroidPatch only and keep the manager.</string>\n    <string name=\"home_dialog_uninstall_all_desc\">Remove AndroidPatch and start the full uninstall flow.</string>\n\n    <string name=\"kpm_control_dialog_title\">Control KPM</string>\n    <string name=\"kpm_control_dialog_content\">Please enter control parameters:</string>\n    <string name=\"kpm_control_paramters\">Parameters</string>\n    <string name=\"kpm_control_outMsg\">Output</string>\n    <string name=\"kpm_control_ok\">Success!</string>\n    <string name=\"kpm_control_failed\">Failed!</string>\n    \n    <string name=\"settings_night_mode_follow_sys\">Dark theme follows system</string>\n    <string name=\"settings_night_mode_follow_sys_summary\">Switch dark theme automatically based on system settings</string>\n    <string name=\"settings_night_theme_enabled\">Dark theme</string>\n    \n    <string name=\"settings_use_system_color_theme\">System color theme</string>\n    <string name=\"settings_use_system_color_theme_summary\">Use color theme generated from wallpaper by system</string>\n    <string name=\"settings_custom_color_theme\">Color theme</string>\n\n    <string name=\"amber_theme\">Amber</string>\n    <string name=\"blue_theme\">Blue</string>\n    <string name=\"blue_grey_theme\">Blue Grey</string>\n    <string name=\"brown_theme\">Brown</string>\n    <string name=\"cyan_theme\">Cyan</string>\n    <string name=\"deep_orange_theme\">Deep Orange</string>\n    <string name=\"deep_purple_theme\">Deep Purple</string>\n    <string name=\"green_theme\">Green</string>\n    <string name=\"indigo_theme\">Indigo</string>\n    <string name=\"light_blue_theme\">Light Blue</string>\n    <string name=\"light_green_theme\">Light Green</string>\n    <string name=\"lime_theme\">Lime</string>\n    <string name=\"orange_theme\">Orange</string>\n    <string name=\"pink_theme\">Pink</string>\n    <string name=\"purple_theme\">Purple</string>\n    <string name=\"red_theme\">Red</string>\n    <string name=\"sakura_theme\">Sakura</string>\n    <string name=\"teal_theme\">Teal</string>\n    <string name=\"yellow_theme\">Yellow</string>\n    <string name=\"ink_wash_theme\">Ink Wash</string>\n\n    <string name=\"theme_color\">Theme Color</string>\n    <string name=\"theme_light\">Light</string>\n    <string name=\"theme_dark\">Dark</string>\n    <string name=\"theme_system\">System</string>\n\n    <string name=\"about_app_desc\">A Root implementation based on KernelPatch, allowing kernel function hooks without recompiling the kernel.</string>\n    <string name=\"about_powered_by\">Powered by %1$s</string>\n    <string name=\"about_telegram_group\">Telegram Group</string>\n    <string name=\"github\">GitHub</string>\n    <string name=\"telegram\">Telegram</string>\n    \n    <!-- Loading Strings -->\n    <string name=\"loading_modules\">Loading modules…</string>\n    <string name=\"loading_scripts\">Finding scripts…</string>\n    <string name=\"loading_themes\">Loading themes…</string>\n    <string name=\"loading_apis\">Loading API sources…</string>\n    <string name=\"retry\">Retry</string>\n\n    <!-- Online Module Strings -->\n    <string name=\"online_module_title\">Online System Module</string>\n    <string name=\"online_module_download_start\">Start downloading: %s</string>\n    <string name=\"online_module_download_complete\">Download complete: %s</string>\n    <string name=\"online_module_download_notification\">Downloading %s. Please check notification panel for progress and Download folder for completed file.</string>\n\n    <!-- Online KPM Strings -->\n    <string name=\"online_kpm_title\">Online Kernel Modules</string>\n    <string name=\"online_kpm_download_start\">Start downloading: %s</string>\n    <string name=\"online_kpm_download_complete\">Download complete: %s</string>\n    <string name=\"online_kpm_download_notification\">Downloading %s. Please check notification panel for progress and Download folder for completed file.</string>\n\n    <!-- Online Script Strings -->\n    <string name=\"online_script_title\">Online Scripts</string>\n    <string name=\"online_script_download_start\">Start downloading: %s</string>\n    <string name=\"online_script_download_complete\">Download complete: %s</string>\n    <string name=\"online_script_download_notification\">Downloading %s</string>\n\n    <!-- Crash Handle Strings -->\n    <string name=\"crash_handle_title\">Crash Report</string>\n    <string name=\"crash_handle_copy\">Copy</string>\n\n    <!-- About Screen Strings -->\n    <string name=\"about_app_version\">App Version: %s</string>\n    <string name=\"about_github\">GitHub</string>\n    <string name=\"about_telegram_channel\">Telegram Channel</string>\n\n    <!-- App Title Strings -->\n    <string name=\"app_title_custom\">Custom</string>\n    <string name=\"app_title_fpatch\">FPatch</string>\n    <string name=\"app_title_apatch_folk\">APatch Folk</string>\n    <string name=\"app_title_apatchx\">APatch X</string>\n    <string name=\"app_title_apatch\">APatch</string>\n    <string name=\"app_title_folkpatch\">FolkPatch</string>\n    <string name=\"app_title_kernelpatch\">KernelPatch</string>\n    <string name=\"app_title_kernelsu\">KernelSU</string>\n    <string name=\"app_title_supersu\">SuperSU</string>\n    <string name=\"app_title_superuser\">Superuser</string>\n    <string name=\"app_title_superpatch\">SuperPatch</string>\n    <string name=\"app_title_magicpatch\">MagicPatch</string>\n\n    <string name=\"settings_app_dpi\">App DPI</string>\n    <string name=\"dpi_apply_settings\">Apply</string>\n    <string name=\"dpi_confirm_title\">Confirm DPI change</string>\n    <string name=\"dpi_confirm_message\">Change app DPI from %1$s to %2$s?</string>\n    <string name=\"settings_magic_mount\">Folk Mount API</string>\n    <string name=\"settings_magic_mount_summary\">Enable the built-in module mounting system</string>\n    <string name=\"settings_new_app_profile_mode\">Default mode for new apps</string>\n    <string name=\"settings_new_app_profile_normal\">NO ROOT</string>\n    <string name=\"settings_new_app_profile_root\">ROOT</string>\n    <string name=\"settings_new_app_profile_exclude\">Exclude</string>\n    <string name=\"settings_hide_service\">FolkPatch Hide</string>\n    <string name=\"settings_hide_service_summary\">Hide Bootloader unlock status to some extent</string>\n    <string name=\"settings_kernel_spoof\">Kernel Spoof Config</string>\n    <string name=\"settings_kernel_spoof_summary\">Spoof kernel version and build time</string>\n    <string name=\"settings_kernel_spoof_version\">Kernel Version</string>\n    <string name=\"settings_kernel_spoof_build_time\">Kernel Build Time</string>\n    <string name=\"settings_kernel_spoof_restore\">Restore</string>\n    <!-- Path Hide -->\n    <string name=\"settings_path_hide\">Path Hide</string>\n    <string name=\"settings_path_hide_summary\">Hide files and directories from apps at kernel level</string>\n    <string name=\"path_hide_paths_label\">Hidden Paths (one per line)</string>\n    <string name=\"path_hide_paths_placeholder\">/storage/emulated/0/file.txt\\n/storage/emulated/0/folder</string>\n    <string name=\"path_hide_paths_helper\">Enter one path per line. Matching paths will return ENOENT.</string>\n    <string name=\"path_hide_save\">Save</string>\n    <string name=\"path_hide_enabled\">Path hide enabled</string>\n    <string name=\"path_hide_disabled\">Path hide disabled</string>\n    <string name=\"path_hide_applied\">Path hide configuration applied</string>\n    <string name=\"path_hide_failed\">Path hide operation failed: %d</string>\n    <string name=\"path_hide_uid_mode\">UID Execution Mode</string>\n    <string name=\"path_hide_uid_mode_summary\">Only hide paths for specific app UIDs</string>\n    <string name=\"path_hide_uids_label\">Target UIDs (one per line)</string>\n    <string name=\"path_hide_uids_helper\">Enter app UIDs. Only these apps will have paths hidden.</string>\n    <string name=\"path_hide_uid_save\">Save UIDs</string>\n    <string name=\"path_hide_uid_mode_enabled\">UID execution mode enabled</string>\n    <string name=\"path_hide_uid_mode_disabled\">UID execution mode disabled</string>\n    <string name=\"path_hide_filter_system\">Filter System UIDs</string>\n    <string name=\"path_hide_filter_system_summary\">Also hide paths from root and system processes (UID &lt; 10000)</string>\n    <string name=\"path_hide_filter_system_enabled\">System UID filtering enabled</string>\n    <string name=\"path_hide_filter_system_disabled\">System UID filtering disabled</string>\n    <string name=\"path_hide_filter_system_warning_title\">Warning</string>\n    <string name=\"path_hide_filter_system_warning_message\">Enabling this will also hide paths from root and system processes (UID &lt; 10000). This may cause some system features to malfunction. Proceed with caution.</string>\n    <string name=\"path_hide_uid_applied\">UID whitelist applied</string>\n    <string name=\"path_hide_select_apps\">Select Apps</string>\n    <string name=\"path_hide_no_apps_selected\">No apps selected</string>\n    <string name=\"path_hide_app_removed\">Removed %d stale entry for uninstalled app(s)</string>\n    <string name=\"path_hide_search_apps\">Search apps…</string>\n    <string name=\"path_hide_show_system\">Show system apps</string>\n    <string name=\"netisolate_title\">Network Isolation</string>\n    <string name=\"netisolate_enable\">Network Isolation enabled</string>\n    <string name=\"netisolate_disable\">Network Isolation disabled</string>\n    <string name=\"netisolate_enable_summary\">Block network access for selected apps at kernel level</string>\n    <string name=\"netisolate_uids_label\">Blocked Apps</string>\n    <string name=\"netisolate_uids_hint\">Enter UIDs to block (one per line)</string>\n    <string name=\"netisolate_no_uids\">No apps blocked</string>\n    <!-- Umount Service -->\n    <string name=\"settings_umount_service\">Umount Service</string>\n    <string name=\"settings_umount_service_summary\">Configure mount points to auto-unmount at boot</string>\n    <string name=\"umount_config_title\">Umount Configuration</string>\n    <string name=\"umount_config_enabled\">Enable Umount</string>\n    <string name=\"umount_config_enabled_summary\">Auto-unmount specified mount points at boot</string>\n    <string name=\"umount_config_paths_label\">Mount Point Paths (one per line)</string>\n    <string name=\"umount_config_paths_placeholder\">/system/vendor/app\\n/system/vendor/priv-app</string>\n    <string name=\"umount_config_paths_helper\">Enter one mount point path per line to unmount</string>\n    <string name=\"umount_config_save\">Save</string>\n    <string name=\"umount_config_save_confirm\">Confirm save configuration?</string>\n    <string name=\"umount_config_save_success\">Configuration saved successfully</string>\n    <string name=\"umount_config_save_failed\">Failed to save configuration</string>\n    <string name=\"settings_app_title\">App Title</string>\n    <string name=\"settings_custom_app_title\">Custom App Name</string>\n    <string name=\"custom_app_title_dialog_title\">Custom App Name</string>\n    <string name=\"custom_app_title_dialog_hint\">Enter app name</string>\n    <string name=\"custom_app_title_dialog_confirm\">Confirm</string>\n    <string name=\"custom_app_title_dialog_empty\">Name cannot be empty</string>\n    <string name=\"cancel\">Cancel</string>\n    <string name=\"settings_custom_background\">Custom Background</string>\n    <string name=\"settings_custom_background_enabled\">Custom Background Enabled</string>\n    <string name=\"settings_custom_background_opacity\">Card opacity</string>\n    <string name=\"settings_custom_background_blur\">Background blur</string>\n    <string name=\"settings_custom_background_dim\">Background dim</string>\n    <string name=\"settings_custom_background_dual_dim\">Enable dual day/night dim</string>\n    <string name=\"settings_custom_background_dual_dim_desc\">Automatically adjust background dim for light and dark themes</string>\n    <string name=\"settings_custom_background_day_dim\">Day mode background dim</string>\n    <string name=\"settings_custom_background_night_dim\">Night mode background dim</string>\n    <string name=\"settings_multi_background_mode\">Multi-Background Mode</string>\n    <string name=\"settings_multi_background_mode_summary\">Set different background images for different pages</string>\n    <string name=\"settings_select_home_background\">Home Page Background</string>\n    <string name=\"settings_select_kernel_background\">Kernel Module Background</string>\n    <string name=\"settings_select_superuser_background\">Superuser Background</string>\n    <string name=\"settings_select_system_module_background\">System Module Background</string>\n    <string name=\"settings_select_settings_background\">Settings Background</string>\n\n    <string name=\"settings_advanced_title_style\">Advanced Title Style</string>\n    <string name=\"settings_advanced_title_style_summary\">Replace top bar title with custom image</string>\n    <string name=\"settings_advanced_title_style_enabled\">Advanced title style enabled</string>\n    <string name=\"settings_select_title_image\">Select Title Image</string>\n    <string name=\"settings_title_image_selected\">Title image selected</string>\n    <string name=\"settings_title_image_saved\">Title image saved successfully</string>\n    <string name=\"settings_title_image_error\">Failed to save title image</string>\n    <string name=\"settings_clear_title_image\">Clear Title Image</string>\n    <string name=\"settings_clear_title_image_confirm\">Are you sure you want to clear the title image?</string>\n    <string name=\"settings_title_image_cleared\">Title image cleared</string>\n    <string name=\"settings_title_image_day_opacity\">Day Mode Opacity</string>\n    <string name=\"settings_title_image_night_opacity\">Night Mode Opacity</string>\n    <string name=\"settings_title_image_dim\">Background Dim</string>\n    <string name=\"settings_title_image_offset_x\">Horizontal Position</string>\n\n    \n    <!-- Font Settings -->\n    <string name=\"settings_custom_font\">Custom Font</string>\n    <string name=\"settings_select_font_file\">Select Font File</string>\n    <string name=\"settings_custom_font_enabled\">Custom Font Enabled</string>\n    <string name=\"settings_custom_font_summary\">Use a custom TTF font for the app</string>\n    <string name=\"settings_font_selected\">Custom Font Selected</string>\n    <string name=\"settings_custom_font_error\">Failed to save font file</string>\n    <string name=\"settings_custom_font_saved\">Custom font saved successfully</string>\n    <string name=\"settings_clear_font\">Restore Default Font</string>\n    <string name=\"settings_clear_font_confirm\">Are you sure you want to restore the default font?</string>\n    <string name=\"settings_font_cleared\">Default font restored</string>\n    <string name=\"settings_font_select_hint\">Select a TTF font file</string>\n    <string name=\"settings_select_background_image\">Select Background Image</string>\n    <string name=\"settings_custom_background_summary\">Set a custom background image</string>\n    <string name=\"settings_custom_background_saved\">Background saved successfully</string>\n    <string name=\"settings_custom_background_error\">Failed to save background</string>\n    <string name=\"settings_background_selected\">Background selected</string>\n    <string name=\"settings_background_image_cleared\">Background image cleared</string>\n    <string name=\"settings_clear_background\">Clear Background</string>\n    <string name=\"settings_clear_background_confirm\">Are you sure you want to clear the background image?</string>\n\n    <string name=\"settings_alt_icon\">Alternate Icon</string>\n    <string name=\"alt_icon_summary\">Use alternate launcher icon</string>\n\n    <!-- Video Background Settings -->\n    <string name=\"settings_video_background\">Video Background</string>\n    <string name=\"settings_video_background_summary\">Use a video as background</string>\n    <string name=\"settings_select_video\">Select Video</string>\n    <string name=\"settings_video_selected\">Video Selected</string>\n    <string name=\"settings_clear_video_background\">Clear Video Background</string>\n    <string name=\"settings_clear_video_background_confirm\">Are you sure you want to clear the video background?</string>\n    <string name=\"settings_video_background_enabled\">Video Background Enabled</string>\n    <string name=\"settings_video_volume\">Video Volume</string>\n    \n    <!-- KPM AutoLoad Strings -->\n    <string name=\"kpm_autoload_title\">KPM Auto-Load</string>\n    <string name=\"kpm_autoload_enabled\">Enable auto-load</string>\n    <string name=\"kpm_autoload_enabled_summary\">Automatically load KPM modules on boot</string>\n    <string name=\"kpm_autoload_json_config\">JSON Configuration</string>\n    <string name=\"kpm_autoload_json_label\">Configuration JSON</string>\n    <string name=\"kpm_autoload_json_placeholder\">{\n  \"enabled\": true,\n  \"kpmPaths\": [\n    \"/path/to/module1.kpm\",\n    \"/path/to/module2.kpm\"\n  ]\n}</string>\n    <string name=\"kpm_autoload_json_error\">Invalid JSON format</string>\n    <string name=\"kpm_autoload_json_helper\">Enter valid JSON with array of KPM file paths</string>\n    <string name=\"kpm_autoload_save\">Save Configuration</string>\n    <string name=\"kpm_autoload_save_confirm\">Save auto-load configuration?</string>\n    <string name=\"kpm_autoload_save_success\">Configuration saved successfully</string>\n    <string name=\"kpm_autoload_save_failed\">Failed to save configuration</string>\n    <string name=\"kpm_autoload_visual_mode\">Visual Mode</string>\n    <string name=\"kpm_autoload_json_mode\">JSON Mode</string>\n    <string name=\"kpm_autoload_add_kpm\">Add KPM</string>\n    <string name=\"kpm_autoload_remove_kpm\">Remove</string>\n    <string name=\"kpm_autoload_edit_kpm\">Editar</string>\n    <string name=\"kpm_autoload_edit_dialog_title\">Editar KPM</string>    <string name=\"kpm_autoload_event_label\">Evento:</string>    <string name=\"kpm_autoload_args_label\">Argumentos:</string>    <string name=\"kpm_autoload_event_service\">service</string>    <string name=\"kpm_autoload_event_post_fs_data\">post-fs-data</string>\n    \n    <!-- Module Backup/Restore Strings -->\n    <string name=\"apm_backup_title\">Backup Modules</string>\n    <string name=\"apm_restore_title\">Restore Modules</string>\n    <string name=\"apm_backup_success\">Backup successful</string>\n    <string name=\"apm_restore_success\">Restore successful</string>\n    <string name=\"apm_backup_failed\">Backup failed</string>\n    <string name=\"apm_restore_failed\">Restore failed</string>\n    <string name=\"apm_backup_failed_msg\">Backup failed: %s</string>\n    <string name=\"apm_restore_failed_msg\">Restore failed: %s</string>\n    <string name=\"apm_copy_list_title\">Copy List</string>\n    <string name=\"apm_copy_list_success\">Module list copied</string>\n\n    <!-- APM Bulk Install Strings -->\n    <string name=\"apm_bulk_install_title\">System Module Bulk Install</string>\n    <string name=\"apm_bulk_install_list_title\">Modules List</string>\n    <string name=\"apm_bulk_install_add\">Add Modules</string>\n    <string name=\"apm_bulk_install_empty\">No modules added</string>\n    <string name=\"apm_bulk_install_action\">Bulk Install</string>\n    <string name=\"apm_bulk_install_log_title\">Bulk Install Log</string>\n    <string name=\"apm_bulk_install_log_start\">Starting bulk installation...</string>\n    <string name=\"apm_bulk_install_log_installing\">Installing %1$s</string>\n    <string name=\"apm_batch_install_full_process\">Complete batch install process</string>\n    <string name=\"apm_batch_install_full_process_summary\">System module batch installation is performed using the complete process</string>\n    <string name=\"next_module\">Next module</string>\n    <string name=\"apm_bulk_install_log_installed\">Module %s installed.</string>\n    <string name=\"apm_bulk_install_log_done\">All operations completed.</string>\n    <string name=\"apm_bulk_install_first_use_text\">This feature allows you to install multiple modules at once. It is a quick install method suitable for modules that do not involve key operations during installation. For modules requiring volume key interactions, please enable the complete process install mode in settings.</string>\n    <string name=\"apm_bulk_install_remove\">Remove</string>\n    <string name=\"apm_first_use_title\">Welcome to System Modules</string>\n    <string name=\"apm_first_use_text\">Welcome to System Modules, this uses modules compatible with the Magisk ecosystem. Click the button in the bottom right corner to install modules. You can also use the installer at the top to batch install modules. A feature to backup all modules with one click is also provided, but note that this solution may not be suitable for all modules, so you should make your own backups.</string>\n\n    <string name=\"kpm_autoload_kpm_list_title\">KPM Modules</string>\n    <string name=\"kpm_autoload_no_kpm_added\">No KPM modules added</string>\n    <string name=\"kpm_autoload_kpm_path\">KPM Path</string>\n    <string name=\"kpm_autoload_file_not_found\">File not found</string>\n    <string name=\"kpm_autoload_select_kpm_file\">Select KPM File</string>\n    <string name=\"kpm_autoload_first_time_title\">About KPM Auto-Load</string>\n    <string name=\"kpm_autoload_first_time_message\">This feature allows you to automatically load all configured KPMs temporarily on boot. This approach is more convenient than embedding directly into the kernel.\n\nFor example, KPMs that prevent partition modification can only be used temporarily, as embedding may cause boot failures. Or if you don\\'t want to modify the BOOT partition, you can use this configuration to quickly load modules.\n\nPlease ensure the app is completely closed and reopened for the commands to execute. Generally, it will also load on boot. It is normal for the manager to have a black screen for a period of time! This uses a pure background loading method, remember to manually swipe down to refresh and check if the modules are loaded correctly!</string>\n    <string name=\"kpm_autoload_first_time_confirm\">Got it</string>\n    <string name=\"kpm_autoload_do_not_show_again\">Don\\'t show again</string>\n\n    <!-- Hide Settings Strings -->\n    <string name=\"home_hide_su_path\">Hide su executable path</string>\n    <string name=\"home_hide_kpatch_version\">Hide kernel patch version</string>\n    <string name=\"home_hide_fingerprint\">Hide fingerprint</string>\n    <string name=\"home_hide_zygisk\">Hide Zygisk implementation</string>\n    <string name=\"home_hide_mount\">Hide mount implementation</string>\n    <string name=\"home_hide_su_path_summary\">Hide su executable path in info card</string>\n    <string name=\"home_hide_kpatch_version_summary\">Hide kernel patch version in info card</string>\n    <string name=\"home_hide_fingerprint_summary\">Hide fingerprint in info card</string>\n    <string name=\"home_hide_zygisk_summary\">Hide Zygisk implementation in info card</string>\n    <string name=\"home_hide_mount_summary\">Hide mount implementation in info card</string>\n\n    <string name=\"kpm_page_first_time_title\">Warning</string>\n    <string name=\"kpm_page_first_time_message\">Kernel modules directly modify the Boot implementation. Unlike system modules, they lack a good recovery mechanism. If issues arise, you can only fix them by entering Fastboot. It is recommended to load the module first to ensure there are no problems before embedding it into Boot. If a module can only be used by loading, you can try the automatic KPM module loading feature. If you do not understand kernel modules, please do not use this feature!</string>\n    \n    \n    <!-- Auto Backup Strings -->\n    <string name=\"settings_auto_backup_module\">Auto Backup System Modules</string>\n    <string name=\"settings_auto_backup_module_summary\">Automatically backup module file to private directory when installing</string>\n    <string name=\"settings_open_backup_dir\">Open Backup Directory</string>\n    <string name=\"backup_dir_empty\">Backup directory is empty</string>\n    <string name=\"backup_dir_open_failed\">Failed to open backup directory</string>\n    <string name=\"auto_backup_failed\">Auto backup failed: %s</string>\n    <string name=\"auto_backup_success\">Auto backup success: %s</string>\n    <string name=\"settings_auto_backup_boot\">Auto Backup Boot</string>\n    <string name=\"settings_auto_backup_boot_summary\">Automatically backup Boot to local storage (Downloads/FolkPatch/BootBackups)</string>\n\n    <!-- Home Layout Strings -->\n    <string name=\"settings_home_layout_style\">Home Layout Style</string>\n    <string name=\"settings_home_layout_default\">ListUI</string>\n    <string name=\"settings_home_layout_grid\">GridUI</string>\n    <string name=\"settings_home_layout_focus\">FocusUI</string>\n    <string name=\"settings_home_layout_sign\">SignUI</string>\n    <string name=\"settings_home_layout_circle\">CircleUI</string>\n    <string name=\"settings_home_layout_dashboard_pro\">DashboardUI</string>\n    <string name=\"settings_home_layout_stats\">StatsUI</string>\n    <string name=\"unofficial_version_title\">Unofficial Version</string>\n    <string name=\"unofficial_version_message\">You are using FolkPatch which is not an official app, please download the official app!</string>\n    <string name=\"go_to_github\">Go to Github</string>\n    <string name=\"settings_save_theme\">Save theme</string>\n    <string name=\"settings_import_theme\">Import theme</string>\n    <string name=\"settings_theme_saved\">Theme saved</string>\n    <string name=\"settings_theme_imported\">Theme imported</string>\n    <string name=\"settings_theme_save_failed\">Failed to save theme</string>\n    <string name=\"settings_theme_import_failed\">Failed to import theme</string>\n    <string name=\"settings_reset_theme\">Reset theme</string>\n    <string name=\"settings_reset_theme_confirm\">Are you sure you want to reset all theme settings to default? This will clear all custom backgrounds, fonts, music, and sound effects.</string>\n    <string name=\"settings_theme_reset\">Theme reset to default</string>\n    <string name=\"settings_theme_reset_failed\">Failed to reset theme</string>\n\n    <!-- Theme Strings -->\n    <string name=\"theme_export_title\">Export Theme</string>\n    <string name=\"theme_import_title\">Import Theme</string>\n    <string name=\"theme_name\">Theme Name</string>\n    <string name=\"theme_type\">Theme Type</string>\n    <string name=\"theme_type_phone\">Phone</string>\n    <string name=\"theme_type_tablet\">Tablet</string>\n    <string name=\"theme_version\">Version</string>\n    <string name=\"theme_author\">Author</string>\n    <string name=\"theme_description\">Description</string>\n    <string name=\"theme_export_action\">Export</string>\n    <string name=\"theme_import_action\">Import</string>\n    <string name=\"theme_import_confirm\">Are you sure you want to import this theme?</string>\n    <string name=\"theme_info\">Theme Info</string>\n\n    <!-- Grid Working Card Background -->\n    <string name=\"settings_grid_working_card_background\">Working Card Background</string>\n    <string name=\"settings_grid_working_card_background_enabled\">Custom card background enabled</string>\n    <string name=\"settings_grid_working_card_background_summary\">Custom background image for the working card</string>\n    <string name=\"settings_grid_working_card_background_selected\">Background selected</string>\n    <string name=\"settings_grid_working_card_dual_opacity\">Enable dual day/night opacity</string>\n    <string name=\"settings_grid_working_card_dual_opacity_desc\">Automatically adjust card opacity for light and dark themes</string>\n    <string name=\"settings_grid_working_card_day_opacity\">Day mode card opacity</string>\n    <string name=\"settings_grid_working_card_night_opacity\">Night mode card opacity</string>\n    <string name=\"settings_clear_grid_working_card_background\">Clear Card Background</string>\n    <string name=\"settings_clear_grid_working_card_background_confirm\">Are you sure you want to clear the card background image?</string>\n    <string name=\"settings_grid_working_card_background_saved\">Card background saved successfully</string>\n    <string name=\"settings_grid_working_card_background_error\">Failed to save card background</string>\n    <string name=\"settings_grid_working_card_background_cleared\">Card background cleared</string>\n\n    <!-- Biometric Strings -->\n    <string name=\"action_biometric\">Biometric Authentication</string>\n    <string name=\"msg_biometric\">Please verify your biometric identity</string>\n    <string name=\"settings_biometric_login\">Biometric Authentication</string>\n    <string name=\"settings_biometric_login_summary\">Require biometric authentication when opening the app</string>\n    <string name=\"settings_strong_biometric\">Strong Biometric Authentication</string>\n    <string name=\"settings_strong_biometric_summary\">Require authentication for installing/uninstalling/disabling modules</string>\n\n    <!-- Theme Store Strings -->\n    <string name=\"theme_store_title\">Theme Store</string>\n    <string name=\"theme_source_official\">Official</string>\n    <string name=\"theme_source_third_party\">Third Party</string>\n    <string name=\"theme_source_local\">Local</string>\n    <string name=\"theme_store_author\">Author: %s</string>\n    <string name=\"theme_store_version\">Version: %s</string>\n    <string name=\"theme_source\">Source</string>\n    <string name=\"theme_store_download_failed\">Download failed</string>\n    <string name=\"theme_store_download\">Download</string>\n    <string name=\"theme_store_search_hint\">Search themes...</string>\n    <string name=\"search_modules\">Search modules...</string>\n    <string name=\"search_scripts\">Search scripts...</string>\n\n    <!-- Update Check Strings -->\n    <string name=\"settings_auto_update_check\">Automatic Update Check</string>\n    <string name=\"settings_auto_update_check_summary\">Automatically check for updates on app startup</string>\n    <string name=\"settings_check_update\">Check for Updates</string>\n    <string name=\"update_available_title\">Update Available</string>\n    <string name=\"update_available_message\">Detected that your version is too low, do you want to download the new version?</string>\n    <string name=\"update_action\">Update</string>\n    <string name=\"update_close\">Close</string>\n    <string name=\"update_latest\">You are on the latest version</string>\n    <string name=\"update_error\">Error checking for updates</string>\n    <string name=\"settings_category_general\">General</string>\n    <string name=\"settings_app_list_loading_scheme\">App List Loading Scheme</string>\n    <string name=\"settings_app_list_loading_scheme_summary\">Choose how to load the application list</string>\n    <string name=\"app_list_loading_scheme_root_service\">RootService (Default)</string>\n    <string name=\"app_list_loading_scheme_package_manager\">PackageManager (System API)</string>\n    <string name=\"app_list_loading_scheme_dialog_title\">Loading Scheme</string>\n    <string name=\"superuser\">Superuser</string>\n    <string name=\"module\">Modules</string>\n    <string name=\"settings_category_appearance\">Appearance</string>\n    <string name=\"settings_category_behavior\">Behavior</string>\n    \n    <!-- Appearance Sub-Categories -->\n    <string name=\"settings_appearance_font\">Font Settings</string>\n    <string name=\"settings_appearance_font_summary\">Custom font configuration</string>\n    <string name=\"settings_appearance_theme\">Theme Settings</string>\n    <string name=\"settings_appearance_theme_summary\">Theme store, save, import and reset</string>\n    <string name=\"settings_appearance_banner\">Banner Settings</string>\n    <string name=\"settings_appearance_banner_summary\">Module banner configuration</string>\n    <string name=\"settings_appearance_layout\">Layout Settings</string>\n    <string name=\"settings_appearance_layout_summary\">Home layout, navigation and card customization</string>\n    <string name=\"settings_appearance_background\">Background Settings</string>\n    <string name=\"settings_appearance_background_summary\">Custom background, video and multi-background</string>\n    <string name=\"settings_appearance_night_mode\">Night Mode Settings</string>\n    <string name=\"settings_appearance_night_mode_summary\">Dark theme and color configuration</string>\n    <string name=\"settings_amoled_theme\">AMOLED Black Theme</string>\n    <string name=\"settings_amoled_theme_desc\">Pure black background for dark mode</string>\n    <string name=\"settings_switch_icon\">Enable Button Indicator</string>\n    <string name=\"settings_switch_icon_desc\">Show status icons on toggle switches</string>\n    <string name=\"settings_category_module\">Modules</string>\n    <string name=\"settings_category_security\">Security</string>\n    <string name=\"settings_category_function\">Function</string>\n    \n    <!-- Background Music Strings -->\n    <string name=\"settings_background_music\">Background Music</string>\n    <string name=\"settings_background_music_summary\">Play background music when app is in foreground</string>\n    <string name=\"settings_background_music_playing\">Playing: %s</string>\n    <string name=\"settings_background_music_enabled\">Background Music Enabled</string>\n    <string name=\"settings_select_music_file\">Select music file</string>\n    <string name=\"settings_music_selected\">Music selected</string>\n    <string name=\"settings_clear_music\">Clear music</string>\n    <string name=\"settings_clear_music_confirm\">Are you sure you want to clear the background music?</string>\n    <string name=\"settings_music_auto_play\">Auto play</string>\n    <string name=\"settings_music_auto_play_summary\">Automatically play music when app opens</string>\n    <string name=\"settings_music_looping\">Loop play</string>\n    <string name=\"settings_music_looping_summary\">Repeat the current song</string>\n    <string name=\"settings_music_volume\">Volume</string>\n    <string name=\"settings_music_saved\">Music file saved</string>\n    <string name=\"settings_music_save_error\">Failed to save music file</string>\n    <string name=\"settings_music_cleared\">Music cleared</string>\n    <string name=\"settings_category_multimedia\">Multimedia</string>\n    <string name=\"settings_category_general_summary\">Language, updates, SELinux, system tweaks</string>\n    <string name=\"settings_category_appearance_summary\">Theme, colors, layout, background, fonts</string>\n    <string name=\"settings_category_behavior_summary\">Web debugging, install behavior, home display</string>\n    <string name=\"settings_category_security_summary\">Biometric, superkey management</string>\n    <string name=\"settings_category_backup_summary\">Local backup, cloud backup, WebDAV</string>\n    <string name=\"settings_category_module_summary\">Module info, sorting, batch install</string>\n    <string name=\"settings_category_function_summary\">FolkPatch Hide, Umount service</string>\n    <string name=\"settings_category_multimedia_summary\">Background music, sounds, vibration</string>\n    <string name=\"settings_use_legacy_su_page\">Single-page Superuser authorization</string>\n    <string name=\"settings_use_legacy_su_page_summary\">Superuser page switched to single-page authorization design</string>\n    <string name=\"settings_music_playback_control\">Playback Control</string>\n\n    <!-- Theme Store Filter -->\n    <string name=\"theme_store_filter_title\">Filter Themes</string>\n    <string name=\"theme_store_filter_author\">Author</string>\n    <string name=\"theme_store_filter_author_hint\">Enter author name</string>\n    <string name=\"theme_store_filter_source\">Source</string>\n    <string name=\"theme_store_filter_source_all\">All</string>\n    <string name=\"theme_store_filter_type\">Device Type</string>\n    <string name=\"theme_store_filter_apply\">Apply</string>\n    <string name=\"theme_store_filter_reset\">Reset</string>\n\n    <!-- My Themes Page -->\n    <string name=\"my_themes_title\">My Themes</string>\n    <string name=\"my_themes_empty\">No themes yet</string>\n    <string name=\"my_themes_empty_action\">Browse Theme Store</string>\n    <string name=\"my_themes_apply\">Apply Theme</string>\n    <string name=\"my_themes_delete\">Delete Theme</string>\n    <string name=\"my_themes_delete_confirm\">Are you sure you want to delete this theme?</string>\n    <string name=\"my_themes_deleted\">Theme deleted</string>\n    <string name=\"my_themes_applied\">Theme applied</string>\n    <string name=\"my_themes_apply_failed\">Failed to apply theme</string>\n    <string name=\"my_themes_details\">Theme Details</string>\n\n    <!-- Download Dialog -->\n    <string name=\"theme_download_title\">Downloading Theme</string>\n    <string name=\"theme_download_completed\">Download completed</string>\n    <string name=\"theme_download_failed\">Download failed</string>\n    <string name=\"theme_download_progress\">Progress</string>\n    <string name=\"theme_download_file\">Theme File</string>\n    <string name=\"theme_download_image\">Preview Image</string>\n    <string name=\"theme_download_cancel\">Cancel</string>\n    <string name=\"theme_download_pause\">Pause</string>\n    <string name=\"theme_download_resume\">Resume</string>\n    <string name=\"theme_download_retry\">Retry</string>\n    <string name=\"theme_download_apply\">Apply Theme</string>\n    <string name=\"theme_download_go_to_my_themes\">My Themes</string>\n    <string name=\"theme_download_retrying\">Retrying (%d/3)...</string>\n    <string name=\"theme_download_preparing\">Preparing download...</string>\n    <string name=\"theme_download_downloading_file\">Downloading theme file...</string>\n    <string name=\"theme_download_downloading_image\">Downloading preview image...</string>\n    <string name=\"theme_download_finalizing\">Finalizing...</string>\n\n    <!-- File Picker -->\n    <string name=\"file_picker_permission_required\">Permission Required</string>\n    <string name=\"file_picker_permission_desc\">To browse files, please grant \\'All files access\\' permission.</string>\n    <string name=\"file_picker_grant_permission\">Grant Permission</string>\n    <string name=\"file_picker_cancel\">Cancel</string>\n    <string name=\"file_picker_internal_storage\">Internal Storage</string>\n    <string name=\"file_picker_no_files\">No files</string>\n\n    <!-- HomeV3 Strings -->\n    <string name=\"home_device_status_title\">Device Status</string>\n    <string name=\"home_device_status_battery_temp\">Battery Temp</string>\n    <string name=\"home_device_status_cpu_load\">CPU Load</string>\n    <string name=\"home_device_status_battery_level\">Battery Level</string>\n    <string name=\"home_device_status_battery_charging\">Charging</string>\n    <string name=\"home_device_status_cpu_temp\">CPU Temperature</string>\n    <string name=\"home_device_status_memory_trend\">Memory Trend</string>\n    <string name=\"home_storage_partitions\">Storage Partitions</string>\n    <string name=\"home_device_status_cpu_freq\">CPU Frequency</string>\n    <string name=\"home_network_rx\">Download</string>\n    <string name=\"home_network_tx\">Upload</string>\n    <string name=\"home_storage_title\">Storage</string>\n    <string name=\"home_storage_internal\">Internal Storage</string>\n    <string name=\"home_storage_ram\">RAM</string>\n    <string name=\"home_storage_zram\">ZRAM</string>\n    <string name=\"home_storage_swap\">Swap File</string>\n    <string name=\"home_info_kernel\">Kernel</string>\n    <string name=\"home_info_superkey\">SuperKey</string>\n    <string name=\"home_info_auth_auth\">Auth</string>\n    <string name=\"home_info_auth_na\">N/A</string>\n    <string name=\"home_info_device_slot\">Device Slot</string>\n    <string name=\"home_info_device_model\">Device Model</string>\n    <string name=\"home_info_running_mode\">Running Mode</string>\n    <string name=\"home_info_mode_full\">Full</string>\n    <string name=\"home_info_mode_half\">Half</string>\n    <string name=\"home_zygisk_implement\">Zygisk Implement</string>\n    <string name=\"home_mount_implement\">Mount Implementation</string>\n\n    <string name=\"settings_list_card_hide_status_badge\">Use Classic Emoji</string>\n    <string name=\"settings_list_card_hide_status_badge_summary\">Turn the home status badge into classic emoji</string>\n    <string name=\"settings_custom_badge_text\">Custom Badge Text</string>\n    <string name=\"settings_custom_badge_text_summary\">Modify the text display of the badge, just for fun</string>\n    <string name=\"settings_custom_badge_text_full_half\">Full/Half (Default)</string>\n    <string name=\"settings_custom_badge_text_lkm\">LKM</string>\n    <string name=\"settings_custom_badge_text_gki\">GKI</string>\n    <string name=\"settings_custom_badge_text_n_gki\">N-GKI</string>\n    <string name=\"settings_custom_badge_text_oki\">OKI</string>\n    <string name=\"settings_custom_badge_text_built_in\">Built-in</string>\n\n    <!-- Install Mode Select -->\n    <string name=\"kp_install_methods\">KernelPatch Patching/Installing</string>\n    <string name=\"restore_boot_methods\">Select a boot to restore to boot partition</string>\n\n\n    <string name=\"su_backup_list\">Backup List</string>\n    <string name=\"su_restore_list\">Restore List</string>\n    <string name=\"backup_success\">Backup successful</string>\n    <string name=\"restore_success\">Restore successful</string>\n\n    <!-- SuperUser App Action Dialog -->\n    <string name=\"su_app_action_title\">App Action</string>\n    <string name=\"su_app_action_content\">Please select an action to perform on the app</string>\n    <string name=\"su_app_action_launch\">Launch App</string>\n    <string name=\"su_app_action_force_stop\">Force Stop</string>\n    <string name=\"su_app_action_launch_success\">Launching %s</string>\n    <string name=\"su_app_action_force_stop_success\">Force stopped %s</string>\n    <string name=\"su_app_action_failed\">Operation failed: %s</string>\n\n    <!-- SU Audit Log -->\n    <string name=\"su_audit_log_title\">Authorization Log</string>\n    <string name=\"su_audit_log_empty\">No authorization records yet</string>\n    <string name=\"su_audit_log_clear\">Clear Log</string>\n    <string name=\"su_audit_log_clear_confirm\">Clear all authorization records?</string>\n    <string name=\"su_audit_action_grant\">Granted</string>\n    <string name=\"su_audit_action_revoke\">Revoked</string>\n    <string name=\"su_audit_action_exclude\">Excluded</string>\n    <string name=\"su_audit_tab_usage\">Usage Log</string>\n    <string name=\"su_audit_tab_operations\">Operations</string>\n\n    <string name=\"patch_output_written_to\"> Output file is written to </string>\n    <string name=\"patch_write_failed\"> Write patched boot.img failed</string>\n\n    <!-- Badge Count Settings -->\n    <string name=\"enable_badge_count\">Badge Count Settings</string>\n    <string name=\"enable_badge_count_summary\">Configure badge count display for navigation items</string>\n    <string name=\"badge_superuser\">Show SuperUser Badge</string>\n    <string name=\"badge_apm\">Show System Module Badge</string>\n    <string name=\"badge_kernel\">Show Kernel Module Badge</string>\n    <string name=\"settings_sound_effect\">Sound Effect</string>\n    <string name=\"settings_sound_effect_summary\">Play sound effect on click</string>\n    <string name=\"settings_sound_effect_enabled\">Enabled</string>\n    <string name=\"settings_sound_effect_playing\">Selected: %s</string>\n    <string name=\"settings_sound_effect_source\">Sound Source</string>\n    <string name=\"settings_sound_effect_source_local\">Local File</string>\n    <string name=\"settings_sound_effect_source_preset\">Preset</string>\n    <string name=\"settings_sound_effect_preset_title\">Preset Sound</string>\n    <string name=\"settings_select_sound_effect\">Select Sound Effect</string>\n    <string name=\"settings_sound_effect_selected\">Sound effect file selected</string>\n    <string name=\"settings_sound_effect_scope\">Effect Scope</string>\n    <string name=\"settings_sound_effect_scope_global\">Global (Anywhere)</string>\n    <string name=\"settings_sound_effect_scope_bottom_bar\">Bottom Bar Only</string>\n    <string name=\"settings_clear_sound_effect\">Clear Sound Effect</string>\n    <string name=\"settings_clear_sound_effect_confirm\">Are you sure you want to clear the sound effect?</string>\n    <string name=\"settings_sound_effect_cleared\">Sound effect cleared</string>\n\n    <string name=\"settings_startup_sound\">Startup Sound</string>\n    <string name=\"settings_startup_sound_summary\">Play sound when app starts</string>\n    <string name=\"settings_startup_sound_enabled\">Startup Sound Enabled</string>\n    <string name=\"settings_startup_sound_playing\">Playing: %s</string>\n    <string name=\"settings_select_startup_sound\">Select Startup Sound</string>\n    <string name=\"settings_startup_sound_selected\">Startup sound selected</string>\n    <string name=\"settings_clear_startup_sound\">Clear Startup Sound</string>\n    <string name=\"settings_clear_startup_sound_confirm\">Are you sure you want to clear the startup sound?</string>\n    <string name=\"settings_startup_sound_cleared\">Startup sound cleared</string>\n\n    <string name=\"settings_vibration\">Vibration Feedback</string>\n    <string name=\"settings_vibration_summary\">Vibrate on touch events</string>\n    <string name=\"settings_vibration_enabled\">Enable Vibration</string>\n    <string name=\"settings_vibration_intensity\">Vibration Intensity</string>\n    <string name=\"settings_vibration_scope\">Vibration Scope</string>\n    <string name=\"settings_vibration_scope_global\">Global (Anywhere)</string>\n    <string name=\"settings_vibration_scope_bottom_bar\">Bottom Bar Only</string>\n\n    <!-- Backup Settings -->\n    <string name=\"settings_category_backup\">Backup</string>\n    <string name=\"settings_enable_cloud_backup\">Enable Cloud Backup</string>\n    <string name=\"settings_enable_cloud_backup_summary\">Automatically backup flashed modules to cloud service</string>\n    <string name=\"settings_configure_webdav\">Configure WebDAV Service</string>\n    <string name=\"webdav_config_title\">WebDAV Configuration</string>\n    <string name=\"webdav_url\">WebDAV URL</string>\n    <string name=\"webdav_username\">Username</string>\n    <string name=\"webdav_password\">Password</string>\n    <string name=\"webdav_test_success\">Test Successful</string>\n    <string name=\"webdav_test_failed\">Test Failed: %s</string>\n    <string name=\"webdav_uploading\">Uploading to WebDAV...</string>\n    <string name=\"webdav_backup_success\">WebDAV Backup Success</string>\n    <string name=\"webdav_backup_failed\">WebDAV Backup Failed: %s</string>\n    <string name=\"save\">Save</string>\n    <string name=\"test\">Test</string>\n    <string name=\"webdav_path_label\">Path (e.g. /Backup)</string>\n    <string name=\"webdav_view_logs\">View Logs</string>\n    <string name=\"webdav_backup_logs_title\">Backup Logs</string>\n    <string name=\"webdav_no_logs\">No logs found.</string>\n    <string name=\"webdav_clear_logs\">Clear</string>\n    <string name=\"settings_enable_local_backup\">Enable Local Backup</string>\n    <string name=\"settings_enable_local_backup_summary\">Backup modules to local storage (Downloads/FolkPatch/ModuleBackups)</string>\n    <string name=\"close\">Close</string>\n\n\n\n    <string name=\"script_library\">Script Library</string>\n    <string name=\"script_library_title\">Script Library</string>\n    <string name=\"script_library_empty\">No scripts, click the add button to add one</string>\n    <string name=\"script_library_add\">Add Script</string>\n    <string name=\"script_library_add_title\">Add Script</string>\n    <string name=\"script_library_select_file\">Select Script File</string>\n    <string name=\"script_library_alias\">Alias</string>\n    <string name=\"script_library_alias_hint\">Enter script alias (optional)</string>\n    <string name=\"script_library_run\">Run</string>\n    <string name=\"script_library_delete\">Delete</string>\n    <string name=\"script_library_path\">Path: %s</string>\n    <string name=\"script_library_confirm_delete\">Confirm to delete script?</string>\n    <string name=\"script_library_delete_success\">Script deleted successfully</string>\n    <string name=\"script_library_delete_failed\">Failed to delete script</string>\n    <string name=\"script_library_add_success\">Script added successfully</string>\n    <string name=\"script_library_add_failed\">Failed to add script: %s</string>\n    <string name=\"script_library_load_failed\">Failed to load script library</string>\n    <string name=\"script_library_output\">Execution Output</string>\n    <string name=\"script_library_no_output\">No output</string>\n    <string name=\"script_library_save_log\">Save Log</string>\n    <string name=\"script_library_log_saved\">Log saved to %s</string>\n    <string name=\"script_library_log_save_failed\">Failed to save log: %s</string>\n    <string name=\"settings_predictive_back\">Predictive Back Gesture</string>\n    <string name=\"settings_predictive_back_summary\">Enable the Android 14+ predictive back gesture animation</string>\n\n    <string name=\"app_title_superroot\">SuperRoot</string>\n    <string name=\"backup_failed\">Backup failed: %s</string>\n    <string name=\"delete_failed\">Delete failed: %s</string>\n    <string name=\"delete_remote_backup_confirm\">Are you sure you want to delete the cloud backup?</string>\n    <string name=\"delete_success\">Delete successful</string>\n    <string name=\"restore_failed\">Restore failed: %s</string>\n    <string name=\"settings_auto_backup\">Auto Backup</string>\n    <string name=\"settings_auto_backup_summary\">Automatically backup once a day</string>\n    <string name=\"settings_backup_now\">Backup Now</string>\n    <string name=\"settings_backup_password\">Backup Password</string>\n    <string name=\"settings_backup_password_hint\">Enter backup password</string>\n    <string name=\"settings_delete_remote_backup\">Delete Cloud Backup</string>\n    <string name=\"settings_encrypt_backup\">Encrypt Backup</string>\n    <string name=\"settings_encrypt_backup_summary\">Encrypt backup file with password</string>\n    <string name=\"settings_launcher_icon\">Launcher Icon</string>\n    <string name=\"settings_restore_backup\">Restore Backup</string>\n    <string name=\"settings_show_disable_all_modules\">Show \\\"Disable All Modules\\\" Button</string>\n    <string name=\"settings_show_disable_all_modules_summary\">Show a button to disable all modules at once on the module page</string>\n    <string name=\"settings_test_webdav\">Test WebDAV Connection</string>\n    <string name=\"settings_webdav_password\">WebDAV Password</string>\n    <string name=\"settings_webdav_url\">WebDAV URL</string>\n    <string name=\"settings_webdav_username\">WebDAV Username</string>\n    <string name=\"theme_download\">Download</string>\n    <string name=\"theme_install\">Install</string>\n    <string name=\"theme_install_failed\">Install failed</string>\n    <string name=\"theme_install_success\">Install successful</string>\n\n    <string name=\"home_stats_more_options\">More options</string>\n    <string name=\"home_stats_kp_label\">KP</string>\n    <string name=\"home_stats_manager_label\">Manager</string>\n    <string name=\"home_device_status_gpu_load\">GPU</string>\n    <string name=\"home_stats_kernel_modules\">Kernel Modules</string>\n    <string name=\"home_stats_apm_modules\">System Modules</string>\n    <string name=\"home_stats_superusers\">Superusers</string>\n\n    <string name=\"kernel_spoof_enabled\">Kernel spoof enabled</string>\n    <string name=\"kernel_spoof_disabled_restored\">Kernel spoof disabled and restored</string>\n    <string name=\"kernel_spoof_failed\">Kernel spoof failed: %d</string>\n    <string name=\"kernel_spoof_applied\">Kernel spoof applied</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <style name=\"Theme.APatch\" parent=\"Theme.AppCompat.DayNight.NoActionBar\">\n        <item name=\"android:windowLayoutInDisplayCutoutMode\" tools:targetApi=\"o_mr1\">shortEdges</item>\n        <item name=\"android:windowIsTranslucent\">false</item>\n    </style>\n\n    <style name=\"Theme.Splash\" parent=\"Theme.SplashScreen\">\n        <item name=\"postSplashScreenTheme\">@style/Theme.APatch</item>\n    </style>\n\n    <style name=\"Theme.APatch.WebUI\" parent=\"Theme.APatch\">\n        <item name=\"android:windowLightStatusBar\">true</item>\n        <item name=\"android:windowLightNavigationBar\" tools:targetApi=\"o_mr1\">true</item>\n    </style>\n\n</resources>"
  },
  {
    "path": "app/src/main/res/values-ar/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\" translatable=\"false\">FolkPatch</string>\n    <string name=\"app_name_fpatch\">FPatch</string>\n\n    <string name=\"desktop_app_name\">اسم تطبيق سطح المكتب</string>\n\n    <string name=\"home\">الرئيسية</string>\n\n    <string name=\"success\">نجاح</string>\n    <string name=\"failure\">فشل</string>\n\n    <string name=\"patch_warnning\">التثبيت ينطوي على مخاطر. يرجى التأكد من نسخ بياناتك احتياطيًا.</string>\n    <string name=\"patch\">تصحيح</string>\n\n    <string name=\"kernel_patch\">تصحيح النواة</string>\n    <string name=\"android_patch\">تصحيح أندرويد</string>\n\n    <string name=\"settings_nav_layout_title\">إعدادات تخطيط التنقل</string>\n    <string name=\"settings_nav_layout_summary\">إخفاء أو إظهار بعض مكونات التنقل</string>\n    <string name=\"settings_nav_scheme\">مخطط التنقل</string>\n    <string name=\"settings_nav_mode\">وضع شريط التنقل</string>\n    <string name=\"settings_nav_mode_summary\">اختر طريقة عرض شريط التنقل</string>\n    <string name=\"settings_nav_mode_auto\">تلقائي تقليدي</string>\n    <string name=\"settings_nav_mode_bottom\">شريط سفلي دائمًا</string>\n    <string name=\"settings_nav_mode_rail\">شريط جانبي دائمًا</string>\n    <string name=\"settings_nav_mode_floating\">شريط سفلي عائم</string>\n    <string name=\"settings_show_apm\">إظهار وحدات النظام</string>\n    <string name=\"settings_show_kpm\">إظهار KPM</string>\n    <string name=\"settings_show_superuser\">إظهار المستخدم المتميز</string>\n    <string name=\"settings_floating_auto_hide\">إخفاء تلقائي</string>\n    <string name=\"settings_floating_auto_hide_summary\">إخفاء الشريط العائم تلقائيًا بعد 3 ثوانٍ بدون تفاعل</string>\n    <string name=\"settings_floating_swipe_hide\">إخفاء بالتمرير</string>\n    <string name=\"settings_floating_swipe_hide_summary\">إخفاء الشريط عند التمرير لأسفل، إظهاره عند التمرير لأعلى</string>\n    <string name=\"settings_navbar_glass_effect\">تأثير الزجاج المصنفر</string>\n    <string name=\"settings_navbar_glass_effect_summary\">تطبيق تأثير الزجاج المصنفر على شريط التنقل العائم</string>\n    <string name=\"settings_navbar_glass_blur_strength\">قوة التمويه</string>\n    <string name=\"settings_navbar_glass_transparency\">شفافية الخلفية</string>\n    <string name=\"settings_navbar_glass_highlight_strength\">شدة التمييز</string>\n    <string name=\"settings_navbar_glass_specular\">انعكاس مرآوي</string>\n    <string name=\"settings_navbar_glass_specular_summary\">تفعيل تمييز يشبه المرآة في أعلى شريط التنقل</string>\n    <string name=\"settings_navbar_glass_inner_glow\">توهج داخلي</string>\n    <string name=\"settings_navbar_glass_inner_glow_summary\">تفعيل تأثير توهج خفيف في أسفل شريط التنقل</string>\n    <string name=\"settings_navbar_glass_border\">إطار زجاجي</string>\n    <string name=\"settings_navbar_glass_border_summary\">تفعيل إطار خفيف حول شريط التنقل</string>\n    <string name=\"settings_stats_top_layout\">تخطيط علوي</string>\n    <string name=\"settings_stats_top_layout_summary\">اختيار نمط بطاقة المعلومات العلوية</string>\n    <string name=\"settings_stats_top_layout_list\">قائمة</string>\n    <string name=\"settings_stats_top_layout_grid\">شبكة</string>\n    <string name=\"settings_block_kernelpatch_update\">حظر تحديث KernelPatch</string>\n    <string name=\"settings_block_kernelpatch_update_summary\">عدم إظهار إشعارات تحديث KernelPatch</string>\n    <string name=\"settings_block_androidpatch_update\">حظر تحديث تصحيح النظام</string>\n    <string name=\"settings_block_androidpatch_update_summary\">عدم إظهار إشعارات تحديث تصحيح النظام (APD)</string>\n    <string name=\"settings_disable_module_update_check\">تعطيل التحقق من تحديث الوحدات</string>\n    <string name=\"settings_disable_module_update_check_summary\">تعطيل التحقق التلقائي من التحديثات للوحدات النظام</string>\n\n    <string name=\"reboot\">إعادة التشغيل</string>\n    <string name=\"settings\">الإعدادات</string>\n    <string name=\"reboot_recovery\">إعادة التشغيل إلى الريكفري</string>\n    <string name=\"reboot_bootloader\">إعادة التشغيل إلى البوتلودر</string>\n    <string name=\"reboot_download\">إعادة التشغيل إلى وضع التنزيل</string>\n    <string name=\"reboot_edl\">إعادة التشغيل إلى وضع EDL</string>\n    <string name=\"reboot_fastbootd\">إعادة التشغيل إلى وضع FastbootD</string>\n    <string name=\"about\">حول</string>\n    <string name=\"developer_and_maintainer\">المطور | المشرف</string>\n    <string name=\"settings_app_language\">اللغة</string>\n    <string name=\"system_default\">افتراضي النظام</string>\n    <string name=\"settings_global_namespace_mode\">وضع مساحة الاسم العام</string>\n    <string name=\"settings_global_namespace_mode_summary\">جميع جلسات الروت تستخدم مساحة اسم المجلدات العامة</string>\n    <string name=\"settings_clear_super_key_dialog\">هل تريد حقًا المتابعة؟</string>\n    <string name=\"settings_grid_working_card_hide_check\">إخفاء أيقونة الحالة</string>\n    <string name=\"settings_grid_working_card_hide_check_summary\">إخفاء علامة الصح أو أيقونة التحذير في بطاقة الحالة</string>\n    <string name=\"settings_grid_working_card_hide_text\">إخفاء نص الحالة</string>\n    <string name=\"settings_grid_working_card_hide_text_summary\">إخفاء نص \"يعمل\" أو \"غير مثبت\" في بطاقة الحالة</string>\n    <string name=\"settings_grid_working_card_hide_mode\">إخفاء وضع العمل</string>\n    <string name=\"settings_grid_working_card_hide_mode_summary\">إخفاء نص \"كامل\" أو \"نصف\" في بطاقة الحالة</string>\n\n    <string name=\"settings_folkx_engine_title\">محرك FolkX للرسوم المتحركة</string>\n    <string name=\"settings_folkx_engine_summary\">استخدام رسوم متحركة زنبركية سلسة قائمة على الفيزياء عند التبديل بين الصفحات الرئيسية</string>\n    <string name=\"settings_folkx_animation_type\">نوع الرسوم المتحركة</string>\n    <string name=\"settings_folkx_animation_speed\">سرعة الرسوم المتحركة</string>\n    <string name=\"settings_folkx_animation_linear\">حركة خطية</string>\n    <string name=\"settings_folkx_animation_spatial\">حركة مكانية</string>\n    <string name=\"settings_folkx_animation_fade\">تلاشي للداخل/للخارج</string>\n    <string name=\"settings_folkx_animation_vertical\">انزلاق عمودي</string>\n    <string name=\"settings_folkx_animation_diagonal\">انزلاق قطري</string>\n\n    <string name=\"su_exclude_all_title\">استثناء الكل</string>\n    <string name=\"su_exclude_all_confirm\">هل أنت متأكد من أنك تريد استثناء جميع التطبيقات التي لا تتطلب صلاحيات الروت؟</string>\n    <string name=\"su_batch_exclude_title\">استثناء دفعة</string>\n    <string name=\"su_batch_exclude_content\">استثناء الحقن لجميع التطبيقات التي لا تتطلب صلاحيات الروت، يرجى تحديد إجراء</string>\n    <string name=\"su_exclude_btn\">استثناء</string>\n    <string name=\"su_exclude_reverse_btn\">عكس</string>\n\n    <!-- SuperUser App Action Dialog -->\n    <string name=\"su_app_action_title\">إجراء التطبيق</string>\n    <string name=\"su_app_action_content\">يرجى تحديد إجراء للتطبيق</string>\n    <string name=\"su_app_action_launch\">تشغيل التطبيق</string>\n    <string name=\"su_app_action_force_stop\">إيقاف إجباري</string>\n    <string name=\"su_app_action_launch_success\">جارٍ تشغيل %s</string>\n    <string name=\"su_app_action_force_stop_success\">تم إيقاف %s إجبارياً</string>\n    <string name=\"su_app_action_failed\">فشلت العملية: %s</string>\n\n    <!-- SU Audit Log -->\n    <string name=\"su_audit_log_title\">سجل التخويل</string>\n    <string name=\"su_audit_log_empty\">لا توجد سجلات تخويل</string>\n    <string name=\"su_audit_log_clear\">مسح السجل</string>\n    <string name=\"su_audit_log_clear_confirm\">تأكيد مسح جميع سجلات التخويل؟</string>\n    <string name=\"su_audit_action_grant\">مُصرح</string>\n    <string name=\"su_audit_action_revoke\">مُلغى</string>\n    <string name=\"su_audit_action_exclude\">مُستثنى</string>\n    <string name=\"su_audit_tab_usage\">سجل الاستخدام</string>\n    <string name=\"su_audit_tab_operations\">سجل العمليات</string>\n\n    <string name=\"home_learn_apatch\">تعلم FolkPatch</string>\n    <string name=\"home_click_to_learn_apatch\">تعرف على ميزات FolkPatch وكيفية استخدامه</string>\n    <string name=\"settings_hide_apatch_card\">إخفاء بطاقة تعلم FolkPatch</string>\n    <string name=\"settings_hide_apatch_card_summary\">إخفاء بطاقة تعلم FolkPatch في الشاشة الرئيسية</string>\n\n    <string name=\"about_source_code\"><![CDATA[<p>عرض الكود المصدري على %1$s<p/>انضم إلى قناتنا على %2$s<p/>انضم إلى مجموعتنا على %3$s]]></string>\n    <string name=\"send_log\">إرسال السجلات</string>\n    <string name=\"save_log\">حفظ السجلات</string>\n    <string name=\"log_saved\">تم حفظ السجلات</string>\n    <string name=\"safe_mode\">الوضع الآمن</string>\n    <string name=\"github\">GitHub</string>\n    <string name=\"telegram\">Telegram</string>\n    <string name=\"support_or_donate\">دعم / تبرع</string>\n\n    <string name=\"super_key\">المفتاح الخارق</string>\n    <string name=\"clear_super_key\">مسح المفتاح الخارق</string>\n    <string name=\"patch_set_superkey\">تعيين المفتاح الخارق</string>\n    <string name=\"home_patch_set_key_desc\">بيانات الاعتماد لـ KernelPatch فقط</string>\n    <string name=\"home_patch_next_step\">الخطوة التالية</string>\n\n    <string name=\"home_not_installed\">غير مثبت</string>\n    <string name=\"home_install_unknown\">غير مثبت أو لم تتم المصادقة</string>\n    <string name=\"home_install_unknown_summary\">انقر للتثبيت</string>\n    <string name=\"home_click_to_install\">انقر للتثبيت</string>\n    <string name=\"home_working\">يعمل</string>\n    <string name=\"home_kp_need_update\">يتوفر إصدار جديد</string>\n    <string name=\"home_kp_cando_update\">تحديث</string>\n\n    <string name=\"home_installing\">جارٍ التثبيت</string>\n\n    <string name=\"kpatch_version\">الإصدار: %s</string>\n    <string name=\"apatch_version\">الإصدار: %s</string>\n\n    <string name=\"kpatch_version_update\" formatted=\"false\">الإصدار: %s -&gt; %s</string>\n    <string name=\"kpatch_shadow_path_title\">kpatch</string>\n    <string name=\"home_auth_key_title\">أدخل المفتاح الخارق</string>\n    <string name=\"home_auth_key_desc\">ابدأ بعد المصادقة</string>\n    <string name=\"home_kpatch_info_title\">معلومات</string>\n\n    <string name=\"home_ap_cando_install\">تثبيت</string>\n\n    <string name=\"home_ap_cando_uninstall\">إلغاء التثبيت</string>\n    <string name=\"home_ap_cando_reboot\">إعادة التشغيل</string>\n\n    <string name=\"patch_title\">تصحيح</string>\n\n    <string name=\"patch_config_title\">التصحيحات</string>\n    <string name=\"patch_mode_bootimg_patch\">الوضع: تصحيح</string>\n    <string name=\"patch_mode_patch_and_install\">الوضع: تصحيح وتثبيت</string>\n    <string name=\"patch_mode_install_to_next_slot\">الوضع: التثبيت في الفتحة غير النشطة (بعد OTA)</string>\n    <string name=\"patch_mode_restore\">الوضع: استعادة</string>\n    <string name=\"patch_mode_uninstall_patch\">الوضع: إلغاء تثبيت KPatch</string>\n    <string name=\"patch_select_bootimg_btn\">تحديد boot</string>\n    <string name=\"patch_embed_kpm_btn\">تضمين KPM</string>\n    <string name=\"patch_start_patch_btn\">بدء</string>\n    <string name=\"patch_start_unpatch_btn\">إلغاء التصحيح</string>\n    <string name=\"patch_item_error\">!!خطأ!!</string>\n    <string name=\"patch_item_bootimg\">bootimg</string>\n    <string name=\"patch_item_bootimg_slot\">الفتحة:</string>\n    <string name=\"patch_item_bootimg_dev\">الجهاز:</string>\n    <string name=\"patch_item_kernel\">النواة</string>\n    <string name=\"patch_item_kpimg\">kpimg</string>\n    <string name=\"patch_item_kpimg_version\">الإصدار:</string>\n    <string name=\"patch_item_kpimg_comile_time\">الوقت:</string>\n    <string name=\"patch_item_kpimg_config\">الإعدادات:</string>\n    <string name=\"patch_item_new_extra_kpm\">تضمين جديد</string>\n    <string name=\"patch_item_existed_extra_kpm\">موجود</string>\n    <string name=\"patch_item_extra_name\">الاسم:</string>\n    <string name=\"patch_item_extra_version\">الإصدار:</string>\n    <string name=\"patch_item_extra_author\">المؤلف:</string>\n    <string name=\"patch_item_extra_kpm_license\">الترخيص:</string>\n    <string name=\"patch_item_extra_kpm_desciption\">الوصف:</string>\n    <string name=\"patch_item_extra_args\">الوسائط:</string>\n    <string name=\"patch_item_extra_event\">الحدث:</string>\n    <string name=\"patch_item_skey\">المفتاح الخارق</string>\n    <string name=\"patch_custom_superkey\">مفتاح خارق مخصص</string>\n    <string name=\"patch_custom_superkey_summary\">لا يزال بإمكانك تعيين مفتاح خارق لإدارة Root والنواة، المدير مفوض بتوقيع مدمج من النواة</string>\n    <string name=\"patch_item_set_skey_label\">يجب أن يتراوح طول المفتاح الخارق بين 8 و 63 حرفًا وأن يتضمن أرقامًا وحروفًا، ولكن بدون أحرف خاصة.</string>\n    <string name=\"patch_confirm_superkey\">تأكيد المفتاح الخارق</string>\n    <string name=\"patch_skey_mismatch\">المفتاح الخارق غير متطابق</string>\n\n    <string name=\"home_kernel\">إصدار النواة</string>\n    <string name=\"home_manager_version\">إصدار المدير</string>\n    <string name=\"home_fingerprint\">البصمة الرقمية</string>\n\n    <string name=\"home_selinux_status\">حالة SELinux</string>\n    <string name=\"home_selinux_status_disabled\">معطل</string>\n    <string name=\"home_selinux_status_enforcing\">مفروض</string>\n    <string name=\"home_selinux_status_permissive\">متساهل</string>\n    <string name=\"home_selinux_status_unknown\">غير معروف</string>\n\n    <string name=\"settings_selinux_mode\">وضع SELinux</string>\n    <string name=\"settings_selinux_mode_summary\">اختر وضع إنفاذ SELinux</string>\n    <string name=\"settings_selinux_mode_enforcing\">مفروض (صارم)</string>\n    <string name=\"settings_selinux_mode_permissive\">متساهل (مرن)</string>\n    <string name=\"settings_selinux_current_mode\">الحالي: %s</string>\n    <string name=\"settings_selinux_mode_enforcing_summary\">SELinux يفرض قواعد الوصول بالكامل</string>\n    <string name=\"settings_selinux_mode_permissive_summary\">SELinux يسجل الانتهاكات فقط، لا يرفض</string>\n\n    <string name=\"home_device_info\">الجهاز</string>\n    <string name=\"home_system_version\">إصدار النظام</string>\n    <string name=\"home_kpatch_version\">إصدار KernelPatch</string>\n    <string name=\"home_su_path\">su القابل للتنفيذ</string>\n    <string name=\"home_apatch_version\">إصدار FolkPatch</string>\n\n    <string name=\"kpm\">وحدة KP</string>\n    <string name=\"kpm_kp_not_installed\">KernelPatch غير مثبت</string>\n    <string name=\"kpm_add_kpm\">إضافة وحدة KP</string>\n    <string name=\"kpm_load\">تحميل</string>\n    <string name=\"kpm_install\">تثبيت</string>\n    <string name=\"kpm_embed\">تضمين</string>\n    <string name=\"kpm_load_toast_succ\">نجح التحميل</string>\n    <string name=\"kpm_load_toast_failed\">فشل التحميل</string>\n    <string name=\"kpm_unload_confirm\">إلغاء تحميل وحدة %s؟</string>\n    <string name=\"kpm_unload\">إلغاء التحميل</string>\n    <string name=\"kpm_control\">تحكم</string>\n    <string name=\"kpm_apm_empty\">لا توجد وحدات محملة</string>\n    <string name=\"kpm_version\">الإصدار</string>\n    <string name=\"kpm_license\">الترخيص</string>\n    <string name=\"kpm_author\">المؤلف</string>\n    <string name=\"kpm_desc\">الوصف</string>\n    <string name=\"kpm_args\">الوسائط</string>\n\n    <string name=\"su_title\">المستخدم الخارق</string>\n    <string name=\"su_selinux_via_hook\">التجاوز عبر الخطاف</string>\n    <string name=\"su_pkg_excluded_label\">استثناء</string>\n    <string name=\"su_pkg_excluded_setting_title\">استثناء التعديلات</string>\n    <string name=\"su_pkg_excluded_setting_summary\">سيمكن تفعيل هذا الخيار FolkPatch من استعادة أي ملفات تم تعديلها بواسطة وحدات هذا التطبيق.</string>\n    <string name=\"su_pkg_root_setting_title\">المستخدم المتميز</string>\n    <string name=\"su_pkg_root_setting_summary\">تفعيل هذا الخيار لمنح صلاحيات المستخدم المتميز لتطبيقك، مما يسمح باستخدام أوامر SU</string>\n    <string name=\"su_pkg_normal_setting_title\">الوضع العادي</string>\n    <string name=\"su_pkg_normal_setting_summary\">لم يتم منح حق الوصول إلى الجذر، يتم تشغيل التطبيق بدون صلاحيات المستخدم المتميز.</string>\n    <string name=\"su_show_system_apps\">إظهار تطبيقات النظام</string>\n    <string name=\"su_hide_system_apps\">إخفاء تطبيقات النظام</string>\n    <string name=\"su_refresh\">تحديث</string>\n\n    <string name=\"apm\">وحدة AP</string>\n    <string name=\"apm_not_installed\">AndroidPatch غير مثبت</string>\n    <string name=\"apm_failed_to_enable\">فشل تمكين الوحدة: %s</string>\n    <string name=\"apm_failed_to_disable\">فشل تعطيل الوحدة: %s</string>\n    <string name=\"apm_empty\">لا توجد وحدات مثبتة</string>\n    <string name=\"apm_remove\">إزالة</string>\n    <string name=\"apm_undo\">تراجع</string>\n    <string name=\"apm_install\">تثبيت</string>\n    <string name=\"apm_uninstall_confirm\">إلغاء تثبيت وحدة %s؟</string>\n    <string name=\"apm_uninstall_success\">تم إلغاء تثبيت %s</string>\n    <string name=\"apm_uninstall_failed\">فشل إلغاء التثبيت: %s</string>\n    <string name=\"apm_undo_uninstall_success\">تم استعادة %s بنجاح</string>\n    <string name=\"apm_undo_uninstall_failed\">فشل الاستعادة: %s</string>\n    <string name=\"apm_version\">الإصدار</string>\n    <string name=\"apm_author\">المؤلف</string>\n    <string name=\"apm_desc\">الوصف</string>\n    <string name=\"apm_overlay_fs_not_available\">الوحدات غير متاحة لأن OverlayFS معطل بواسطة النواة!</string>\n    <string name=\"apm_magisk_conflict\">الوحدات غير متاحة بسبب تعارض مع Magisk!</string>\n    <string name=\"apm_mount_warning_title\">إشعار مهم</string>\n    <string name=\"apm_mount_warning_message\">الوحدات غير مثبتة افتراضياً. يرجى استخدام نظام التركيب المدمج أو الوحدات الفوقية.</string>\n    <string name=\"apm_mount_warning_button\">فهمت</string>\n    <string name=\"apm_reboot_to_apply\">أعد التشغيل للتطبيق</string>\n    <string name=\"apm_changelog\">سجل التغييرات</string>\n    <string name=\"apm_update\">تحديث</string>\n    <string name=\"apm_downloading\">جارٍ تنزيل الوحدة: %s</string>\n    <string name=\"apm_start_downloading\">بدء التنزيل: %s</string>\n    <string name=\"apm_new_version_available\">الإصدار الجديد %s متاح، انقر للترقية.</string>\n\n    <string name=\"hide_apatch_manager\">إخفاء مدير APatch</string>\n    <string name=\"hide_apatch_manager_summary\">تثبيت تطبيق وكيل بمعرف حزمة عشوائي وتسمية تطبيق مخصصة</string>\n    <string name=\"hide_apatch_dialog_new_manager_name\">اسم المدير الجديد</string>\n    <string name=\"hide_apatch_dialog_summary\">سيتم استخدامه كتسمية التطبيق الجديدة المعروضة في اللانشر</string>\n    <string name=\"hide_apatch_manager_failure\">فشل الإخفاء. يرجى الإبلاغ عن الخطأ!</string>\n\n    <string name=\"setting_reset_su_path\">إعادة تعيين مسار su</string>\n    <string name=\"setting_reset_su_new_path\">المسار الكامل الجديد</string>\n\n    <string name=\"apm_webui_open\">فتح</string>\n    <string name=\"apm_action\">إجراء</string>\n    <string name=\"module_shortcut_add\">اختصار</string>\n    <string name=\"module_shortcut_not_supported\">المشغل لا يدعم اختصارات سطح المكتب.</string>\n    <string name=\"module_shortcut_created\">تم إنشاء اختصار على سطح المكتب.</string>\n    <string name=\"module_shortcut_updated\">تم تحديث الاختصار.</string>\n    <string name=\"module_shortcut_permission_tip_xiaomi\">الرجاء تفعيل \"إنشاء اختصارات سطح المكتب\" لهذا التطبيق في إعدادات Xiaomi.</string>\n    <string name=\"module_shortcut_permission_tip_oppo\">الرجاء تفعيل \"اختصار سطح المكتب\" لهذا التطبيق في إعدادات OPPO.</string>\n    <string name=\"module_shortcut_permission_tip_default\">إذا فشل إنشاء الاختصار، الرجاء تفعيل إذن اختصار سطح المكتب لهذا التطبيق في إعدادات النظام.</string>\n    <string name=\"module_shortcut_name\">اسم الاختصار</string>\n    <string name=\"module_shortcut_icon\">أيقونة الاختصار</string>\n    <string name=\"module_shortcut_type\">نوع الاختصار</string>\n    <string name=\"module_shortcut_type_webui\">WebUI</string>\n    <string name=\"module_shortcut_type_action\">Action</string>\n    <string name=\"module_shortcut_icon_default\">استخدام الأيقونة الافتراضية</string>\n    <string name=\"module_shortcut_icon_select\">اختيار أيقونة</string>\n    <string name=\"enable_web_debugging\">تمكين تصحيح أخطاء WebView</string>\n    <string name=\"enable_web_debugging_summary\">يمكن استخدامه لتصحيح أخطاء واجهة المستخدم الرسومية للويب. يرجى تمكينه عند الحاجة فقط.</string>\n    <string name=\"settings_apm_install_confirm\">تأكيد قبل التثبيت</string>\n    <string name=\"settings_apm_install_confirm_summary\">إظهار مربع حوار تأكيد قبل تثبيت الوحدات</string>\n    <string name=\"settings_show_more_module_info\">إظهار تفاصيل الوحدة</string>\n    <string name=\"settings_show_more_module_info_summary\">إظهار معرف الوحدة وحجمها في قائمة الوحدات</string>\n    <string name=\"settings_apm_stay_on_page\">البقاء في صفحة العملية</string>\n    <string name=\"settings_apm_stay_on_page_summary\">عدم الرجوع تلقائيًا بعد تنفيذ إجراء الوحدة</string>\n    <string name=\"settings_enable_module_shortcut_add\">تمكين زر إضافة اختصار</string>\n    <string name=\"settings_enable_module_shortcut_add_summary\">إظهار زر إضافة اختصار واجهة المستخدم الرسومية للويب</string>\n    <string name=\"settings_module_sort_optimization\">تحسين فرز الوحدات</string>\n    <string name=\"settings_module_sort_optimization_summary\">جعل وحدات النظام مع واجهة المستخدم الرسومية للويب والإجراء تظهر في الأعلى</string>\n    <string name=\"settings_fold_system_module\">طي وحدات النظام</string>\n    <string name=\"settings_fold_system_module_summary\">انقر على بطاقة الوحدة للتوسيع/الطي الإجراءات</string>\n        <string name=\"settings_simple_list_bottom_bar\">شريط سفلي بسيط</string>\n        <string name=\"settings_simple_list_bottom_bar_summary\">استخدم أزرار الأيقونات فقط لإجراءات الوحدة، مستوحى من APatch</string>\n    <string name=\"settings_spliced_card_group\">Spliced Card Group</string>\n    <string name=\"settings_spliced_card_group_summary\">Use M3E continuous card group style for module list</string>\n    <string name=\"apm_install_confirm_title\">تثبيت الوحدة</string>\n    <string name=\"apm_install_confirm_content\">هل أنت متأكد من أنك تريد تثبيت %s؟</string>\n    <string name=\"apm_disable_all_title\">تعطيل كل الوحدات</string>\n    <string name=\"apm_disable_all_confirm\">هل أنت متأكد من أنك تريد تعطيل جميع الوحدات؟ سيقوم هذا بتعطيل جميع الوحدات المثبتة.</string>\n    <string name=\"apm_enable_module_banner\">تمكين لافتة الوحدة</string>\n    <string name=\"apm_enable_module_banner_summary\">عرض صورة اللافتة للوحدات إذا كانت متوفرة</string>\n    <string name=\"apm_enable_folk_banner\">تخصيص لافتة الوحدة</string>\n    <string name=\"apm_enable_folk_banner_summary\">اضغط مطولًا على بطاقة الوحدة لاختيار صورة اللافتة</string>\n    <string name=\"apm_folk_banner_title\">FolkBanner</string>\n    <string name=\"apm_folk_banner_select\">اختيار صورة</string>\n    <string name=\"apm_folk_banner_clear\">مسح FolkBanner</string>\n    <string name=\"apm_folk_banner_saved\">تم حقن FolkBanner لـ %s.</string>\n    <string name=\"apm_folk_banner_cleared\">تم مسح FolkBanner لـ %s.</string>\n    <string name=\"apm_folk_banner_failed\">فشل تحديث FolkBanner لـ %s.</string>\n    <string name=\"apm_banner_api_mode\">وضع API</string>\n    <string name=\"apm_banner_api_mode_summary\">استخدام API للصور العشوائية أو دليل محلي للافتات الوحدات</string>\n    <string name=\"apm_banner_api_source\">تكوين مصدر API</string>\n    <string name=\"apm_banner_api_source_hint\">أدخل عنوان API أو المسار المحلي</string>\n    <string name=\"apm_banner_api_source_saved\">تم حفظ مصدر API</string>\n    <string name=\"apm_banner_api_source_not_configured\">غير مكوّن</string>\n    <string name=\"apm_banner_api_url_configured\">عنوان API مكوّن</string>\n    <string name=\"apm_banner_local_dir_configured\">الدليل المحلي مكوّن</string>\n    <string name=\"apm_banner_clear_cache\">مسح ذاكرة التخزين المؤقت</string>\n    <string name=\"apm_banner_cache_cleared\">تم مسح ذاكرة التخزين المؤقت للافتات</string>\n    <string name=\"apm_banner_api_config_title\">تكوين مصدر API</string>\n    <string name=\"apm_banner_api_config_desc\">أدخل عنوان API للصور العشوائية أو مسار دليل محلي. ستتلقى كل وحدة لافتة فريدة.</string>\n    <string name=\"apm_banner_api_examples_title\">أمثلة:</string>\n    <string name=\"apm_banner_api_examples\">API: https://picsum.photos/800/400\\nمحلي: /sdcard/Pictures/Banners</string>\n    <!-- سوق واجهة برمجة التطبيقات -->\n    <string name=\"apm_api_marketplace_title\">سوق واجهة برمجة التطبيقات</string>\n    <string name=\"apm_api_preview\">معاينة</string>\n    <string name=\"apm_api_apply\">تطبيق</string>\n    <string name=\"apm_api_preview_title\">معاينة واجهة برمجة التطبيقات</string>\n    <string name=\"apm_api_preview_failed\">فشل تحميل المعاينة</string>\n    <string name=\"apm_api_url_label\">عنوان واجهة برمجة التطبيقات:</string>\n    <string name=\"apm_api_apply_success\">تم تطبيق مصدر واجهة برمجة التطبيقات بنجاح</string>\n    <string name=\"apm_api_verifying\">جاري التحقق…</string>\n    <string name=\"apm_api_retry\">إعادة المحاولة</string>\n    <string name=\"apm_api_empty\">لا تتوفر مصادر واجهة برمجة التطبيقات</string>\n    <string name=\"settings_banner_custom_opacity\">عتامة اللافتة</string>\n    <string name=\"settings_banner_custom_opacity_summary\">استخدام عتامة مخصصة لللافتات بدلاً من اتباع وضع خلفية الشاشة</string>\n    <string name=\"settings_banner_opacity\">شفافية اللافتة</string>\n\n    <string name=\"settings_donot_store_superkey\">عدم تخزين المفتاح الخارق محليًا</string>\n    <string name=\"settings_donot_store_superkey_summary\">المصادقة على المفتاح الخارق في كل مرة يبدأ فيها المدير\n</string>\n\n    <string name=\"mode_select_page_title\">تثبيت</string>\n    <string name=\"mode_select_page_patch_and_install\">تصحيح وتثبيت</string>\n    <string name=\"mode_select_page_select_file\">حدد صورة boot لتصحيحها</string>\n    <string name=\"restore_select_file\">حدد ملف قسم boot لاستعادته</string>\n    <string name=\"mode_select_page_install_inactive_slot\">التثبيت في الفتحة غير النشطة (بعد OTA)</string>\n    <string name=\"mode_select_page_install_inactive_slot_warning\">سيتم **إجبار** جهازك على الإقلاع إلى الفتحة غير النشطة الحالية بعد إعادة التشغيل!\\nاستخدم هذا الخيار فقط بعد الانتهاء من OTA.\\nهل تريد المتابعة؟</string>\n    <string name=\"mode_select_page_select_kpimg\">استخدام ملف ترقيع محلي (KPimg)</string>\n    <string name=\"patch_custom_kpimg_label\">KPimg مخصص</string>\n    <string name=\"patch_custom_kpimg_file\">الملف: %s</string>\n    <string name=\"patch_select_kpimg_btn\">اختيار KPimg</string>\n\n    <string name=\"home_dialog_auth_fail_title\">فشلت المصادقة</string>\n    <string name=\"home_dialog_auth_fail_content\">تعذر المصادقة على المفتاح الخارق، مما يؤدي إلى فشل تنشيط FolkPatch.\nفيما يلي بعض الأسباب المحتملة لفشل المصادقة—يرجى التحقق من السبب الذي ينطبق عليك:\n\n\n\\n1. لم تستخدم KernelPatch لتصحيح boot.img على الإطلاق، أو نسيت ما فعلته بالفعل.\n\\n2. boot.img المصحح لا يزال نائمًا على جهاز الكمبيوتر الخاص بك، ولم يتم وميضه في الجهاز أبدًا.\n\\n3. تم إدخال المفتاح الخارق بشكل غير صحيح، أو يحتوي على رموز غامضة، مثل أحرف من لغات كوكبية.\n\\n4. قد يكون جهازك غير متوافق مع FolkPatch و KernelPatch، والمحاولات القسرية بلا طائل.\n\\n5. قد تكون قد قمت ببعض العمليات الغامضة—مثل استخدام وحدات معينة تستبعد أسماء الحزم التي منعت FolkPatch، مما أدى إلى إخراج نفسك من اللعبة.\n\n</string>\n\n    <string name=\"home_more_menu_feedback_or_suggestion\">ملاحظات أو اقتراحات</string>\n    <string name=\"home_more_menu_about\">حول</string>\n    <string name=\"home_more_menu_document\">توثيق</string>\n\n    <string name=\"home_dialog_uninstall_title\">إلغاء التثبيت</string>\n    \n    <string name=\"home_dialog_uninstall_ap_only\">إلغاء تثبيت التصحيح فقط</string>\n    <string name=\"home_dialog_uninstall_all\">إلغاء التثبيت بالكامل</string>\n    <string name=\"home_dialog_uninstall_ap_only_desc\">إزالة AndroidPatch فقط والاحتفاظ بالمدير.</string>\n    <string name=\"home_dialog_uninstall_all_desc\">إزالة AndroidPatch وبدء عملية إلغاء التثبيت الكاملة.</string>\n\n    <string name=\"kpm_control_dialog_title\">التحكم في KPM</string>\n    <string name=\"kpm_control_dialog_content\">الرجاء إدخال معلمات التحكم:</string>\n    <string name=\"kpm_control_paramters\">المعلمات</string>\n    <string name=\"kpm_control_outMsg\">الناتج</string>\n    <string name=\"kpm_control_ok\">نجاح!</string>\n    <string name=\"kpm_control_failed\">فشل!</string>\n    \n    <string name=\"settings_night_mode_follow_sys\">السمة الداكنة تتبع النظام</string>\n    <string name=\"settings_night_mode_follow_sys_summary\">تبديل السمة الداكنة تلقائيًا بناءً على إعدادات النظام</string>\n    <string name=\"settings_night_theme_enabled\">السمة الداكنة</string>\n    \n    <string name=\"settings_use_system_color_theme\">سمة ألوان النظام</string>\n    <string name=\"settings_use_system_color_theme_summary\">استخدام سمة الألوان التي تم إنشاؤها من خلفية الشاشة بواسطة النظام</string>\n    <string name=\"settings_custom_color_theme\">سمة اللون</string>\n\n    <string name=\"amber_theme\">عنبري</string>\n    <string name=\"blue_theme\">أزرق</string>\n    <string name=\"blue_grey_theme\">أزرق رمادي</string>\n    <string name=\"brown_theme\">بني</string>\n    <string name=\"cyan_theme\">سماوي</string>\n    <string name=\"deep_orange_theme\">برتقالي داكن</string>\n    <string name=\"deep_purple_theme\">أرجواني داكن</string>\n    <string name=\"green_theme\">أخضر</string>\n    <string name=\"indigo_theme\">نيلي</string>\n    <string name=\"light_blue_theme\">أزرق فاتح</string>\n    <string name=\"light_green_theme\">أخضر فاتح</string>\n    <string name=\"lime_theme\">ليموني</string>\n    <string name=\"orange_theme\">برتقالي</string>\n    <string name=\"pink_theme\">وردي</string>\n    <string name=\"purple_theme\">أرجواني</string>\n    <string name=\"red_theme\">أحمر</string>\n    <string name=\"sakura_theme\">ساكورا</string>\n    <string name=\"teal_theme\">أزرق مخضر</string>\n    <string name=\"yellow_theme\">أصفر</string>\n    <string name=\"theme_color\">لون السمة</string>\n    <string name=\"theme_light\">فاتح</string>\n    <string name=\"theme_dark\">داكن</string>\n    <string name=\"theme_system\">النظام</string>\n    <string name=\"loading_modules\">جارٍ تحميل الوحدات…</string>\n    <string name=\"loading_scripts\">جارٍ البحث عن البرامج النصية…</string>\n    <string name=\"loading_themes\">جارٍ تحميل السمات…</string>\n    <string name=\"loading_apis\">جارٍ تحميل مصادر واجهة برمجة التطبيقات…</string>\n\n    <string name=\"about_app_desc\">تطبيق Root مبني على KernelPatch، يسمح بخطاف وظائف النواة دون إعادة تجميع النواة.</string>\n    <string name=\"about_powered_by\">مدعوم من %1$s</string>\n    <string name=\"about_telegram_group\">مجموعة تيليجرام</string>\n    \n    <!-- Online Module Strings -->\n    <string name=\"online_module_title\">وحدة النظام عبر الإنترنت</string>\n    <string name=\"online_module_download_start\">بدء التنزيل: %s</string>\n    <string name=\"online_module_download_complete\">اكتمل التنزيل: %s</string>\n    <string name=\"online_module_download_notification\">جارٍ تنزيل %s. يرجى التحقق من لوحة الإشعارات للتقدم ومجلد التنزيل للملف المكتمل.</string>\n\n    <!-- Online KPM Strings -->\n    <string name=\"online_kpm_title\">وحدات النواة عبر الإنترنت</string>\n    <string name=\"online_kpm_download_start\">بدء التنزيل: %s</string>\n    <string name=\"online_kpm_download_complete\">اكتمل التنزيل: %s</string>\n    <string name=\"online_kpm_download_notification\">جارٍ تنزيل %s. يرجى التحقق من لوحة الإشعارات للتقدم ومجلد التنزيل للملف المكتمل。</string>\n\n    <!-- Online Script Strings -->\n    <string name=\"online_script_title\">سكربتات عبر الإنترنت</string>\n    <string name=\"online_script_download_start\">بدء التنزيل: %s</string>\n    <string name=\"online_script_download_complete\">اكتمل التنزيل: %s</string>\n    <string name=\"online_script_download_notification\">جارٍ تنزيل %s</string>\n\n    <!-- Crash Handle Strings -->\n    <string name=\"crash_handle_title\">تقرير التعطل</string>\n    <string name=\"crash_handle_copy\">نسخ</string>\n\n    <!-- About Screen Strings -->\n    <string name=\"about_app_version\">إصدار التطبيق: %s</string>\n    <string name=\"about_github\">GitHub</string>\n    <string name=\"about_telegram_channel\">قناة تيليجرام</string>\n\n    <string name=\"settings_app_dpi\">DPI التطبيق</string>\n    <string name=\"dpi_apply_settings\">تطبيق</string>\n    <string name=\"dpi_confirm_title\">تأكيد تغيير DPI</string>\n    <string name=\"dpi_confirm_message\">تغيير DPI التطبيق من %1$s إلى %2$s؟</string>\n    <string name=\"settings_alt_icon\">أيقونة بديلة</string>\n    <string name=\"alt_icon_summary\">استخدم أيقونة المشغل البديلة</string>\n    <string name=\"settings_magic_mount\">Folk Mount API</string>\n    <string name=\"settings_magic_mount_summary\">تفعيل نظام تركيب الوحدات المدمج</string>\n    <string name=\"settings_new_app_profile_mode\">الوضع الافتراضي للتطبيقات الجديدة</string>\n    <string name=\"settings_new_app_profile_normal\">بدون صلاحية</string>\n    <string name=\"settings_new_app_profile_root\">صلاحية الجذر</string>\n    <string name=\"settings_new_app_profile_exclude\">استبعاد</string>\n    <string name=\"settings_hide_service\">FolkPatch Hide</string>\n    <string name=\"settings_hide_service_summary\">إخفاء حالة فتح قفل محمل الإقلاع إلى حد ما</string>\n    <string name=\"settings_app_title\">عنوان التطبيق</string>\n    <string name=\"app_title_custom\">مخصص</string>\n    <string name=\"settings_custom_app_title\">تعيين اسم التطبيق</string>\n    <string name=\"custom_app_title_dialog_title\">اسم التطبيق المخصص</string>\n    <string name=\"custom_app_title_dialog_hint\">أدخل اسم التطبيق</string>\n    <string name=\"custom_app_title_dialog_confirm\">تأكيد</string>\n    <string name=\"custom_app_title_dialog_empty\">لا يمكن أن يكون الاسم فارغاً</string>\n    <string name=\"cancel\">إلغاء</string>\n    <string name=\"settings_custom_background\">خلفية مخصصة</string>\n    <string name=\"settings_custom_background_enabled\">تمكين الخلفية المخصصة</string>\n    <string name=\"settings_custom_background_opacity\">شفافية البطاقة</string>\n    <string name=\"settings_custom_background_blur\">تمويه الخلفية</string>\n    <string name=\"settings_custom_background_dim\">تعتيم الخلفية</string>\n    <string name=\"settings_custom_background_dual_dim\">تمكين تعتيم مزدوج لليوم/الليل</string>\n    <string name=\"settings_custom_background_dual_dim_desc\">ضبط تعتيم الخلفية تلقائيًا للسمات الفاتحة والداكنة</string>\n    <string name=\"settings_custom_background_day_dim\">تعتيم الخلفية في وضع النهار</string>\n    <string name=\"settings_custom_background_night_dim\">تعتيم الخلفية في وضع الليل</string>\n    <string name=\"settings_multi_background_mode\">وضع الخلفيات المتعددة</string>\n    <string name=\"settings_multi_background_mode_summary\">تعيين صور خلفية مختلفة لصفحات مختلفة</string>\n    <string name=\"settings_select_home_background\">خلفية الصفحة الرئيسية</string>\n    <string name=\"settings_select_kernel_background\">خلفية وحدة النواة</string>\n    <string name=\"settings_select_superuser_background\">خلفية المستخدم الخارق</string>\n    <string name=\"settings_select_system_module_background\">خلفية وحدة النظام</string>\n    <string name=\"settings_select_settings_background\">خلفية الإعدادات</string>\n\n    <string name=\"settings_advanced_title_style\">نمط العنوان المتقدم</string>\n    <string name=\"settings_advanced_title_style_summary\">استبدال العنوان العلوي بصورة مخصصة</string>\n    <string name=\"settings_advanced_title_style_enabled\">تمكين نمط العنوان المتقدم</string>\n    <string name=\"settings_select_title_image\">تحديد صورة العنوان</string>\n    <string name=\"settings_title_image_selected\">تم تحديد صورة العنوان</string>\n    <string name=\"settings_title_image_saved\">تم حفظ صورة العنوان بنجاح</string>\n    <string name=\"settings_title_image_error\">فشل حفظ صورة العنوان</string>\n    <string name=\"settings_clear_title_image\">مسح صورة العنوان</string>\n    <string name=\"settings_clear_title_image_confirm\">هل أنت متأكد من أنك تريد مسح صورة العنوان؟</string>\n    <string name=\"settings_title_image_cleared\">تم مسح صورة العنوان</string>\n    <string name=\"settings_title_image_day_opacity\">شفافية وضع النهار</string>\n    <string name=\"settings_title_image_night_opacity\">شفافية وضع الليل</string>\n    <string name=\"settings_title_image_dim\">تعتيم الخلفية</string>\n    <string name=\"settings_title_image_offset_x\">الموضع الأفقي</string>\n\n\n    <!-- Font Settings -->\n    <string name=\"settings_custom_font\">خط مخصص</string>\n    <string name=\"settings_select_font_file\">تحديد ملف الخط</string>\n    <string name=\"settings_custom_font_enabled\">تمكين الخط المخصص</string>\n    <string name=\"settings_custom_font_summary\">استخدام خط TTF مخصص للتطبيق</string>\n    <string name=\"settings_font_selected\">تم تحديد الخط المخصص</string>\n    <string name=\"settings_custom_font_error\">فشل حفظ ملف الخط</string>\n    <string name=\"settings_custom_font_saved\">تم حفظ الخط المخصص بنجاح</string>\n    <string name=\"settings_clear_font\">استعادة الخط الافتراضي</string>\n    <string name=\"settings_clear_font_confirm\">هل أنت متأكد من أنك تريد استعادة الخط الافتراضي؟</string>\n    <string name=\"settings_font_cleared\">تمت استعادة الخط الافتراضي</string>\n    <string name=\"settings_font_select_hint\">حدد ملف خط TTF</string>\n    <string name=\"settings_select_background_image\">تحديد صورة الخلفية</string>\n    <string name=\"settings_custom_background_summary\">تعيين صورة خلفية مخصصة</string>\n    <string name=\"settings_custom_background_saved\">تم حفظ الخلفية بنجاح</string>\n    <string name=\"settings_custom_background_error\">فشل حفظ الخلفية</string>\n    <string name=\"settings_background_selected\">تم تحديد الخلفية</string>\n    <string name=\"settings_background_image_cleared\">تم مسح صورة الخلفية</string>\n    <string name=\"settings_clear_background\">مسح الخلفية</string>\n    <string name=\"settings_clear_background_confirm\">هل أنت متأكد من أنك تريد مسح صورة الخلفية؟</string>\n\n    <string name=\"settings_launcher_icon\">أيقونة اللانشر</string>\n\n    <!-- Video Background Settings -->\n    <string name=\"settings_video_background\">خلفية الفيديو</string>\n    <string name=\"settings_video_background_summary\">استخدام فيديو كخلفية</string>\n    <string name=\"settings_select_video\">تحديد فيديو</string>\n    <string name=\"settings_video_selected\">تم تحديد الفيديو</string>\n    <string name=\"settings_clear_video_background\">مسح خلفية الفيديو</string>\n    <string name=\"settings_clear_video_background_confirm\">هل أنت متأكد من أنك تريد مسح خلفية الفيديو؟</string>\n    <string name=\"settings_video_background_enabled\">تمكين خلفية الفيديو</string>\n    <string name=\"settings_video_volume\">مستوى صوت الفيديو</string>\n    \n    <!-- KPM AutoLoad Strings -->\n    <string name=\"kpm_autoload_title\">التحميل التلقائي لـ KPM</string>\n    <string name=\"kpm_autoload_enabled\">تمكين التحميل التلقائي</string>\n    <string name=\"kpm_autoload_enabled_summary\">تحميل وحدات KPM تلقائيًا عند بدء تشغيل الجهاز</string>\n    <string name=\"kpm_autoload_json_config\">تكوين JSON</string>\n    <string name=\"kpm_autoload_json_label\">تكوين JSON</string>\n<string name=\"kpm_autoload_json_placeholder\">{\n  \"enabled\": true,\n  \"kpmPaths\": [\n    \"/path/to/module1.kpm\",\n    \"/path/to/module2.kpm\"\n  ]\n}</string>\n    <string name=\"kpm_autoload_json_error\">تنسيق JSON غير صالح</string>\n    <string name=\"kpm_autoload_json_helper\">أدخل JSON صالح مع مصفوفة من مسارات ملفات KPM</string>\n    <string name=\"kpm_autoload_save\">حفظ التكوين</string>\n    <string name=\"kpm_autoload_save_confirm\">حفظ تكوين التحميل التلقائي؟</string>\n    <string name=\"kpm_autoload_save_success\">تم حفظ التكوين بنجاح</string>\n    <string name=\"kpm_autoload_save_failed\">فشل حفظ التكوين</string>\n    <string name=\"kpm_autoload_visual_mode\">الوضع المرئي</string>\n    <string name=\"kpm_autoload_json_mode\">وضع JSON</string>\n    <string name=\"kpm_autoload_add_kpm\">إضافة KPM</string>\n    <string name=\"kpm_autoload_remove_kpm\">إزالة</string>\n    <string name=\"kpm_autoload_edit_kpm\">Editar</string>\n    <string name=\"kpm_autoload_edit_dialog_title\">Editar KPM</string>    <string name=\"kpm_autoload_event_label\">Evento:</string>    <string name=\"kpm_autoload_args_label\">Argumentos:</string>    <string name=\"kpm_autoload_event_service\">service</string>    <string name=\"kpm_autoload_event_post_fs_data\">post-fs-data</string>\n    \n    <!-- Module Backup/Restore Strings -->\n    <string name=\"apm_backup_title\">النسخ الاحتياطي للوحدات</string>\n    <string name=\"apm_restore_title\">استعادة الوحدات</string>\n    <string name=\"apm_backup_success\">نجح النسخ الاحتياطي</string>\n    <string name=\"apm_restore_success\">نجحت الاستعادة</string>\n    <string name=\"apm_backup_failed\">فشل النسخ الاحتياطي</string>\n    <string name=\"apm_restore_failed\">فشلت الاستعادة</string>\n    <string name=\"apm_backup_failed_msg\">فشل النسخ الاحتياطي: %s</string>\n    <string name=\"apm_restore_failed_msg\">فشلت الاستعادة: %s</string>\n    <string name=\"apm_copy_list_title\">نسخ القائمة</string>\n    <string name=\"apm_copy_list_success\">تم نسخ قائمة الوحدات</string>\n\n    <!-- APM Bulk Install Strings -->\n    <string name=\"apm_bulk_install_title\">التثبيت المجمع لوحدات النظام</string>\n    <string name=\"apm_bulk_install_list_title\">قائمة الوحدات</string>\n    <string name=\"apm_bulk_install_add\">إضافة وحدات</string>\n    <string name=\"apm_bulk_install_empty\">لم تتم إضافة أي وحدات</string>\n    <string name=\"apm_bulk_install_action\">تثبيت مجمع</string>\n    <string name=\"apm_bulk_install_log_title\">سجل التثبيت المجمع</string>\n    <string name=\"apm_bulk_install_log_start\">بدء التثبيت المجمع...</string>\n    <string name=\"apm_bulk_install_log_installing\">جارٍ تثبيت %1$s</string>\n    <string name=\"apm_batch_install_full_process\">عملية التثبيت الدفعي الكاملة</string>\n    <string name=\"apm_batch_install_full_process_summary\">يتم إجراء التثبيت الدفعي لمواد النظام باستخدام العملية الكاملة</string>\n    <string name=\"next_module\">الوحدة التالية</string>\n    <string name=\"apm_bulk_install_log_installed\">تم تثبيت الوحدة %s.</string>\n    <string name=\"apm_bulk_install_log_done\">اكتملت جميع العمليات.</string>\n    <string name=\"apm_bulk_install_first_use_text\">تتيح لك هذه الميزة تثبيت وحدات متعددة في وقت واحد. إنها طريقة تثبيت سريعة مناسبة للوحدات التي لا تتضمن عمليات مفتاحية أثناء التثبيت. بالنسبة للوحدات التي تتطلب تفاعلات مفاتيح الصوت، يرجى تفعيل وضع التثبيت الكامل في الإعدادات.</string>\n    <string name=\"apm_bulk_install_remove\">إزالة</string>\n    <string name=\"apm_first_use_title\">مرحبًا بك في وحدات النظام</string>\n    <string name=\"apm_first_use_text\">مرحبًا بك في وحدات النظام، يستخدم هذا وحدات متوافقة مع نظام Magisk البيئي. انقر فوق الزر في الزاوية اليمنى السفلية لتثبيت الوحدات. يمكنك أيضًا استخدام المثبت في الأعلى لتثبيت الوحدات بشكل مجمع. يتم توفير ميزة لنسخ احتياطي لجميع الوحدات بنقرة واحدة أيضًا، ولكن لاحظ أن هذا الحل قد لا يكون مناسبًا لجميع الوحدات، لذلك يجب عليك عمل نسخ احتياطية خاصة بك.</string>\n\n    <string name=\"kpm_autoload_kpm_list_title\">وحدات KPM</string>\n    <string name=\"kpm_autoload_no_kpm_added\">لم تتم إضافة أي وحدات KPM</string>\n    <string name=\"kpm_autoload_kpm_path\">مسار KPM</string>\n    <string name=\"kpm_autoload_file_not_found\">لم يتم العثور على الملف</string>\n    <string name=\"kpm_autoload_select_kpm_file\">تحديد ملف KPM</string>\n    <string name=\"kpm_autoload_first_time_title\">حول التحميل التلقائي لـ KPM</string>\n    <string name=\"kpm_autoload_first_time_message\">تتيح لك هذه الميزة تحميل جميع KPMs المكونة مؤقتًا عند الإقلاع. هذا النهج أكثر ملاءمة من التضمين مباشرة في النواة.\n\nعلى سبيل المثال، لا يمكن استخدام KPMs التي تمنع تعديل القسم إلا مؤقتًا، حيث قد يتسبب التضمين في فشل الإقلاع. أو إذا كنت لا ترغب في تعديل قسم BOOT، يمكنك استخدام هذا التكوين لتحميل الوحدات بسرعة.\n\nيرجى التأكد من إغلاق التطبيق بالكامل وإعادة فتحه لتنفيذ الأوامر. بشكل عام، سيتم تحميله أيضًا عند الإقلاع. من الطبيعي أن يكون لدى المدير شاشة سوداء لفترة من الوقت! يستخدم هذا طريقة تحميل خلفية خالصة، تذكر أن تسحب لأسفل يدويًا للتحديث والتحقق مما إذا كانت الوحدات قد تم تحميلها بشكل صحيح!</string>\n    <string name=\"kpm_autoload_first_time_confirm\">فهمت</string>\n    <string name=\"kpm_autoload_do_not_show_again\">لا تظهر مرة أخرى</string>\n\n    <!-- Hide Settings Strings -->\n    <string name=\"home_hide_su_path\">إخفاء مسار su القابل للتنفيذ</string>\n    <string name=\"home_hide_kpatch_version\">إخفاء إصدار تصحيح النواة</string>\n    <string name=\"home_hide_fingerprint\">إخفاء البصمة الرقمية</string>\n    <string name=\"home_hide_zygisk\">إخفاء تنفيذ Zygisk</string>\n    <string name=\"home_hide_mount\">إخفاء تنفيذ التثبيت</string>\n    <string name=\"home_hide_su_path_summary\">إخفاء مسار su القابل للتنفيذ في بطاقة المعلومات</string>\n    <string name=\"home_hide_kpatch_version_summary\">إخفاء إصدار تصحيح النواة في بطاقة المعلومات</string>\n    <string name=\"home_hide_zygisk_summary\">إخفاء تنفيذ Zygisk في بطاقة المعلومات</string>\n    <string name=\"home_hide_mount_summary\">إخفاء تنفيذ التثبيت في بطاقة المعلومات</string>\n    <string name=\"home_hide_fingerprint_summary\">إخفاء البصمة الرقمية في بطاقة المعلومات</string>\n\n    <string name=\"kpm_page_first_time_title\">تحذير</string>\n    <string name=\"kpm_page_first_time_message\">تعدل وحدات النواة مباشرة تنفيذ Boot. على عكس وحدات النظام، فإنها تفتقر إلى آلية استرداد جيدة. في حالة حدوث مشكلات، يمكنك إصلاحها فقط عن طريق الدخول إلى Fastboot. يوصى بتحميل الوحدة أولاً للتأكد من عدم وجود مشاكل قبل تضمينها في Boot. إذا كان لا يمكن استخدام الوحدة إلا عن طريق التحميل، فيمكنك تجربة ميزة تحميل وحدة KPM التلقائية. إذا كنت لا تفهم وحدات النواة، فالرجاء عدم استخدام هذه الميزة!</string>\n    \n    \n    <!-- Auto Backup Strings -->\n    <string name=\"settings_auto_backup_module\">النسخ الاحتياطي التلقائي لوحدات النظام</string>\n    <string name=\"settings_auto_backup_module_summary\">النسخ الاحتياطي التلقائي لملف الوحدة إلى الدليل الخاص عند التثبيت</string>\n    <string name=\"settings_open_backup_dir\">فتح دليل النسخ الاحتياطي</string>\n    <string name=\"backup_dir_empty\">دليل النسخ الاحتياطي فارغ</string>\n    <string name=\"backup_dir_open_failed\">فشل فتح دليل النسخ الاحتياطي</string>\n    <string name=\"auto_backup_failed\">فشل النسخ الاحتياطي التلقائي: %s</string>\n    <string name=\"auto_backup_success\">نجح النسخ الاحتياطي التلقائي: %s</string>\n    <string name=\"settings_auto_backup_boot\">النسخ الاحتياطي التلقائي لـ Boot</string>\n    <string name=\"settings_auto_backup_boot_summary\">النسخ الاحتياطي التلقائي لـ Boot إلى التخزين المحلي (Downloads/FolkPatch/BootBackups)</string>\n\n    <!-- Home Layout Strings -->\n    <string name=\"settings_home_layout_style\">نمط تخطيط الشاشة الرئيسية</string>\n    <string name=\"settings_home_layout_default\">واجهة قائمة</string>\n    <string name=\"settings_home_layout_grid\">واجهة شبكة</string>\n    <string name=\"settings_home_layout_focus\">واجهة تركيز</string>\n    <string name=\"settings_home_layout_sign\">واجهة إشارة</string>\n    <string name=\"settings_home_layout_circle\">واجهة دائرية</string>\n    <string name=\"settings_home_layout_dashboard_pro\">واجهة لوحة معلومات</string>\n    <string name=\"unofficial_version_title\">إصدار غير رسمي</string>\n    <string name=\"unofficial_version_message\">أنت تستخدم FolkPatch وهو ليس تطبيقًا رسميًا، يرجى تنزيل التطبيق الرسمي!</string>\n    <string name=\"go_to_github\">اذهب إلى Github</string>\n    <string name=\"settings_save_theme\">حفظ السمة</string>\n    <string name=\"settings_import_theme\">استيراد السمة</string>\n    <string name=\"settings_theme_saved\">تم حفظ السمة</string>\n    <string name=\"settings_theme_imported\">تم استيراد السمة</string>\n    <string name=\"settings_theme_save_failed\">فشل حفظ السمة</string>\n    <string name=\"settings_theme_import_failed\">فشل استيراد السمة</string>\n    <string name=\"settings_reset_theme\">إعادة تعيين السمة</string>\n    <string name=\"settings_reset_theme_confirm\">هل أنت متأكد من أنك تريد إعادة تعيين جميع إعدادات السمة إلى القيم الافتراضية؟ سؤؤدي هذا إلى مسح جميع الخلفيات والخطوط والموسيقى والمؤثرات الصوتية المخصصة.</string>\n    <string name=\"settings_theme_reset\">تمت إعادة تعيين السمة</string>\n    <string name=\"settings_theme_reset_failed\">فشل في إعادة تعيين السمة</string>\n\n    <!-- Theme Strings -->\n    <string name=\"theme_export_title\">تصدير السمة</string>\n    <string name=\"theme_import_title\">استيراد السمة</string>\n    <string name=\"theme_name\">اسم السمة</string>\n    <string name=\"theme_type\">نوع السمة</string>\n    <string name=\"theme_type_phone\">هاتف</string>\n    <string name=\"theme_type_tablet\">جهاز لوحي</string>\n    <string name=\"theme_version\">الإصدار</string>\n    <string name=\"theme_author\">المؤلف</string>\n    <string name=\"theme_description\">الوصف</string>\n    <string name=\"theme_export_action\">تصدير</string>\n    <string name=\"theme_import_action\">استيراد</string>\n    <string name=\"theme_import_confirm\">هل أنت متأكد من أنك تريد استيراد هذه السمة؟</string>\n    <string name=\"theme_info\">معلومات السمة</string>\n\n    <!-- Grid Working Card Background -->\n    <string name=\"settings_grid_working_card_background\">خلفية بطاقة العمل</string>\n    <string name=\"settings_grid_working_card_background_enabled\">تمكين خلفية البطاقة المخصصة</string>\n    <string name=\"settings_grid_working_card_background_summary\">صورة خلفية مخصصة لبطاقة العمل</string>\n    <string name=\"settings_grid_working_card_background_selected\">تم تحديد الخلفية</string>\n    <string name=\"settings_grid_working_card_dual_opacity\">تمكين شفافية مزدوجة لليوم/الليل</string>\n    <string name=\"settings_grid_working_card_dual_opacity_desc\">ضبط شفافية البطاقة تلقائيًا للسمات الفاتحة والداكنة</string>\n    <string name=\"settings_grid_working_card_day_opacity\">شفافية البطاقة في وضع النهار</string>\n    <string name=\"settings_grid_working_card_night_opacity\">شفافية البطاقة في وضع الليل</string>\n    <string name=\"settings_clear_grid_working_card_background\">مسح خلفية البطاقة</string>\n    <string name=\"settings_clear_grid_working_card_background_confirm\">هل أنت متأكد من أنك تريد مسح صورة خلفية البطاقة؟</string>\n    <string name=\"settings_grid_working_card_background_saved\">تم حفظ خلفية البطاقة بنجاح</string>\n    <string name=\"settings_grid_working_card_background_error\">فشل حفظ خلفية البطاقة</string>\n    <string name=\"settings_grid_working_card_background_cleared\">تم مسح خلفية البطاقة</string>\n\n    <!-- Biometric Strings -->\n    <string name=\"action_biometric\">المصادقة البيومترية</string>\n    <string name=\"msg_biometric\">يرجى التحقق من هويتك البيومترية</string>\n    <string name=\"settings_biometric_login\">المصادقة البيومترية</string>\n    <string name=\"settings_biometric_login_summary\">تتطلب مصادقة بيومترية عند فتح التطبيق</string>\n    <string name=\"settings_strong_biometric\">المصادقة البيومترية القوية</string>\n    <string name=\"settings_strong_biometric_summary\">تتطلب المصادقة عند تثبيت/إلغاء تثبيت/تعطيل الوحدات</string>\n\n    <!-- Theme Store Strings -->\n    <string name=\"theme_store_title\">متجر السمات</string>\n    <string name=\"theme_source_official\">رسمي</string>\n    <string name=\"theme_source_third_party\">طرف ثالث</string>\n    <string name=\"theme_source_local\">محلي</string>\n    <string name=\"theme_store_author\">المؤلف: %s</string>\n    <string name=\"theme_store_version\">الإصدار: %s</string>\n    <string name=\"theme_source\">المصدر</string>\n    <string name=\"theme_store_download_failed\">فشل التنزيل</string>\n    <string name=\"theme_store_download\">تنزيل</string>\n    <string name=\"theme_store_search_hint\">ابحث عن سمات...</string>\n\n    <!-- Update Check Strings -->\n    <string name=\"settings_auto_update_check\">التحقق التلقائي من التحديثات</string>\n    <string name=\"settings_auto_update_check_summary\">التحقق تلقائيًا من وجود تحديثات عند بدء تشغيل التطبيق</string>\n    <string name=\"settings_check_update\">التحقق من وجود تحديثات</string>\n    <string name=\"update_available_title\">التحديث متاح</string>\n    <string name=\"update_available_message\">تم اكتشاف أن إصدارك منخفض جدًا، هل تريد تنزيل الإصدار الجديد؟</string>\n    <string name=\"update_action\">تحديث</string>\n    <string name=\"update_close\">إغلاق</string>\n    <string name=\"update_latest\">أنت على أحدث إصدار</string>\n    <string name=\"update_error\">خطأ في التحقق من وجود تحديثات</string>\n    <string name=\"settings_category_general\">عام</string>\n    <string name=\"settings_app_list_loading_scheme\">مخطط تحميل قائمة التطبيقات</string>\n    <string name=\"settings_app_list_loading_scheme_summary\">اختر كيفية تحميل قائمة التطبيقات</string>\n    <string name=\"app_list_loading_scheme_root_service\">RootService (الافتراضي)</string>\n    <string name=\"app_list_loading_scheme_package_manager\">PackageManager (واجهة برمجة تطبيقات النظام)</string>\n    <string name=\"app_list_loading_scheme_dialog_title\">مخطط التحميل</string>\n    <string name=\"settings_category_appearance\">المظهر</string>\n    <string name=\"settings_appearance_font\">إعدادات الخط</string>\n    <string name=\"settings_appearance_font_summary\">تكوين الخط المخصص</string>\n    <string name=\"settings_appearance_theme\">إعدادات المظهر</string>\n    <string name=\"settings_appearance_theme_summary\">متجر المواضيع وحفظ واستيراد وإعادة تعيين</string>\n    <string name=\"settings_appearance_banner\">إعدادات اللافتة</string>\n    <string name=\"settings_appearance_banner_summary\">تكوين لافتة الوحدة</string>\n    <string name=\"settings_appearance_layout\">إعدادات التخطيط</string>\n    <string name=\"settings_appearance_layout_summary\">تخطيط الصفحة الرئيسية والتنقل وتخصيص البطاقة</string>\n    <string name=\"settings_appearance_background\">إعدادات الخلفية</string>\n    <string name=\"settings_appearance_background_summary\">خلفية مخصصة وفيديو والعديد من الخلفيات</string>\n    <string name=\"settings_appearance_night_mode\">إعدادات الوضع الليلي</string>\n    <string name=\"settings_appearance_night_mode_summary\">السمة الداكنة وتكوين الألوان</string>\n    <string name=\"settings_amoled_theme\">سمة AMOLED السوداء</string>\n    <string name=\"settings_amoled_theme_desc\">خلفية سوداء بالكامل للوضع الداكن</string>\n    <string name=\"settings_switch_icon\">تمكين مؤشر الزر</string>\n    <string name=\"settings_switch_icon_desc\">إظهار أيقونات الحالة على أزرار التبديل</string>\n    <string name=\"settings_category_behavior\">السلوك</string>\n    <string name=\"settings_category_function\">الوظيفة</string>\n    <string name=\"settings_use_legacy_su_page\">تفويض المستخدم المتميز لصفحة واحدة</string>\n    <string name=\"settings_use_legacy_su_page_summary\">تم تبديل صفحة المستخدم المتميز إلى تصميم تفويض لصفحة واحدة</string>\n    <string name=\"settings_category_module\">الوحدات</string>\n    <string name=\"settings_category_security\">الأمان</string>\n    \n    <!-- Background Music Strings -->\n    <string name=\"settings_background_music\">موسيقى الخلفية</string>\n    <string name=\"settings_background_music_summary\">تشغيل موسيقى الخلفية عندما يكون التطبيق في المقدمة</string>\n    <string name=\"settings_background_music_playing\">قيد التشغيل: %s</string>\n    <string name=\"settings_background_music_enabled\">تمكين موسيقى الخلفية</string>\n    <string name=\"settings_select_music_file\">تحديد ملف موسيقى</string>\n    <string name=\"settings_music_selected\">تم تحديد الموسيقى</string>\n    <string name=\"settings_clear_music\">مسح الموسيقى</string>\n    <string name=\"settings_clear_music_confirm\">هل أنت متأكد من أنك تريد مسح موسيقى الخلفية؟</string>\n    <string name=\"settings_music_auto_play\">تشغيل تلقائي</string>\n    <string name=\"settings_music_auto_play_summary\">تشغيل الموسيقى تلقائيًا عند فتح التطبيق</string>\n    <string name=\"settings_music_looping\">تشغيل متكرر</string>\n    <string name=\"settings_music_looping_summary\">تكرار الأغنية الحالية</string>\n    <string name=\"settings_music_volume\">مستوى الصوت</string>\n    <string name=\"settings_music_saved\">تم حفظ ملف الموسيقى</string>\n    <string name=\"settings_music_save_error\">فشل حفظ ملف الموسيقى</string>\n    <string name=\"settings_music_cleared\">تم مسح الموسيقى</string>\n    <string name=\"settings_category_multimedia\">الوسائط المتعددة</string>\n    <string name=\"settings_category_general_summary\">اللغة، التحديثات، SELinux، تعديلات النظام</string>\n    <string name=\"settings_category_appearance_summary\">السمة، الألوان، التخطيط، الخلفية، الخطوط</string>\n    <string name=\"settings_category_behavior_summary\">تصحيح أخطاء الويب، سلوك التثبيت، عرض الشاشة الرئيسية</string>\n    <string name=\"settings_category_security_summary\">القياسات الحيوية، إدارة المفاتيح الفائقة</string>\n    <string name=\"settings_category_backup_summary\">نسخ احتياطي محلي، سحابي، WebDAV</string>\n    <string name=\"settings_category_module_summary\">معلومات الوحدات، الفرز، التثبيت الدفعي</string>\n    <string name=\"settings_category_function_summary\">إخفاء FolkPatch، خدمة Umount</string>\n    <string name=\"settings_category_multimedia_summary\">موسيقى الخلفية، الأصوات، الاهتزاز</string>\n    <string name=\"settings_music_playback_control\">التحكم في التشغيل</string>\n\n    <!-- Theme Store Filter -->\n    <string name=\"theme_store_filter_title\">تصفية السمات</string>\n    <string name=\"theme_store_filter_author\">المؤلف</string>\n    <string name=\"theme_store_filter_author_hint\">أدخل اسم المؤلف</string>\n    <string name=\"theme_store_filter_source\">المصدر</string>\n    <string name=\"theme_store_filter_source_all\">الكل</string>\n    <string name=\"theme_store_filter_type\">نوع الجهاز</string>\n    <string name=\"theme_store_filter_apply\">تطبيق</string>\n    <string name=\"theme_store_filter_reset\">إعادة تعيين</string>\n\n    <!-- File Picker -->\n    <string name=\"file_picker_permission_required\">الإذن مطلوب</string>\n    <string name=\"file_picker_permission_desc\">لتصفح الملفات، يرجى منح إذن \"الوصول إلى جميع الملفات\".</string>\n    <string name=\"file_picker_grant_permission\">منح الإذن</string>\n    <string name=\"file_picker_cancel\">إلغاء</string>\n    <string name=\"file_picker_internal_storage\">التخزين الداخلي</string>\n    <string name=\"file_picker_no_files\">لا توجد ملفات</string>\n\n    <!-- HomeV3 Strings -->\n    <string name=\"home_device_status_title\">حالة الجهاز</string>\n    <string name=\"home_device_status_battery_temp\">درجة حرارة البطارية</string>\n    <string name=\"home_device_status_cpu_load\">تحميل وحدة المعالجة المركزية</string>\n    <string name=\"home_device_status_battery_level\">مستوى البطارية</string>\n    <string name=\"home_storage_title\">التخزين</string>\n    <string name=\"home_storage_internal\">التخزين الداخلي</string>\n    <string name=\"home_storage_ram\">ذاكرة الوصول العشوائي</string>\n    <string name=\"home_storage_zram\">ZRAM</string>\n    <string name=\"home_storage_swap\">ملف المبادلة</string>\n    <string name=\"home_info_kernel\">النواة</string>\n    <string name=\"home_info_superkey\">المفتاح الخارق</string>\n    <string name=\"home_info_auth_auth\">مصادقة</string>\n    <string name=\"home_info_auth_na\">غير متاح</string>\n    <string name=\"home_info_device_slot\">فتحة الجهاز</string>\n    <string name=\"home_info_device_model\">طراز الجهاز</string>\n    <string name=\"home_info_running_mode\">وضع التشغيل</string>\n    <string name=\"home_info_mode_full\">كامل</string>\n    <string name=\"home_info_mode_half\">نصف</string>\n    <string name=\"home_version\">النسخة: %s</string>\n    <string name=\"home_zygisk_implement\">تنفيذ Zygisk</string>\n    <string name=\"home_mount_implement\">تنفيذ التثبيت</string>\n\n    <string name=\"settings_list_card_hide_status_badge\">استخدم الإيموجي الكلاسيكي</string>\n    <string name=\"settings_list_card_hide_status_badge_summary\">شارة الحالة في الصفحة الرئيسية تتحول إلى إيموجي كلاسيكي</string>\n    <string name=\"settings_custom_badge_text\">نص الشارة المخصص</string>\n    <string name=\"settings_custom_badge_text_summary\">تعديل عرض نص الشارة، للمتعة فقط</string>\n\n    <!-- Install Mode Select -->\n    <string name=\"kp_install_methods\">تصحيح/تثبيت KernelPatch</string>\n    <string name=\"restore_boot_methods\">حدد boot لاستعادته إلى قسم boot</string>\n\n    <string name=\"su_backup_list\">قائمة النسخ الاحتياطي</string>\n    <string name=\"su_restore_list\">قائمة الاستعادة</string>\n    <string name=\"backup_success\">تم النسخ الاحتياطي بنجاح</string>\n    <!-- Badge Count Settings -->\n    <string name=\"enable_badge_count\">إعدادات عدد الشارات</string>\n    <string name=\"enable_badge_count_summary\">تكوين عرض عدد الشارات لعناصر التنقل</string>\n    <string name=\"badge_superuser\">إظهار شارة المستخدم المتميز</string>\n    <string name=\"badge_apm\">إظهار شارة وحدات النظام</string>\n    <string name=\"badge_kernel\">إظهار شارة وحدة النواة</string>\n    <string name=\"settings_sound_effect\">Sound Effect</string>\n    <string name=\"settings_sound_effect_summary\">Play sound effect on click</string>\n    <string name=\"settings_sound_effect_enabled\">Enabled</string>\n    <string name=\"settings_sound_effect_playing\">Selected: %s</string>\n    <string name=\"settings_sound_effect_source\">مصدر الصوت</string>\n    <string name=\"settings_sound_effect_source_local\">ملف محلي</string>\n    <string name=\"settings_sound_effect_source_preset\">مسبق</string>\n    <string name=\"settings_sound_effect_preset_title\">أصوات مسبقة</string>\n    <string name=\"settings_select_sound_effect\">Select Sound Effect</string>\n    <string name=\"settings_sound_effect_selected\">Sound effect file selected</string>\n    <string name=\"settings_sound_effect_scope\">Effect Scope</string>\n    <string name=\"settings_sound_effect_scope_global\">Global (Anywhere)</string>\n    <string name=\"settings_sound_effect_scope_bottom_bar\">Bottom Bar Only</string>\n    <string name=\"settings_clear_sound_effect\">Clear Sound Effect</string>\n    <string name=\"settings_clear_sound_effect_confirm\">Are you sure you want to clear the sound effect?</string>\n    <string name=\"settings_sound_effect_cleared\">Sound effect cleared</string>\n\n    <string name=\"settings_startup_sound\">صوت بدء التشغيل</string>\n    <string name=\"settings_startup_sound_summary\">تشغيل صوت عند بدء التطبيق</string>\n    <string name=\"settings_startup_sound_enabled\">صوت بدء التشغيل مفعّل</string>\n    <string name=\"settings_startup_sound_playing\">قيد التشغيل: %s</string>\n    <string name=\"settings_select_startup_sound\">اختر صوت بدء التشغيل</string>\n    <string name=\"settings_startup_sound_selected\">تم اختيار صوت بدء التشغيل</string>\n    <string name=\"settings_clear_startup_sound\">مسح صوت بدء التشغيل</string>\n    <string name=\"settings_clear_startup_sound_confirm\">هل أنت متأكد أنك تريد مسح صوت بدء التشغيل؟</string>\n    <string name=\"settings_startup_sound_cleared\">تم مسح صوت بدء التشغيل</string>\n\n    <string name=\"settings_enable_cloud_backup_summary\">النسخ الاحتياطي تلقائيًا من السحابة</string>\n    <string name=\"settings_configure_webdav\">تكوين خدمة WebDAV</string>\n    <string name=\"webdav_config_title\">تكوين WebDAV</string>\n    <string name=\"webdav_url\">رابط WebDAV</string>\n    <string name=\"webdav_username\">اسم المستخدم</string>\n    <string name=\"webdav_password\">كلمة المرور</string>\n    <string name=\"webdav_uploading\">جاري الرفع إلى WebDAV...</string>\n    <string name=\"webdav_backup_success\">نجح النسخ الاحتياطي عبر WebDAV</string>\n    <string name=\"webdav_backup_failed\">فشل النسخ الاحتياطي عبر WebDAV: %s</string>\n    <string name=\"save\">حفظ</string>\n    <string name=\"test\">اختبار</string>\n    <string name=\"webdav_path_label\">المسار (مثال /Backup)</string>\n    <string name=\"webdav_view_logs\">عرض السجلات</string>\n    <string name=\"webdav_backup_logs_title\">سجلات النسخ الاحتياطي</string>\n    <string name=\"webdav_no_logs\">لا توجد سجلات</string>\n    <string name=\"webdav_clear_logs\">مسح</string>\n    <string name=\"settings_enable_local_backup\">تفعيل النسخ الاحتياطي المحلي</string>\n    <string name=\"settings_enable_local_backup_summary\">نسخ احتياطي إلى التخزين المحلي (Downloads/FolkPatch/ModuleBackups)</string>\n    <string name=\"close\">إغلاق</string>\n\n    <!-- Backup Settings -->\n    <string name=\"settings_category_backup\">إعدادات النسخ الاحتياطي</string>\n    <string name=\"settings_enable_cloud_backup\">تفعيل النسخ الاحتياطي السحابي</string>\n    <string name=\"settings_test_webdav\">اختبار اتصال WebDAV</string>\n    <string name=\"webdav_test_success\">نجح الاختبار</string>\n    <string name=\"webdav_test_failed\">فشل الاختبار: %s</string>\n    <string name=\"patch_output_written_to\"> تم التصحيح بنجاح، تم كتابة ملف الإخراج إلى </string>\n    <string name=\"patch_write_failed\"> فشل تصحيح boot.img</string>\n\n    <!-- Script Library Strings -->\n    <string name=\"script_library\">مكتبة السكربتات</string>\n    <string name=\"script_library_title\">مكتبة السكربتات</string>\n    <string name=\"script_library_empty\">لا توجد سكربتات، اضغط على زر الإضافة</string>\n    <string name=\"script_library_add\">إضافة سكربت</string>\n    <string name=\"script_library_add_title\">إضافة سكربت</string>\n    <string name=\"script_library_select_file\">اختيار ملف السكربت</string>\n    <string name=\"script_library_alias\">الاسم المستعار</string>\n    <string name=\"script_library_alias_hint\">أدخل اسم السكربت (اختياري)</string>\n    <string name=\"script_library_run\">تشغيل</string>\n    <string name=\"script_library_delete\">حذف</string>\n    <string name=\"script_library_path\">المسار: %s</string>\n    <string name=\"script_library_confirm_delete\">تأكيد حذف السكربت؟</string>\n    <string name=\"script_library_delete_success\">تم حذف السكربت بنجاح</string>\n    <string name=\"script_library_delete_failed\">فشل حذف السكربت</string>\n    <string name=\"script_library_add_success\">تمت إضافة السكربت بنجاح</string>\n    <string name=\"script_library_add_failed\">فشل إضافة السكربت: %s</string>\n    <string name=\"script_library_load_failed\">فشل تحميل مكتبة السكربتات</string>\n    <string name=\"script_library_output\">مخرجات التنفيذ</string>\n    <string name=\"script_library_no_output\">لا توجد مخرجات</string>\n    <string name=\"script_library_save_log\">حفظ السجل</string>\n    <string name=\"script_library_log_saved\">تم حفظ السجل في %s</string>\n    <string name=\"script_library_log_save_failed\">فشل حفظ السجل: %s</string>\n    <string name=\"settings_vibration\">الاهتزاز واللمس</string>\n    <string name=\"settings_vibration_summary\">الاهتزاز عند اللمس</string>\n    <string name=\"settings_vibration_enabled\">تفعيل الاهتزاز</string>\n    <string name=\"settings_vibration_intensity\">شدة الاهتزاز</string>\n    <string name=\"settings_vibration_scope\">نطاق الاهتزاز</string>\n    <string name=\"settings_vibration_scope_global\">عالمي (في كل مكان)</string>\n    <string name=\"settings_vibration_scope_bottom_bar\">الشريط السفلي فقط</string>\n    <string name=\"superuser\">المستخدم الخارق</string>\n    <string name=\"module\">وحدة</string>\n    <string name=\"search_modules\">بحث في الوحدات...</string>\n    <string name=\"search_scripts\">بحث في السكربتات...</string>\n\n    <!-- Umount Service -->\n    <string name=\"settings_umount_service\">Umount Service</string>\n    <string name=\"settings_umount_service_summary\">تكوين نقاط التثبيت للإلغاء التلقائي عند التشغيل</string>\n    <string name=\"umount_config_title\">تكوين Umount</string>\n    <string name=\"umount_config_enabled\">تفعيل Umount</string>\n    <string name=\"umount_config_enabled_summary\">الإلغاء التلقائي لنقاط التثبيت المحددة عند التشغيل</string>\n    <string name=\"umount_config_paths_label\">مسارات نقاط التثبيت (واحد في كل سطر)</string>\n    <string name=\"umount_config_paths_placeholder\">/system/vendor/app\\n/system/vendor/priv-app</string>\n    <string name=\"umount_config_paths_helper\">أدخل مسار نقطة تثبيت واحد في كل سطر لإلغاء التثبيت</string>\n    <string name=\"umount_config_save\">حفظ</string>\n    <string name=\"umount_config_save_confirm\">تأكيد حفظ التكوين؟</string>\n    <string name=\"umount_config_save_success\">تم حفظ التكوين بنجاح</string>\n    <string name=\"umount_config_save_failed\">فشل حفظ التكوين</string>\n\n    <!-- صفحة سماتي -->\n    <string name=\"my_themes_title\">سماتي</string>\n    <string name=\"my_themes_empty\">لا توجد سمات بعد</string>\n    <string name=\"my_themes_empty_action\">تصفح متجر السمات</string>\n    <string name=\"my_themes_apply\">تطبيق السمة</string>\n    <string name=\"my_themes_delete\">حذف السمة</string>\n    <string name=\"my_themes_delete_confirm\">هل أنت متأكد من حذف هذه السمة؟</string>\n    <string name=\"my_themes_deleted\">تم حذف السمة</string>\n    <string name=\"my_themes_applied\">تم تطبيق السمة</string>\n    <string name=\"my_themes_apply_failed\">فشل تطبيق السمة</string>\n    <string name=\"my_themes_details\">تفاصيل السمة</string>\n\n    <!-- حوار التنزيل -->\n    <string name=\"theme_download_title\">جارٍ تنزيل السمة</string>\n    <string name=\"theme_download_completed\">اكتمل التنزيل</string>\n    <string name=\"theme_download_failed\">فشل التنزيل</string>\n    <string name=\"theme_download_progress\">التقدم</string>\n    <string name=\"theme_download_file\">ملف السمة</string>\n    <string name=\"theme_download_image\">صورة المعاينة</string>\n    <string name=\"theme_download_cancel\">إلغاء</string>\n    <string name=\"theme_download_pause\">إيقاف مؤقت</string>\n    <string name=\"theme_download_resume\">استئناف</string>\n    <string name=\"theme_download_retry\">إعادة المحاولة</string>\n    <string name=\"theme_download_apply\">تطبيق السمة</string>\n    <string name=\"theme_download_go_to_my_themes\">سماتي</string>\n    <string name=\"theme_download_retrying\">إعادة المحاولة (%d/3)...</string>\n    <string name=\"theme_download_preparing\">جارٍ تحضير التنزيل...</string>\n    <string name=\"theme_download_downloading_file\">جارٍ تنزيل ملف السمة...</string>\n    <string name=\"theme_download_downloading_image\">جارٍ تنزيل صورة المعاينة...</string>\n    <string name=\"theme_download_finalizing\">جارٍ الانتهاء...</string>\n    <string name=\"settings_predictive_back\">إيماءة العودة التنبؤية</string>\n    <string name=\"settings_predictive_back_summary\">تمكين رسوم إيماءة العودة التنبؤية المتحركة في Android 14+</string>\n\n    <string name=\"home_device_status_battery_charging\">جارٍ الشحن</string>\n    <string name=\"home_device_status_cpu_temp\">درجة حرارة المعالج</string>\n    <string name=\"home_device_status_memory_trend\">اتجاه الذاكرة</string>\n    <string name=\"home_storage_partitions\">أقسام التخزين</string>\n    <string name=\"home_device_status_cpu_freq\">تردد المعالج</string>\n    <string name=\"home_network_rx\">تنزيل</string>\n    <string name=\"home_network_tx\">رفع</string>\n    <string name=\"settings_home_layout_stats\">StatsUI</string>\n    <string name=\"home_stats_more_options\">خيارات أخرى</string>\n    <string name=\"home_stats_kp_label\">KP</string>\n    <string name=\"home_stats_manager_label\">المدير</string>\n    <string name=\"home_device_status_gpu_load\">GPU</string>\n    <string name=\"home_stats_kernel_modules\">وحدات النواة</string>\n    <string name=\"home_stats_apm_modules\">وحدات النظام</string>\n    <string name=\"home_stats_superusers\">المستخدمون الخارقون</string>\n    <string name=\"settings_kernel_spoof\">تكوين التمويه للنواة</string>\n    <string name=\"settings_kernel_spoof_summary\">تمويه إصدار النواة ووقت البناء</string>\n    <string name=\"settings_kernel_spoof_version\">إصدار النواة</string>\n    <string name=\"settings_kernel_spoof_build_time\">وقت بناء النواة</string>\n    <string name=\"settings_kernel_spoof_restore\">استعادة</string>\n\n    <string name=\"kernel_spoof_enabled\">تم تفعيل تمويه النواة</string>\n    <string name=\"kernel_spoof_disabled_restored\">تم تعطيل تمويه النواة واستعادتها</string>\n    <string name=\"kernel_spoof_failed\">فشل تمويه النواة: %d</string>\n    <string name=\"kernel_spoof_applied\">تم تطبيق تمويه النواة</string>\n\n    <string name=\"settings_path_hide\">إخفاء المسار</string>\n    <string name=\"settings_path_hide_summary\">إخفاء الملفات والأدلة من التطبيقات على مستوى النواة</string>\n    <string name=\"path_hide_paths_label\">المسارات المخفية (واحد في كل سطر)</string>\n\n    <string name=\"path_hide_paths_helper\">أدخل مساراً واحداً في كل سطر. المسارات المتطابقة ستعيد ENOENT.</string>\n    <string name=\"path_hide_save\">حفظ</string>\n    <string name=\"path_hide_enabled\">تم تفعيل إخفاء المسار</string>\n    <string name=\"path_hide_disabled\">تم تعطيل إخفاء المسار</string>\n    <string name=\"path_hide_applied\">تم تطبيق إعدادات إخفاء المسار</string>\n    <string name=\"path_hide_failed\">فشلت عملية إخفاء المسار: %d</string>\n    <string name=\"path_hide_uid_mode\">وضع تنفيذ UID</string>\n    <string name=\"path_hide_uid_mode_summary\">إخفاء المسارات لتطبيقات محددة فقط بواسطة UID</string>\n    <string name=\"path_hide_uids_label\">معرّفات UID المستهدفة (واحد في كل سطر)</string>\n    <string name=\"path_hide_uids_helper\">أدخل معرّفات UID للتطبيقات. فقط هذه التطبيقات سيتم إخفاء مساراتها.</string>\n    <string name=\"path_hide_uid_save\">حفظ معرّفات UID</string>\n    <string name=\"path_hide_uid_mode_enabled\">تم تفعيل وضع تنفيذ UID</string>\n    <string name=\"path_hide_uid_mode_disabled\">تم تعطيل وضع تنفيذ UID</string>\n    <string name=\"path_hide_filter_system\">تصفية معرّفات UID النظام</string>\n    <string name=\"path_hide_filter_system_summary\">إخفاء المسارات أيضًا من عمليات الجذر والنظام (UID &lt; 10000)</string>\n    <string name=\"path_hide_filter_system_enabled\">تم تفعيل تصفية معرّفات UID النظام</string>\n    <string name=\"path_hide_filter_system_disabled\">تم تعطيل تصفية معرّفات UID النظام</string>\n    <string name=\"path_hide_filter_system_warning_title\">تحذير</string>\n    <string name=\"path_hide_filter_system_warning_message\">سيؤدي التفعيل إلى إخفاء المسارات أيضًا من عمليات الجذر والنظام (UID &lt; 10000). قد يتسبب ذلك في خلل في بعض وظائف النظام. يرجى الحذر.</string>\n    <string name=\"path_hide_uid_applied\">تم تطبيق القائمة البيضاء لـ UID</string>\n    <string name=\"path_hide_select_apps\">اختر التطبيقات</string>\n    <string name=\"path_hide_no_apps_selected\">لم يتم اختيار أي تطبيق</string>\n    <string name=\"path_hide_app_removed\">تمت إزالة إعدادات %d تطبيق تم إلغاء تثبيته</string>\n    <string name=\"path_hide_search_apps\">البحث عن التطبيقات…</string>\n    <string name=\"path_hide_show_system\">عرض تطبيقات النظام</string>\n    <string name=\"netisolate_title\">عزل الشبكة</string>\n    <string name=\"netisolate_enable\">تم تفعيل عزل الشبكة</string>\n    <string name=\"netisolate_disable\">تم تعطيل عزل الشبكة</string>\n    <string name=\"netisolate_enable_summary\">حظر الوصول إلى الشبكة للتطبيقات المحددة على مستوى النواة</string>\n    <string name=\"netisolate_uids_label\">التطبيقات المحظورة</string>\n    <string name=\"netisolate_uids_hint\">أدخل معرّفات UID للحظر (واحد لكل سطر)</string>\n    <string name=\"netisolate_no_uids\">لا توجد تطبيقات محظورة</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\" translatable=\"false\">FolkPatch</string>\n    <string name=\"app_name_fpatch\">FPatch</string>\n\n    <string name=\"desktop_app_name\">Nombre de la aplicación de escritorio</string>\n\n    <string name=\"home\">Inicio</string>\n\n    <string name=\"success\">Éxito</string>\n    <string name=\"failure\">Fallo</string>\n\n    <string name=\"patch_warnning\">La instalación conlleva riesgos. Por favor, asegúrate de tener una copia de seguridad de tus datos.</string>\n    <string name=\"patch\">Parchear</string>\n\n    <string name=\"kernel_patch\">KernelPatch</string>\n    <string name=\"android_patch\">AndroidPatch</string>\n\n    <string name=\"settings_nav_layout_title\">Configuración del diseño de navegación</string>\n    <string name=\"settings_nav_layout_summary\">Ocultar o mostrar algunos componentes de navegación</string>\n    <string name=\"settings_nav_scheme\">Esquema de navegación</string>\n    <string name=\"settings_nav_mode\">Modo de barra de navegación</string>\n    <string name=\"settings_nav_mode_summary\">Elige cómo se muestra la barra de navegación</string>\n    <string name=\"settings_nav_mode_auto\">Automático Tradicional</string>\n    <string name=\"settings_nav_mode_bottom\">Siempre barra inferior</string>\n    <string name=\"settings_nav_mode_rail\">Siempre barra lateral</string>\n    <string name=\"settings_nav_mode_floating\">Barra inferior flotante</string>\n    <string name=\"settings_show_apm\">Mostrar módulos del sistema</string>\n    <string name=\"settings_show_kpm\">Mostrar KPM</string>\n    <string name=\"settings_show_superuser\">Mostrar SuperUsuario</string>\n    <string name=\"settings_floating_auto_hide\">Ocultar automáticamente</string>\n    <string name=\"settings_floating_auto_hide_summary\">Ocultar automáticamente la barra flotante tras 3 segundos de inactividad</string>\n    <string name=\"settings_floating_swipe_hide\">Ocultar al deslizar</string>\n    <string name=\"settings_floating_swipe_hide_summary\">Ocultar la barra al deslizar hacia abajo, mostrar al deslizar hacia arriba</string>\n    <string name=\"settings_navbar_glass_effect\">Efecto de cristal esmerilado</string>\n    <string name=\"settings_navbar_glass_effect_summary\">Aplicar efecto de cristal esmerilado a la barra de navegación flotante</string>\n    <string name=\"settings_navbar_glass_blur_strength\">Intensidad de desenfoque</string>\n    <string name=\"settings_navbar_glass_transparency\">Transparencia del fondo</string>\n    <string name=\"settings_navbar_glass_highlight_strength\">Intensidad del resplandor</string>\n    <string name=\"settings_navbar_glass_specular\">Reflejo especular</string>\n    <string name=\"settings_navbar_glass_specular_summary\">Activar reflejo tipo espejo en la parte superior de la barra de navegación</string>\n    <string name=\"settings_navbar_glass_inner_glow\">Brillo interior</string>\n    <string name=\"settings_navbar_glass_inner_glow_summary\">Activar sutil efecto de brillo en la parte inferior de la barra de navegación</string>\n    <string name=\"settings_navbar_glass_border\">Borde de cristal</string>\n    <string name=\"settings_navbar_glass_border_summary\">Activar sutil borde alrededor de la barra de navegación</string>\n    <string name=\"settings_stats_top_layout\">Diseño superior</string>\n    <string name=\"settings_stats_top_layout_summary\">Elegir el estilo de tarjeta de información superior</string>\n    <string name=\"settings_stats_top_layout_list\">Lista</string>\n    <string name=\"settings_stats_top_layout_grid\">Cuadrícula</string>\n    <string name=\"settings_block_kernelpatch_update\">Bloquear actualización de KernelPatch</string>\n    <string name=\"settings_block_kernelpatch_update_summary\">No mostrar notificaciones de actualización de KernelPatch</string>\n    <string name=\"settings_block_androidpatch_update\">Bloquear actualización del parche del sistema</string>\n    <string name=\"settings_block_androidpatch_update_summary\">No mostrar notificaciones de actualización del parche del sistema (APD)</string>\n    <string name=\"settings_disable_module_update_check\">Desactivar verificación de actualización de módulos</string>\n    <string name=\"settings_disable_module_update_check_summary\">Desactivar verificación automática de actualizaciones para módulos del sistema</string>\n\n    <string name=\"reboot\">Reiniciar</string>\n    <string name=\"settings\">Ajustes</string>\n    <string name=\"reboot_recovery\">Reiniciar en Recovery</string>\n    <string name=\"reboot_bootloader\">Reiniciar en Bootloader</string>\n    <string name=\"reboot_download\">Reiniciar en Download</string>\n    <string name=\"reboot_edl\">Reiniciar en EDL</string>\n    <string name=\"reboot_fastbootd\">Reiniciar en FastbootD</string>\n    <string name=\"about\">Acerca de</string>\n    <string name=\"developer_and_maintainer\">Desarrollador | Mantenedor</string>\n    <string name=\"settings_app_language\">Idioma</string>\n    <string name=\"system_default\">Predeterminado del sistema</string>\n    <string name=\"settings_global_namespace_mode\">Modo de espacio de nombres global</string>\n    <string name=\"settings_global_namespace_mode_summary\">Todas las sesiones root usan el espacio de nombres de montaje global</string>\n    <string name=\"settings_clear_super_key_dialog\">¿Realmente deseas proceder?</string>\n    <string name=\"settings_grid_working_card_hide_check\">Ocultar icono de estado</string>\n    <string name=\"settings_grid_working_card_hide_check_summary\">Ocultar la marca de verificación o icono de advertencia en la tarjeta de estado</string>\n    <string name=\"settings_grid_working_card_hide_text\">Ocultar texto de estado</string>\n    <string name=\"settings_grid_working_card_hide_text_summary\">Ocultar el texto \\'Funcionando\\' o \\'No instalado\\' en la tarjeta de estado</string>\n    <string name=\"settings_grid_working_card_hide_mode\">Ocultar modo de trabajo</string>\n    <string name=\"settings_grid_working_card_hide_mode_summary\">Ocultar el texto \\'Full\\' o \\'Half\\' en la tarjeta de estado</string>\n\n    <string name=\"settings_folkx_engine_title\">Motor de Animación FolkX</string>\n    <string name=\"settings_folkx_engine_summary\">Usar animación elástica basada en física al cambiar entre páginas principales</string>\n    <string name=\"settings_folkx_animation_type\">Tipo de animación</string>\n    <string name=\"settings_folkx_animation_speed\">Velocidad de animación</string>\n    <string name=\"settings_folkx_animation_linear\">Movimiento lineal</string>\n    <string name=\"settings_folkx_animation_spatial\">Movimiento espacial</string>\n    <string name=\"settings_folkx_animation_fade\">Desvanecer Entrada/Salida</string>\n    <string name=\"settings_folkx_animation_vertical\">Deslizar verticalmente</string>\n    <string name=\"settings_folkx_animation_diagonal\">Deslizar diagonalmente</string>\n\n    <string name=\"su_exclude_all_title\">Excluir todo</string>\n    <string name=\"su_exclude_all_confirm\">¿Estás seguro de que deseas excluir todas las apps no root?</string>\n    <string name=\"su_batch_exclude_title\">Exclusión por lotes</string>\n    <string name=\"su_batch_exclude_content\">Excluir inyección para todas las apps no ROOT, por favor selecciona una acción</string>\n    <string name=\"su_exclude_btn\">Excluir</string>\n    <string name=\"su_exclude_reverse_btn\">Invertir</string>\n\n    <!-- SuperUser App Action Dialog -->\n    <string name=\"su_app_action_title\">Acción de Aplicación</string>\n    <string name=\"su_app_action_content\">Por favor selecciona una acción para la aplicación</string>\n    <string name=\"su_app_action_launch\">Iniciar Aplicación</string>\n    <string name=\"su_app_action_force_stop\">Forzar Detención</string>\n    <string name=\"su_app_action_launch_success\">Iniciando %s</string>\n    <string name=\"su_app_action_force_stop_success\">%s detenida forzosamente</string>\n    <string name=\"su_app_action_failed\">Operación fallida: %s</string>\n\n    <!-- SU Audit Log -->\n    <string name=\"su_audit_log_title\">Registro de autorización</string>\n    <string name=\"su_audit_log_empty\">No hay registros de autorización</string>\n    <string name=\"su_audit_log_clear\">Borrar registro</string>\n    <string name=\"su_audit_log_clear_confirm\">¿Confirmar eliminación de todos los registros de autorización?</string>\n    <string name=\"su_audit_action_grant\">Autorizado</string>\n    <string name=\"su_audit_action_revoke\">Revocado</string>\n    <string name=\"su_audit_action_exclude\">Excluido</string>\n    <string name=\"su_audit_tab_usage\">Registro de uso</string>\n    <string name=\"su_audit_tab_operations\">Registro de operaciones</string>\n\n    <string name=\"home_learn_apatch\">Aprender FolkPatch</string>\n    <string name=\"home_click_to_learn_apatch\">Aprende sobre las características de FolkPatch y cómo usarlo</string>\n    <string name=\"settings_hide_apatch_card\">Ocultar tarjeta Aprender FolkPatch</string>\n    <string name=\"settings_hide_apatch_card_summary\">Oculta la tarjeta de aprendizaje de FolkPatch en la pantalla de inicio</string>\n\n    <string name=\"about_source_code\"><![CDATA[<p>Ver código fuente en %1$s<p/>Únete a nuestro canal de %2$s<p/>Únete a nuestro grupo de %3$s]]></string>\n    <string name=\"send_log\">Enviar registros</string>\n    <string name=\"save_log\">Guardar registros</string>\n    <string name=\"log_saved\">Registros guardados</string>\n    <string name=\"safe_mode\">Modo seguro</string>\n    <string name=\"github\">GitHub</string>\n    <string name=\"telegram\">Telegram</string>\n    <string name=\"support_or_donate\">Apoyar / Donar</string>\n\n    <string name=\"super_key\">SuperKey</string>\n    <string name=\"clear_super_key\">Borrar SuperKey</string>\n    <string name=\"patch_set_superkey\">Establecer SuperKey</string>\n    <string name=\"home_patch_set_key_desc\">Única credencial para KernelPatch</string>\n    <string name=\"home_patch_next_step\">Siguiente paso</string>\n\n    <string name=\"home_not_installed\">No instalado</string>\n    <string name=\"home_install_unknown\">No instalado o no autenticado</string>\n    <string name=\"home_install_unknown_summary\">Pulsa para instalar</string>\n    <string name=\"home_click_to_install\">Pulsa para instalar</string>\n    <string name=\"home_working\">Funcionando</string>\n    <string name=\"home_kp_need_update\">Nueva versión disponible</string>\n    <string name=\"home_kp_cando_update\">Actualizar</string>\n\n    <string name=\"home_installing\">Instalando</string>\n\n    <string name=\"kpatch_version\">Versión: %s</string>\n    <string name=\"apatch_version\">Versión: %s</string>\n\n    <string name=\"kpatch_version_update\" formatted=\"false\">Versión: %s -&gt; %s</string>\n    <string name=\"kpatch_shadow_path_title\">kpatch</string>\n    <string name=\"home_auth_key_title\">Ingresar SuperKey</string>\n    <string name=\"home_auth_key_desc\">Iniciar tras certificación</string>\n    <string name=\"home_kpatch_info_title\">Info</string>\n\n    <string name=\"home_ap_cando_install\">Instalar</string>\n\n    <string name=\"home_ap_cando_uninstall\">Desinstalar</string>\n    <string name=\"home_ap_cando_reboot\">Reiniciar</string>\n\n    <string name=\"patch_title\">Parchear</string>\n\n    <string name=\"patch_config_title\">Parches</string>\n    <string name=\"patch_mode_bootimg_patch\">Modo: Parchear</string>\n    <string name=\"patch_mode_patch_and_install\">Modo: Parchear e instalar</string>\n    <string name=\"patch_mode_install_to_next_slot\">Modo: Instalar en ranura inactiva (Tras OTA)</string>\n    <string name=\"patch_mode_restore\">Modo: Restaurar</string>\n    <string name=\"patch_mode_uninstall_patch\">Modo: Desinstalar KPatch</string>\n    <string name=\"patch_select_bootimg_btn\">Seleccionar boot</string>\n    <string name=\"patch_embed_kpm_btn\">Incrustar KPM</string>\n    <string name=\"patch_start_patch_btn\">Iniciar</string>\n    <string name=\"patch_start_unpatch_btn\">Desparchear</string>\n    <string name=\"patch_item_error\">!!ERROR!!</string>\n    <string name=\"patch_item_bootimg\">bootimg</string>\n    <string name=\"patch_item_bootimg_slot\">Ranura:</string>\n    <string name=\"patch_item_bootimg_dev\">Dispositivo:</string>\n    <string name=\"patch_item_kernel\">Kernel</string>\n    <string name=\"patch_item_kpimg\">kpimg</string>\n    <string name=\"patch_item_kpimg_version\">Versión:</string>\n    <string name=\"patch_item_kpimg_comile_time\">Hora:</string>\n    <string name=\"patch_item_kpimg_config\">Config:</string>\n    <string name=\"patch_item_new_extra_kpm\">Incrustar nuevo</string>\n    <string name=\"patch_item_existed_extra_kpm\">Existente</string>\n    <string name=\"patch_item_extra_name\">Nombre:</string>\n    <string name=\"patch_item_extra_version\">Versión:</string>\n    <string name=\"patch_item_extra_author\">Autor:</string>\n    <string name=\"patch_item_extra_kpm_license\">Licencia:</string>\n    <string name=\"patch_item_extra_kpm_desciption\">Descripción:</string>\n    <string name=\"patch_item_extra_args\">Args:</string>\n    <string name=\"patch_item_extra_event\">Evento:</string>\n    <string name=\"patch_item_skey\">SuperKey</string>\n    <string name=\"patch_custom_superkey\">SuperKey personalizado</string>\n    <string name=\"patch_custom_superkey_summary\">Aún puedes configurar un SuperKey para gestionar Root y el kernel, el gestor está autorizado por la firma integrada del kernel</string>\n    <string name=\"patch_item_set_skey_label\">La SuperKey debe tener entre 8 y 63 caracteres e incluir números y letras, pero no caracteres especiales.</string>\n    <string name=\"patch_confirm_superkey\">Confirmar SuperKey</string>\n    <string name=\"patch_skey_mismatch\">SuperKey no coincide</string>\n\n    <string name=\"home_kernel\">Versión del kernel</string>\n    <string name=\"home_manager_version\">Versión del gestor</string>\n    <string name=\"home_fingerprint\">Huella digital</string>\n\n    <string name=\"home_selinux_status\">Estado SELinux</string>\n    <string name=\"home_selinux_status_disabled\">Deshabilitado</string>\n    <string name=\"home_selinux_status_enforcing\">Enforcing (Obligatorio)</string>\n    <string name=\"home_selinux_status_permissive\">Permissive (Permisivo)</string>\n    <string name=\"home_selinux_status_unknown\">Desconocido</string>\n\n    <string name=\"settings_selinux_mode\">Modo SELinux</string>\n    <string name=\"settings_selinux_mode_summary\">Seleccionar modo de cumplimiento de SELinux</string>\n    <string name=\"settings_selinux_mode_enforcing\">Enforcing (Estricto)</string>\n    <string name=\"settings_selinux_mode_permissive\">Permissive (Permisivo)</string>\n    <string name=\"settings_selinux_current_mode\">Actual: %s</string>\n    <string name=\"settings_selinux_mode_enforcing_summary\">SELinux aplica completamente las reglas de acceso</string>\n    <string name=\"settings_selinux_mode_permissive_summary\">SELinux solo registra violaciones, no deniega</string>\n\n    <string name=\"home_device_info\">Dispositivo</string>\n    <string name=\"home_system_version\">Versión del sistema</string>\n    <string name=\"home_kpatch_version\">Versión KernelPatch</string>\n    <string name=\"home_su_path\">Ejecutable su</string>\n    <string name=\"home_apatch_version\">Versión FolkPatch</string>\n\n    <string name=\"kpm\">KPModule</string>\n    <string name=\"kpm_kp_not_installed\">KernelPatch no instalado</string>\n    <string name=\"kpm_add_kpm\">Añadir KPM</string>\n    <string name=\"kpm_load\">Cargar</string>\n    <string name=\"kpm_install\">Instalar</string>\n    <string name=\"kpm_embed\">Incrustar</string>\n    <string name=\"kpm_load_toast_succ\">Carga exitosa</string>\n    <string name=\"kpm_load_toast_failed\">Error en la carga</string>\n    <string name=\"kpm_unload_confirm\">¿Descargar el módulo %s?</string>\n    <string name=\"kpm_unload\">Descargar</string>\n    <string name=\"kpm_control\">Control</string>\n    <string name=\"kpm_apm_empty\">Ningún módulo cargado</string>\n    <string name=\"kpm_version\">Versión</string>\n    <string name=\"kpm_license\">Licencia</string>\n    <string name=\"kpm_author\">Autor</string>\n    <string name=\"kpm_desc\">Desc</string>\n    <string name=\"kpm_args\">Args</string>\n\n    <string name=\"su_title\">Superusuario</string>\n    <string name=\"su_selinux_via_hook\">Omitir mediante hook</string>\n    <string name=\"su_pkg_excluded_label\">Excluir</string>\n    <string name=\"su_pkg_excluded_setting_title\">Excluir modificaciones</string>\n    <string name=\"su_pkg_excluded_setting_summary\">Habilitar esta opción permitirá a FolkPatch restaurar cualquier archivo modificado por los módulos de esta app.</string>\n    <string name=\"su_pkg_root_setting_title\">Superusuario</string>\n    <string name=\"su_pkg_root_setting_summary\">Habilitar esta opción para otorgar acceso de superusuario a tu app, permitiendo usar comandos SU</string>\n    <string name=\"su_pkg_normal_setting_title\">Modo normal</string>\n    <string name=\"su_pkg_normal_setting_summary\">No se ha concedido acceso root, la aplicación se ejecuta sin privilegios de superusuario.</string>\n    <string name=\"su_show_system_apps\">Mostrar apps del sistema</string>\n    <string name=\"su_hide_system_apps\">Ocultar apps del sistema</string>\n    <string name=\"su_refresh\">Actualizar</string>\n\n    <string name=\"apm\">Módulo del Sistema</string>\n    <string name=\"apm_not_installed\">AndroidPatch no instalado</string>\n    <string name=\"apm_failed_to_enable\">Error al habilitar módulo: %s</string>\n    <string name=\"apm_failed_to_disable\">Error al deshabilitar módulo: %s</string>\n    <string name=\"apm_empty\">Ningún módulo instalado</string>\n    <string name=\"apm_remove\">Eliminar</string>\n    <string name=\"apm_undo\">Deshacer</string>\n    <string name=\"apm_install\">Instalar</string>\n    <string name=\"apm_uninstall_confirm\">¿Desinstalar el módulo %s?</string>\n    <string name=\"apm_uninstall_success\">%s desinstalado</string>\n    <string name=\"apm_uninstall_failed\">Error al desinstalar: %s</string>\n    <string name=\"apm_undo_uninstall_success\">%s restaurado correctamente</string>\n    <string name=\"apm_undo_uninstall_failed\">Error al restaurar: %s</string>\n    <string name=\"apm_version\">Versión</string>\n    <string name=\"apm_author\">Autor</string>\n    <string name=\"apm_desc\">Desc</string>\n    <string name=\"apm_overlay_fs_not_available\">¡Los módulos no están disponibles porque OverlayFS está deshabilitado por el kernel!</string>\n    <string name=\"apm_magisk_conflict\">¡Los módulos no están disponibles debido a un conflicto con Magisk!</string>\n    <string name=\"apm_mount_warning_title\">Aviso importante</string>\n    <string name=\"apm_mount_warning_message\">Por defecto, los módulos no se montan. Utilice el sistema de montado integrado o metamódulos.</string>\n    <string name=\"apm_mount_warning_button\">Entendido</string>\n    <string name=\"apm_reboot_to_apply\">Reiniciar para aplicar</string>\n    <string name=\"apm_changelog\">Registro de cambios</string>\n    <string name=\"apm_update\">Actualizar</string>\n    <string name=\"apm_downloading\">Descargando módulo: %s</string>\n    <string name=\"apm_start_downloading\">Iniciando descarga: %s</string>\n    <string name=\"apm_new_version_available\">Nueva versión %s disponible, pulsa para actualizar.</string>\n\n    <string name=\"hide_apatch_manager\">Ocultar Gestor APatch</string>\n    <string name=\"hide_apatch_manager_summary\">Instalar una app proxy con ID de paquete aleatorio y etiqueta personalizada</string>\n    <string name=\"hide_apatch_dialog_new_manager_name\">Nombre del nuevo gestor</string>\n    <string name=\"hide_apatch_dialog_summary\">Se usará como la nueva etiqueta de la app mostrada en el launcher</string>\n    <string name=\"hide_apatch_manager_failure\">Error al ocultar. ¡Por favor reporta el fallo!</string>\n\n    <string name=\"setting_reset_su_path\">Restablecer ruta su</string>\n    <string name=\"setting_reset_su_new_path\">Nueva ruta completa</string>\n\n    <string name=\"apm_webui_open\">Abrir</string>\n    <string name=\"apm_action\">Acción</string>\n    <string name=\"module_shortcut_add\">Acceso directo</string>\n    <string name=\"module_shortcut_not_supported\">El iniciador no soporta accesos directos de escritorio.</string>\n    <string name=\"module_shortcut_created\">Acceso directo creado en el escritorio.</string>\n    <string name=\"module_shortcut_updated\">Acceso directo actualizado.</string>\n    <string name=\"module_shortcut_permission_tip_xiaomi\">Habilite el permiso \"Crear accesos directos de escritorio\" para esta aplicación en configuración de Xiaomi.</string>\n    <string name=\"module_shortcut_permission_tip_oppo\">Habilite el permiso \"Acceso directo de escritorio\" para esta aplicación en configuración de OPPO.</string>\n    <string name=\"module_shortcut_permission_tip_default\">Si falla la creación del acceso directo, habilite el permiso de acceso directo de escritorio para esta aplicación en configuración del sistema.</string>\n    <string name=\"module_shortcut_name\">Nombre del acceso directo</string>\n    <string name=\"module_shortcut_icon\">Icono del acceso directo</string>\n    <string name=\"module_shortcut_type\">Tipo de acceso directo</string>\n    <string name=\"module_shortcut_type_webui\">WebUI</string>\n    <string name=\"module_shortcut_type_action\">Action</string>\n    <string name=\"module_shortcut_icon_default\">Usar icono predeterminado</string>\n    <string name=\"module_shortcut_icon_select\">Seleccionar icono</string>\n    <string name=\"enable_web_debugging\">Habilitar depuración WebView</string>\n    <string name=\"enable_web_debugging_summary\">Se puede usar para depurar la WebUI. Habilítalo solo si es necesario.</string>\n    <string name=\"settings_apm_install_confirm\">Confirmar antes de instalar</string>\n    <string name=\"settings_apm_install_confirm_summary\">Mostrar un diálogo de confirmación antes de instalar módulos</string>\n    <string name=\"settings_show_more_module_info\">Mostrar detalles del módulo</string>\n    <string name=\"settings_show_more_module_info_summary\">Mostrar ID y tamaño del módulo en la lista</string>\n    <string name=\"settings_apm_stay_on_page\">Permanecer en la página</string>\n    <string name=\"settings_apm_stay_on_page_summary\">No regresar automáticamente tras ejecutar una acción del módulo</string>\n    <string name=\"settings_enable_module_shortcut_add\">Habilitar botón de agregar acceso directo</string>\n    <string name=\"settings_enable_module_shortcut_add_summary\">Mostrar botón de agregar acceso directo WebUI</string>\n    <string name=\"settings_module_sort_optimization\">Optimización de orden de módulos</string>\n    <string name=\"settings_module_sort_optimization_summary\">Hacer que los módulos del sistema con WebUI y Acción aparezcan arriba</string>\n    <string name=\"settings_fold_system_module\">Plegar Módulos del Sistema</string>\n    <string name=\"settings_fold_system_module_summary\">Haz clic en la tarjeta del módulo para expandir/contraer acciones</string>\n    <string name=\"settings_simple_list_bottom_bar\">Barra Inferior Simple</string>\n    <string name=\"settings_simple_list_bottom_bar_summary\">Usar botones solo con iconos para acciones de módulo, inspirado en APatch</string>\n    <string name=\"settings_spliced_card_group\">Spliced Card Group</string>\n    <string name=\"settings_spliced_card_group_summary\">Use M3E continuous card group style for module list</string>\n    <string name=\"apm_install_confirm_title\">Instalar Módulo</string>\n    <string name=\"apm_install_confirm_content\">¿Seguro que deseas instalar %s?</string>\n    <string name=\"apm_disable_all_title\">Deshabilitar todos los módulos</string>\n    <string name=\"apm_disable_all_confirm\">¿Seguro que deseas deshabilitar todos los módulos? Esto desactivará todos los módulos instalados.</string>\n    <string name=\"apm_enable_module_banner\">Habilitar banner de módulo</string>\n    <string name=\"apm_enable_module_banner_summary\">Mostrar imagen de banner para módulos si está disponible</string>\n    <string name=\"apm_enable_folk_banner\">Personalizar banner de módulo</string>\n    <string name=\"apm_enable_folk_banner_summary\">Presiona largo en la tarjeta del módulo para seleccionar la imagen del banner</string>\n    <string name=\"apm_folk_banner_title\">FolkBanner</string>\n    <string name=\"apm_folk_banner_select\">Seleccionar imagen</string>\n    <string name=\"apm_folk_banner_clear\">Limpiar FolkBanner</string>\n    <string name=\"apm_folk_banner_saved\">FolkBanner inyectado para %s.</string>\n    <string name=\"apm_folk_banner_cleared\">FolkBanner limpiado para %s.</string>\n    <string name=\"apm_folk_banner_failed\">Error al actualizar FolkBanner para %s.</string>\n    <string name=\"apm_banner_api_mode\">Modo API</string>\n    <string name=\"apm_banner_api_mode_summary\">Usar API de imágenes aleatorias o directorio local para banners de módulos</string>\n    <string name=\"apm_banner_api_source\">Configurar fuente API</string>\n    <string name=\"apm_banner_api_source_hint\">Introduce URL de API o ruta local</string>\n    <string name=\"apm_banner_api_source_saved\">Fuente API guardada</string>\n    <string name=\"apm_banner_api_source_not_configured\">Sin configurar</string>\n    <string name=\"apm_banner_api_url_configured\">URL de API configurada</string>\n    <string name=\"apm_banner_local_dir_configured\">Directorio local configurado</string>\n    <string name=\"apm_banner_clear_cache\">Limpiar caché</string>\n    <string name=\"apm_banner_cache_cleared\">Caché de banners limpiada</string>\n    <string name=\"apm_banner_api_config_title\">Configurar fuente API</string>\n    <string name=\"apm_banner_api_config_desc\">Introduce una URL de API de imágenes aleatorias o una ruta de directorio local. Cada módulo obtendrá un banner único.</string>\n    <string name=\"apm_banner_api_examples_title\">Ejemplos:</string>\n    <string name=\"apm_banner_api_examples\">API: https://picsum.photos/800/400\\nLocal: /sdcard/Pictures/Banners</string>\n    <!-- Mercado de API -->\n    <string name=\"apm_api_marketplace_title\">Mercado de API</string>\n    <string name=\"apm_api_preview\">Vista previa</string>\n    <string name=\"apm_api_apply\">Aplicar</string>\n    <string name=\"apm_api_preview_title\">Vista previa de API</string>\n    <string name=\"apm_api_preview_failed\">Error al cargar la vista previa</string>\n    <string name=\"apm_api_url_label\">URL de API:</string>\n    <string name=\"apm_api_apply_success\">Fuente de API aplicada correctamente</string>\n    <string name=\"apm_api_verifying\">Verificando…</string>\n    <string name=\"apm_api_retry\">Reintentar</string>\n    <string name=\"apm_api_empty\">No hay fuentes de API disponibles</string>\n    <string name=\"settings_banner_custom_opacity\">Opacidad del banner</string>\n    <string name=\"settings_banner_custom_opacity_summary\">Usar opacidad personalizada para banners en lugar de seguir el modo de fondo de pantalla</string>\n    <string name=\"settings_banner_opacity\">Transparencia del banner</string>\n\n    <string name=\"settings_donot_store_superkey\">No guardar SuperKey localmente</string>\n    <string name=\"settings_donot_store_superkey_summary\">Autenticar la SuperKey cada vez que inicie el gestor</string>\n\n    <string name=\"mode_select_page_title\">Instalar</string>\n    <string name=\"mode_select_page_patch_and_install\">Parchear e instalar</string>\n    <string name=\"mode_select_page_select_file\">Selecciona una imagen boot para parchear</string>\n    <string name=\"restore_select_file\">Selecciona un archivo boot para restaurar</string>\n    <string name=\"mode_select_page_install_inactive_slot\">Instalar en ranura inactiva (Tras OTA)</string>\n    <string name=\"mode_select_page_install_inactive_slot_warning\">¡Tu dispositivo será **FORZADO** a arrancar en la ranura inactiva actual tras reiniciar!\\nUsa esta opción solo después de completar la OTA.\\n¿Continuar?</string>\n    <string name=\"mode_select_page_select_kpimg\">Usar archivo de parche local (KPimg)</string>\n    <string name=\"patch_custom_kpimg_label\">KPimg personalizado</string>\n    <string name=\"patch_custom_kpimg_file\">Archivo: %s</string>\n    <string name=\"patch_select_kpimg_btn\">Seleccionar KPimg</string>\n\n    <string name=\"home_dialog_auth_fail_title\">Error de autenticación</string>\n    <string name=\"home_dialog_auth_fail_content\">No se pudo autenticar la SuperKey, causando que la activación de FolkPatch falle.\nAquí hay algunas posibles razones para el fallo de autenticación—por favor verifica cuál se aplica a ti:\n\n\n\\n1. No usaste KernelPatch para parchear boot.img en absoluto, o olvidaste lo que realmente hiciste.\n\\n2. El boot.img parcheado todavía está durmiendo en tu computadora, nunca fue flasheado al dispositivo.\n\\n3. La SuperKey fue ingresada incorrectamente, o contiene símbolos misteriosos, como caracteres de idiomas alienígenas.\n\\n4. Tu dispositivo puede no ser compatible con FolkPatch y KernelPatch, los intentos forzados son en vano.\n\\n5. Puede que hayas hecho algunas operaciones misteriosas—como usar ciertos módulos que excluyen nombres de paquetes que bloquearon FolkPatch, resultando en sacarte a ti mismo del juego.\n\n</string>\n\n    <string name=\"home_more_menu_feedback_or_suggestion\">Comentarios o sugerencias</string>\n    <string name=\"home_more_menu_about\">Acerca de</string>\n    <string name=\"home_more_menu_document\">Documentación</string>\n\n    <string name=\"home_dialog_uninstall_title\">Desinstalar</string>\n    \n    <string name=\"home_dialog_uninstall_ap_only\">Desinstalar solo parche</string>\n    <string name=\"home_dialog_uninstall_all\">Desinstalar completamente</string>\n    <string name=\"home_dialog_uninstall_ap_only_desc\">Eliminar solo AndroidPatch y conservar el gestor.</string>\n    <string name=\"home_dialog_uninstall_all_desc\">Eliminar AndroidPatch e iniciar el flujo de desinstalación completa.</string>\n\n    <string name=\"kpm_control_dialog_title\">Controlar KPM</string>\n    <string name=\"kpm_control_dialog_content\">Por favor ingresa parámetros de control:</string>\n    <string name=\"kpm_control_paramters\">Parámetros</string>\n    <string name=\"kpm_control_outMsg\">Salida</string>\n    <string name=\"kpm_control_ok\">¡Éxito!</string>\n    <string name=\"kpm_control_failed\">¡Fallo!</string>\n    \n    <string name=\"settings_night_mode_follow_sys\">Tema oscuro sigue al sistema</string>\n    <string name=\"settings_night_mode_follow_sys_summary\">Cambiar tema oscuro automáticamente según ajustes del sistema</string>\n    <string name=\"settings_night_theme_enabled\">Tema oscuro</string>\n    \n    <string name=\"settings_use_system_color_theme\">Tema de color del sistema</string>\n    <string name=\"settings_use_system_color_theme_summary\">Usar tema de color generado desde el fondo de pantalla por el sistema</string>\n    <string name=\"settings_custom_color_theme\">Tema de color</string>\n\n    <string name=\"amber_theme\">Ámbar</string>\n    <string name=\"blue_theme\">Azul</string>\n    <string name=\"blue_grey_theme\">Gris azulado</string>\n    <string name=\"brown_theme\">Marrón</string>\n    <string name=\"cyan_theme\">Cian</string>\n    <string name=\"deep_orange_theme\">Naranja oscuro</string>\n    <string name=\"deep_purple_theme\">Púrpura oscuro</string>\n    <string name=\"green_theme\">Verde</string>\n    <string name=\"indigo_theme\">Índigo</string>\n    <string name=\"light_blue_theme\">Azul claro</string>\n    <string name=\"light_green_theme\">Verde claro</string>\n    <string name=\"lime_theme\">Lima</string>\n    <string name=\"orange_theme\">Naranja</string>\n    <string name=\"pink_theme\">Rosa</string>\n    <string name=\"purple_theme\">Púrpura</string>\n    <string name=\"red_theme\">Rojo</string>\n    <string name=\"sakura_theme\">Sakura</string>\n    <string name=\"teal_theme\">Verde azulado</string>\n    <string name=\"yellow_theme\">Amarillo</string>\n    <string name=\"theme_color\">Color del tema</string>\n    <string name=\"theme_light\">Claro</string>\n    <string name=\"theme_dark\">Oscuro</string>\n    <string name=\"theme_system\">Sistema</string>\n    <string name=\"loading_modules\">Cargando módulos…</string>\n    <string name=\"loading_scripts\">Buscando scripts…</string>\n    <string name=\"loading_themes\">Cargando temas…</string>\n    <string name=\"loading_apis\">Cargando fuentes de API…</string>\n\n    <string name=\"about_app_desc\">Una implementación Root basada en KernelPatch, permitiendo hooks de funciones del kernel sin recompilar el kernel.</string>\n    <string name=\"about_powered_by\">Impulsado por %1$s</string>\n    <string name=\"about_telegram_group\">Grupo de Telegram</string>\n    \n    <!-- Online Module Strings -->\n    <string name=\"online_module_title\">Módulos del Sistema Online</string>\n    <string name=\"online_module_download_start\">Iniciando descarga: %s</string>\n    <string name=\"online_module_download_complete\">Descarga completa: %s</string>\n    <string name=\"online_module_download_notification\">Descargando %s. Revisa el panel de notificaciones para el progreso y la carpeta Download para el archivo.</string>\n\n    <!-- Online KPM Strings -->\n    <string name=\"online_kpm_title\">Módulos de Kernel Online</string>\n    <string name=\"online_kpm_download_start\">Iniciando descarga: %s</string>\n    <string name=\"online_kpm_download_complete\">Descarga completa: %s</string>\n    <string name=\"online_kpm_download_notification\">Descargando %s. Revisa el panel de notificaciones para el progreso y la carpeta Download para el archivo。</string>\n\n    <!-- Online Script Strings -->\n    <string name=\"online_script_title\">Scripts Online</string>\n    <string name=\"online_script_download_start\">Iniciando descarga: %s</string>\n    <string name=\"online_script_download_complete\">Descarga completa: %s</string>\n    <string name=\"online_script_download_notification\">Descargando %s</string>\n\n    <!-- Crash Handle Strings -->\n    <string name=\"crash_handle_title\">Reporte de Fallo</string>\n    <string name=\"crash_handle_copy\">Copiar</string>\n\n    <!-- About Screen Strings -->\n    <string name=\"about_app_version\">Versión de la App: %s</string>\n    <string name=\"about_github\">GitHub</string>\n    <string name=\"about_telegram_channel\">Canal de Telegram</string>\n\n    <string name=\"settings_app_dpi\">DPI de la App</string>\n    <string name=\"dpi_apply_settings\">Aplicar</string>\n    <string name=\"dpi_confirm_title\">Confirmar cambio de DPI</string>\n    <string name=\"dpi_confirm_message\">¿Cambiar DPI de la app de %1$s a %2$s?</string>\n    <string name=\"settings_alt_icon\">Icono alternativo</string>\n    <string name=\"alt_icon_summary\">Usar icono de lanzador alternativo</string>\n    <string name=\"settings_magic_mount\">Folk Mount API</string>\n    <string name=\"settings_magic_mount_summary\">Activar el sistema de montaje de módulos integrado</string>\n    <string name=\"settings_new_app_profile_mode\">Modo predeterminado para nuevas apps</string>\n    <string name=\"settings_new_app_profile_normal\">SIN ROOT</string>\n    <string name=\"settings_new_app_profile_root\">ROOT</string>\n    <string name=\"settings_new_app_profile_exclude\">Excluir</string>\n    <string name=\"settings_hide_service\">FolkPatch Hide</string>\n    <string name=\"settings_hide_service_summary\">Ocultar el estado de desbloqueo del bootloader hasta cierto punto</string>\n    <string name=\"settings_app_title\">Título de la App</string>\n    <string name=\"app_title_custom\">Personalizado</string>\n    <string name=\"settings_custom_app_title\">Establecer nombre de la app</string>\n    <string name=\"custom_app_title_dialog_title\">Nombre personalizado de la app</string>\n    <string name=\"custom_app_title_dialog_hint\">Introduce el nombre de la app</string>\n    <string name=\"custom_app_title_dialog_confirm\">Confirmar</string>\n    <string name=\"custom_app_title_dialog_empty\">El nombre no puede estar vacío</string>\n    <string name=\"cancel\">Cancelar</string>\n    <string name=\"settings_custom_background\">Fondo Personalizado</string>\n    <string name=\"settings_custom_background_enabled\">Fondo Personalizado Habilitado</string>\n    <string name=\"settings_custom_background_opacity\">Opacidad de tarjetas</string>\n    <string name=\"settings_custom_background_blur\">Desenfoque del fondo</string>\n    <string name=\"settings_custom_background_dim\">Atenuación del fondo</string>\n    <string name=\"settings_custom_background_dual_dim\">Habilitar atenuación dual día/noche</string>\n    <string name=\"settings_custom_background_dual_dim_desc\">Ajustar automáticamente el atenuación del fondo para temas claros y oscuros</string>\n    <string name=\"settings_custom_background_day_dim\">Atenuación de fondo en modo día</string>\n    <string name=\"settings_custom_background_night_dim\">Atenuación de fondo en modo noche</string>\n    <string name=\"settings_multi_background_mode\">Modo Multi-Fondo</string>\n    <string name=\"settings_multi_background_mode_summary\">Establecer diferentes fondos para diferentes páginas</string>\n    <string name=\"settings_select_home_background\">Fondo de Inicio</string>\n    <string name=\"settings_select_kernel_background\">Fondo Módulos de Kernel</string>\n    <string name=\"settings_select_superuser_background\">Fondo Superusuario</string>\n    <string name=\"settings_select_system_module_background\">Fondo Módulos del Sistema</string>\n    <string name=\"settings_select_settings_background\">Fondo Ajustes</string>\n\n    <string name=\"settings_advanced_title_style\">Estilo de Título Avanzado</string>\n    <string name=\"settings_advanced_title_style_summary\">Reemplazar el título de la barra superior con una imagen personalizada</string>\n    <string name=\"settings_advanced_title_style_enabled\">Estilo de título avanzado activado</string>\n    <string name=\"settings_select_title_image\">Seleccionar Imagen de Título</string>\n    <string name=\"settings_title_image_selected\">Imagen de título seleccionada</string>\n    <string name=\"settings_title_image_saved\">Imagen de título guardada exitosamente</string>\n    <string name=\"settings_title_image_error\">Error al guardar la imagen de título</string>\n    <string name=\"settings_clear_title_image\">Limpiar Imagen de Título</string>\n    <string name=\"settings_clear_title_image_confirm\">¿Estás seguro de que deseas limpiar la imagen de título?</string>\n    <string name=\"settings_title_image_cleared\">Imagen de título limpiada</string>\n    <string name=\"settings_title_image_day_opacity\">Opacidad Modo Día</string>\n    <string name=\"settings_title_image_night_opacity\">Opacidad Modo Noche</string>\n    <string name=\"settings_title_image_dim\">Atenuación de Fondo</string>\n    <string name=\"settings_title_image_offset_x\">Posición Horizontal</string>\n\n    <!-- Font Settings -->\n    <string name=\"settings_custom_font\">Fuente Personalizada</string>\n    <string name=\"settings_select_font_file\">Seleccionar archivo de fuente</string>\n    <string name=\"settings_custom_font_enabled\">Fuente Personalizada Habilitada</string>\n    <string name=\"settings_custom_font_summary\">Usar una fuente TTF personalizada para la app</string>\n    <string name=\"settings_font_selected\">Fuente personalizada seleccionada</string>\n    <string name=\"settings_custom_font_error\">Error al guardar archivo de fuente</string>\n    <string name=\"settings_custom_font_saved\">Fuente personalizada guardada con éxito</string>\n    <string name=\"settings_clear_font\">Restaurar Fuente Predeterminada</string>\n    <string name=\"settings_clear_font_confirm\">¿Seguro que deseas restaurar la fuente predeterminada?</string>\n    <string name=\"settings_font_cleared\">Fuente predeterminada restaurada</string>\n    <string name=\"settings_font_select_hint\">Selecciona un archivo de fuente TTF</string>\n    <string name=\"settings_select_background_image\">Seleccionar Imagen de Fondo</string>\n    <string name=\"settings_custom_background_summary\">Establecer una imagen de fondo personalizada</string>\n    <string name=\"settings_custom_background_saved\">Fondo guardado con éxito</string>\n    <string name=\"settings_custom_background_error\">Error al guardar fondo</string>\n    <string name=\"settings_background_selected\">Fondo seleccionado</string>\n    <string name=\"settings_background_image_cleared\">Imagen de fondo borrada</string>\n    <string name=\"settings_clear_background\">Borrar Fondo</string>\n    <string name=\"settings_clear_background_confirm\">¿Seguro que deseas borrar la imagen de fondo?</string>\n\n    <string name=\"settings_launcher_icon\">Icono del Launcher</string>\n\n    <!-- Video Background Settings -->\n    <string name=\"settings_video_background\">Fondo de Video</string>\n    <string name=\"settings_video_background_summary\">Usar un video como fondo</string>\n    <string name=\"settings_select_video\">Seleccionar Video</string>\n    <string name=\"settings_video_selected\">Video Seleccionado</string>\n    <string name=\"settings_clear_video_background\">Borrar Fondo de Video</string>\n    <string name=\"settings_clear_video_background_confirm\">¿Seguro que deseas borrar el fondo de video?</string>\n    <string name=\"settings_video_background_enabled\">Fondo de Video Habilitado</string>\n    <string name=\"settings_video_volume\">Volumen del Video</string>\n    \n    <!-- KPM AutoLoad Strings -->\n    <string name=\"kpm_autoload_title\">Auto-carga de KPM</string>\n    <string name=\"kpm_autoload_enabled\">Habilitar auto-carga</string>\n    <string name=\"kpm_autoload_enabled_summary\">Cargar módulos KPM automáticamente al iniciar el dispositivo</string>\n    <string name=\"kpm_autoload_json_config\">Configuración JSON</string>\n    <string name=\"kpm_autoload_json_label\">JSON de Configuración</string>\n    <string name=\"kpm_autoload_json_placeholder\">{\n  \"enabled\": true,\n  \"kpmPaths\": [\n    \"/ruta/al/modulo1.kpm\",\n    \"/ruta/al/modulo2.kpm\"\n  ]\n}</string>\n    <string name=\"kpm_autoload_json_error\">Formato JSON inválido</string>\n    <string name=\"kpm_autoload_json_helper\">Ingresa un JSON válido con un array de rutas de archivos KPM</string>\n    <string name=\"kpm_autoload_save\">Guardar Configuración</string>\n    <string name=\"kpm_autoload_save_confirm\">¿Guardar configuración de auto-carga?</string>\n    <string name=\"kpm_autoload_save_success\">Configuración guardada con éxito</string>\n    <string name=\"kpm_autoload_save_failed\">Error al guardar configuración</string>\n    <string name=\"kpm_autoload_visual_mode\">Modo Visual</string>\n    <string name=\"kpm_autoload_json_mode\">Modo JSON</string>\n    <string name=\"kpm_autoload_add_kpm\">Añadir KPM</string>\n    <string name=\"kpm_autoload_remove_kpm\">Eliminar</string>\n    <string name=\"kpm_autoload_edit_kpm\">Editar</string>\n    <string name=\"kpm_autoload_edit_dialog_title\">Editar KPM</string>    <string name=\"kpm_autoload_event_label\">Evento:</string>    <string name=\"kpm_autoload_args_label\">Argumentos:</string>    <string name=\"kpm_autoload_event_service\">service</string>    <string name=\"kpm_autoload_event_post_fs_data\">post-fs-data</string>\n    \n    <!-- Module Backup/Restore Strings -->\n    <string name=\"apm_backup_title\">Respaldar Módulos</string>\n    <string name=\"apm_restore_title\">Restaurar Módulos</string>\n    <string name=\"apm_backup_success\">Respaldo exitoso</string>\n    <string name=\"apm_restore_success\">Restauración exitosa</string>\n    <string name=\"apm_backup_failed\">Fallo en el respaldo</string>\n    <string name=\"apm_restore_failed\">Fallo en la restauración</string>\n    <string name=\"apm_backup_failed_msg\">El respaldo falló: %s</string>\n    <string name=\"apm_restore_failed_msg\">La restauración falló: %s</string>\n    <string name=\"apm_copy_list_title\">Copiar lista</string>\n    <string name=\"apm_copy_list_success\">Lista de módulos copiada</string>\n\n    <!-- APM Bulk Install Strings -->\n    <string name=\"apm_bulk_install_title\">Instalación Masiva de Módulos del Sistema</string>\n    <string name=\"apm_bulk_install_list_title\">Lista de Módulos</string>\n    <string name=\"apm_bulk_install_add\">Añadir Módulos</string>\n    <string name=\"apm_bulk_install_empty\">No se añadieron módulos</string>\n    <string name=\"apm_bulk_install_action\">Instalación Masiva</string>\n    <string name=\"apm_bulk_install_log_title\">Registro de Instalación Masiva</string>\n    <string name=\"apm_bulk_install_log_start\">Iniciando instalación masiva...</string>\n    <string name=\"apm_bulk_install_log_installing\">Instalando %1$s</string>\n    <string name=\"apm_batch_install_full_process\">Proceso de instalación por lotes completo</string>\n    <string name=\"apm_batch_install_full_process_summary\">Se realiza la instalación por lotes de módulos del sistema usando el proceso completo</string>\n    <string name=\"next_module\">Siguiente módulo</string>\n    <string name=\"apm_bulk_install_log_installed\">Módulo %s instalado.</string>\n    <string name=\"apm_bulk_install_log_done\">Todas las operaciones completadas.</string>\n    <string name=\"apm_bulk_install_first_use_text\">Esta función te permite instalar múltiples módulos a la vez. Es un método rápido adecuado para módulos que no requieren operaciones de teclas durante la instalación. Para módulos que requieren interacciones con teclas de volumen, por favor activa el modo de instalación completa en la configuración.</string>\n    <string name=\"apm_bulk_install_remove\">Eliminar</string>\n    <string name=\"apm_first_use_title\">Bienvenido a Módulos del Sistema</string>\n    <string name=\"apm_first_use_text\">Bienvenido a Módulos del Sistema, esto usa módulos compatibles con el ecosistema Magisk. Pulsa el botón en la esquina inferior derecha para instalar módulos. También puedes usar el instalador en la parte superior para instalar módulos por lotes. Se proporciona una función para respaldar todos los módulos con un clic, pero ten en cuenta que esta solución puede no ser adecuada para todos los módulos, así que deberías hacer tus propios respaldos.</string>\n\n    <string name=\"kpm_autoload_kpm_list_title\">Módulos KPM</string>\n    <string name=\"kpm_autoload_no_kpm_added\">No se añadieron módulos KPM</string>\n    <string name=\"kpm_autoload_kpm_path\">Ruta KPM</string>\n    <string name=\"kpm_autoload_file_not_found\">Archivo no encontrado</string>\n    <string name=\"kpm_autoload_select_kpm_file\">Seleccionar Archivo KPM</string>\n    <string name=\"kpm_autoload_first_time_title\">Acerca de Auto-carga KPM</string>\n    <string name=\"kpm_autoload_first_time_message\">Esta función te permite cargar automáticamente todos los KPM configurados temporalmente al arrancar. Este enfoque es más conveniente que incrustarlos directamente en el kernel.\n\nPor ejemplo, los KPM que previenen la modificación de particiones solo pueden usarse temporalmente, ya que incrustarlos podría causar fallos de arranque. O si no quieres modificar la partición BOOT, puedes usar esta configuración para cargar módulos rápidamente.\n\n¡Por favor asegúrate de cerrar completamente la app y reabrirla para que los comandos se ejecuten. Generalmente, también cargará al arrancar. Es normal que el gestor tenga una pantalla negra por un periodo de tiempo! ¡Esto usa un método de carga puramente en segundo plano, recuerda deslizar hacia abajo para actualizar y verificar si los módulos se cargaron correctamente!</string>\n    <string name=\"kpm_autoload_first_time_confirm\">Entendido</string>\n    <string name=\"kpm_autoload_do_not_show_again\">No mostrar de nuevo</string>\n\n    <!-- Hide Settings Strings -->\n    <string name=\"home_hide_su_path\">Ocultar ruta del ejecutable su</string>\n    <string name=\"home_hide_kpatch_version\">Ocultar versión del parche de kernel</string>\n    <string name=\"home_hide_fingerprint\">Ocultar huella digital</string>\n    <string name=\"home_hide_zygisk\">Ocultar implementación de Zygisk</string>\n    <string name=\"home_hide_mount\">Ocultar implementación de montaje</string>\n    <string name=\"home_hide_su_path_summary\">Ocultar ruta del ejecutable su en la tarjeta de info</string>\n    <string name=\"home_hide_kpatch_version_summary\">Ocultar versión del parche de kernel en la tarjeta de info</string>\n    <string name=\"home_hide_zygisk_summary\">Ocultar implementación de Zygisk en la tarjeta de info</string>\n    <string name=\"home_hide_mount_summary\">Ocultar implementación de montaje en la tarjeta de info</string>\n    <string name=\"home_hide_fingerprint_summary\">Ocultar huella digital en la tarjeta de info</string>\n\n    <string name=\"kpm_page_first_time_title\">Advertencia</string>\n    <string name=\"kpm_page_first_time_message\">Los módulos de kernel modifican directamente la implementación del Boot. A diferencia de los módulos del sistema, carecen de un buen mecanismo de recuperación. Si surgen problemas, solo puedes arreglarlos entrando en Fastboot. Se recomienda cargar el módulo primero para asegurar que no haya problemas antes de incrustarlo en el Boot. ¡Si un módulo solo puede usarse cargándolo, puedes probar la función de carga automática de módulos KPM. ¡Si no entiendes los módulos de kernel, por favor no uses esta función!</string>\n\n    <!-- Auto Backup Strings -->\n    <string name=\"settings_auto_backup_module\">Auto-respaldo de Módulos del Sistema</string>\n    <string name=\"settings_auto_backup_module_summary\">Respaldar automáticamente el archivo del módulo al directorio privado al instalar</string>\n    <string name=\"settings_open_backup_dir\">Abrir Directorio de Respaldo</string>\n    <string name=\"backup_dir_empty\">El directorio de respaldo está vacío</string>\n    <string name=\"backup_dir_open_failed\">Error al abrir directorio de respaldo</string>\n    <string name=\"auto_backup_failed\">Fallo en auto-respaldo: %s</string>\n    <string name=\"auto_backup_success\">Auto-respaldo exitoso: %s</string>\n    <string name=\"settings_auto_backup_boot\">Auto-respaldo de Boot</string>\n    <string name=\"settings_auto_backup_boot_summary\">Respaldar automáticamente Boot al almacenamiento local (Downloads/FolkPatch/BootBackups)</string>\n\n    <!-- Home Layout Strings -->\n    <string name=\"settings_home_layout_style\">Estilo de Diseño de Inicio</string>\n    <string name=\"settings_home_layout_default\">Lista (ListUI)</string>\n    <string name=\"settings_home_layout_grid\">Cuadrícula (GridUI)</string>\n    <string name=\"settings_home_layout_focus\">Enfoque (FocusUI)</string>\n    <string name=\"settings_home_layout_sign\">SignUI</string>\n    <string name=\"settings_home_layout_circle\">CircleUI</string>\n    <string name=\"settings_home_layout_dashboard_pro\">DashboardUI</string>\n    <string name=\"unofficial_version_title\">Versión No Oficial</string>\n    <string name=\"unofficial_version_message\">Estás usando FolkPatch que no es una app oficial, ¡por favor descarga la app oficial!</string>\n    <string name=\"go_to_github\">Ir a Github</string>\n    <string name=\"settings_save_theme\">Guardar tema</string>\n    <string name=\"settings_import_theme\">Importar tema</string>\n    <string name=\"settings_theme_saved\">Tema guardado</string>\n    <string name=\"settings_theme_imported\">Tema importado</string>\n    <string name=\"settings_theme_save_failed\">Error al guardar tema</string>\n    <string name=\"settings_theme_import_failed\">Error al importar tema</string>\n    <string name=\"settings_reset_theme\">Restablecer tema</string>\n    <string name=\"settings_reset_theme_confirm\">¿Estás seguro de que deseas restablecer todas las configuraciones del tema a los valores predeterminados? Esto eliminará todos los fondos, fuentes, música y efectos de sonido personalizados.</string>\n    <string name=\"settings_theme_reset\">Tema restablecido</string>\n    <string name=\"settings_theme_reset_failed\">Error al restablecer el tema</string>\n\n    <!-- Theme Strings -->\n    <string name=\"theme_export_title\">Exportar Tema</string>\n    <string name=\"theme_import_title\">Importar Tema</string>\n    <string name=\"theme_name\">Nombre del Tema</string>\n    <string name=\"theme_type\">Tipo de Tema</string>\n    <string name=\"theme_type_phone\">Teléfono</string>\n    <string name=\"theme_type_tablet\">Tablet</string>\n    <string name=\"theme_version\">Versión</string>\n    <string name=\"theme_author\">Autor</string>\n    <string name=\"theme_description\">Descripción</string>\n    <string name=\"theme_export_action\">Exportar</string>\n    <string name=\"theme_import_action\">Importar</string>\n    <string name=\"theme_import_confirm\">¿Estás seguro de que deseas importar este tema?</string>\n    <string name=\"theme_info\">Info del Tema</string>\n\n    <!-- Grid Working Card Background -->\n    <string name=\"settings_grid_working_card_background\">Fondo de Tarjeta de Estado</string>\n    <string name=\"settings_grid_working_card_background_enabled\">Fondo de tarjeta personalizado habilitado</string>\n    <string name=\"settings_grid_working_card_background_summary\">Imagen de fondo personalizada para la tarjeta de estado</string>\n    <string name=\"settings_grid_working_card_background_selected\">Fondo seleccionado</string>\n    <string name=\"settings_grid_working_card_dual_opacity\">Habilitar opacidad dual día/noche</string>\n    <string name=\"settings_grid_working_card_dual_opacity_desc\">Ajustar automáticamente la opacidad de la tarjeta para temas claros y oscuros</string>\n    <string name=\"settings_grid_working_card_day_opacity\">Opacidad de tarjeta en modo día</string>\n    <string name=\"settings_grid_working_card_night_opacity\">Opacidad de tarjeta en modo noche</string>\n    <string name=\"settings_clear_grid_working_card_background\">Borrar Fondo de Tarjeta</string>\n    <string name=\"settings_clear_grid_working_card_background_confirm\">¿Seguro que deseas borrar la imagen de fondo de la tarjeta?</string>\n    <string name=\"settings_grid_working_card_background_saved\">Fondo de tarjeta guardado con éxito</string>\n    <string name=\"settings_grid_working_card_background_error\">Error al guardar fondo de tarjeta</string>\n    <string name=\"settings_grid_working_card_background_cleared\">Fondo de tarjeta borrado</string>\n\n    <!-- Biometric Strings -->\n    <string name=\"action_biometric\">Autenticación Biométrica</string>\n    <string name=\"msg_biometric\">Por favor verifica tu identidad biométrica</string>\n    <string name=\"settings_biometric_login\">Autenticación Biométrica</string>\n    <string name=\"settings_biometric_login_summary\">Requerir autenticación biométrica al abrir la app</string>\n    <string name=\"settings_strong_biometric\">Autenticación biométrica fuerte</string>\n    <string name=\"settings_strong_biometric_summary\">Requerir autenticación para instalar/desinstalar/desactivar módulos</string>\n\n    <!-- Theme Store Strings -->\n    <string name=\"theme_store_title\">Tienda de Temas</string>\n    <string name=\"theme_source_official\">Oficial</string>\n    <string name=\"theme_source_third_party\">Terceros</string>\n    <string name=\"theme_store_author\">Autor: %s</string>\n    <string name=\"theme_store_version\">Versión: %s</string>\n    <string name=\"theme_source\">Fuente</string>\n    <string name=\"theme_store_download_failed\">Descarga fallida</string>\n    <string name=\"theme_store_download\">Descargar</string>\n    <string name=\"theme_store_search_hint\">Buscar temas...</string>\n\n    <!-- Update Check Strings -->\n    <string name=\"settings_auto_update_check\">Verificación Automática de Actualizaciones</string>\n    <string name=\"settings_auto_update_check_summary\">Buscar actualizaciones automáticamente al iniciar la app</string>\n    <string name=\"settings_check_update\">Buscar Actualizaciones</string>\n    <string name=\"update_available_title\">Actualización Disponible</string>\n    <string name=\"update_available_message\">Se detectó que tu versión es antigua, ¿deseas descargar la nueva versión?</string>\n    <string name=\"update_action\">Actualizar</string>\n    <string name=\"update_close\">Cerrar</string>\n    <string name=\"update_latest\">Estás en la última versión</string>\n    <string name=\"update_error\">Error al buscar actualizaciones</string>\n    <string name=\"settings_category_general\">General</string>\n    <string name=\"superuser\">Superusuario</string>\n    <string name=\"module\">Módulos</string>\n    <string name=\"settings_category_appearance\">Apariencia</string>\n    <string name=\"settings_appearance_font\">Configuración de fuente</string>\n    <string name=\"settings_appearance_font_summary\">Configuración de fuente personalizada</string>\n    <string name=\"settings_appearance_theme\">Configuración de tema</string>\n    <string name=\"settings_appearance_theme_summary\">Tienda de temas, guardar, importar y restablecer</string>\n    <string name=\"settings_appearance_banner\">Configuración de banner</string>\n    <string name=\"settings_appearance_banner_summary\">Configuración de banner de módulo</string>\n    <string name=\"settings_appearance_layout\">Configuración de diseño</string>\n    <string name=\"settings_appearance_layout_summary\">Diseño de inicio, navegación y personalización de tarjetas</string>\n    <string name=\"settings_appearance_background\">Configuración de fondo</string>\n    <string name=\"settings_appearance_background_summary\">Fondo personalizado, video y multi-fondo</string>\n    <string name=\"settings_appearance_night_mode\">Configuración de modo nocturno</string>\n    <string name=\"settings_appearance_night_mode_summary\">Tema oscuro y configuración de color</string>\n    <string name=\"settings_amoled_theme\">Tema Negro AMOLED</string>\n    <string name=\"settings_amoled_theme_desc\">Fondo negro puro para el modo oscuro</string>\n    <string name=\"settings_switch_icon\">Indicador de botón</string>\n    <string name=\"settings_switch_icon_desc\">Mostrar iconos de estado en los interruptores</string>\n    <string name=\"settings_category_behavior\">Comportamiento</string>\n    <string name=\"settings_category_function\">Función</string>\n    <string name=\"settings_use_legacy_su_page\">Autorización de superusuario de página única</string>\n    <string name=\"settings_use_legacy_su_page_summary\">La página de superusuario cambió a diseño de autorización de página única</string>\n    <string name=\"settings_category_module\">Módulos</string>\n    <string name=\"settings_category_security\">Seguridad</string>\n    \n    <!-- Background Music Strings -->\n    <string name=\"settings_background_music\">Música de Fondo</string>\n    <string name=\"settings_background_music_summary\">Reproducir música de fondo cuando la app esté en primer plano</string>\n    <string name=\"settings_background_music_playing\">Reproduciendo: %s</string>\n    <string name=\"settings_background_music_enabled\">Música de Fondo Habilitada</string>\n    <string name=\"settings_select_music_file\">Seleccionar archivo de música</string>\n    <string name=\"settings_music_selected\">Música seleccionada</string>\n    <string name=\"settings_clear_music\">Borrar música</string>\n    <string name=\"settings_clear_music_confirm\">¿Seguro que deseas borrar la música de fondo?</string>\n    <string name=\"settings_music_auto_play\">Reproducción automática</string>\n    <string name=\"settings_music_auto_play_summary\">Reproducir música automáticamente al abrir la app</string>\n    <string name=\"settings_music_looping\">Bucle</string>\n    <string name=\"settings_music_looping_summary\">Repetir la canción actual</string>\n    <string name=\"settings_music_volume\">Volumen</string>\n    <string name=\"settings_music_saved\">Archivo de música guardado</string>\n    <string name=\"settings_music_save_error\">Error al guardar archivo de música</string>\n    <string name=\"settings_music_cleared\">Música borrada</string>\n    <string name=\"settings_category_multimedia\">Multimedia</string>\n    <string name=\"settings_category_general_summary\">Idioma, actualizaciones, SELinux, ajustes del sistema</string>\n    <string name=\"settings_category_appearance_summary\">Tema, colores, diseño, fondo, fuentes</string>\n    <string name=\"settings_category_behavior_summary\">Depuración web, comportamiento de instalación, pantalla de inicio</string>\n    <string name=\"settings_category_security_summary\">Biometría, gestión de superclaves</string>\n    <string name=\"settings_category_backup_summary\">Copia de seguridad local, en la nube, WebDAV</string>\n    <string name=\"settings_category_module_summary\">Info de módulos, ordenación, instalación por lotes</string>\n    <string name=\"settings_category_function_summary\">Ocultar FolkPatch, servicio Umount</string>\n    <string name=\"settings_category_multimedia_summary\">Música de fondo, sonidos, vibración</string>\n    <string name=\"settings_music_playback_control\">Control de Reproducción</string>\n\n    <!-- Theme Store Filter -->\n    <string name=\"theme_store_filter_title\">Filtrar Temas</string>\n    <string name=\"theme_store_filter_author\">Autor</string>\n    <string name=\"theme_store_filter_author_hint\">Ingresa nombre del autor</string>\n    <string name=\"theme_store_filter_source\">Fuente</string>\n    <string name=\"theme_store_filter_source_all\">Todos</string>\n    <string name=\"theme_store_filter_type\">Tipo de Dispositivo</string>\n    <string name=\"theme_store_filter_apply\">Aplicar</string>\n    <string name=\"theme_store_filter_reset\">Restablecer</string>\n\n    <!-- File Picker -->\n    <string name=\"file_picker_permission_required\">Permiso Requerido</string>\n    <string name=\"file_picker_permission_desc\">Para explorar archivos, por favor concede el permiso \\'Acceso a todos los archivos\\'.</string>\n    <string name=\"file_picker_grant_permission\">Conceder Permiso</string>\n    <string name=\"file_picker_cancel\">Cancelar</string>\n    <string name=\"file_picker_internal_storage\">Almacenamiento Interno</string>\n    <string name=\"file_picker_no_files\">Sin archivos</string>\n\n    <!-- HomeV3 Strings -->\n    <string name=\"home_device_status_title\">Estado del Dispositivo</string>\n    <string name=\"home_device_status_battery_temp\">Temp. Batería</string>\n    <string name=\"home_device_status_cpu_load\">Carga CPU</string>\n    <string name=\"home_device_status_battery_level\">Nivel de Batería</string>\n    <string name=\"home_storage_title\">Almacenamiento</string>\n    <string name=\"home_storage_internal\">Almacenamiento Interno</string>\n    <string name=\"home_storage_ram\">RAM</string>\n    <string name=\"home_storage_zram\">ZRAM</string>\n    <string name=\"home_storage_swap\">Archivo Swap</string>\n    <string name=\"home_info_kernel\">Kernel</string>\n    <string name=\"home_info_superkey\">SuperKey</string>\n    <string name=\"home_info_auth_auth\">Autenticado</string>\n    <string name=\"home_info_auth_na\">N/D</string>\n    <string name=\"home_info_device_slot\">Ranura del Dispositivo</string>\n    <string name=\"home_info_device_model\">Modelo del Dispositivo</string>\n    <string name=\"home_info_running_mode\">Modo de Ejecución</string>\n    <string name=\"home_info_mode_full\">Completo (Full)</string>\n    <string name=\"home_info_mode_half\">Medio (Half)</string>\n    <string name=\"home_version\">Versión: %s</string>\n    <string name=\"home_zygisk_implement\">Implementación de Zygisk</string>\n    <string name=\"home_mount_implement\">Implementación de Montaje</string>\n\n    <string name=\"settings_list_card_hide_status_badge\">Usar Emojis Clásicos</string>\n    <string name=\"settings_list_card_hide_status_badge_summary\">La insignia de estado en la página de inicio se convierte en emoji clásico</string>\n    <string name=\"settings_custom_badge_text\">Personalizar Texto de Insignia</string>\n    <string name=\"settings_custom_badge_text_summary\">Modificar la visualización del texto de la insignia, solo por diversión</string>\n\n    <!-- Install Mode Select -->\n    <string name=\"kp_install_methods\">Parcheando/Instalando KernelPatch</string>\n    <string name=\"restore_boot_methods\">Selecciona un boot para restaurar a la partición boot</string>\n\n    <string name=\"settings_app_list_loading_scheme\">Esquema de Carga de Lista de Aplicaciones</string>\n    <string name=\"settings_app_list_loading_scheme_summary\">Elige cómo cargar la lista de aplicaciones</string>\n    <string name=\"app_list_loading_scheme_root_service\">RootService (Predeterminado)</string>\n    <string name=\"app_list_loading_scheme_package_manager\">PackageManager (API del Sistema)</string>\n    <string name=\"app_list_loading_scheme_dialog_title\">Esquema de Carga</string>\n    <string name=\"su_backup_list\">Lista de copia de seguridad</string>\n    <string name=\"su_restore_list\">Lista de restauración</string>\n    <string name=\"backup_success\">Copia de seguridad exitosa</string>\n    <!-- Badge Count Settings -->\n    <string name=\"enable_badge_count\">Configuración de conteo de insignias</string>\n    <string name=\"enable_badge_count_summary\">Configurar la visualización del conteo de insignias para elementos de navegación</string>\n    <string name=\"badge_superuser\">Mostrar insignia de superusuario</string>\n    <string name=\"badge_apm\">Mostrar insignia de módulo del sistema</string>\n    <string name=\"badge_kernel\">Mostrar insignia de módulo del kernel</string>\n    <string name=\"settings_sound_effect\">Efecto de sonido</string>\n    <string name=\"settings_sound_effect_summary\">Reproducir efecto de sonido al hacer clic</string>\n    <string name=\"settings_sound_effect_enabled\">Habilitado</string>\n    <string name=\"settings_sound_effect_playing\">Seleccionado: %s</string>\n    <string name=\"settings_sound_effect_source\">Fuente de sonido</string>\n    <string name=\"settings_sound_effect_source_local\">Archivo local</string>\n    <string name=\"settings_sound_effect_source_preset\">Predeterminado</string>\n    <string name=\"settings_sound_effect_preset_title\">Sonidos predeterminados</string>\n    <string name=\"settings_select_sound_effect\">Seleccionar archivo de sonido</string>\n    <string name=\"settings_sound_effect_selected\">Archivo de sonido seleccionado</string>\n    <string name=\"settings_sound_effect_scope\">Alcance del efecto</string>\n    <string name=\"settings_sound_effect_scope_global\">Global (En todas partes)</string>\n    <string name=\"settings_sound_effect_scope_bottom_bar\">Solo barra inferior</string>\n    <string name=\"settings_clear_sound_effect\">Borrar efecto de sonido</string>\n    <string name=\"settings_clear_sound_effect_confirm\">¿Estás seguro de que quieres borrar el efecto de sonido?</string>\n    <string name=\"settings_sound_effect_cleared\">Efecto de sonido borrado</string>\n\n    <string name=\"settings_startup_sound\">Sonido de inicio</string>\n    <string name=\"settings_startup_sound_summary\">Reproducir sonido al iniciar la aplicación</string>\n    <string name=\"settings_startup_sound_enabled\">Sonido de inicio habilitado</string>\n    <string name=\"settings_startup_sound_playing\">Reproduciendo: %s</string>\n    <string name=\"settings_select_startup_sound\">Seleccionar sonido de inicio</string>\n    <string name=\"settings_startup_sound_selected\">Sonido de inicio seleccionado</string>\n    <string name=\"settings_clear_startup_sound\">Borrar sonido de inicio</string>\n    <string name=\"settings_clear_startup_sound_confirm\">¿Estás seguro de que quieres borrar el sonido de inicio?</string>\n    <string name=\"settings_startup_sound_cleared\">Sonido de inicio borrado</string>\n\n    <string name=\"settings_enable_cloud_backup_summary\">Copia de seguridad automática en la nube</string>\n    <string name=\"settings_configure_webdav\">Configurar servicio WebDAV</string>\n    <string name=\"webdav_config_title\">Configuración WebDAV</string>\n    <string name=\"webdav_url\">URL de WebDAV</string>\n    <string name=\"webdav_username\">Nombre de usuario</string>\n    <string name=\"webdav_password\">Contraseña</string>\n    <string name=\"webdav_uploading\">Subiendo a WebDAV...</string>\n    <string name=\"webdav_backup_success\">Copia de seguridad WebDAV exitosa</string>\n    <string name=\"webdav_backup_failed\">Error en copia de seguridad WebDAV: %s</string>\n    <string name=\"save\">Guardar</string>\n    <string name=\"test\">Probar</string>\n    <string name=\"webdav_path_label\">Ruta (ej. /Backup)</string>\n    <string name=\"webdav_view_logs\">Ver registros</string>\n    <string name=\"webdav_backup_logs_title\">Registros de copia de seguridad</string>\n    <string name=\"webdav_no_logs\">No hay registros</string>\n    <string name=\"webdav_clear_logs\">Borrar</string>\n    <string name=\"settings_enable_local_backup\">Habilitar copia de seguridad local</string>\n    <string name=\"settings_enable_local_backup_summary\">Copia de seguridad en almacenamiento local (Downloads/FolkPatch/ModuleBackups)</string>\n    <string name=\"close\">Cerrar</string>\n\n    <!-- Backup Settings -->\n    <string name=\"settings_category_backup\">Configuración de copia de seguridad</string>\n    <string name=\"settings_enable_cloud_backup\">Habilitar copia de seguridad en la nube</string>\n    <string name=\"settings_webdav_url\">URL de WebDAV</string>\n    <string name=\"settings_webdav_username\">Usuario de WebDAV</string>\n    <string name=\"settings_webdav_password\">Contraseña de WebDAV</string>\n    <string name=\"settings_test_webdav\">Probar conexión WebDAV</string>\n    <string name=\"webdav_test_success\">Prueba exitosa</string>\n    <string name=\"webdav_test_failed\">Prueba fallida: %s</string>\n    <string name=\"settings_backup_now\">Hacer copia de seguridad ahora</string>\n    <string name=\"backup_failed\">Copia de seguridad fallida: %s</string>\n    <string name=\"settings_restore_backup\">Restaurar copia de seguridad</string>\n    <string name=\"restore_success\">Restauración exitosa</string>\n    <string name=\"restore_failed\">Restauración fallida: %s</string>\n    <string name=\"settings_auto_backup\">Copia de seguridad automática</string>\n    <string name=\"settings_auto_backup_summary\">Copia de seguridad automática una vez al día</string>\n    <string name=\"settings_delete_remote_backup\">Eliminar copia de seguridad en la nube</string>\n    <string name=\"delete_remote_backup_confirm\">¿Está seguro de que desea eliminar la copia de seguridad en la nube?</string>\n    <string name=\"delete_success\">Eliminación exitosa</string>\n    <string name=\"delete_failed\">Eliminación fallida: %s</string>\n    <string name=\"settings_encrypt_backup\">Cifrar copia de seguridad</string>\n    <string name=\"settings_encrypt_backup_summary\">Cifrar archivo de copia de seguridad con contraseña</string>\n    <string name=\"settings_backup_password\">Contraseña de copia de seguridad</string>\n    <string name=\"settings_backup_password_hint\">Ingrese contraseña de copia de seguridad</string>\n\n    <string name=\"patch_output_written_to\"> Parcheado con éxito, archivo escrito en </string>\n    <string name=\"patch_write_failed\"> Error al escribir boot.img parcheado</string>\n\n    <!-- Script Library Strings -->\n    <string name=\"script_library\">Biblioteca de Scripts</string>\n    <string name=\"script_library_title\">Biblioteca de Scripts</string>\n    <string name=\"script_library_empty\">Sin scripts, haz clic en añadir para agregar</string>\n    <string name=\"script_library_add\">Añadir Script</string>\n    <string name=\"script_library_add_title\">Añadir Script</string>\n    <string name=\"script_library_select_file\">Seleccionar archivo de script</string>\n    <string name=\"script_library_alias\">Alias</string>\n    <string name=\"script_library_alias_hint\">Ingresa el alias del script (opcional)</string>\n    <string name=\"script_library_run\">Ejecutar</string>\n    <string name=\"script_library_delete\">Eliminar</string>\n    <string name=\"script_library_path\">Ruta: %s</string>\n    <string name=\"script_library_confirm_delete\">¿Confirmar eliminación del script?</string>\n    <string name=\"script_library_delete_success\">Script eliminado correctamente</string>\n    <string name=\"script_library_delete_failed\">Error al eliminar script</string>\n    <string name=\"script_library_add_success\">Script añadido correctamente</string>\n    <string name=\"script_library_add_failed\">Error al añadir script: %s</string>\n    <string name=\"script_library_load_failed\">Error al cargar biblioteca de scripts</string>\n    <string name=\"script_library_output\">Salida de ejecución</string>\n    <string name=\"script_library_no_output\">Sin salida</string>\n    <string name=\"script_library_save_log\">Guardar registro</string>\n    <string name=\"script_library_log_saved\">Registro guardado en %s</string>\n    <string name=\"script_library_log_save_failed\">Error al guardar registro: %s</string>\n    <string name=\"settings_vibration\">Vibración y háptica</string>\n    <string name=\"settings_vibration_summary\">Vibrar al tocar</string>\n    <string name=\"settings_vibration_enabled\">Activar vibración</string>\n    <string name=\"settings_vibration_intensity\">Intensidad de vibración</string>\n    <string name=\"settings_vibration_scope\">Alcance de vibración</string>\n    <string name=\"settings_vibration_scope_global\">Global (En todas partes)</string>\n    <string name=\"settings_vibration_scope_bottom_bar\">Solo barra inferior</string>\n    <string name=\"search_modules\">Buscar módulos...</string>\n    <string name=\"search_scripts\">Buscar scripts...</string>\n\n    <!-- Umount Service -->\n    <string name=\"settings_umount_service\">Umount Service</string>\n    <string name=\"settings_umount_service_summary\">Configurar puntos de montaje para desmontar automáticamente al iniciar</string>\n    <string name=\"umount_config_title\">Configuración de Umount</string>\n    <string name=\"umount_config_enabled\">Activar Umount</string>\n    <string name=\"umount_config_enabled_summary\">Desmontar automáticamente puntos de montaje específicos al iniciar</string>\n    <string name=\"umount_config_paths_label\">Rutas de puntos de montaje (uno por línea)</string>\n    <string name=\"umount_config_paths_placeholder\">/system/vendor/app\\n/system/vendor/priv-app</string>\n    <string name=\"umount_config_paths_helper\">Ingrese una ruta de punto de montaje por línea para desmontar</string>\n    <string name=\"umount_config_save\">Guardar</string>\n    <string name=\"umount_config_save_confirm\">¿Confirmar guardar configuración?</string>\n    <string name=\"umount_config_save_success\">Configuración guardada exitosamente</string>\n    <string name=\"umount_config_save_failed\">Error al guardar configuración</string>\n\n    <!-- Página Mis Temas -->\n    <string name=\"my_themes_title\">Mis Temas</string>\n    <string name=\"my_themes_empty\">Aún no hay temas</string>\n    <string name=\"my_themes_empty_action\">Explorar Tienda de Temas</string>\n    <string name=\"my_themes_apply\">Aplicar Tema</string>\n    <string name=\"my_themes_delete\">Eliminar Tema</string>\n    <string name=\"my_themes_delete_confirm\">¿Estás seguro de que quieres eliminar este tema?</string>\n    <string name=\"my_themes_deleted\">Tema eliminado</string>\n    <string name=\"my_themes_applied\">Tema aplicado</string>\n    <string name=\"my_themes_apply_failed\">Error al aplicar el tema</string>\n    <string name=\"my_themes_details\">Detalles del Tema</string>\n\n    <!-- Diálogo de Descarga -->\n    <string name=\"theme_download_title\">Descargando Tema</string>\n    <string name=\"theme_download_completed\">Descarga completada</string>\n    <string name=\"theme_download_failed\">Descarga fallida</string>\n    <string name=\"theme_download_progress\">Progreso</string>\n    <string name=\"theme_download_file\">Archivo de Tema</string>\n    <string name=\"theme_download_image\">Imagen de Vista Previa</string>\n    <string name=\"theme_download_cancel\">Cancelar</string>\n    <string name=\"theme_download_pause\">Pausar</string>\n    <string name=\"theme_download_resume\">Reanudar</string>\n    <string name=\"theme_download_retry\">Reintentar</string>\n    <string name=\"theme_download_apply\">Aplicar Tema</string>\n    <string name=\"theme_download_go_to_my_themes\">Mis Temas</string>\n    <string name=\"theme_download_retrying\">Reintentando (%d/3)...</string>\n    <string name=\"theme_download_preparing\">Preparando descarga...</string>\n    <string name=\"theme_download_downloading_file\">Descargando archivo de tema...</string>\n    <string name=\"theme_download_downloading_image\">Descargando imagen de vista previa...</string>\n    <string name=\"theme_download_finalizing\">Finalizando...</string>\n    <string name=\"settings_predictive_back\">Gesto atrás predictivo</string>\n    <string name=\"settings_predictive_back_summary\">Habilitar la animación del gesto atrás predictivo de Android 14+</string>\n\n    <string name=\"home_device_status_battery_charging\">Cargando</string>\n    <string name=\"home_device_status_cpu_temp\">Temp. CPU</string>\n    <string name=\"home_device_status_memory_trend\">Tendencia de Memoria</string>\n    <string name=\"home_storage_partitions\">Particiones de Almacenamiento</string>\n    <string name=\"home_device_status_cpu_freq\">Frecuencia CPU</string>\n    <string name=\"home_network_rx\">Descarga</string>\n    <string name=\"home_network_tx\">Subida</string>\n    <string name=\"settings_home_layout_stats\">StatsUI</string>\n    <string name=\"home_stats_more_options\">Más opciones</string>\n    <string name=\"home_stats_kp_label\">KP</string>\n    <string name=\"home_stats_manager_label\">Gestor</string>\n    <string name=\"home_device_status_gpu_load\">GPU</string>\n    <string name=\"home_stats_kernel_modules\">Módulos del Kernel</string>\n    <string name=\"home_stats_apm_modules\">Módulos del Sistema</string>\n    <string name=\"home_stats_superusers\">Superusuarios</string>\n    <string name=\"settings_kernel_spoof\">Configuración de Falsificación del Kernel</string>\n    <string name=\"settings_kernel_spoof_summary\">Falsificar versión del kernel y tiempo de compilación</string>\n    <string name=\"settings_kernel_spoof_version\">Versión del Kernel</string>\n    <string name=\"settings_kernel_spoof_build_time\">Tiempo de Compilación del Kernel</string>\n    <string name=\"settings_kernel_spoof_restore\">Restaurar</string>\n\n    <string name=\"kernel_spoof_enabled\">Falsificación del kernel habilitada</string>\n    <string name=\"kernel_spoof_disabled_restored\">Falsificación del kernel deshabilitada y restaurada</string>\n    <string name=\"kernel_spoof_failed\">Error de falsificación del kernel: %d</string>\n    <string name=\"kernel_spoof_applied\">Falsificación del kernel aplicada</string>\n\n    <string name=\"settings_path_hide\">Ocultar ruta</string>\n    <string name=\"settings_path_hide_summary\">Ocultar archivos y directorios de las aplicaciones a nivel de kernel</string>\n    <string name=\"path_hide_paths_label\">Rutas ocultas (una por línea)</string>\n\n    <string name=\"path_hide_paths_helper\">Ingrese una ruta por línea. Las rutas coincidentes devolverán ENOENT.</string>\n    <string name=\"path_hide_save\">Guardar</string>\n    <string name=\"path_hide_enabled\">Ocultar ruta habilitado</string>\n    <string name=\"path_hide_disabled\">Ocultar ruta deshabilitado</string>\n    <string name=\"path_hide_applied\">Configuración de ocultar ruta aplicada</string>\n    <string name=\"path_hide_failed\">Error en la operación de ocultar ruta: %d</string>\n    <string name=\"path_hide_uid_mode\">Modo de ejecución por UID</string>\n    <string name=\"path_hide_uid_mode_summary\">Ocultar rutas solo para UID de aplicaciones específicas</string>\n    <string name=\"path_hide_uids_label\">UID de destino (uno por línea)</string>\n    <string name=\"path_hide_uids_helper\">Introduce los UID de las aplicaciones. Solo estas aplicaciones tendrán rutas ocultas.</string>\n    <string name=\"path_hide_uid_save\">Guardar UID</string>\n    <string name=\"path_hide_uid_mode_enabled\">Modo de ejecución por UID activado</string>\n    <string name=\"path_hide_uid_mode_disabled\">Modo de ejecución por UID desactivado</string>\n    <string name=\"path_hide_filter_system\">Filtrar UIDs del sistema</string>\n    <string name=\"path_hide_filter_system_summary\">También ocultar rutas de procesos root y del sistema (UID &lt; 10000)</string>\n    <string name=\"path_hide_filter_system_enabled\">Filtrado de UIDs del sistema activado</string>\n    <string name=\"path_hide_filter_system_disabled\">Filtrado de UIDs del sistema desactivado</string>\n    <string name=\"path_hide_filter_system_warning_title\">Advertencia</string>\n    <string name=\"path_hide_filter_system_warning_message\">Al activar esto también se ocultarán rutas de procesos root y del sistema (UID &lt; 10000). Esto puede causar que algunas funciones del sistema no funcionen correctamente. Proceda con precaución.</string>\n    <string name=\"path_hide_uid_applied\">Lista blanca de UID aplicada</string>\n    <string name=\"path_hide_select_apps\">Seleccionar aplicaciones</string>\n    <string name=\"path_hide_no_apps_selected\">Ninguna aplicación seleccionada</string>\n    <string name=\"path_hide_app_removed\">Se eliminaron %d entradas de aplicaciones desinstaladas</string>\n    <string name=\"path_hide_search_apps\">Buscar aplicaciones…</string>\n    <string name=\"path_hide_show_system\">Mostrar aplicaciones del sistema</string>\n    <string name=\"netisolate_title\">Aislamiento de red</string>\n    <string name=\"netisolate_enable\">Aislamiento de red activado</string>\n    <string name=\"netisolate_disable\">Aislamiento de red desactivado</string>\n    <string name=\"netisolate_enable_summary\">Bloquear el acceso a la red de las aplicaciones seleccionadas a nivel del kernel</string>\n    <string name=\"netisolate_uids_label\">Aplicaciones bloqueadas</string>\n    <string name=\"netisolate_uids_hint\">Ingrese los UIDs a bloquear (uno por línea)</string>\n    <string name=\"netisolate_no_uids\">No hay aplicaciones bloqueadas</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-in/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\" translatable=\"false\">FolkPatch</string>\n    <string name=\"app_name_fpatch\">FPatch</string>\n\n    <string name=\"desktop_app_name\">Nama Aplikasi Desktop</string>\n\n    <string name=\"home\">Home</string>\n\n    <string name=\"settings_apm_stay_on_page\">Tetap di halaman operasi</string>\n    <string name=\"settings_apm_stay_on_page_summary\">Jangan kembali secara otomatis setelah tindakan modul selesai</string>\n\n    <string name=\"success\">Berhasil</string>\n    <string name=\"failure\">Gagal</string>\n\n    <string name=\"patch_warnning\">Pemasangan memiliki risiko. Pastikan Anda memiliki cadangan data.</string>\n    <string name=\"patch\">Patch</string>\n\n    <string name=\"kernel_patch\">KernelPatch</string>\n    <string name=\"android_patch\">AndroidPatch</string>\n\n    <string name=\"settings_nav_layout_title\">Pengaturan Tata Letak Navigasi</string>\n    <string name=\"settings_nav_layout_summary\">Sembunyikan atau tampilkan beberapa komponen navigasi</string>\n    <string name=\"settings_nav_scheme\">Skema Navigasi</string>\n    <string name=\"settings_nav_mode\">Mode Bilah Navigasi</string>\n    <string name=\"settings_nav_mode_summary\">Pilih cara tampilan bilah navigasi</string>\n    <string name=\"settings_nav_mode_auto\">Otomatis Tradisional</string>\n    <string name=\"settings_nav_mode_bottom\">Selalu Bilah Bawah</string>\n    <string name=\"settings_nav_mode_rail\">Selalu Bilah Samping</string>\n    <string name=\"settings_nav_mode_floating\">Bilah Bawah Mengambang</string>\n    <string name=\"settings_show_apm\">Tampilkan Modul Sistem</string>\n    <string name=\"settings_show_kpm\">Tampilkan KPM</string>\n    <string name=\"settings_show_superuser\">Tampilkan SuperUser</string>\n    <string name=\"settings_floating_auto_hide\">Sembunyi Otomatis</string>\n    <string name=\"settings_floating_auto_hide_summary\">Sembunyikan bilah mengambang secara otomatis setelah 3 detik tanpa aktivitas</string>\n    <string name=\"settings_floating_swipe_hide\">Sembunyikan dengan Geser</string>\n    <string name=\"settings_floating_swipe_hide_summary\">Sembunyikan saat geser ke bawah, tampilkan saat geser ke atas</string>\n    <string name=\"settings_navbar_glass_effect\">Efek Kaca Buram</string>\n    <string name=\"settings_navbar_glass_effect_summary\">Terapkan efek kaca buram pada bilah navigasi mengambang</string>\n    <string name=\"settings_navbar_glass_blur_strength\">Kekuatan Blur</string>\n    <string name=\"settings_navbar_glass_transparency\">Transparansi Latar Belakang</string>\n    <string name=\"settings_navbar_glass_highlight_strength\">Intensitas Sorotan</string>\n    <string name=\"settings_navbar_glass_specular\">Refleksi Spekular</string>\n    <string name=\"settings_navbar_glass_specular_summary\">Aktifkan sorotan cermin di bagian atas bilah navigasi</string>\n    <string name=\"settings_navbar_glass_inner_glow\">Cahaya Dalam</string>\n    <string name=\"settings_navbar_glass_inner_glow_summary\">Aktifkan efek cahaya halus di bagian bawah bilah navigasi</string>\n    <string name=\"settings_navbar_glass_border\">Batas Kaca</string>\n    <string name=\"settings_navbar_glass_border_summary\">Aktifkan batas halus di sekitar bilah navigasi</string>\n    <string name=\"settings_stats_top_layout\">Tata Letak Atas</string>\n    <string name=\"settings_stats_top_layout_summary\">Pilih gaya kartu info atas</string>\n    <string name=\"settings_stats_top_layout_list\">Daftar</string>\n    <string name=\"settings_stats_top_layout_grid\">Grid</string>\n    <string name=\"settings_block_kernelpatch_update\">Blokir Pembaruan KernelPatch</string>\n    <string name=\"settings_block_kernelpatch_update_summary\">Jangan tampilkan notifikasi pembaruan KernelPatch</string>\n    <string name=\"settings_block_androidpatch_update\">Blokir Pembaruan Patch Sistem</string>\n    <string name=\"settings_block_androidpatch_update_summary\">Jangan tampilkan notifikasi pembaruan patch sistem (APD)</string>\n    <string name=\"settings_disable_module_update_check\">Nonaktifkan pemeriksaan pembaruan modul</string>\n    <string name=\"settings_disable_module_update_check_summary\">Nonaktifkan pemeriksaan pembaruan otomatis untuk modul sistem</string>\n\n    <string name=\"reboot\">Mulai Ulang</string>\n    <string name=\"settings\">Pengaturan</string>\n    <string name=\"reboot_recovery\">Mulai Ulang ke Recovery</string>\n    <string name=\"reboot_bootloader\">Mulai Ulang ke Bootloader</string>\n    <string name=\"reboot_download\">Mulai Ulang ke Download</string>\n    <string name=\"reboot_edl\">Mulai Ulang ke EDL</string>\n    <string name=\"reboot_fastbootd\">Mulai Ulang ke FastbootD</string>\n    <string name=\"about\">Tentang Aplikasi</string>\n    <string name=\"developer_and_maintainer\">Pengembang | Pemelihara</string>\n    <string name=\"settings_app_language\">Bahasa</string>\n    <string name=\"system_default\">Bawaan Sistem</string>\n    <string name=\"settings_global_namespace_mode\">Mode Ruang Nama Global</string>\n    <string name=\"settings_global_namespace_mode_summary\">Semua sesi root menggunakan ruang mount global</string>\n    <string name=\"settings_clear_super_key_dialog\">Apakah Anda yakin ingin melanjutkan?</string>\n\n    <string name=\"su_exclude_all_title\">Kecualikan semua</string>\n    <string name=\"su_exclude_all_confirm\">Apakah Anda yakin ingin mengecualikan semua aplikasi tanpa hak root?</string>\n\n    <string name=\"home_learn_apatch\">Pelajari FolkPatch</string>\n    <string name=\"home_click_to_learn_apatch\">Pelajari tentang fitur FolkPatch dan cara menggunakannya</string>\n    <string name=\"settings_hide_apatch_card\">Sembunyikan kartu \"Pelajari FolkPatch\"</string>\n    <string name=\"settings_hide_apatch_card_summary\">Sembunyikan kartu \"Pelajari FolkPatch\" di layar utama</string>\n\n    <string name=\"about_source_code\"><![CDATA[<p>Lihat kode sumber di %1$s<p/>Bergabunglah dengan saluran %2$s kami<p/>Bergabunglah dengan grup %3$s kami]]></string>\n    <string name=\"send_log\">Kirim Log</string>\n    <string name=\"save_log\">Simpan Log</string>\n    <string name=\"log_saved\">Log tersimpan</string>\n    <string name=\"safe_mode\">Mode Aman</string>\n    <string name=\"github\">GitHub</string>\n    <string name=\"telegram\">Telegram</string>\n    <string name=\"support_or_donate\">Dukung / Donasi</string>\n\n    <string name=\"super_key\">SuperKey</string>\n    <string name=\"clear_super_key\">Hapus SuperKey</string>\n    <string name=\"patch_set_superkey\">Atur SuperKey</string>\n    <string name=\"home_patch_set_key_desc\">Kredensial tunggal untuk KernelPatch</string>\n    <string name=\"home_patch_next_step\">Langkah Selanjutnya</string>\n\n    <string name=\"home_not_installed\">Tidak terinstal</string>\n    <string name=\"home_install_unknown\">Tidak terinstal atau tidak terverifikasi</string>\n    <string name=\"home_install_unknown_summary\">Klik untuk menginstal</string>\n    <string name=\"home_click_to_install\">Klik untuk menginstal</string>\n    <string name=\"home_working\">Berfungsi</string>\n    <string name=\"home_kp_need_update\">Versi baru tersedia</string>\n    <string name=\"home_kp_cando_update\">Perbarui</string>\n\n    <string name=\"home_installing\">Menginstal</string>\n\n    <string name=\"kpatch_version\">Versi: %s</string>\n    <string name=\"apatch_version\">Versi: %s</string>\n\n    <string name=\"kpatch_version_update\" formatted=\"false\">Versi: %s -&gt; %s</string>\n    <string name=\"kpatch_shadow_path_title\">kpatch</string>\n    <string name=\"home_auth_key_title\">Masukkan SuperKey</string>\n    <string name=\"home_auth_key_desc\">Jalankan setelah autentikasi</string>\n    <string name=\"home_kpatch_info_title\">Informasi</string>\n\n    <string name=\"home_ap_cando_install\">Instal</string>\n\n    <string name=\"home_ap_cando_uninstall\">Hapus Instalasi</string>\n    <string name=\"home_ap_cando_reboot\">Mulai Ulang</string>\n\n    <string name=\"patch_title\">Patch</string>\n\n    <string name=\"patch_config_title\">Patch</string>\n    <string name=\"patch_mode_bootimg_patch\">Mode: Patch</string>\n    <string name=\"patch_mode_patch_and_install\">Mode: Patch dan instal</string>\n    <string name=\"patch_mode_install_to_next_slot\">Mode: Instal ke slot tidak aktif (Setelah OTA)</string>\n    <string name=\"patch_mode_uninstall_patch\">Mode: Hapus Instalasi KPatch</string>\n    <string name=\"patch_select_bootimg_btn\">Pilih boot</string>\n    <string name=\"patch_embed_kpm_btn\">Sematkan KPM</string>\n    <string name=\"patch_start_patch_btn\">Mulai</string>\n    <string name=\"patch_start_unpatch_btn\">Hapus Patch</string>\n    <string name=\"patch_item_error\">!!KESALAHAN!!</string>\n    <string name=\"patch_item_bootimg\">bootimg</string>\n    <string name=\"patch_item_bootimg_slot\">Slot:</string>\n    <string name=\"patch_item_bootimg_dev\">Perangkat:</string>\n    <string name=\"patch_item_kernel\">Kernel</string>\n    <string name=\"patch_item_kpimg\">kpimg</string>\n    <string name=\"patch_item_kpimg_version\">Versi:</string>\n    <string name=\"patch_item_kpimg_comile_time\">Waktu:</string>\n    <string name=\"patch_item_kpimg_config\">Konfigurasi:</string>\n    <string name=\"patch_item_new_extra_kpm\">Sematkan baru</string>\n    <string name=\"patch_item_existed_extra_kpm\">Sudah ada</string>\n    <string name=\"patch_item_extra_name\">Nama:</string>\n    <string name=\"patch_item_extra_version\">Versi:</string>\n    <string name=\"patch_item_extra_author\">Penulis:</string>\n    <string name=\"patch_item_extra_kpm_license\">Lisensi:</string>\n    <string name=\"patch_item_extra_kpm_desciption\">Deskripsi:</string>\n    <string name=\"patch_item_extra_args\">Argumen:</string>\n    <string name=\"patch_item_extra_event\">Acara:</string>\n    <string name=\"patch_item_skey\">SuperKey</string>\n    <string name=\"patch_custom_superkey\">SuperKey kustom</string>\n    <string name=\"patch_custom_superkey_summary\">Anda masih dapat mengatur SuperKey untuk mengelola Root dan kernel, manajer telah diotorisasi oleh tanda tangan bawaan kernel</string>\n    <string name=\"patch_item_set_skey_label\">SuperKey harus berisi 8-63 karakter dan menyertakan angka dan huruf, tetapi tanpa karakter khusus.</string>\n    <string name=\"patch_confirm_superkey\">Konfirmasi SuperKey</string>\n    <string name=\"patch_skey_mismatch\">SuperKey tidak cocok</string>\n\n    <string name=\"home_kernel\">Versi Kernel</string>\n    <string name=\"home_manager_version\">Versi Manajer</string>\n    <string name=\"home_fingerprint\">Sidik Jari</string>\n\n    <string name=\"home_selinux_status\">Status SELinux</string>\n    <string name=\"home_selinux_status_disabled\">Dinonaktifkan</string>\n    <string name=\"home_selinux_status_enforcing\">Diterapkan</string>\n    <string name=\"home_selinux_status_permissive\">Diizinkan</string>\n    <string name=\"home_selinux_status_unknown\">Tidak diketahui</string>\n\n    <string name=\"settings_selinux_mode\">Mode SELinux</string>\n    <string name=\"settings_selinux_mode_summary\">Pilih mode penegakan SELinux</string>\n    <string name=\"settings_selinux_mode_enforcing\">Diterapkan (Ketat)</string>\n    <string name=\"settings_selinux_mode_permissive\">Diizinkan (Longgar)</string>\n    <string name=\"settings_selinux_current_mode\">Saat ini: %s</string>\n    <string name=\"settings_selinux_mode_enforcing_summary\">SELinux sepenuhnya menerapkan aturan akses</string>\n    <string name=\"settings_selinux_mode_permissive_summary\">SELinux hanya mencatat pelanggaran, tidak menolak</string>\n\n    <string name=\"home_device_info\">Perangkat</string>\n    <string name=\"home_system_version\">Versi Sistem</string>\n    <string name=\"home_kpatch_version\">Versi KernelPatch</string>\n    <string name=\"home_su_path\">File yang dapat dieksekusi su</string>\n    <string name=\"home_apatch_version\">Versi FolkPatch</string>\n\n    <string name=\"kpm\">KPModule</string>\n    <string name=\"kpm_kp_not_installed\">KernelPatch tidak terinstal</string>\n    <string name=\"kpm_add_kpm\">Tambahkan KPM</string>\n    <string name=\"kpm_load\">Muat</string>\n    <string name=\"kpm_install\">Instal</string>\n    <string name=\"kpm_embed\">Sematkan</string>\n    <string name=\"kpm_load_toast_succ\">Pemuatan berhasil</string>\n    <string name=\"kpm_load_toast_failed\">Pemuatan gagal</string>\n    <string name=\"kpm_unload_confirm\">Lepas modul %s?</string>\n    <string name=\"kpm_unload\">Lepas</string>\n    <string name=\"kpm_control\">Kontrol</string>\n    <string name=\"kpm_apm_empty\">Tidak ada modul yang dimuat</string>\n    <string name=\"kpm_version\">Versi</string>\n    <string name=\"kpm_license\">Lisensi</string>\n    <string name=\"kpm_author\">Penulis</string>\n    <string name=\"kpm_desc\">Deskripsi</string>\n    <string name=\"kpm_args\">Argumen</string>\n\n    <string name=\"su_title\">Superuser</string>\n    <string name=\"su_selinux_via_hook\">Lewati melalui Hook</string>\n    <string name=\"su_pkg_excluded_label\">Kecualikan</string>\n    <string name=\"su_pkg_excluded_setting_title\">Kecualikan perubahan</string>\n    <string name=\"su_pkg_excluded_setting_summary\">Mengaktifkan opsi ini akan memungkinkan FolkPatch memulihkan setiap file yang dimodifikasi oleh modul aplikasi ini.</string>\n    <string name=\"su_pkg_root_setting_title\">Superuser</string>\n    <string name=\"su_pkg_root_setting_summary\">Mengaktifkan opsi ini untuk memberikan akses superuser ke aplikasi Anda, aplikasi dapat menggunakan perintah SU</string>\n    <string name=\"su_pkg_normal_setting_title\">Mode Normal</string>\n    <string name=\"su_pkg_normal_setting_summary\">Tidak ada akses root yang diberikan, aplikasi berjalan tanpa hak istimewa superuser.</string>\n    <string name=\"su_batch_exclude_title\">Pengecualian Massal</string>\n    <string name=\"su_batch_exclude_content\">Kecualikan injeksi untuk semua aplikasi non-Root, pilih tindakan</string>\n    <string name=\"su_exclude_btn\">Kecualikan</string>\n    <string name=\"su_exclude_reverse_btn\">Balikkan</string>\n\n    <!-- SuperUser App Action Dialog -->\n    <string name=\"su_app_action_title\">Tindakan Aplikasi</string>\n    <string name=\"su_app_action_content\">Silakan pilih tindakan untuk aplikasi</string>\n    <string name=\"su_app_action_launch\">Jalankan Aplikasi</string>\n    <string name=\"su_app_action_force_stop\">Hentikan Paksa</string>\n    <string name=\"su_app_action_launch_success\">Memulai %s</string>\n    <string name=\"su_app_action_force_stop_success\">%s dihentikan paksa</string>\n    <string name=\"su_app_action_failed\">Operasi gagal: %s</string>\n\n    <!-- SU Audit Log -->\n    <string name=\"su_audit_log_title\">Log Otorisasi</string>\n    <string name=\"su_audit_log_empty\">Belum ada catatan otorisasi</string>\n    <string name=\"su_audit_log_clear\">Hapus Log</string>\n    <string name=\"su_audit_log_clear_confirm\">Hapus semua catatan otorisasi?</string>\n    <string name=\"su_audit_action_grant\">Diizinkan</string>\n    <string name=\"su_audit_action_revoke\">Dicabut</string>\n    <string name=\"su_audit_action_exclude\">Dikecualikan</string>\n    <string name=\"su_audit_tab_usage\">Log Penggunaan</string>\n    <string name=\"su_audit_tab_operations\">Log Operasi</string>\n\n    <string name=\"su_show_system_apps\">Tampilkan aplikasi sistem</string>\n    <string name=\"su_hide_system_apps\">Sembunyikan aplikasi sistem</string>\n    <string name=\"su_refresh\">Segarkan</string>\n\n    <string name=\"apm\">Modul Sistem</string>\n    <string name=\"apm_not_installed\">AndroidPatch tidak terinstal</string>\n    <string name=\"apm_failed_to_enable\">Gagal mengaktifkan modul: %s</string>\n    <string name=\"apm_failed_to_disable\">Gagal menonaktifkan modul: %s</string>\n    <string name=\"apm_empty\">Tidak ada modul yang terinstal</string>\n    <string name=\"apm_remove\">Hapus</string>\n    <string name=\"apm_undo\">Urungkan</string>\n    <string name=\"apm_install\">Instal</string>\n    <string name=\"apm_uninstall_confirm\">Hapus instalasi modul %s?</string>\n    <string name=\"apm_uninstall_success\">%s dihapus</string>\n    <string name=\"apm_uninstall_failed\">Gagal menghapus instalasi: %s</string>\n    <string name=\"apm_undo_uninstall_success\">%s berhasil dipulihkan</string>\n    <string name=\"apm_undo_uninstall_failed\">Gagal memulihkan: %s</string>\n    <string name=\"apm_version\">Versi</string>\n    <string name=\"apm_author\">Penulis</string>\n    <string name=\"apm_desc\">Deskripsi</string>\n    <string name=\"apm_overlay_fs_not_available\">Modul tidak tersedia karena OverlayFS dinonaktifkan oleh kernel!</string>\n    <string name=\"apm_magisk_conflict\">Modul tidak tersedia karena konflik dengan Magisk!</string>\n    <string name=\"apm_mount_warning_title\">Penting</string>\n    <string name=\"apm_mount_warning_message\">Secara default, modul tidak dipasang. Gunakan sistem pemasangan bawaan atau metamodul.</string>\n    <string name=\"apm_mount_warning_button\">Mengerti</string>\n    <string name=\"apm_reboot_to_apply\">Mulai ulang untuk menerapkan perubahan</string>\n    <string name=\"apm_changelog\">Daftar Perubahan</string>\n    <string name=\"apm_update\">Perbarui</string>\n    <string name=\"apm_downloading\">Mengunduh modul: %s</string>\n    <string name=\"apm_start_downloading\">Mulai mengunduh: %s</string>\n    <string name=\"apm_new_version_available\">Versi baru %s tersedia, klik untuk memperbarui.</string>\n\n    <string name=\"hide_apatch_manager\">Sembunyikan manajer APatch</string>\n    <string name=\"hide_apatch_manager_summary\">Instal aplikasi proxy dengan ID paket acak dan label aplikasi kustom</string>\n    <string name=\"hide_apatch_dialog_new_manager_name\">Nama manajer baru</string>\n    <string name=\"hide_apatch_dialog_summary\">Akan digunakan sebagai label aplikasi baru yang ditampilkan di peluncur</string>\n    <string name=\"hide_apatch_manager_failure\">Gagal menyembunyikan. Harap laporkan bug!</string>\n\n    <string name=\"setting_reset_su_path\">Reset jalur su</string>\n    <string name=\"setting_reset_su_new_path\">Jalur penuh baru</string>\n\n    <string name=\"settings_folkx_engine_title\">Mesin Animasi FolkX</string>\n    <string name=\"settings_folkx_engine_summary\">Gunakan animasi pegas fisik yang halus saat beralih halaman tingkat atas</string>\n    <string name=\"settings_folkx_animation_type\">Jenis Animasi</string>\n    <string name=\"settings_folkx_animation_speed\">Kecepatan Animasi</string>\n    <string name=\"settings_folkx_animation_linear\">Gerakan Linier</string>\n    <string name=\"settings_folkx_animation_spatial\">Gerakan Spasial</string>\n    <string name=\"settings_folkx_animation_fade\">Muncul/Hilang Bertahap</string>\n    <string name=\"settings_folkx_animation_vertical\">Geser Vertikal</string>\n    <string name=\"settings_folkx_animation_diagonal\">Geser Diagonal</string>\n\n    <string name=\"apm_webui_open\">Buka</string>\n    <string name=\"apm_action\">Tindakan</string>\n    <string name=\"module_shortcut_add\">Pintasan</string>\n    <string name=\"module_shortcut_not_supported\">Peluncur tidak mendukung pintasan desktop.</string>\n    <string name=\"module_shortcut_created\">Pintasan dibuat di desktop.</string>\n    <string name=\"module_shortcut_updated\">Pintasan diperbarui.</string>\n    <string name=\"module_shortcut_permission_tip_xiaomi\">Harap aktifkan izin \"Buat pintasan desktop\" untuk aplikasi ini di pengaturan Xiaomi.</string>\n    <string name=\"module_shortcut_permission_tip_oppo\">Harap aktifkan izin \"Pintasan desktop\" untuk aplikasi ini di pengaturan OPPO.</string>\n    <string name=\"module_shortcut_permission_tip_default\">Jika pembuatan pintasan gagal, harap aktifkan izin pintasan desktop untuk aplikasi ini di pengaturan sistem.</string>\n    <string name=\"module_shortcut_name\">Nama pintasan</string>\n    <string name=\"module_shortcut_icon\">Ikon pintasan</string>\n    <string name=\"module_shortcut_type\">Tipe pintasan</string>\n    <string name=\"module_shortcut_type_webui\">WebUI</string>\n    <string name=\"module_shortcut_type_action\">Action</string>\n    <string name=\"module_shortcut_icon_default\">Gunakan ikon default</string>\n    <string name=\"module_shortcut_icon_select\">Pilih ikon</string>\n    <string name=\"enable_web_debugging\">Aktifkan *debugging* WebView</string>\n    <string name=\"enable_web_debugging_summary\">Dapat digunakan untuk men-*debug* WebUI. Aktifkan hanya jika diperlukan.</string>\n    <string name=\"settings_apm_install_confirm\">Konfirmasi sebelum menginstal</string>\n    <string name=\"settings_apm_install_confirm_summary\">Tampilkan dialog konfirmasi sebelum menginstal modul</string>\n    <string name=\"settings_enable_module_shortcut_add\">Aktifkan tombol tambah pintasan</string>\n    <string name=\"settings_enable_module_shortcut_add_summary\">Tampilkan tombol tambah pintasan WebUI di halaman modul sistem</string>\n    <string name=\"settings_module_sort_optimization\">Optimasi Pengurutan Modul</string>\n    <string name=\"settings_module_sort_optimization_summary\">Jadikan modul sistem dengan WebUI dan Action muncul di bagian atas</string>\n    <string name=\"settings_fold_system_module\">Lipat Modul Sistem</string>\n    <string name=\"settings_fold_system_module_summary\">Klik kartu modul untuk memperluas/menyembunyikan tindakan</string>\n    <string name=\"settings_simple_list_bottom_bar\">Bar Bawah Daftar Sederhana</string>\n    <string name=\"settings_simple_list_bottom_bar_summary\">Gunakan gaya tombol hanya ikon untuk tindakan modul, terinspirasi dari APatch</string>\n    <string name=\"settings_spliced_card_group\">Spliced Card Group</string>\n    <string name=\"settings_spliced_card_group_summary\">Use M3E continuous card group style for module list</string>\n    <string name=\"settings_show_more_module_info\">Tampilkan detail modul</string>\n    <string name=\"settings_show_more_module_info_summary\">Tampilkan ID dan ukuran modul dalam daftar modul</string>\n    <string name=\"apm_install_confirm_title\">Instal modul</string>\n    <string name=\"apm_install_confirm_content\">Apakah Anda yakin ingin tetap menginstal %s?</string>\n    <string name=\"apm_disable_all_title\">Nonaktifkan semua modul</string>\n    <string name=\"apm_disable_all_confirm\">Apakah Anda yakin ingin menonaktifkan semua modul? Ini akan menonaktifkan semua modul yang terinstall.</string>\n    <string name=\"apm_enable_module_banner\">Aktifkan banner modul</string>\n    <string name=\"apm_enable_module_banner_summary\">Tampilkan gambar banner untuk modul jika tersedia</string>\n    <string name=\"apm_enable_folk_banner\">Kustomisasi banner modul</string>\n    <string name=\"apm_enable_folk_banner_summary\">Tekan lama pada kartu modul untuk memilih gambar banner</string>\n    <string name=\"apm_folk_banner_title\">FolkBanner</string>\n    <string name=\"apm_folk_banner_select\">Pilih gambar</string>\n    <string name=\"apm_folk_banner_clear\">Hapus FolkBanner</string>\n    <string name=\"apm_folk_banner_saved\">FolkBanner disuntikkan untuk %s.</string>\n    <string name=\"apm_folk_banner_cleared\">FolkBanner dihapus untuk %s.</string>\n    <string name=\"apm_folk_banner_failed\">Gagal memperbarui FolkBanner untuk %s.</string>\n    <string name=\"apm_banner_api_mode\">Mode API</string>\n    <string name=\"apm_banner_api_mode_summary\">Gunakan API gambar acak atau direktori lokal untuk banner modul</string>\n    <string name=\"apm_banner_api_source\">Konfigurasi Sumber API</string>\n    <string name=\"apm_banner_api_source_hint\">Masukkan URL API atau jalur lokal</string>\n    <string name=\"apm_banner_api_source_saved\">Sumber API disimpan</string>\n    <string name=\"apm_banner_api_source_not_configured\">Belum dikonfigurasi</string>\n    <string name=\"apm_banner_api_url_configured\">URL API dikonfigurasi</string>\n    <string name=\"apm_banner_local_dir_configured\">Direktori lokal dikonfigurasi</string>\n    <string name=\"apm_banner_clear_cache\">Hapus Cache</string>\n    <string name=\"apm_banner_cache_cleared\">Cache banner dihapus</string>\n    <string name=\"apm_banner_api_config_title\">Konfigurasi Sumber API</string>\n    <string name=\"apm_banner_api_config_desc\">Masukkan URL API gambar acak atau jalur direktori lokal. Setiap modul akan mendapatkan banner unik.</string>\n    <string name=\"apm_banner_api_examples_title\">Contoh:</string>\n    <string name=\"apm_banner_api_examples\">API: https://picsum.photos/800/400\\nLokal: /sdcard/Pictures/Banners</string>\n    <!-- Pasar API -->\n    <string name=\"apm_api_marketplace_title\">Pasar API</string>\n    <string name=\"apm_api_preview\">Pratinjau</string>\n    <string name=\"apm_api_apply\">Terapkan</string>\n    <string name=\"apm_api_preview_title\">Pratinjau API</string>\n    <string name=\"apm_api_preview_failed\">Gagal memuat pratinjau</string>\n    <string name=\"apm_api_url_label\">URL API:</string>\n    <string name=\"apm_api_apply_success\">Sumber API berhasil diterapkan</string>\n    <string name=\"apm_api_verifying\">Memverifikasi…</string>\n    <string name=\"apm_api_retry\">Coba lagi</string>\n    <string name=\"apm_api_empty\">Tidak ada sumber API yang tersedia</string>\n    <string name=\"settings_banner_custom_opacity\">Opasitas banner</string>\n    <string name=\"settings_banner_custom_opacity_summary\">Gunakan opasitas kustom untuk banner alih-alih mengikuti mode latar belakang</string>\n    <string name=\"settings_banner_opacity\">Transparansi banner</string>\n\n    <string name=\"settings_donot_store_superkey\">Jangan simpan SuperKey secara lokal</string>\n    <string name=\"settings_donot_store_superkey_summary\">Autentikasi SuperKey setiap kali manajer diluncurkan</string>\n\n    <string name=\"mode_select_page_title\">Instalasi</string>\n    <string name=\"mode_select_page_patch_and_install\">Patch dan instal</string>\n    <string name=\"mode_select_page_select_file\">Pilih *boot image* untuk *patch*</string>\n    <string name=\"mode_select_page_install_inactive_slot\">Instal ke slot tidak aktif (Setelah OTA)</string>\n    <string name=\"mode_select_page_install_inactive_slot_warning\">Perangkat Anda **AKAN DIPAKSA** untuk boot ke slot tidak aktif saat ini setelah *reboot*!\\nGunakan opsi ini hanya setelah OTA selesai.\\nLanjutkan?</string>\n    <string name=\"mode_select_page_select_kpimg\">Gunakan file *patch* lokal (KPimg)</string>\n    <string name=\"patch_custom_kpimg_label\">KPimg kustom</string>\n    <string name=\"patch_custom_kpimg_file\">Berkas: %s</string>\n    <string name=\"patch_select_kpimg_btn\">Pilih KPimg</string>\n\n    <string name=\"home_dialog_auth_fail_title\">Kesalahan autentikasi</string>\n    <string name=\"home_dialog_auth_fail_content\">Tidak dapat mengautentikasi SuperKey, menyebabkan aktivasi FolkPatch gagal.\nBerikut adalah beberapa kemungkinan penyebab kegagalan autentikasi—silakan cek mana yang berlaku untuk Anda:\n\\n1. Anda sama sekali tidak menggunakan KernelPatch untuk mem-*patch* boot.img, atau lupa apa yang sebenarnya Anda lakukan.\n\\n2. boot.img yang sudah di-*patch* masih tidur di komputer Anda, belum di-*flash* ke perangkat.\n\\n3. SuperKey dimasukkan secara tidak benar, atau mengandung simbol misterius, seperti karakter dari bahasa alien.\n\\n4. Perangkat Anda mungkin tidak kompatibel dengan FolkPatch dan KernelPatch, usaha paksa adalah sia-sia.\n\\n5. Anda mungkin melakukan beberapa operasi misterius—seperti menggunakan modul tertentu yang mengecualikan nama paket yang memblokir FolkPatch, mengakibatkan Anda mengeluarkan diri sendiri dari permainan.\n\n</string>\n\n    <string name=\"home_more_menu_feedback_or_suggestion\">Umpan Balik atau Saran</string>\n    <string name=\"home_more_menu_about\">Tentang Aplikasi</string>\n    <string name=\"home_more_menu_document\">Dokumentasi</string>\n\n    <string name=\"home_dialog_uninstall_title\">Hapus Instalasi</string>\n\n    <string name=\"home_dialog_uninstall_ap_only\">Hapus Patch Saja</string>\n    <string name=\"home_dialog_uninstall_ap_only_desc\">Hapus hanya AndroidPatch dan simpan manajernya.</string>\n    <string name=\"home_dialog_uninstall_all\">Hapus Instalasi Penuh</string>\n    <string name=\"home_dialog_uninstall_all_desc\">Hapus AndroidPatch dan mulai proses uninstall penuh.</string>\n    \n    <!-- Install Mode Select -->\n    <string name=\"kp_install_methods\">KernelPatch Tambal/Pemasangan</string>\n    <string name=\"restore_boot_methods\">Pilih boot untuk dipulihkan ke partisi boot</string>\n    <string name=\"restore_select_file\">Pilih file boot partisi yang akan dipulihkan</string>\n\n    <string name=\"kpm_control_dialog_title\">Kontrol KPM</string>\n    <string name=\"kpm_control_dialog_content\">Silakan masukkan parameter kontrol:</string>\n    <string name=\"kpm_control_paramters\">Parameter</string>\n    <string name=\"kpm_control_outMsg\">Keluaran</string>\n    <string name=\"kpm_control_ok\">Berhasil!</string>\n    <string name=\"kpm_control_failed\">Gagal!</string>\n\n    <string name=\"settings_night_mode_follow_sys\">Tema Gelap mengikuti sistem</string>\n    <string name=\"settings_night_mode_follow_sys_summary\">Beralih tema gelap secara otomatis berdasarkan pengaturan sistem</string>\n    <string name=\"settings_night_theme_enabled\">Tema Gelap</string>\n\n    <string name=\"settings_use_system_color_theme\">Tema Warna Sistem</string>\n    <string name=\"settings_use_system_color_theme_summary\">Gunakan tema warna yang dihasilkan sistem dari wallpaper</string>\n    <string name=\"settings_custom_color_theme\">Tema Warna</string>\n\n    <string name=\"amber_theme\">Kuning Amber</string>\n    <string name=\"blue_theme\">Biru</string>\n    <string name=\"blue_grey_theme\">Biru Keabu-abuan</string>\n    <string name=\"brown_theme\">Cokelat</string>\n    <string name=\"cyan_theme\">Sian</string>\n    <string name=\"deep_orange_theme\">Jingga Tua</string>\n    <string name=\"deep_purple_theme\">Ungu Tua</string>\n    <string name=\"green_theme\">Hijau</string>\n    <string name=\"indigo_theme\">Nila</string>\n    <string name=\"light_blue_theme\">Biru Muda</string>\n    <string name=\"light_green_theme\">Hijau Muda</string>\n    <string name=\"lime_theme\">Hijau Limau</string>\n    <string name=\"orange_theme\">Jingga</string>\n    <string name=\"pink_theme\">Merah Jambu</string>\n    <string name=\"purple_theme\">Ungu</string>\n    <string name=\"red_theme\">Merah</string>\n    <string name=\"sakura_theme\">Sakura</string>\n    <string name=\"teal_theme\">Hijau Kebiru-biruan</string>\n    <string name=\"yellow_theme\">Kuning</string>\n    <string name=\"theme_color\">Warna Tema</string>\n    <string name=\"theme_light\">Terang</string>\n    <string name=\"theme_dark\">Gelap</string>\n    <string name=\"theme_system\">Sistem</string>\n    <string name=\"loading_modules\">Memuat modul…</string>\n    <string name=\"loading_scripts\">Mencari skrip…</string>\n    <string name=\"loading_themes\">Memuat tema…</string>\n    <string name=\"loading_apis\">Memuat sumber API…</string>\n\n    <string name=\"about_app_desc\">Implementasi Root berbasis KernelPatch, memungkinkan *hook* fungsi kernel tanpa kompilasi ulang kernel.</string>\n    <string name=\"about_powered_by\">Didukung oleh %1$s</string>\n    <string name=\"about_telegram_group\">Grup Telegram</string>\n\n    <string name=\"online_module_title\">Modul Sistem Online</string>\n    <string name=\"online_module_download_start\">Mulai mengunduh: %s</string>\n    <string name=\"online_module_download_complete\">Pengunduhan selesai: %s</string>\n    <string name=\"online_module_download_notification\">Mengunduh %s. Periksa panel notifikasi untuk melacak kemajuan, dan folder Unduhan untuk file yang sudah selesai.</string>\n\n    <string name=\"online_kpm_title\">Modul Kernel Online</string>\n    <string name=\"online_kpm_download_start\">Mulai mengunduh: %s</string>\n    <string name=\"online_kpm_download_complete\">Pengunduhan selesai: %s</string>\n    <string name=\"online_kpm_download_notification\">Mengunduh %s. Periksa panel notifikasi untuk melacak kemajuan, dan folder Unduhan untuk file yang sudah selesai.</string>\n\n    <!-- Online Script Strings -->\n    <string name=\"online_script_title\">Skrip Online</string>\n    <string name=\"online_script_download_start\">Mulai mengunduh: %s</string>\n    <string name=\"online_script_download_complete\">Pengunduhan selesai: %s</string>\n    <string name=\"online_script_download_notification\">Mengunduh %s</string>\n\n    <string name=\"crash_handle_title\">Laporan *Crash*</string>\n    <string name=\"crash_handle_copy\">Salin</string>\n\n    <string name=\"about_app_version\">Versi Aplikasi: %s</string>\n    <string name=\"about_github\">GitHub</string>\n    <string name=\"about_telegram_channel\">Saluran Telegram</string>\n\n    <string name=\"settings_app_dpi\">DPI Aplikasi</string>\n    <string name=\"dpi_apply_settings\">Terapkan</string>\n    <string name=\"dpi_confirm_title\">Konfirmasi perubahan DPI</string>\n    <string name=\"dpi_confirm_message\">Ubah DPI aplikasi dari %1$s ke %2$s?</string>\n    <string name=\"settings_magic_mount\">Folk Mount API</string>\n    <string name=\"settings_magic_mount_summary\">Aktifkan sistem pemasangan modul bawaan</string>\n    <string name=\"settings_new_app_profile_mode\">Mode default untuk aplikasi baru</string>\n    <string name=\"settings_new_app_profile_normal\">TANPA ROOT</string>\n    <string name=\"settings_new_app_profile_root\">ROOT</string>\n    <string name=\"settings_new_app_profile_exclude\">Kecualikan</string>\n    <string name=\"settings_hide_service\">FolkPatch Hide</string>\n    <string name=\"settings_hide_service_summary\">Sembunyikan status buka kunci bootloader sampai tingkat tertentu</string>\n    <string name=\"settings_app_title\">Judul Aplikasi</string>\n    <string name=\"app_title_custom\">Kustom</string>\n    <string name=\"settings_custom_app_title\">Atur nama aplikasi</string>\n    <string name=\"custom_app_title_dialog_title\">Nama aplikasi kustom</string>\n    <string name=\"custom_app_title_dialog_hint\">Masukkan nama aplikasi</string>\n    <string name=\"custom_app_title_dialog_confirm\">Konfirmasi</string>\n    <string name=\"custom_app_title_dialog_empty\">Nama tidak boleh kosong</string>\n    <string name=\"cancel\">Batal</string>\n    <string name=\"settings_custom_background\">Latar Belakang Kustom</string>\n    <string name=\"settings_custom_background_enabled\">Latar Belakang Kustom diaktifkan</string>\n    <string name=\"settings_custom_background_opacity\">Opasitas Kartu</string>\n    <string name=\"settings_custom_background_blur\">Buram Latar Belakang</string>\n    <string name=\"settings_custom_background_dim\">Redup Latar Belakang</string>\n    <string name=\"settings_select_background_image\">Pilih gambar latar belakang</string>\n    <string name=\"settings_custom_background_summary\">Atur gambar latar belakang kustom</string>\n    <string name=\"settings_custom_background_saved\">Latar belakang berhasil disimpan</string>\n    <string name=\"settings_custom_background_error\">Gagal menyimpan latar belakang</string>\n    <string name=\"settings_alt_icon\">Ikon Alternatif</string>\n    <string name=\"alt_icon_summary\">Gunakan ikon peluncur alternatif</string>\n    <string name=\"settings_background_selected\">Latar belakang dipilih</string>\n    <string name=\"settings_background_image_cleared\">Gambar latar belakang dihapus</string>\n    <string name=\"settings_clear_background\">Hapus latar belakang</string>\n    <string name=\"settings_clear_background_confirm\">Apakah Anda yakin ingin menghapus gambar latar belakang?</string>\n    <string name=\"settings_multi_background_mode\">Mode Multi-Latar Belakang</string>\n    <string name=\"settings_multi_background_mode_summary\">Atur gambar latar belakang yang berbeda untuk halaman yang berbeda</string>\n    <string name=\"settings_select_home_background\">Latar Belakang Halaman Utama</string>\n    <string name=\"settings_select_kernel_background\">Latar Belakang Modul Kernel</string>\n    <string name=\"settings_select_superuser_background\">Latar Belakang Superuser</string>\n    <string name=\"settings_select_system_module_background\">Latar Belakang Modul Sistem</string>\n    <string name=\"settings_select_settings_background\">Latar Belakang Halaman Pengaturan</string>\n\n    <string name=\"settings_advanced_title_style\">Gaya Judul Lanjutan</string>\n    <string name=\"settings_advanced_title_style_summary\">Ganti judul bilah atas dengan gambar kustom</string>\n    <string name=\"settings_advanced_title_style_enabled\">Gaya judul lanjutan diaktifkan</string>\n    <string name=\"settings_select_title_image\">Pilih Gambar Judul</string>\n    <string name=\"settings_title_image_selected\">Gambar judul dipilih</string>\n    <string name=\"settings_title_image_saved\">Gambar judul berhasil disimpan</string>\n    <string name=\"settings_title_image_error\">Gagal menyimpan gambar judul</string>\n    <string name=\"settings_clear_title_image\">Hapus Gambar Judul</string>\n    <string name=\"settings_clear_title_image_confirm\">Apakah Anda yakin ingin menghapus gambar judul?</string>\n    <string name=\"settings_title_image_cleared\">Gambar judul dihapus</string>\n    <string name=\"settings_title_image_day_opacity\">Opresitas Mode Siang</string>\n    <string name=\"settings_title_image_night_opacity\">Opresitas Mode Malam</string>\n    <string name=\"settings_title_image_dim\">Redupkan Latar Belakang</string>\n    <string name=\"settings_title_image_offset_x\">Posisi Horizontal</string>\n\n    <string name=\"settings_launcher_icon\">Ikon Peluncur</string>\n\n    <string name=\"kpm_autoload_title\">Muat Otomatis KPM</string>\n    <string name=\"kpm_autoload_enabled\">Aktifkan muat otomatis</string>\n    <string name=\"kpm_autoload_enabled_summary\">Muat modul KPM secara otomatis saat perangkat booting</string>\n    <string name=\"kpm_autoload_json_config\">Konfigurasi JSON</string>\n    <string name=\"kpm_autoload_json_label\">Konfigurasi JSON</string>\n    <string name=\"kpm_autoload_json_placeholder\">{\n  \"enabled\": true,\n  \"kpmPaths\": [\n    \"/path/to/module1.kpm\",\n    \"/path/to/module2.kpm\"\n  ]\n}</string>\n    <string name=\"kpm_autoload_json_error\">Format JSON tidak valid</string>\n    <string name=\"kpm_autoload_json_helper\">Masukkan JSON yang valid dengan array jalur file KPM</string>\n    <string name=\"settings_grid_working_card_hide_check\">Sembunyikan ikon status</string>\n    <string name=\"settings_grid_working_card_hide_check_summary\">Sembunyikan tanda centang atau ikon peringatan pada kartu kerja</string>\n    <string name=\"settings_grid_working_card_hide_text\">Sembunyikan teks status</string>\n    <string name=\"settings_grid_working_card_hide_text_summary\">Sembunyikan teks \"Bekerja\" atau \"Tidak terinstal\" pada kartu kerja</string>\n    <string name=\"settings_grid_working_card_hide_mode\">Sembunyikan mode kerja</string>\n    <string name=\"settings_grid_working_card_hide_mode_summary\">Sembunyikan teks \"Full\" atau \"Half\" pada kartu kerja</string>\n    <string name=\"settings_list_card_hide_status_badge\">Gunakan Emoji Klasik</string>\n    <string name=\"settings_list_card_hide_status_badge_summary\">Lencana status di halaman utama berubah menjadi emoji klasik</string>\n    <string name=\"settings_custom_badge_text\">Teks Lencana Kustom</string>\n    <string name=\"settings_custom_badge_text_summary\">Ubah tampilan teks lencana, hanya untuk hiburan</string>\n    <string name=\"settings_custom_background_dual_dim\">Aktifkan redup siang/malam ganda</string>\n    <string name=\"settings_custom_background_dual_dim_desc\">Otomatis menyesuaikan redup latar belakang untuk tema terang dan gelap</string>\n    <string name=\"settings_custom_background_day_dim\">Redup latar belakang mode siang</string>\n    <string name=\"settings_custom_background_night_dim\">Redup latar belakang mode malam</string>\n    <string name=\"settings_grid_working_card_dual_opacity\">Aktifkan transparansi siang/malam ganda</string>\n    <string name=\"settings_grid_working_card_dual_opacity_desc\">Otomatis menyesuaikan transparansi kartu untuk tema terang dan gelap</string>\n    <string name=\"settings_grid_working_card_day_opacity\">Transparansi kartu mode siang</string>\n    <string name=\"settings_grid_working_card_night_opacity\">Transparansi kartu mode malam</string>\n    <string name=\"kpm_autoload_save\">Simpan konfigurasi</string>\n    <string name=\"kpm_autoload_save_confirm\">Simpan konfigurasi muat otomatis?</string>\n    <string name=\"kpm_autoload_save_success\">Konfigurasi berhasil disimpan</string>\n    <string name=\"kpm_autoload_save_failed\">Gagal menyimpan konfigurasi</string>\n    <string name=\"kpm_autoload_visual_mode\">Mode Visual</string>\n    <string name=\"kpm_autoload_json_mode\">Mode JSON</string>\n    <string name=\"kpm_autoload_add_kpm\">Tambahkan KPM</string>\n    <string name=\"kpm_autoload_remove_kpm\">Hapus</string>\n    <string name=\"kpm_autoload_edit_kpm\">Editar</string>\n    <string name=\"kpm_autoload_edit_dialog_title\">Editar KPM</string>    <string name=\"kpm_autoload_event_label\">Evento:</string>    <string name=\"kpm_autoload_args_label\">Argumentos:</string>    <string name=\"kpm_autoload_event_service\">service</string>    <string name=\"kpm_autoload_event_post_fs_data\">post-fs-data</string>\n    <string name=\"kpm_autoload_kpm_list_title\">Modul KPM</string>\n    <string name=\"kpm_autoload_no_kpm_added\">Tidak ada modul KPM yang ditambahkan</string>\n    <string name=\"kpm_autoload_kpm_path\">Jalur KPM</string>\n    <string name=\"kpm_autoload_file_not_found\">File tidak ditemukan</string>\n    <string name=\"kpm_autoload_select_kpm_file\">Pilih file KPM</string>\n    <string name=\"kpm_autoload_first_time_title\">Tentang Muat Otomatis KPM</string>\n    <string name=\"kpm_autoload_first_time_message\">Fitur ini memungkinkan Anda memuat semua KPM yang dikonfigurasi secara otomatis untuk sementara waktu saat boot. Pendekatan ini lebih nyaman daripada menyematkannya langsung ke kernel.\n\nMisalnya, KPM yang mencegah modifikasi partisi hanya dapat digunakan untuk sementara waktu, karena penyematan dapat menyebabkan kegagalan boot. Atau jika Anda tidak ingin memodifikasi partisi BOOT, Anda dapat menggunakan konfigurasi ini untuk memuat modul dengan cepat.\n\nPastikan aplikasi ditutup sepenuhnya dan dimulai ulang untuk menjalankan perintah. Ini juga akan dimuat saat boot normal. Adalah normal jika manajer mengalami layar hitam untuk beberapa saat! Ini menggunakan metode pemuatan latar belakang yang bersih, jangan lupa untuk menarik ke bawah secara manual untuk menyegarkan dan memeriksa apakah modul dimuat dengan benar!</string>\n    <string name=\"kpm_autoload_first_time_confirm\">Mengerti</string>\n    <string name=\"kpm_autoload_do_not_show_again\">Jangan tampilkan lagi</string>\n\n    <string name=\"apm_bulk_install_title\">Instalasi Massal Modul Sistem</string>\n    <string name=\"apm_bulk_install_list_title\">Daftar Modul</string>\n    <string name=\"apm_bulk_install_add\">Tambahkan Modul</string>\n    <string name=\"apm_bulk_install_empty\">Tidak ada modul yang ditambahkan</string>\n    <string name=\"apm_bulk_install_action\">Instalasi Massal</string>\n    <string name=\"apm_bulk_install_log_title\">Log Instalasi Massal</string>\n    <string name=\"apm_bulk_install_log_start\">Memulai instalasi massal...</string>\n    <string name=\"apm_bulk_install_log_installing\">Menginstal %1$s</string>\n    <string name=\"apm_batch_install_full_process\">Proses instalasi batch lengkap</string>\n    <string name=\"apm_batch_install_full_process_summary\">Instalasi batch modul sistem dilakukan menggunakan proses lengkap</string>\n    <string name=\"next_module\">Modul berikutnya</string>\n    <string name=\"apm_bulk_install_log_installed\">Modul %s terinstal.</string>\n    <string name=\"apm_bulk_install_log_done\">Semua operasi selesai.</string>\n    <string name=\"apm_bulk_install_first_use_text\">Fitur ini memungkinkan Anda menginstal beberapa modul sekaligus. Ini adalah metode instalasi cepat, cocok untuk modul yang tidak memerlukan operasi tombol selama instalasi. Untuk modul yang memerlukan interaksi tombol volume, silakan aktifkan mode instalasi proses lengkap di pengaturan.</string>\n    <string name=\"apm_bulk_install_remove\">Hapus</string>\n    <string name=\"apm_first_use_title\">Selamat datang di Modul Sistem</string>\n    <string name=\"apm_first_use_text\">Selamat datang di Modul Sistem, yang menggunakan modul yang kompatibel dengan ekosistem Magisk. Klik tombol di kanan bawah untuk menginstal modul. Anda juga dapat menggunakan penginstal di bagian atas untuk instalasi modul secara massal. Fungsi pencadangan semua modul dengan sekali klik juga disediakan, tetapi perhatikan bahwa solusi ini mungkin tidak cocok untuk semua modul, jadi buatlah cadangan Anda sendiri.</string>\n\n    <string name=\"home_hide_su_path\">Sembunyikan jalur file yang dapat dieksekusi su</string>\n    <string name=\"home_hide_kpatch_version\">Sembunyikan versi patch kernel</string>\n    <string name=\"home_hide_fingerprint\">Sembunyikan sidik jari</string>\n    <string name=\"home_hide_zygisk\">Sembunyikan implementasi Zygisk</string>\n    <string name=\"home_hide_mount\">Sembunyikan implementasi pemasangan</string>\n    <string name=\"home_hide_su_path_summary\">Sembunyikan jalur file yang dapat dieksekusi su di kartu informasi</string>\n    <string name=\"home_hide_kpatch_version_summary\">Sembunyikan versi patch kernel di kartu informasi</string>\n    <string name=\"home_hide_zygisk_summary\">Sembunyikan implementasi Zygisk di kartu informasi</string>\n    <string name=\"home_hide_mount_summary\">Sembunyikan implementasi pemasangan di kartu informasi</string>\n    <string name=\"home_hide_fingerprint_summary\">Sembunyikan sidik jari di kartu informasi</string>\n\n    <string name=\"kpm_page_first_time_title\">Peringatan</string>\n    <string name=\"kpm_page_first_time_message\">Modul Kernel secara langsung memodifikasi implementasi BOOT. Tidak seperti modul sistem, mereka tidak memiliki mekanisme pemulihan yang baik. Jika ada masalah, Anda hanya dapat memperbaikinya dengan masuk ke Fastboot. Disarankan untuk memuat modul terlebih dahulu untuk memastikan tidak ada masalah sebelum menyematkannya ke BOOT. Jika modul hanya dapat digunakan dengan pemuatan, Anda dapat mencoba fitur muat otomatis modul KPM. Jika Anda tidak memahami modul kernel, jangan gunakan fitur ini!</string>\n    <string name=\"apm_backup_title\">Cadangkan Modul</string>\n    <string name=\"apm_restore_title\">Pulihkan Modul</string>\n    <string name=\"apm_backup_success\">Pencadangan berhasil</string>\n    <string name=\"apm_restore_success\">Pemulihan berhasil</string>\n    <string name=\"apm_backup_failed\">Pencadangan gagal</string>\n    <string name=\"apm_restore_failed\">Pemulihan gagal</string>\n    <string name=\"apm_backup_failed_msg\">Pencadangan gagal: %s</string>\n    <string name=\"apm_restore_failed_msg\">Pemulihan gagal: %s</string>\n    <string name=\"apm_copy_list_title\">Salin daftar</string>\n    <string name=\"apm_copy_list_success\">Daftar modul disalin</string>\n\n    <string name=\"settings_custom_font\">Font Kustom</string>\n    <string name=\"settings_select_font_file\">Pilih file font</string>\n    <string name=\"settings_custom_font_enabled\">Font kustom diaktifkan</string>\n    <string name=\"settings_custom_font_summary\">Gunakan font TTF kustom</string>\n    <string name=\"settings_font_selected\">Font kustom dipilih</string>\n    <string name=\"settings_custom_font_error\">Gagal menyimpan file font</string>\n    <string name=\"settings_custom_font_saved\">Font kustom berhasil disimpan</string>\n    <string name=\"settings_clear_font\">Pulihkan font bawaan</string>\n    <string name=\"settings_clear_font_confirm\">Apakah Anda yakin ingin memulihkan font bawaan?</string>\n    <string name=\"settings_font_cleared\">Font bawaan dipulihkan</string>\n    <string name=\"settings_font_select_hint\">Pilih file font TTF</string>\n\n    <string name=\"settings_auto_backup_module\">Cadangan Modul Otomatis</string>\n    <string name=\"settings_auto_backup_module_summary\">Secara otomatis menyimpan salinan modul ke direktori pribadi saat instalasi</string>\n    <string name=\"settings_open_backup_dir\">Buka direktori cadangan</string>\n    <string name=\"backup_dir_empty\">Direktori cadangan kosong</string>\n    <string name=\"backup_dir_open_failed\">Gagal membuka direktori cadangan</string>\n    <string name=\"auto_backup_failed\">Pencadangan otomatis gagal: %s</string>\n    <string name=\"auto_backup_success\">Pencadangan otomatis berhasil: %s</string>\n    <string name=\"settings_auto_backup_boot\">Backup Otomatis Boot</string>\n    <string name=\"settings_auto_backup_boot_summary\">Otomatis mem-backup Boot ke penyimpanan lokal (Downloads/FolkPatch/BootBackups)</string>\n\n    <string name=\"settings_home_layout_style\">Gaya tata letak utama</string>\n    <string name=\"settings_home_layout_default\">ListUI</string>\n    <string name=\"settings_home_layout_grid\">GridUI</string>\n    <string name=\"settings_home_layout_focus\">FocusUI</string>\n    <string name=\"settings_home_layout_sign\">SignUI</string>\n    <string name=\"settings_home_layout_circle\">CircleUI</string>\n    <string name=\"settings_home_layout_dashboard_pro\">DashboardUI</string>\n    <string name=\"unofficial_version_title\">Versi Tidak Resmi</string>\n    <string name=\"unofficial_version_message\">Anda menggunakan FolkPatch, yang bukan aplikasi resmi, harap unduh aplikasi resmi!</string>\n    <string name=\"go_to_github\">Buka Github</string>\n    <string name=\"settings_save_theme\">Simpan Tema</string>\n    <string name=\"settings_import_theme\">Impor Tema</string>\n    <string name=\"settings_theme_saved\">Tema tersimpan</string>\n    <string name=\"settings_theme_imported\">Tema terimpor</string>\n    <string name=\"settings_theme_save_failed\">Gagal menyimpan tema</string>\n    <string name=\"settings_theme_import_failed\">Gagal mengimpor tema</string>\n    <string name=\"settings_reset_theme\">Setel Ulang Tema</string>\n    <string name=\"settings_reset_theme_confirm\">Apakah Anda yakin ingin mengatur ulang semua pengaturan tema ke default? Ini akan menghapus semua latar belakang, font, musik, dan efek suara kustom.</string>\n    <string name=\"settings_theme_reset\">Tema diatur ulang</string>\n    <string name=\"settings_theme_reset_failed\">Gagal mengatur ulang tema</string>\n\n    <string name=\"theme_export_title\">Ekspor Tema</string>\n    <string name=\"theme_import_title\">Impor Tema</string>\n    <string name=\"theme_name\">Nama Tema</string>\n    <string name=\"theme_type\">Tipe Tema</string>\n    <string name=\"theme_type_phone\">Ponsel</string>\n    <string name=\"theme_type_tablet\">Tablet</string>\n    <string name=\"theme_version\">Versi</string>\n    <string name=\"theme_author\">Penulis</string>\n    <string name=\"theme_description\">Deskripsi</string>\n    <string name=\"theme_export_action\">Ekspor</string>\n    <string name=\"theme_import_action\">Impor</string>\n    <string name=\"theme_import_confirm\">Apakah Anda yakin ingin mengimpor tema ini?</string>\n    <string name=\"theme_info\">Informasi Tema</string>\n    <string name=\"settings_grid_working_card_background\">Latar Belakang Kartu Kerja</string>\n    <string name=\"settings_grid_working_card_background_enabled\">Latar belakang kartu kustom diaktifkan</string>\n    <string name=\"settings_grid_working_card_background_summary\">Gambar latar belakang kustom untuk kartu kerja</string>\n    <string name=\"settings_grid_working_card_background_selected\">Latar belakang dipilih</string>\n    <string name=\"settings_clear_grid_working_card_background\">Hapus latar belakang kartu</string>\n    <string name=\"settings_clear_grid_working_card_background_confirm\">Apakah Anda yakin ingin menghapus gambar latar belakang kartu?</string>\n    <string name=\"settings_grid_working_card_background_saved\">Latar belakang kartu berhasil disimpan</string>\n    <string name=\"settings_grid_working_card_background_error\">Gagal menyimpan latar belakang kartu</string>\n    <string name=\"settings_grid_working_card_background_cleared\">Latar belakang kartu dihapus</string>\n\n    <string name=\"action_biometric\">Auntentikasi Biometrik</string>\n    <string name=\"msg_biometric\">Harap konfirmasi identitas biometrik Anda</string>\n    <string name=\"settings_biometric_login\">Auntentikasi Biometrik</string>\n    <string name=\"settings_biometric_login_summary\">Wajibkan Autentikasi biometrik saat membuka aplikasi</string>\n    <string name=\"settings_strong_biometric\">Autentikasi Biometrik Kuat</string>\n    <string name=\"settings_strong_biometric_summary\">Memerlukan autentikasi saat menginstal/menghapus/menonaktifkan modul</string>\n\n    <!-- Theme Store Strings -->\n    <string name=\"theme_store_title\">Toko Tema</string>\n    <string name=\"theme_store_search_hint\">Cari tema...</string>\n    <string name=\"theme_source_official\">Resmi</string>\n    <string name=\"theme_source_third_party\">Pihak Ketiga</string>\n    <string name=\"theme_source_local\">Lokal</string>\n    <string name=\"theme_store_author\">Pencipta: %s</string>\n    <string name=\"theme_store_version\">Versi: %s</string>\n    <string name=\"theme_source\">Sumber</string>\n    <string name=\"theme_store_download_failed\">Gagal Mengunduh</string>\n    <string name=\"theme_store_download\">Unduh</string>\n\n    <!-- Update Check Strings -->\n    <string name=\"settings_auto_update_check\">Pemeriksaan Pembaruan Otomatis</string>\n    <string name=\"settings_auto_update_check_summary\">Secara otomatis memeriksa pembaruan saat aplikasi dimulai</string>\n    <string name=\"settings_check_update\">Periksa Pembaruan</string>\n    <string name=\"update_available_title\">Pembaruan Tersedia</string>\n    <string name=\"update_available_message\">Terdeteksi bahwa versi Anda terlalu rendah, apakah Anda ingin mengunduh versi baru?</string>\n    <string name=\"update_action\">Perbarui</string>\n    <string name=\"update_close\">Tutup</string>\n    <string name=\"update_latest\">Anda sudah menggunakan versi terbaru</string>\n    <string name=\"update_error\">Kesalahan saat memeriksa pembaruan</string>\n    <!--Opsi Lipat-->\n    <string name=\"settings_category_general\">Umum</string>\n    <string name=\"settings_category_appearance\">Tampilan</string>\n    <string name=\"settings_appearance_font\">Pengaturan font</string>\n    <string name=\"settings_appearance_font_summary\">Konfigurasi font kustom</string>\n    <string name=\"settings_appearance_theme\">Pengaturan tema</string>\n    <string name=\"settings_appearance_theme_summary\">Toko tema, simpan, impor, dan reset</string>\n    <string name=\"settings_appearance_banner\">Pengaturan banner</string>\n    <string name=\"settings_appearance_banner_summary\">Konfigurasi banner modul</string>\n    <string name=\"settings_appearance_layout\">Pengaturan tata letak</string>\n    <string name=\"settings_appearance_layout_summary\">Tata letak beranda, navigasi, dan kustomisasi kartu</string>\n    <string name=\"settings_appearance_background\">Pengaturan latar belakang</string>\n    <string name=\"settings_appearance_background_summary\">Latar belakang kustom, video, dan multi-latar belakang</string>\n    <string name=\"settings_appearance_night_mode\">Pengaturan mode malam</string>\n    <string name=\"settings_appearance_night_mode_summary\">Tema gelap dan konfigurasi warna</string>\n    <string name=\"settings_amoled_theme\">Tema Hitam AMOLED</string>\n    <string name=\"settings_amoled_theme_desc\">Latar belakang hitam penuh untuk mode gelap</string>\n    <string name=\"settings_switch_icon\">Indikator Tombol</string>\n    <string name=\"settings_switch_icon_desc\">Tampilkan ikon status pada tombol saklar</string>\n    <string name=\"settings_category_behavior\">Perilaku</string>\n    <string name=\"settings_category_function\">Fungsi</string>\n    <string name=\"settings_use_legacy_su_page\">Otorisasi superuser satu halaman</string>\n    <string name=\"settings_use_legacy_su_page_summary\">Halaman superuser beralih ke desain otorisasi satu halaman</string>\n    <string name=\"settings_category_module\">Modul</string>\n    <string name=\"settings_category_security\">Keamanan</string>\n    \n    <!-- Background Music Strings -->\n    <string name=\"settings_background_music\">Musik Latar Belakang</string>\n    <string name=\"settings_background_music_summary\">Putar musik latar belakang saat aplikasi di latar depan</string>\n    <string name=\"settings_background_music_playing\">Memutar: %s</string>\n    <string name=\"settings_background_music_enabled\">Musik Latar Belakang Diaktifkan</string>\n    <string name=\"settings_select_music_file\">Pilih file musik</string>\n    <string name=\"settings_music_selected\">Musik dipilih</string>\n    <string name=\"settings_clear_music\">Hapus musik</string>\n    <string name=\"settings_clear_music_confirm\">Apakah Anda yakin ingin menghapus musik latar belakang?</string>\n    <string name=\"settings_music_auto_play\">Putar otomatis</string>\n    <string name=\"settings_music_auto_play_summary\">Otomatis memutar musik saat aplikasi dibuka</string>\n    <string name=\"settings_music_volume\">Volume</string>\n    <string name=\"settings_music_saved\">File musik disimpan</string>\n    <string name=\"settings_music_save_error\">Gagal menyimpan file musik</string>\n    <string name=\"settings_music_cleared\">Musik dihapus</string>\n    <string name=\"settings_category_multimedia\">Multimedia</string>\n    <string name=\"settings_category_general_summary\">Bahasa, pembaruan, SELinux, penyesuaian sistem</string>\n    <string name=\"settings_category_appearance_summary\">Tema, warna, tata letak, latar belakang, font</string>\n    <string name=\"settings_category_behavior_summary\">Debug Web, perilaku pemasangan, tampilan beranda</string>\n    <string name=\"settings_category_security_summary\">Biometrik, manajemen superkey</string>\n    <string name=\"settings_category_backup_summary\">Cadangan lokal, cadangan awan, WebDAV</string>\n    <string name=\"settings_category_module_summary\">Info modul, pengurutan, pemasangan batch</string>\n    <string name=\"settings_category_function_summary\">Sembunyikan FolkPatch, layanan Umount</string>\n    <string name=\"settings_category_multimedia_summary\">Musik latar, suara, getaran</string>\n    <string name=\"settings_music_playback_control\">Kontrol Pemutaran</string>\n    <string name=\"settings_music_looping\">Putar berulang</string>\n    <string name=\"settings_music_looping_summary\">Ulangi lagu saat ini</string>\n    <!-- Theme Store Filter -->\n    <string name=\"theme_store_filter_title\">Filter Tema</string>\n    <string name=\"theme_store_filter_author\">Penulis</string>\n    <string name=\"theme_store_filter_author_hint\">Masukkan nama penulis</string>\n    <string name=\"theme_store_filter_source\">Sumber</string>\n    <string name=\"theme_store_filter_source_all\">Semua</string>\n    <string name=\"theme_store_filter_type\">Tipe Perangkat</string>\n    <string name=\"theme_store_filter_apply\">Terapkan</string>\n    <string name=\"theme_store_filter_reset\">Reset</string>\n\n    <!-- Video Background Settings -->\n    <string name=\"settings_video_background\">Latar Belakang Video</string>\n    <string name=\"settings_video_background_summary\">Gunakan video sebagai latar belakang</string>\n    <string name=\"settings_select_video\">Pilih Video</string>\n    <string name=\"settings_video_selected\">Video Dipilih</string>\n    <string name=\"settings_video_background_enabled\">Latar Belakang Video Diaktifkan</string>\n    <string name=\"settings_clear_video_background\">Hapus Wallpaper Video</string>\n    <string name=\"settings_clear_video_background_confirm\">Apakah Anda yakin ingin menghapus wallpaper video?</string>\n    <string name=\"settings_video_volume\">Volume Video</string>\n    <!-- File Picker -->\n    <string name=\"file_picker_permission_required\">Izin Diperlukan</string>\n    <string name=\"file_picker_permission_desc\">Untuk menjelajahi file, berikan izin \\'Akses ke semua file\\'.</string>\n    <string name=\"file_picker_grant_permission\">Berikan Izin</string>\n    <string name=\"file_picker_cancel\">Batal</string>\n    <string name=\"file_picker_internal_storage\">Penyimpanan Internal</string>\n    <string name=\"file_picker_no_files\">Tidak ada file</string>\n    <!-- HomeV3 Strings -->\n    <string name=\"home_device_status_title\">Status Perangkat</string>\n    <string name=\"home_device_status_battery_temp\">Suhu Baterai</string>\n    <string name=\"home_device_status_cpu_load\">Beban CPU</string>\n    <string name=\"home_device_status_battery_level\">Level Baterai</string>\n    <string name=\"home_storage_title\">Penyimpanan</string>\n    <string name=\"home_storage_internal\">Penyimpanan Internal</string>\n    <string name=\"home_storage_ram\">RAM</string>\n    <string name=\"home_storage_zram\">ZRAM</string>\n    <string name=\"home_storage_swap\">Berkas Swap</string>\n    <string name=\"home_info_kernel\">Kernel</string>\n    <string name=\"home_info_superkey\">SuperKey</string>\n    <string name=\"home_info_auth_auth\">Otor</string>\n    <string name=\"home_info_auth_na\">T/A</string>\n    <string name=\"home_info_device_slot\">Slot Perangkat</string>\n    <string name=\"home_info_device_model\">Model Perangkat</string>\n    <string name=\"home_info_running_mode\">Mode Berjalan</string>\n    <string name=\"home_info_mode_full\">Penuh</string>\n    <string name=\"home_info_mode_half\">Setengah</string>\n    <string name=\"home_version\">Versi: %s</string>\n    <string name=\"home_zygisk_implement\">Implementasi Zygisk</string>\n    <string name=\"home_mount_implement\">Implementasi pemasangan</string>\n\n    <string name=\"settings_app_list_loading_scheme\">Skema Pemuatan Daftar Aplikasi</string>\n    <string name=\"settings_app_list_loading_scheme_summary\">Pilih cara memuat daftar aplikasi</string>\n    <string name=\"app_list_loading_scheme_root_service\">RootService (Default)</string>\n    <string name=\"app_list_loading_scheme_package_manager\">PackageManager (API Sistem)</string>\n    <string name=\"app_list_loading_scheme_dialog_title\">Skema Pemuatan</string>\n    <string name=\"su_backup_list\">Daftar Cadangan</string>\n    <string name=\"su_restore_list\">Daftar Pemulihan</string>\n    <string name=\"backup_success\">Pencadangan Berhasil</string>\n    <!-- Badge Count Settings -->\n    <string name=\"enable_badge_count\">Pengaturan Hitung Badge</string>\n    <string name=\"enable_badge_count_summary\">Konfigurasi tampilan hitung badge untuk item navigasi</string>\n    <string name=\"badge_superuser\">Tampilkan Badge Superuser</string>\n    <string name=\"badge_apm\">Tampilkan Badge Modul Sistem</string>\n    <string name=\"badge_kernel\">Tampilkan Badge Modul Kernel</string>\n    <string name=\"settings_sound_effect\">Efek Suara</string>\n    <string name=\"settings_sound_effect_summary\">Mainkan suara saat klik</string>\n    <string name=\"settings_sound_effect_enabled\">Diaktifkan</string>\n    <string name=\"settings_sound_effect_playing\">Dipilih: %s</string>\n    <string name=\"settings_sound_effect_source\">Sumber Suara</string>\n    <string name=\"settings_sound_effect_source_local\">File Lokal</string>\n    <string name=\"settings_sound_effect_source_preset\">Preset</string>\n    <string name=\"settings_sound_effect_preset_title\">Suara Preset</string>\n    <string name=\"settings_select_sound_effect\">Pilih file suara</string>\n    <string name=\"settings_sound_effect_selected\">File suara dipilih</string>\n    <string name=\"settings_sound_effect_scope\">Cakupan Efek</string>\n    <string name=\"settings_sound_effect_scope_global\">Global (Di mana saja)</string>\n    <string name=\"settings_sound_effect_scope_bottom_bar\">Hanya Bar Bawah</string>\n    <string name=\"settings_clear_sound_effect\">Hapus Efek Suara</string>\n    <string name=\"settings_clear_sound_effect_confirm\">Apakah Anda yakin ingin menghapus efek suara?</string>\n    <string name=\"settings_sound_effect_cleared\">Efek suara dihapus</string>\n\n    <string name=\"settings_startup_sound\">Suara Startup</string>\n    <string name=\"settings_startup_sound_summary\">Mainkan suara saat aplikasi dimulai</string>\n    <string name=\"settings_startup_sound_enabled\">Suara Startup Diaktifkan</string>\n    <string name=\"settings_startup_sound_playing\">Memutar: %s</string>\n    <string name=\"settings_select_startup_sound\">Pilih Suara Startup</string>\n    <string name=\"settings_startup_sound_selected\">Suara startup dipilih</string>\n    <string name=\"settings_clear_startup_sound\">Hapus Suara Startup</string>\n    <string name=\"settings_clear_startup_sound_confirm\">Apakah Anda yakin ingin menghapus suara startup?</string>\n    <string name=\"settings_startup_sound_cleared\">Suara startup dihapus</string>\n\n    <!-- Backup Settings -->\n    <string name=\"settings_category_backup\">Pengaturan Cadangan</string>\n    <string name=\"settings_enable_cloud_backup\">Aktifkan Cadangan Cloud</string>\n    <string name=\"settings_webdav_url\">URL WebDAV</string>\n    <string name=\"settings_webdav_username\">Nama Pengguna WebDAV</string>\n    <string name=\"settings_webdav_password\">Kata Sandi WebDAV</string>\n    <string name=\"settings_test_webdav\">Uji Koneksi WebDAV</string>\n    <string name=\"webdav_test_success\">Uji Berhasil</string>\n    <string name=\"webdav_test_failed\">Uji Gagal: %s</string>\n    <string name=\"settings_backup_now\">Cadangkan Sekarang</string>\n    <string name=\"backup_failed\">Cadangan Gagal: %s</string>\n    <string name=\"settings_restore_backup\">Pulihkan Cadangan</string>\n    <string name=\"restore_success\">Pemulihan Berhasil</string>\n    <string name=\"restore_failed\">Pemulihan Gagal: %s</string>\n    <string name=\"settings_auto_backup\">Cadangan Otomatis</string>\n    <string name=\"settings_auto_backup_summary\">Cadangan otomatis sekali sehari</string>\n    <string name=\"settings_delete_remote_backup\">Hapus Cadangan Cloud</string>\n    <string name=\"delete_remote_backup_confirm\">Apakah Anda yakin ingin menghapus cadangan cloud?</string>\n    <string name=\"delete_success\">Penghapusan Berhasil</string>\n    <string name=\"delete_failed\">Penghapusan Gagal: %s</string>\n    <string name=\"settings_encrypt_backup\">Enkripsi Cadangan</string>\n    <string name=\"settings_encrypt_backup_summary\">Enkripsi file cadangan dengan kata sandi</string>\n    <string name=\"settings_backup_password\">Kata Sandi Cadangan</string>\n    <string name=\"settings_backup_password_hint\">Masukkan Kata Sandi Cadangan</string>\n    <string name=\"settings_enable_cloud_backup_summary\">Cadangan otomatis ke cloud</string>\n    <string name=\"settings_configure_webdav\">Konfigurasi Layanan WebDAV</string>\n    <string name=\"webdav_config_title\">Konfigurasi WebDAV</string>\n    <string name=\"webdav_url\">URL WebDAV</string>\n    <string name=\"webdav_username\">Nama Pengguna</string>\n    <string name=\"webdav_password\">Kata Sandi</string>\n    <string name=\"webdav_uploading\">Mengunggah ke WebDAV...</string>\n    <string name=\"webdav_backup_success\">Backup WebDAV Berhasil</string>\n    <string name=\"webdav_backup_failed\">Backup WebDAV Gagal: %s</string>\n    <string name=\"save\">Simpan</string>\n    <string name=\"test\">Uji</string>\n    <string name=\"webdav_path_label\">Path (mis. /Backup)</string>\n    <string name=\"webdav_view_logs\">Lihat Log</string>\n    <string name=\"webdav_backup_logs_title\">Log Backup</string>\n    <string name=\"webdav_no_logs\">Tidak ada log</string>\n    <string name=\"webdav_clear_logs\">Hapus</string>\n    <string name=\"settings_enable_local_backup\">Aktifkan Backup Lokal</string>\n    <string name=\"settings_enable_local_backup_summary\">Backup modul ke penyimpanan lokal (Downloads/FolkPatch/ModuleBackups)</string>\n    <string name=\"close\">Tutup</string>\n\n    <string name=\"patch_output_written_to\"> Patch berhasil, file ditulis ke </string>\n    <string name=\"patch_write_failed\"> Gagal menulis boot.img yang dipatch</string>\n\n    <!-- Script Library Strings -->\n    <string name=\"script_library\">Pustaka Skrip</string>\n    <string name=\"script_library_title\">Pustaka Skrip</string>\n    <string name=\"script_library_empty\">Tidak ada skrip, klik tombol tambah untuk menambah</string>\n    <string name=\"script_library_add\">Tambah Skrip</string>\n    <string name=\"script_library_add_title\">Tambah Skrip</string>\n    <string name=\"script_library_select_file\">Pilih file skrip</string>\n    <string name=\"script_library_alias\">Alias</string>\n    <string name=\"script_library_alias_hint\">Masukkan alias skrip (opsional)</string>\n    <string name=\"script_library_run\">Jalankan</string>\n    <string name=\"script_library_delete\">Hapus</string>\n    <string name=\"script_library_path\">Path: %s</string>\n    <string name=\"script_library_confirm_delete\">Konfirmasi hapus skrip?</string>\n    <string name=\"script_library_delete_success\">Skrip dihapus berhasil</string>\n    <string name=\"script_library_delete_failed\">Gagal menghapus skrip</string>\n    <string name=\"script_library_add_success\">Skrip ditambahkan berhasil</string>\n    <string name=\"script_library_add_failed\">Gagal menambahkan skrip: %s</string>\n    <string name=\"script_library_load_failed\">Gagal memuat pustaka skrip</string>\n    <string name=\"script_library_output\">Output eksekusi</string>\n    <string name=\"script_library_no_output\">Tidak ada output</string>\n    <string name=\"script_library_save_log\">Simpan log</string>\n    <string name=\"script_library_log_saved\">Log disimpan ke %s</string>\n    <string name=\"script_library_log_save_failed\">Gagal menyimpan log: %s</string>\n    <string name=\"settings_vibration\">Getaran &amp; Haptik</string>\n    <string name=\"settings_vibration_summary\">Getar saat disentuh</string>\n    <string name=\"settings_vibration_enabled\">Aktifkan getaran</string>\n    <string name=\"settings_vibration_intensity\">Intensitas getaran</string>\n    <string name=\"settings_vibration_scope\">Cakupan getaran</string>\n    <string name=\"settings_vibration_scope_global\">Global (Di mana saja)</string>\n    <string name=\"settings_vibration_scope_bottom_bar\">Hanya bilah bawah</string>\n    <string name=\"superuser\">Superuser</string>\n    <string name=\"module\">Modul</string>\n    <string name=\"search_modules\">Cari modul...</string>\n    <string name=\"search_scripts\">Cari skrip...</string>\n\n    <!-- Umount Service -->\n    <string name=\"settings_umount_service\">Umount Service</string>\n    <string name=\"settings_umount_service_summary\">Konfigurasi titik mount untuk unmount otomatis saat boot</string>\n    <string name=\"umount_config_title\">Konfigurasi Umount</string>\n    <string name=\"umount_config_enabled\">Aktifkan Umount</string>\n    <string name=\"umount_config_enabled_summary\">Unmount otomatis titik mount yang ditentukan saat boot</string>\n    <string name=\"umount_config_paths_label\">Jalur Titik Mount (satu per baris)</string>\n    <string name=\"umount_config_paths_placeholder\">/system/vendor/app\\n/system/vendor/priv-app</string>\n    <string name=\"umount_config_paths_helper\">Masukkan satu jalur titik mount per baris untuk di-unmount</string>\n    <string name=\"umount_config_save\">Simpan</string>\n    <string name=\"umount_config_save_confirm\">Konfirmasi simpan konfigurasi?</string>\n    <string name=\"umount_config_save_success\">Konfigurasi berhasil disimpan</string>\n    <string name=\"umount_config_save_failed\">Gagal menyimpan konfigurasi</string>\n\n    <!-- Halaman Tema Saya -->\n    <string name=\"my_themes_title\">Tema Saya</string>\n    <string name=\"my_themes_empty\">Belum ada tema</string>\n    <string name=\"my_themes_empty_action\">Jelajahi Toko Tema</string>\n    <string name=\"my_themes_apply\">Terapkan Tema</string>\n    <string name=\"my_themes_delete\">Hapus Tema</string>\n    <string name=\"my_themes_delete_confirm\">Apakah Anda yakin ingin menghapus tema ini?</string>\n    <string name=\"my_themes_deleted\">Tema dihapus</string>\n    <string name=\"my_themes_applied\">Tema diterapkan</string>\n    <string name=\"my_themes_apply_failed\">Gagal menerapkan tema</string>\n    <string name=\"my_themes_details\">Detail Tema</string>\n\n    <!-- Dialog Unduh -->\n    <string name=\"theme_download_title\">Mengunduh Tema</string>\n    <string name=\"theme_download_completed\">Unduhan selesai</string>\n    <string name=\"theme_download_failed\">Unduhan gagal</string>\n    <string name=\"theme_download_progress\">Progres</string>\n    <string name=\"theme_download_file\">File Tema</string>\n    <string name=\"theme_download_image\">Gambar Pratinjau</string>\n    <string name=\"theme_download_cancel\">Batal</string>\n    <string name=\"theme_download_pause\">Jeda</string>\n    <string name=\"theme_download_resume\">Lanjutkan</string>\n    <string name=\"theme_download_retry\">Coba Lagi</string>\n    <string name=\"theme_download_apply\">Terapkan Tema</string>\n    <string name=\"theme_download_go_to_my_themes\">Tema Saya</string>\n    <string name=\"theme_download_retrying\">Mencoba Lagi (%d/3)...</string>\n    <string name=\"theme_download_preparing\">Menyiapkan unduhan...</string>\n    <string name=\"theme_download_downloading_file\">Mengunduh file tema...</string>\n    <string name=\"theme_download_downloading_image\">Mengunduh gambar pratinjau...</string>\n    <string name=\"theme_download_finalizing\">Menyelesaikan...</string>\n    <string name=\"settings_predictive_back\">Gestur Kembali Prediktif</string>\n    <string name=\"settings_predictive_back_summary\">Aktifkan animasi gestur kembali prediktif Android 14+</string>\n\n    <string name=\"home_device_status_battery_charging\">Mengisi Daya</string>\n    <string name=\"home_device_status_cpu_temp\">Suhu CPU</string>\n    <string name=\"home_device_status_memory_trend\">Tren Memori</string>\n    <string name=\"home_storage_partitions\">Partisi Penyimpanan</string>\n    <string name=\"home_device_status_cpu_freq\">Frekuensi CPU</string>\n    <string name=\"home_network_rx\">Unduhan</string>\n    <string name=\"home_network_tx\">Unggahan</string>\n    <string name=\"settings_home_layout_stats\">StatsUI</string>\n    <string name=\"home_stats_more_options\">Opsi lainnya</string>\n    <string name=\"home_stats_kp_label\">KP</string>\n    <string name=\"home_stats_manager_label\">Manajer</string>\n    <string name=\"home_device_status_gpu_load\">GPU</string>\n    <string name=\"home_stats_kernel_modules\">Modul Kernel</string>\n    <string name=\"home_stats_apm_modules\">Modul Sistem</string>\n    <string name=\"home_stats_superusers\">Superuser</string>\n    <string name=\"settings_kernel_spoof\">Konfigurasi Pemalsuan Kernel</string>\n    <string name=\"settings_kernel_spoof_summary\">Palsukan versi kernel dan waktu pembuatan</string>\n    <string name=\"settings_kernel_spoof_version\">Versi Kernel</string>\n    <string name=\"settings_kernel_spoof_build_time\">Waktu Pembuatan Kernel</string>\n    <string name=\"settings_kernel_spoof_restore\">Pulihkan</string>\n\n    <string name=\"kernel_spoof_enabled\">Pemalsuan kernel diaktifkan</string>\n    <string name=\"kernel_spoof_disabled_restored\">Pemalsuan kernel dinonaktifkan dan dipulihkan</string>\n    <string name=\"kernel_spoof_failed\">Pemalsuan kernel gagal: %d</string>\n    <string name=\"kernel_spoof_applied\">Pemalsuan kernel diterapkan</string>\n\n    <string name=\"settings_path_hide\">Sembunyikan Jalur</string>\n    <string name=\"settings_path_hide_summary\">Sembunyikan file dan direktori dari aplikasi di level kernel</string>\n    <string name=\"path_hide_paths_label\">Jalur Tersembunyi (satu per baris)</string>\n\n    <string name=\"path_hide_paths_helper\">Masukkan satu jalur per baris. Jalur yang cocok akan mengembalikan ENOENT.</string>\n    <string name=\"path_hide_save\">Simpan</string>\n    <string name=\"path_hide_enabled\">Sembunyikan jalur diaktifkan</string>\n    <string name=\"path_hide_disabled\">Sembunyikan jalur dinonaktifkan</string>\n    <string name=\"path_hide_applied\">Konfigurasi sembunyikan jalur diterapkan</string>\n    <string name=\"path_hide_failed\">Operasi sembunyikan jalur gagal: %d</string>\n    <string name=\"path_hide_uid_mode\">Mode Eksekusi UID</string>\n    <string name=\"path_hide_uid_mode_summary\">Sembunyikan jalur hanya untuk UID aplikasi tertentu</string>\n    <string name=\"path_hide_uids_label\">UID target (satu per baris)</string>\n    <string name=\"path_hide_uids_helper\">Masukkan UID aplikasi. Hanya aplikasi ini yang jalurnya akan disembunyikan.</string>\n    <string name=\"path_hide_uid_save\">Simpan UID</string>\n    <string name=\"path_hide_uid_mode_enabled\">Mode eksekusi UID diaktifkan</string>\n    <string name=\"path_hide_uid_mode_disabled\">Mode eksekusi UID dinonaktifkan</string>\n    <string name=\"path_hide_filter_system\">Filter UID Sistem</string>\n    <string name=\"path_hide_filter_system_summary\">Juga sembunyikan jalur dari proses root dan sistem (UID &lt; 10000)</string>\n    <string name=\"path_hide_filter_system_enabled\">Filter UID sistem diaktifkan</string>\n    <string name=\"path_hide_filter_system_disabled\">Filter UID sistem dinonaktifkan</string>\n    <string name=\"path_hide_filter_system_warning_title\">Peringatan</string>\n    <string name=\"path_hide_filter_system_warning_message\">Mengaktifkan ini juga akan menyembunyikan jalur dari proses root dan sistem (UID &lt; 10000). Ini dapat menyebabkan beberapa fungsi sistem tidak berfungsi normal. Harap berhati-hati.</string>\n    <string name=\"path_hide_uid_applied\">Daftar putih UID diterapkan</string>\n    <string name=\"path_hide_select_apps\">Pilih aplikasi</string>\n    <string name=\"path_hide_no_apps_selected\">Tidak ada aplikasi yang dipilih</string>\n    <string name=\"path_hide_app_removed\">Dihapus %d entri konfigurasi aplikasi yang sudah di-uninstall</string>\n    <string name=\"path_hide_search_apps\">Cari aplikasi…</string>\n    <string name=\"path_hide_show_system\">Tampilkan aplikasi sistem</string>\n    <string name=\"netisolate_title\">Isolasi Jaringan</string>\n    <string name=\"netisolate_enable\">Isolasi Jaringan diaktifkan</string>\n    <string name=\"netisolate_disable\">Isolasi Jaringan dinonaktifkan</string>\n    <string name=\"netisolate_enable_summary\">Blokir akses jaringan untuk aplikasi yang dipilih di level kernel</string>\n    <string name=\"netisolate_uids_label\">Aplikasi Diblokir</string>\n    <string name=\"netisolate_uids_hint\">Masukkan UID untuk diblokir (satu per baris)</string>\n    <string name=\"netisolate_no_uids\">Tidak ada aplikasi yang diblokir</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-ja/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name_fpatch\">FPatch</string>\n    <string name=\"desktop_app_name\">デスクトップのアプリ名</string>\n    <string name=\"home\">ホーム</string>\n    <string name=\"success\">成功しました。</string>\n    <string name=\"failure\">失敗しました。</string>\n    <string name=\"patch_warnning\">インストールにはリスクが伴います。必ずデータのバックアップを行ってください。</string>\n    <string name=\"patch\">パッチ</string>\n    <string name=\"kernel_patch\">KernelPatch</string>\n    <string name=\"android_patch\">AndroidPatch</string>\n    <string name=\"settings_nav_layout_title\">ナビゲーションレイアウトの設定</string>\n    <string name=\"settings_nav_layout_summary\">一部のナビゲーションコンポーネントを表示または非表示にします。</string>\n    <string name=\"settings_nav_scheme\">ナビゲーションスキーム</string>\n    <string name=\"settings_nav_mode\">ナビゲーションバーモード</string>\n    <string name=\"settings_nav_mode_summary\">ナビゲーションバーの表示方法を選択します。</string>\n    <string name=\"settings_nav_mode_auto\">自動伝統</string>\n    <string name=\"settings_nav_mode_bottom\">常にボトムバー</string>\n    <string name=\"settings_nav_mode_rail\">常にサイドレール</string>\n    <string name=\"settings_nav_mode_floating\">フローティングボトムバー</string>\n    <string name=\"settings_show_apm\">システムモジュールを表示</string>\n    <string name=\"settings_show_kpm\">KPM を表示</string>\n    <string name=\"settings_show_superuser\">スーパーユーザーを表示</string>\n    <string name=\"settings_floating_auto_hide\">自動非表示</string>\n    <string name=\"settings_floating_auto_hide_summary\">3秒間操作がないとフローティングバーを自動的に非表示</string>\n    <string name=\"settings_floating_swipe_hide\">スワイプで非表示</string>\n    <string name=\"settings_floating_swipe_hide_summary\">下にスワイプで非表示、上にスワイプで表示</string>\n    <string name=\"settings_navbar_glass_effect\">すりガラス効果</string>\n    <string name=\"settings_navbar_glass_effect_summary\">フローティングナビゲーションバーにすりガラス効果を適用</string>\n    <string name=\"settings_navbar_glass_blur_strength\">ぼかし強度</string>\n    <string name=\"settings_navbar_glass_transparency\">背景の透明度</string>\n    <string name=\"settings_navbar_glass_highlight_strength\">ハイライト強度</string>\n    <string name=\"settings_navbar_glass_specular\">鏡面反射</string>\n    <string name=\"settings_navbar_glass_specular_summary\">ナビゲーションバーの上部に鏡面ハイライトを有効にする</string>\n    <string name=\"settings_navbar_glass_inner_glow\">インナーグロー</string>\n    <string name=\"settings_navbar_glass_inner_glow_summary\">ナビゲーションバーの下部にさりげない発光効果を有効にする</string>\n    <string name=\"settings_navbar_glass_border\">ガラスボーダー</string>\n    <string name=\"settings_navbar_glass_border_summary\">ナビゲーションバーの周囲にさりげないボーダーを有効にする</string>\n    <string name=\"settings_stats_top_layout\">上部レイアウト</string>\n    <string name=\"settings_stats_top_layout_summary\">上部情報カードのスタイルを選択</string>\n    <string name=\"settings_stats_top_layout_list\">リスト</string>\n    <string name=\"settings_stats_top_layout_grid\">グリッド</string>\n    <string name=\"settings_block_kernelpatch_update\">KernelPatch の更新をブロック</string>\n    <string name=\"settings_block_kernelpatch_update_summary\">KernelPatch の更新通知を表示しないようにします。</string>\n    <string name=\"settings_block_androidpatch_update\">システムパッチの更新をブロック</string>\n    <string name=\"settings_block_androidpatch_update_summary\">システムパッチ(APD)の更新通知を表示しないようにします。</string>\n    <string name=\"reboot\">再起動</string>\n    <string name=\"settings\">設定</string>\n    <string name=\"reboot_recovery\">リカバリーで再起動</string>\n    <string name=\"reboot_bootloader\">Bootloader で再起動</string>\n    <string name=\"reboot_download\">ダウンロードモードで再起動</string>\n    <string name=\"reboot_edl\">EDL で再起動</string>\n    <string name=\"reboot_fastbootd\">FastbootD で再起動</string>\n    <string name=\"about\">アプリについて</string>\n    <string name=\"developer_and_maintainer\">開発者 | メンテナ</string>\n    <string name=\"settings_app_language\">言語</string>\n    <string name=\"system_default\">システムのデフォルト</string>\n    <string name=\"settings_global_namespace_mode\">グローバル名前空間モード</string>\n    <string name=\"settings_global_namespace_mode_summary\">すべてのルートセッションはグローバルマウント名前空間を使用します。</string>\n    <string name=\"settings_clear_super_key_dialog\">続行してもよろしいですか？</string>\n    <string name=\"settings_grid_working_card_hide_check\">ステータスアイコンを非表示</string>\n    <string name=\"settings_grid_working_card_hide_check_summary\">動作中のカードのチェックマークまは警告アイコンを非表示にします。</string>\n    <string name=\"settings_grid_working_card_hide_text\">ステータステキストを非表示</string>\n    <string name=\"settings_grid_working_card_hide_text_summary\">動作中のカードの「動作中」または「未インストール」のテキストを非表示にします。</string>\n    <string name=\"settings_grid_working_card_hide_mode\">動作モードを非表示</string>\n    <string name=\"settings_grid_working_card_hide_mode_summary\">動作中のカードの「完全」または「一部」のテキストを非表示にします。</string>\n    <string name=\"settings_folkx_engine_title\">FolkX アニメーションエンジン</string>\n    <string name=\"settings_folkx_engine_summary\">トップレベルのページの切り替え時にスムーズな物理ベースのスプリングアニメーションを使用します。</string>\n    <string name=\"settings_folkx_animation_type\">アニメーションタイプ</string>\n    <string name=\"settings_folkx_animation_speed\">アニメーション速度</string>\n    <string name=\"settings_folkx_animation_linear\">リニアモーション</string>\n    <string name=\"settings_folkx_animation_spatial\">空間モーション</string>\n    <string name=\"settings_folkx_animation_fade\">フェードイン/アウト</string>\n    <string name=\"settings_folkx_animation_vertical\">垂直にスライド</string>\n    <string name=\"settings_folkx_animation_diagonal\">斜めにスライド</string>\n    <string name=\"su_exclude_all_title\">すべて除外</string>\n    <string name=\"su_exclude_all_confirm\">すべての非 root アプリを除外してもよろしいですか？</string>\n    <string name=\"su_batch_exclude_title\">一括で除外</string>\n    <string name=\"su_batch_exclude_content\">すべての非 root アプリのインジェクションを除外するには、アクションを選択してください</string>\n    <string name=\"su_exclude_btn\">除外</string>\n    <string name=\"su_exclude_reverse_btn\">反転</string>\n    <string name=\"home_learn_apatch\">APatch について学習する</string>\n    <string name=\"home_click_to_learn_apatch\">APatch の機能と使用方法について学習します。</string>\n    <string name=\"settings_hide_apatch_card\">APatch について学習するを非表示</string>\n    <string name=\"settings_hide_apatch_card_summary\">「APatch について学習する」のカードを非表示にします。</string>\n    <string name=\"about_source_code\"><![CDATA[<p>%1$s でソースコードを表示<p/>%2$s のチャンネルに参加<p/>%3$s のグループに参加]]></string>\n    <string name=\"send_log\">ログを送信</string>\n    <string name=\"save_log\">ログを保存</string>\n    <string name=\"log_saved\">ログを保存しました。</string>\n    <string name=\"safe_mode\">セーフモード</string>\n    <string name=\"support_or_donate\">サポート / 寄付</string>\n    <string name=\"super_key\">SuperKey</string>\n    <string name=\"clear_super_key\">SuperKey を消去</string>\n    <string name=\"patch_set_superkey\">SuperKey を設定</string>\n    <string name=\"home_patch_set_key_desc\">KernelPatch の認証情報のみです</string>\n    <string name=\"home_patch_next_step\">次のステップ</string>\n    <string name=\"home_not_installed\">未インストール</string>\n    <string name=\"home_install_unknown\">未インストールまたは認証されていません</string>\n    <string name=\"home_install_unknown_summary\">タップでインストール</string>\n    <string name=\"home_click_to_install\">タップでインストール</string>\n    <string name=\"home_working\">動作中</string>\n    <string name=\"home_version\">バージョン: %s</string>\n    <string name=\"home_kp_need_update\">新しいバージョンが利用可能です</string>\n    <string name=\"home_kp_cando_update\">更新</string>\n    <string name=\"home_installing\">インストール中</string>\n    <string name=\"kpatch_version\">バージョン: %s</string>\n    <string name=\"apatch_version\">バージョン: %s</string>\n    <string name=\"kpatch_version_update\" formatted=\"false\">バージョン: %s -&gt; %s</string>\n    <string name=\"kpatch_shadow_path_title\">KPatch</string>\n    <string name=\"home_auth_key_title\">SuperKey を入力</string>\n    <string name=\"home_auth_key_desc\">認証後に開始します</string>\n    <string name=\"home_kpatch_info_title\">情報</string>\n    <string name=\"home_ap_cando_install\">インストール</string>\n    <string name=\"home_ap_cando_uninstall\">アンインストール</string>\n    <string name=\"home_ap_cando_reboot\">再起動</string>\n    <string name=\"patch_title\">パッチ</string>\n    <string name=\"patch_config_title\">パッチ</string>\n    <string name=\"patch_mode_bootimg_patch\">モード: パッチ</string>\n    <string name=\"patch_mode_patch_and_install\">モード: パッチとインストール</string>\n    <string name=\"patch_mode_install_to_next_slot\">モード: 非アクティブスロットにインストール (OTA 後)</string>\n    <string name=\"patch_mode_restore\">モード: 復元</string>\n    <string name=\"patch_mode_uninstall_patch\">モード: KPatch をアンインストール</string>\n    <string name=\"patch_select_bootimg_btn\">boot を選択</string>\n    <string name=\"patch_embed_kpm_btn\">KPM を埋め込む</string>\n    <string name=\"patch_start_patch_btn\">開始</string>\n    <string name=\"patch_start_unpatch_btn\">パッチを解除</string>\n    <string name=\"patch_item_error\">！！エラー！！</string>\n    <string name=\"patch_item_bootimg\">boot イメージ</string>\n    <string name=\"patch_item_bootimg_slot\">スロット:</string>\n    <string name=\"patch_item_bootimg_dev\">デバイス:</string>\n    <string name=\"patch_item_kernel\">カーネル</string>\n    <string name=\"patch_item_kpimg\">KP イメージ</string>\n    <string name=\"patch_item_kpimg_version\">バージョン:</string>\n    <string name=\"patch_item_kpimg_comile_time\">時間:</string>\n    <string name=\"patch_item_kpimg_config\">構成:</string>\n    <string name=\"patch_item_new_extra_kpm\">新しく埋め込む</string>\n    <string name=\"patch_item_existed_extra_kpm\">存在</string>\n    <string name=\"patch_item_extra_name\">名前:</string>\n    <string name=\"patch_item_extra_version\">バージョン:</string>\n    <string name=\"patch_item_extra_author\">作者:</string>\n    <string name=\"patch_item_extra_kpm_license\">ライセンス:</string>\n    <string name=\"patch_item_extra_kpm_desciption\">説明:</string>\n    <string name=\"patch_item_extra_args\">引数:</string>\n    <string name=\"patch_item_extra_event\">イベント:</string>\n    <string name=\"patch_item_skey\">SuperKey</string>\n    <string name=\"patch_custom_superkey\">カスタム SuperKey</string>\n    <string name=\"patch_custom_superkey_summary\">Root とカーネルの管理に SuperKey を引き続き使用できます。マネージャーはカーネルの組み込み署名で認証されています</string>\n    <string name=\"patch_item_set_skey_label\">SuperKey の長さは 8～63 文字で、数字と文字を含めることができますが、特殊文字を含めることはできません。</string>\n    <string name=\"patch_confirm_superkey\">SuperKey を確認</string>\n    <string name=\"patch_skey_mismatch\">SuperKey が一致しません</string>\n\n    <string name=\"home_kernel\">カーネル バージョン</string>\n    <string name=\"home_manager_version\">マネージャー バージョン</string>\n    <string name=\"home_fingerprint\">フィンガープリント</string>\n    <string name=\"home_selinux_status\">SELinux ステータス</string>\n    <string name=\"home_selinux_status_disabled\">無効</string>\n    <string name=\"home_selinux_status_enforcing\">Enforcing</string>\n    <string name=\"home_selinux_status_permissive\">Permissive</string>\n    <string name=\"home_selinux_status_unknown\">不明</string>\n    <string name=\"settings_selinux_mode\">SELinux モード</string>\n    <string name=\"settings_selinux_mode_summary\">SELinux の Enforcing モードを選択します。</string>\n    <string name=\"settings_selinux_mode_enforcing\">Enforcing (厳格)</string>\n    <string name=\"settings_selinux_mode_permissive\">Permissive (寛大)</string>\n    <string name=\"settings_selinux_current_mode\">現在: %s</string>\n    <string name=\"settings_selinux_mode_enforcing_summary\">SELinux はアクセスルールを完全に施行します。</string>\n    <string name=\"settings_selinux_mode_permissive_summary\">SELinux は違反をログに記録しますが、拒否しません。</string>\n    <string name=\"home_device_info\">デバイス</string>\n    <string name=\"home_system_version\">システム バージョン</string>\n    <string name=\"home_kpatch_version\">KernelPatch バージョン</string>\n    <string name=\"home_su_path\">実行可能な su</string>\n    <string name=\"home_apatch_version\">FolkPatch バージョン</string>\n    <string name=\"kpm\">KP モジュール</string>\n    <string name=\"kpm_kp_not_installed\">KernelPatch はインストールされていません</string>\n    <string name=\"kpm_add_kpm\">KPM を追加</string>\n    <string name=\"kpm_load\">読み込み</string>\n    <string name=\"kpm_install\">インストール</string>\n    <string name=\"kpm_embed\">埋め込む</string>\n    <string name=\"kpm_load_toast_succ\">読み込みに成功</string>\n    <string name=\"kpm_load_toast_failed\">読み込みに失敗</string>\n    <string name=\"kpm_unload_confirm\">「%s」のモジュールをアンロードしますか？</string>\n    <string name=\"kpm_unload\">アンロード</string>\n    <string name=\"kpm_control\">コントロール</string>\n    <string name=\"kpm_apm_empty\">モジュールは読み込まれていません</string>\n    <string name=\"kpm_version\">バージョン</string>\n    <string name=\"kpm_license\">ライセンス</string>\n    <string name=\"kpm_author\">作者</string>\n    <string name=\"kpm_desc\">説明</string>\n    <string name=\"kpm_args\">引数</string>\n    <string name=\"su_title\">スーパーユーザー</string>\n    <string name=\"su_selinux_via_hook\">フックでバイパス</string>\n    <string name=\"su_pkg_excluded_label\">除外</string>\n    <string name=\"su_pkg_excluded_setting_title\">変更を除外</string>\n    <string name=\"su_pkg_excluded_setting_summary\">このオプションを有効化すると、FolkPatch はこのアプリのモジュールによって変更されたすべてのファイルを復元できるようになります。</string>\n    <string name=\"su_pkg_root_setting_title\">スーパーユーザー</string>\n    <string name=\"su_pkg_root_setting_summary\">このオプションを有効化すると、アプリへのスーパーユーザーアクセスが許可され SU コマンドを使用できるようになります。</string>\n    <string name=\"su_pkg_normal_setting_title\">通常モード</string>\n    <string name=\"su_pkg_normal_setting_summary\">デフォルト、特別な処理はしません。</string>\n    <string name=\"su_show_system_apps\">システムアプリを表示</string>\n    <string name=\"su_hide_system_apps\">システムアプリを非表示</string>\n    <string name=\"su_refresh\">更新</string>\n    <string name=\"apm\">AP モジュール</string>\n    <string name=\"apm_not_installed\">AndroidPatch はインストールされていません</string>\n    <string name=\"apm_failed_to_enable\">モジュールの有効化に失敗: %s</string>\n    <string name=\"apm_failed_to_disable\">モジュールの無効化に失敗: %s</string>\n    <string name=\"apm_empty\">モジュールはインストールされていません</string>\n    <string name=\"apm_remove\">削除</string>\n    <string name=\"apm_undo\">元に戻す</string>\n    <string name=\"apm_install\">インストール</string>\n    <string name=\"apm_uninstall_confirm\">「%s」のモジュールをアンインストールしますか？</string>\n    <string name=\"apm_uninstall_success\">「%s」はアンインストールされました</string>\n    <string name=\"apm_uninstall_failed\">アンインストールに失敗: %s</string>\n    <string name=\"apm_undo_uninstall_success\">%s 個の復元が成功しました。</string>\n    <string name=\"apm_undo_uninstall_failed\">復元に失敗: %s</string>\n    <string name=\"apm_version\">バージョン</string>\n    <string name=\"apm_author\">作者</string>\n    <string name=\"apm_desc\">説明</string>\n    <string name=\"apm_overlay_fs_not_available\">OverlayFS がカーネルによって無効化されているため、モジュールは使用できません。</string>\n    <string name=\"apm_magisk_conflict\">Magisk との競合のため、モジュールは使用できません！</string>\n    <string name=\"apm_mount_warning_title\">重要なお知らせ</string>\n    <string name=\"apm_mount_warning_message\">モジュールはデフォルトでマウントされません。内蔵のマウントシステムまたは、メタモジュールを使用してください。</string>\n    <string name=\"apm_mount_warning_button\">理解しました</string>\n    <string name=\"apm_reboot_to_apply\">適用するには再起動してください</string>\n    <string name=\"apm_changelog\">更新履歴</string>\n    <string name=\"apm_update\">更新</string>\n    <string name=\"apm_downloading\">モジュールをダウンロード中: %s</string>\n    <string name=\"apm_start_downloading\">ダウンロードを開始中: %s</string>\n    <string name=\"apm_new_version_available\">新しいバージョンの %s が利用可能です。タップしてアップグレードしてください。</string>\n    <string name=\"hide_apatch_manager\">APatch Manager を非表示</string>\n    <string name=\"hide_apatch_manager_summary\">ランダムなパッケージ ID とカスタムアプリラベルを持つプロキシアプリをインストールします。</string>\n    <string name=\"hide_apatch_dialog_new_manager_name\">新しいマネージャーの名前</string>\n    <string name=\"hide_apatch_dialog_summary\">ランチャーに表示される新しいアプリラベルとして使用されます。</string>\n    <string name=\"hide_apatch_manager_failure\">非表示に失敗しました。バグを報告してください！</string>\n    <string name=\"setting_reset_su_path\">su のパスをリセット</string>\n    <string name=\"setting_reset_su_new_path\">新しいフルパス</string>\n    <string name=\"apm_webui_open\">開く</string>\n    <string name=\"apm_action\">アクション</string>\n    <string name=\"module_shortcut_add\">ショートカット</string>\n    <string name=\"module_shortcut_not_supported\">ランチャーはデスクトップショートカットに非対応です。</string>\n    <string name=\"module_shortcut_created\">デスクトップにショートカットを作成しました。</string>\n    <string name=\"module_shortcut_updated\">ショートカットを更新しました。</string>\n    <string name=\"module_shortcut_permission_tip_xiaomi\">Xiaomi の設定でこのアプリの「デスクトップショートカットの作成」の権限を有効化してください。</string>\n    <string name=\"module_shortcut_permission_tip_oppo\">OPPO の設定でこのアプリの「デスクトップショートカット」の権限を有効化してください。</string>\n    <string name=\"module_shortcut_permission_tip_default\">ショートカットの作成に失敗した場合は、システム設定でこのアプリのデスクトップショートカットの許可を有効化してください。</string>\n    <string name=\"enable_web_debugging\">WebView デバッグを有効化</string>\n    <string name=\"enable_web_debugging_summary\">WebUI のデバッグに使用できます。必要な場合のみ有効化してください。</string>\n    <string name=\"settings_apm_install_confirm\">確認後にインストール</string>\n    <string name=\"settings_apm_install_confirm_summary\">モジュールをインストールする前に確認ダイアログを表示します。</string>\n    <string name=\"settings_show_more_module_info\">モジュールの詳細を表示</string>\n    <string name=\"settings_show_more_module_info_summary\">モジュールリストにモジュール ID とサイズを表示します。</string>\n    <string name=\"settings_apm_stay_on_page\">操作ページに留まる</string>\n    <string name=\"settings_apm_stay_on_page_summary\">モジュールのアクションを実行後に自動で戻らなくなります。</string>\n    <string name=\"settings_enable_module_shortcut_add\">ショートカット追加ボタンを有効化</string>\n    <string name=\"settings_enable_module_shortcut_add_summary\">WebUI ショートカット追加ボタンを表示</string>\n    <string name=\"settings_disable_module_update_check\">モジュールの更新の確認を無効化する</string>\n    <string name=\"settings_disable_module_update_check_summary\">システムモジュールの自動更新の確認を無効化します。</string>\n    <string name=\"settings_module_sort_optimization\">モジュールの並べ替えを最適化</string>\n    <string name=\"settings_module_sort_optimization_summary\">WebUI とアクションを備えたシステムモジュールを上部に表示します。</string>\n    <string name=\"settings_fold_system_module\">システムモジュールを折りたたむ</string>\n    <string name=\"settings_fold_system_module_summary\">モジュールカードをタップで展開/折りたたみのアクションをします。</string>\n    <string name=\"settings_simple_list_bottom_bar\">シンプルリストボトムバー</string>\n    <string name=\"settings_simple_list_bottom_bar_summary\">モジュールアクションにアイコンのみのボタンスタイルを使用します (APatch にインスパイア)。</string>\n    <string name=\"settings_spliced_card_group\">Spliced Card Group</string>\n    <string name=\"settings_spliced_card_group_summary\">Use M3E continuous card group style for module list</string>\n    <string name=\"apm_install_confirm_title\">モジュールをインストール</string>\n    <string name=\"apm_install_confirm_content\">「%s」をインストールしてもよろしいですか？</string>\n    <string name=\"apm_disable_all_title\">すべてのモジュールを無効化</string>\n    <string name=\"module_shortcut_name\">ショートカット名</string>\n    <string name=\"module_shortcut_icon\">ショートカットアイコン</string>\n    <string name=\"module_shortcut_type\">ショートカットタイプ</string>\n    <string name=\"module_shortcut_type_webui\">WebUI</string>\n    <string name=\"module_shortcut_type_action\">アクション</string>\n    <string name=\"module_shortcut_icon_default\">デフォルトのアイコンを使用</string>\n    <string name=\"module_shortcut_icon_select\">アイコンを選択</string>\n    <string name=\"apm_disable_all_confirm\">すべてのモジュールを無効化してもよろしいですか？これにより、インストールされているすべてのモジュールが無効化されます。</string>\n    <string name=\"apm_enable_module_banner\">モジュールバナーを有効化</string>\n    <string name=\"apm_enable_module_banner_summary\">モジュールのバナー画像が利用可能な場合に表示します。</string>\n    <string name=\"apm_enable_folk_banner\">カスタムモジュールバナー</string>\n    <string name=\"apm_enable_folk_banner_summary\">モジュールカードを長押しでバナー画像を選択します。</string>\n    <string name=\"apm_folk_banner_title\">Folk バナー</string>\n    <string name=\"apm_folk_banner_select\">画像を選択</string>\n    <string name=\"apm_folk_banner_clear\">Folk バナーを消去</string>\n    <string name=\"apm_folk_banner_saved\">「%s」に Folk バナーをインジェクトしました。</string>\n    <string name=\"apm_folk_banner_cleared\">「%s」の Folk バナーを消去しました。</string>\n    <string name=\"apm_folk_banner_failed\">「%s」の Folk バナーの更新に失敗しました。</string>\n    <string name=\"apm_banner_api_mode\">API モード</string>\n    <string name=\"apm_banner_api_mode_summary\">モジュールバナーでランダム画像 API またはローカルディレクトリを使用します。</string>\n    <string name=\"apm_banner_api_source\">構成 API ソース</string>\n    <string name=\"apm_banner_api_source_hint\">API URL またはローカルパスを入力</string>\n    <string name=\"apm_banner_api_source_saved\">API ソースを保存しました</string>\n    <string name=\"apm_banner_api_source_not_configured\">未構成</string>\n    <string name=\"apm_banner_api_url_configured\">API URL 構成済み</string>\n    <string name=\"apm_banner_local_dir_configured\">ローカルディレクトリが構成されています。</string>\n    <string name=\"apm_banner_clear_cache\">キャッシュを消去</string>\n    <string name=\"apm_banner_cache_cleared\">バナーキャッシュを消去しました。</string>\n    <string name=\"apm_banner_api_config_title\">構成 API ソース</string>\n    <string name=\"apm_banner_api_config_desc\">ランダム画像 API URL またはローカルディレクトリのパスを入力してください。各モジュールに固有のバナー画像が付与されます。</string>\n    <string name=\"apm_banner_api_examples_title\">例:</string>\n    <string name=\"apm_banner_api_examples\">API: https://picsum.photos/800/400\\nローカル: /sdcard/Pictures/Banners</string>\n    <!-- API Marketplace -->\n    <string name=\"apm_api_marketplace_title\">API マーケットプレイス</string>\n    <string name=\"apm_api_preview\">プレビュー</string>\n    <string name=\"apm_api_apply\">適用</string>\n    <string name=\"apm_api_preview_title\">API プレビュー</string>\n    <string name=\"apm_api_preview_failed\">プレビューの読み込みに失敗しました。</string>\n    <string name=\"apm_api_url_label\">API URL:</string>\n    <string name=\"apm_api_apply_success\">API ソースを適用しました。</string>\n    <string name=\"apm_api_verifying\">検証中…</string>\n    <string name=\"apm_api_retry\">再試行</string>\n    <string name=\"apm_api_empty\">利用可能な API ソースがありません</string>\n    <string name=\"settings_banner_custom_opacity\">バナーの透過</string>\n    <string name=\"settings_banner_custom_opacity_summary\">壁紙モードを使用する代わりにカスタム可能な透過バナーを使用します。</string>\n    <string name=\"settings_banner_opacity\">バナーの透過率</string>\n    <string name=\"settings_donot_store_superkey\">SuperKey をローカルに保存しない</string>\n    <string name=\"settings_donot_store_superkey_summary\">マネージャーを起動するたびに SuperKey の認証を要求します。</string>\n    <string name=\"mode_select_page_title\">インストール</string>\n    <string name=\"mode_select_page_patch_and_install\">パッチとインストール</string>\n    <string name=\"mode_select_page_select_file\">boot イメージを選択でパッチ</string>\n    <string name=\"restore_select_file\">boot イメージを選択で復元</string>\n    <string name=\"mode_select_page_install_inactive_slot\">非アクティブスロットにインストール (OTA 後)</string>\n    <string name=\"mode_select_page_install_inactive_slot_warning\">再起動後にデバイスは、非アクティブなスロットから**強制的に**起動されます！\\nこのオプションは、OTA の完了後にのみ使用してください。\\n続行しますか？</string>\n    <string name=\"mode_select_page_select_kpimg\">ローカルパッチファイルを使用 (KPimg)</string>\n    <string name=\"patch_custom_kpimg_label\">カスタム KPimg</string>\n    <string name=\"patch_custom_kpimg_file\">ファイル: %s</string>\n    <string name=\"patch_select_kpimg_btn\">KPimg を選択</string>\n    <string name=\"home_dialog_auth_fail_title\">認証に失敗しました。</string>\n    <string name=\"home_dialog_auth_fail_content\">SuperKey を認証できないため、FolkPatch の有効化に失敗しました。\n認証の失敗について考えられる原因は以下の通りです。\n該当するものをご確認ください:\n\n\\n1. KernelPatch を使用して boot.img にパッチを適用していないか、実際にパッチを適用した内容を忘れている。\n\\n2. パッチを適用した boot.img がコンピューター上でスリープ状態のままで、デバイスに書き込まれていない。\n\\n3. SuperKey が誤って入力されたか、不明な記号 (例: 外国の文字) が含まれている。\n\\n4. デバイスが、FolkPatch および KernelPatch と互換性がない可能性。これは強制的に試行しても無理です。\n\\n5. FolkPatch をブロックするパッケージ名を除外する特定のモジュールを使用するなど、不明な操作を行った結果、ゲームから脱落した可能性があります。</string>\n    <string name=\"home_more_menu_feedback_or_suggestion\">フィードバックまたは提案</string>\n    <string name=\"home_more_menu_about\">アプリについて</string>\n    <string name=\"home_more_menu_document\">ドキュメント</string>\n    <string name=\"home_dialog_uninstall_title\">アンインストール</string>\n    <string name=\"home_dialog_uninstall_ap_only\">パッチのアンインストールのみ</string>\n    <string name=\"home_dialog_uninstall_all\">完全にアンインストール</string>\n    <string name=\"home_dialog_uninstall_ap_only_desc\">AndroidPatch のみを削除し、マネージャーは保持します。</string>\n    <string name=\"home_dialog_uninstall_all_desc\">AndroidPatch を削除し、完全なアンインストールフローを行います。</string>\n    <string name=\"kpm_control_dialog_title\">KPM のコントロール</string>\n    <string name=\"kpm_control_dialog_content\">コントロールパラメータを入力:</string>\n    <string name=\"kpm_control_paramters\">パラメータ</string>\n    <string name=\"kpm_control_outMsg\">出力</string>\n    <string name=\"kpm_control_ok\">成功しました！</string>\n    <string name=\"kpm_control_failed\">失敗しました！</string>\n    <string name=\"settings_night_mode_follow_sys\">システムに従ってダークテーマにする</string>\n    <string name=\"settings_night_mode_follow_sys_summary\">システム設定に基づいてダークテーマを自動的に切り替えます。</string>\n    <string name=\"settings_night_theme_enabled\">ダークテーマ</string>\n    <string name=\"settings_use_system_color_theme\">システムカラーテーマ</string>\n    <string name=\"settings_use_system_color_theme_summary\">システムによって壁紙から生成されたカラーテーマを使用します。</string>\n    <string name=\"settings_custom_color_theme\">カラーテーマ</string>\n    <string name=\"amber_theme\">アンバー</string>\n    <string name=\"blue_theme\">ブルー</string>\n    <string name=\"blue_grey_theme\">ブルーグレー</string>\n    <string name=\"brown_theme\">ブラウン</string>\n    <string name=\"cyan_theme\">シアン</string>\n    <string name=\"deep_orange_theme\">ディープオレンジ</string>\n    <string name=\"deep_purple_theme\">ディープパープル</string>\n    <string name=\"green_theme\">グリーン</string>\n    <string name=\"indigo_theme\">インディゴ</string>\n    <string name=\"light_blue_theme\">ライトブルー</string>\n    <string name=\"light_green_theme\">ライトグリーン</string>\n    <string name=\"lime_theme\">ライム</string>\n    <string name=\"orange_theme\">オレンジ</string>\n    <string name=\"pink_theme\">ピンク</string>\n    <string name=\"purple_theme\">パープル</string>\n    <string name=\"red_theme\">レッド</string>\n    <string name=\"sakura_theme\">サクラ</string>\n    <string name=\"teal_theme\">ティール</string>\n    <string name=\"yellow_theme\">イエロー</string>\n    <string name=\"ink_wash_theme\">インクウォッシュ</string>\n    <string name=\"theme_color\">テーマカラー</string>\n    <string name=\"theme_light\">ライト</string>\n    <string name=\"theme_dark\">ダーク</string>\n    <string name=\"theme_system\">システム</string>\n    <string name=\"loading_modules\">モジュールを読み込み中…</string>\n    <string name=\"loading_scripts\">スクリプトを検索中…</string>\n    <string name=\"loading_themes\">テーマを読み込み中…</string>\n    <string name=\"loading_apis\">APIソースを読み込み中…</string>\n    <string name=\"about_app_desc\">KernelPatch ベースの root 実装、カーネルをリコンパイルせずにカーネル関数フックを可能にします。</string>\n    <string name=\"about_powered_by\">Powered by %1$s</string>\n    <string name=\"about_telegram_group\">Telegram グループ</string>\n    <string name=\"github\">GitHub</string>\n    <string name=\"telegram\">Telegram</string>\n    <!-- Online Module Strings -->\n    <string name=\"online_module_title\">オンラインシステムモジュール</string>\n    <string name=\"online_module_download_start\">ダウンロードを開始中: %s</string>\n    <string name=\"online_module_download_complete\">ダウンロードが完了: %s</string>\n    <string name=\"online_module_download_notification\">「%s」をダウンロード中です。進行状況は通知パネル、完了したファイルはダウンロードフォルダでご確認ください。</string>\n    <!-- Online KPM Strings -->\n    <string name=\"online_kpm_title\">オンラインカーネルモジュール</string>\n    <string name=\"online_kpm_download_start\">ダウンロードを開始中: %s</string>\n    <string name=\"online_kpm_download_complete\">ダウンロードが完了: %s</string>\n    <string name=\"online_kpm_download_notification\">「%s」をダウンロード中です。進行状況は通知パネル、完了したファイルはダウンロードフォルダでご確認ください。</string>\n    <!-- Online Script Strings -->\n    <string name=\"online_script_title\">オンラインスクリプト</string>\n    <string name=\"online_script_download_start\">ダウンロードを開始: %s</string>\n    <string name=\"online_script_download_complete\">ダウンロード完了: %s</string>\n    <string name=\"online_script_download_notification\">「%s」をダウンロード中です</string>\n    <!-- Crash Handle Strings -->\n    <string name=\"crash_handle_title\">クラッシュレポート</string>\n    <string name=\"crash_handle_copy\">コピー</string>\n    <!-- About Screen Strings -->\n    <string name=\"about_app_version\">アプリのバージョン: %s</string>\n    <string name=\"about_github\">GitHub</string>\n    <string name=\"about_telegram_channel\">Telegram チャンネル</string>\n    <!-- App Title Strings -->\n    <string name=\"app_title_fpatch\">FPatch</string>\n    <string name=\"app_title_apatch_folk\">APatch Folk</string>\n    <string name=\"app_title_apatchx\">APatch X</string>\n    <string name=\"app_title_apatch\">APatch</string>\n    <string name=\"app_title_folkpatch\">FolkPatch</string>\n    <string name=\"app_title_kernelpatch\">KernelPatch</string>\n    <string name=\"app_title_kernelsu\">KernelSU</string>\n    <string name=\"app_title_supersu\">SuperSU</string>\n    <string name=\"app_title_superuser\">Superuser</string>\n    <string name=\"app_title_superpatch\">SuperPatch</string>\n    <string name=\"app_title_magicpatch\">MagicPatch</string>\n    <string name=\"settings_app_dpi\">アプリの DPI</string>\n    <string name=\"dpi_apply_settings\">適用</string>\n    <string name=\"dpi_confirm_title\">DPI変更の確認</string>\n    <string name=\"dpi_confirm_message\">アプリのDPIを%1$sから%2$sに変更しますか？</string>\n    <string name=\"settings_magic_mount\">Folk Mount API</string>\n    <string name=\"settings_magic_mount_summary\">内蔵のモジュールマウントシステムを有効化します。</string>\n    <string name=\"settings_new_app_profile_mode\">新しいアプリのデフォルトモード</string>\n    <string name=\"settings_new_app_profile_normal\">ROOTなし</string>\n    <string name=\"settings_new_app_profile_root\">ROOT</string>\n    <string name=\"settings_new_app_profile_exclude\">除外</string>\n    <string name=\"settings_hide_service\">FolkPatch Hide</string>\n    <string name=\"settings_hide_service_summary\">Bootloader Unlock 状態をある程度非表示にします。</string>\n    <!-- Umount Service -->\n    <string name=\"settings_umount_service\">Umount Service</string>\n    <string name=\"settings_umount_service_summary\">起動時に自動アンマウントするマウントポイントを構成します。</string>\n    <string name=\"umount_config_title\">アンマウントの構成</string>\n    <string name=\"umount_config_enabled\">アンマウントを有効化</string>\n    <string name=\"umount_config_enabled_summary\">起動時に指定したマウントポイントを自動でアンマウントします。</string>\n    <string name=\"umount_config_paths_label\">マウントポイントのパス (1 行に 1 つ)</string>\n    <string name=\"umount_config_paths_placeholder\">/system/vendor/app\\n/system/vendor/priv-app</string>\n    <string name=\"umount_config_paths_helper\">アンマウントするには、1 行につき 1 つのマウントポイントパスを入力</string>\n    <string name=\"umount_config_save\">保存</string>\n    <string name=\"umount_config_save_confirm\">構成を保存を確認しますか？</string>\n    <string name=\"umount_config_save_success\">構成の保存に成功しました。</string>\n    <string name=\"umount_config_save_failed\">構成の保存に失敗しました。</string>\n    <string name=\"settings_app_title\">アプリのタイトル</string>\n    <string name=\"app_title_custom\">カスタム</string>\n    <string name=\"settings_custom_app_title\">アプリ名を設定</string>\n    <string name=\"custom_app_title_dialog_title\">カスタムアプリ名</string>\n    <string name=\"custom_app_title_dialog_hint\">アプリ名を入力</string>\n    <string name=\"custom_app_title_dialog_confirm\">決定</string>\n    <string name=\"custom_app_title_dialog_empty\">名前を入力してください</string>\n    <string name=\"cancel\">キャンセル</string>\n    <string name=\"settings_custom_background\">カスタム背景</string>\n    <string name=\"settings_custom_background_enabled\">カスタム背景が有効化されています</string>\n    <string name=\"settings_custom_background_opacity\">カードの透過度</string>\n    <string name=\"settings_custom_background_blur\">背景のぼかし</string>\n    <string name=\"settings_custom_background_dim\">背景を暗転させる</string>\n    <string name=\"settings_custom_background_dual_dim\">日中/夜間のデュアル調光を有効化する</string>\n    <string name=\"settings_custom_background_dual_dim_desc\">ライトテーマとダークテーマに合わせて背景の暗さを自動調整</string>\n    <string name=\"settings_custom_background_day_dim\">日中時に背景を暗転</string>\n    <string name=\"settings_custom_background_night_dim\">夜間時に背景を暗転</string>\n    <string name=\"settings_multi_background_mode\">マルチ背景モード</string>\n    <string name=\"settings_multi_background_mode_summary\">ページごとの背景画像を設定します。</string>\n    <string name=\"settings_select_home_background\">ホームページの背景</string>\n    <string name=\"settings_select_kernel_background\">カーネルモジュールの背景</string>\n    <string name=\"settings_select_superuser_background\">スーパーユーザーの背景</string>\n    <string name=\"settings_select_system_module_background\">システムモジュールの背景</string>\n    <string name=\"settings_select_settings_background\">設定の背景</string>\n    <string name=\"settings_advanced_title_style\">高度なタイトルスタイル</string>\n    <string name=\"settings_advanced_title_style_summary\">トップバータイトルをカスタム画像に置き換えます。</string>\n    <string name=\"settings_advanced_title_style_enabled\">高度なタイトルスタイルが有効化されています</string>\n    <string name=\"settings_select_title_image\">タイトル画像を選択</string>\n    <string name=\"settings_title_image_selected\">タイトル画像が選択されました。</string>\n    <string name=\"settings_title_image_saved\">タイトル画像の保存に成功しました。</string>\n    <string name=\"settings_title_image_error\">タイトル画像の保存に失敗しました。</string>\n    <string name=\"settings_clear_title_image\">タイトル画像を消去</string>\n    <string name=\"settings_clear_title_image_confirm\">このタイトル画像を消去してもよろしいですか？</string>\n    <string name=\"settings_title_image_cleared\">タイトル画像を消去しました。</string>\n    <string name=\"settings_title_image_day_opacity\">日中モードの不透明度</string>\n    <string name=\"settings_title_image_night_opacity\">夜間モードの不透明度</string>\n    <string name=\"settings_title_image_dim\">背景を暗くする</string>\n    <string name=\"settings_title_image_offset_x\">水平の位置</string>\n    <!-- Font Settings -->\n    <string name=\"settings_custom_font\">カスタムフォント</string>\n    <string name=\"settings_select_font_file\">フォントファイルを選択</string>\n    <string name=\"settings_custom_font_enabled\">カスタムフォントが有効化されています</string>\n    <string name=\"settings_custom_font_summary\">アプリにカスタム TTF フォントを使用します。</string>\n    <string name=\"settings_font_selected\">カスタムフォントが選択されました。</string>\n    <string name=\"settings_custom_font_error\">フォントファイルの保存に失敗しました。</string>\n    <string name=\"settings_custom_font_saved\">カスタムフォントの保存に成功しました。</string>\n    <string name=\"settings_clear_font\">デフォルトのフォントに復元</string>\n    <string name=\"settings_clear_font_confirm\">デフォルトのフォントに復元しますか？</string>\n    <string name=\"settings_font_cleared\">デフォルトのフォントに復元しました。</string>\n    <string name=\"settings_font_select_hint\">TTF フォントファイルを選択</string>\n    <string name=\"settings_select_background_image\">背景画像を選択</string>\n    <string name=\"settings_custom_background_summary\">カスタム背景画像を設定します</string>\n    <string name=\"settings_custom_background_saved\">背景の保存が成功しました。</string>\n    <string name=\"settings_custom_background_error\">背景の保存に失敗しました。</string>\n    <string name=\"settings_background_selected\">背景が選択されました。</string>\n    <string name=\"settings_background_image_cleared\">背景画像を消去しました</string>\n    <string name=\"settings_clear_background\">背景を消去</string>\n    <string name=\"settings_clear_background_confirm\">背景画像を消去してもよろしいですか？</string>\n    <string name=\"settings_alt_icon\">代替アイコン</string>\n    <string name=\"alt_icon_summary\">代替アイコンをランチャーに使用します。</string>\n    <!-- Video Background Settings -->\n    <string name=\"settings_video_background\">動画の背景</string>\n    <string name=\"settings_video_background_summary\">動画を背景として使用します。</string>\n    <string name=\"settings_select_video\">動画を選択</string>\n    <string name=\"settings_video_selected\">動画が選択されました。</string>\n    <string name=\"settings_clear_video_background\">動画の背景を消去</string>\n    <string name=\"settings_clear_video_background_confirm\">動画の背景を消去してもよろしいですか？</string>\n    <string name=\"settings_video_background_enabled\">動画の背景が有効化されています</string>\n    <string name=\"settings_video_volume\">動画の音量</string>\n    <!-- KPM AutoLoad Strings -->\n    <string name=\"kpm_autoload_title\">KPM を自動で読み込み</string>\n    <string name=\"kpm_autoload_enabled\">自動で読み込みが有効化されています</string>\n    <string name=\"kpm_autoload_enabled_summary\">端末の起動時に KP モジュールを自動で読み込みます。</string>\n    <string name=\"kpm_autoload_json_config\">JSON を構成</string>\n    <string name=\"kpm_autoload_json_label\">JSON を構成します</string>\n    <string name=\"kpm_autoload_json_placeholder\">{\\n  \\\"enabled\\\": true,\\n  \\\"kpmPaths\\\": [\\n    \\\"/path/to/module1.kpm\\\",\\n    \\\"/path/to/module2.kpm\\\"\\n  ]\\n}</string>\n    <string name=\"kpm_autoload_json_error\">無効な JSON 形式</string>\n    <string name=\"kpm_autoload_json_helper\">KPM ファイルパスの配列を含む有効な JSON ファイルを入力してください</string>\n    <string name=\"kpm_autoload_save\">構成を保存</string>\n    <string name=\"kpm_autoload_save_confirm\">自動で読み込みの構成を保存しますか？</string>\n    <string name=\"kpm_autoload_save_success\">構成の保存が成功しました。</string>\n    <string name=\"kpm_autoload_save_failed\">構成の保存が失敗しました。</string>\n    <string name=\"kpm_autoload_visual_mode\">視覚モード</string>\n    <string name=\"kpm_autoload_json_mode\">JSON モード</string>\n    <string name=\"kpm_autoload_add_kpm\">KPM を追加</string>\n    <string name=\"kpm_autoload_remove_kpm\">削除</string>\n    <string name=\"kpm_autoload_edit_kpm\">編集</string>\n    <string name=\"kpm_autoload_edit_dialog_title\">KPM を編集</string>    <string name=\"kpm_autoload_event_label\">イベント:</string>    <string name=\"kpm_autoload_args_label\">引数:</string>    <string name=\"kpm_autoload_event_service\">service</string>    <string name=\"kpm_autoload_event_post_fs_data\">post-fs-data</string>\n    <!-- Module Backup/Restore Strings -->\n    <string name=\"apm_backup_title\">モジュールをバックアップ</string>\n    <string name=\"apm_restore_title\">モジュールを復元</string>\n    <string name=\"apm_backup_success\">バックアップが成功しました。</string>\n    <string name=\"apm_restore_success\">復元が成功しました。</string>\n    <string name=\"apm_backup_failed\">バックアップに失敗しました。</string>\n    <string name=\"apm_restore_failed\">復元に失敗しました。</string>\n    <string name=\"apm_backup_failed_msg\">バックアップに失敗: %s</string>\n    <string name=\"apm_restore_failed_msg\">復元に失敗: %s</string>\n    <string name=\"apm_copy_list_title\">リストをコピー</string>\n    <string name=\"apm_copy_list_success\">モジュールのリストをコピーしました。</string>\n    <!-- APM Bulk Install Strings -->\n    <string name=\"apm_bulk_install_title\">システムモジュールを一括インストール</string>\n    <string name=\"apm_bulk_install_list_title\">モジュールのリスト</string>\n    <string name=\"apm_bulk_install_add\">モジュールを追加</string>\n    <string name=\"apm_bulk_install_empty\">モジュールが追加されていません</string>\n    <string name=\"apm_bulk_install_action\">一括インストール</string>\n    <string name=\"apm_bulk_install_log_title\">一括インストールのログ</string>\n    <string name=\"apm_bulk_install_log_start\">一括インストールを開始中...</string>\n    <string name=\"apm_bulk_install_log_installing\">「%1$s」をインストール中</string>\n    <string name=\"apm_batch_install_full_process\">一括インストールをフルプロセスで実行する</string>\n    <string name=\"apm_batch_install_full_process_summary\">システムモジュールの一括インストールでフルプロセスを使用して実行します。</string>\n    <string name=\"next_module\">次のモジュール</string>\n    <string name=\"apm_bulk_install_log_installed\">「%s」をインストールしました。</string>\n    <string name=\"apm_bulk_install_log_done\">すべての操作が完了しました。</string>\n    <string name=\"apm_bulk_install_first_use_text\">この機能を使用すると複数のモジュールを一度にインストールできます。これは、インストール時にボタン操作を必要としないモジュールに適したクイックインストール方法です。音量ボタンの操作が必要なモジュールの場合は、設定でフルプロセスインストールモードを有効化してください。</string>\n    <string name=\"apm_bulk_install_remove\">削除</string>\n    <string name=\"apm_first_use_title\">システムモジュールへようこそ</string>\n    <string name=\"apm_first_use_text\">システムモジュールへようこそ。Magisk エコシステムとの互換性のあるモジュールを使用しています。右下のボタンをタップしてモジュールをインストールしてください。また、上部のインストーラーを使用してモジュールを一括でインストールすることもできます。ワンタップですべてのモジュールをバックアップする機能も提供されていますが、このソリューションはすべてのモジュールに適しているわけではないため、ご自身でバックアップを作成してください。</string>\n    <string name=\"kpm_autoload_kpm_list_title\">KP モジュール</string>\n    <string name=\"kpm_autoload_no_kpm_added\">KP モジュールが追加されていません</string>\n    <string name=\"kpm_autoload_kpm_path\">KPM のパス</string>\n    <string name=\"kpm_autoload_file_not_found\">ファイルがありません</string>\n    <string name=\"kpm_autoload_select_kpm_file\">KPM ファイルを選択</string>\n    <string name=\"kpm_autoload_first_time_title\">KPM の自動読み込みについて</string>\n    <string name=\"kpm_autoload_first_time_message\">この機能を使用すると、設定済みのすべての KPM を起動時に自動で一時的に読み込めます。この方法は、カーネルに直接埋め込むよりも便利です。\\n\\n例えば、パーティションの変更を禁止する KPM は、埋め込みによって起動に失敗する可能性があるため、一時的にしか使用できません。または、boot パーティションを変更したくない場合は、この設定を使用してモジュールを迅速に読み込めます。\\n\\nコマンドを実行するには、アプリを完全に閉じて再起動してください。起動時で通常に読み込まれます。マネージャーの画面が一時的に暗転するのは正常です！これは純粋なバックグラウンドでの読み込み方式を使用しているため、手動で下にスワイプして更新してモジュールが正しく読み込まれているか確認してください！</string>\n    <string name=\"kpm_autoload_first_time_confirm\">理解しました</string>\n    <string name=\"kpm_autoload_do_not_show_again\">再度表示しない</string>\n    <!-- Hide Settings Strings -->\n    <string name=\"home_hide_su_path\">su 実行可能パスを非表示</string>\n    <string name=\"home_hide_kpatch_version\">Kernel Patch バージョンを非表示</string>\n    <string name=\"home_hide_fingerprint\">フィンガープリントを非表示</string>\n    <string name=\"home_hide_zygisk\">Zygisk の実装を非表示</string>\n    <string name=\"home_hide_mount\">マウントの実装を非表示</string>\n    <string name=\"home_hide_su_path_summary\">su 実行可能パス情報のカードを非表示にします。</string>\n    <string name=\"home_hide_kpatch_version_summary\">Kernel Patch バージョン情報のカードを非表示にします。</string>\n    <string name=\"home_hide_fingerprint_summary\">フィンガープリント情報のカードを非表示にします。</string>\n    <string name=\"home_hide_zygisk_summary\">Zygisk の実装情報のカードを非表示</string>\n    <string name=\"home_hide_mount_summary\">マウントの実装情報のカードを非表示</string>\n    <string name=\"kpm_page_first_time_title\">警告</string>\n    <string name=\"kpm_page_first_time_message\">カーネルモジュールは boot 実装を直接的に変更します。システムモジュールとは異なり、優れた復旧メカニズムが備わっていないため fastboot を起動して修正するしか方法がありません。boot に埋め込む前に、モジュールを読み込んで問題がないことを確認しておくことを推奨します。読み込まないと使用できないモジュールの場合は、KPM の自動読み込み機能を試すことができます。カーネルモジュールについてよく理解していない場合は、この機能を使用しないでください！</string>\n    <!-- Auto Backup Strings -->\n    <string name=\"settings_auto_backup_module\">システムモジュールを自動でバックアップ</string>\n    <string name=\"settings_auto_backup_module_summary\">インストール時にモジュールファイルを自動でプライベートディレクトリにバックアップします。</string>\n    <string name=\"settings_open_backup_dir\">バックアップディレクトリを開く</string>\n    <string name=\"backup_dir_empty\">バックアップディレクトリは空です</string>\n    <string name=\"backup_dir_open_failed\">バックアップディレクトリの展開に失敗しました。</string>\n    <string name=\"auto_backup_failed\">自動バックアップが失敗: %s</string>\n    <string name=\"auto_backup_success\">自動バックアップが成功: %s</string>\n    <string name=\"settings_auto_backup_boot\">boot を自動でバックアップ</string>\n    <string name=\"settings_auto_backup_boot_summary\">boot をローカルストレージに自動でバックアップします (Downloads/FolkPatch/BootBackups)。</string>\n    <!-- Home Layout Strings -->\n    <string name=\"settings_home_layout_style\">ホームレイアウトのスタイル</string>\n    <string name=\"settings_home_layout_default\">リスト UI</string>\n    <string name=\"settings_home_layout_grid\">グリッド UI</string>\n    <string name=\"settings_home_layout_focus\">フォーカス UI</string>\n    <string name=\"settings_home_layout_sign\">サイン UI</string>\n    <string name=\"settings_home_layout_circle\">サークル UI</string>\n    <string name=\"settings_home_layout_dashboard_pro\">ダッシュボード UI</string>\n    <string name=\"unofficial_version_title\">非公式なバージョン</string>\n    <string name=\"unofficial_version_message\">非公式な FolkPatch を使用しています。公式のアプリをダウンロードしてください！</string>\n    <string name=\"go_to_github\">GitHub を開く</string>\n    <string name=\"settings_save_theme\">テーマを保存</string>\n    <string name=\"settings_import_theme\">テーマをインポート</string>\n    <string name=\"settings_theme_saved\">テーマを保存しました。</string>\n    <string name=\"settings_theme_imported\">テーマをインポートしました。</string>\n    <string name=\"settings_theme_save_failed\">テーマの保存に失敗しました。</string>\n    <string name=\"settings_theme_import_failed\">テーマのインポートに失敗しました。</string>\n    <string name=\"settings_reset_theme\">テーマをリセット</string>\n    <string name=\"settings_reset_theme_confirm\">すべてのテーマ設定をデフォルトにリセットしてもよろしいですか？すべてのカスタム背景、フォント、音楽、サウンド効果が消去されます。</string>\n    <string name=\"settings_theme_reset\">テーマをデフォルトにリセット</string>\n    <string name=\"settings_theme_reset_failed\">テーマのリセットに失敗しました。</string>\n    <!-- Theme Strings -->\n    <string name=\"theme_export_title\">テーマをエクスポート</string>\n    <string name=\"theme_import_title\">テーマをインポート</string>\n    <string name=\"theme_name\">テーマ名</string>\n    <string name=\"theme_type\">テーマタイプ</string>\n    <string name=\"theme_type_phone\">スマートフォン</string>\n    <string name=\"theme_type_tablet\">タブレット</string>\n    <string name=\"theme_version\">バージョン</string>\n    <string name=\"theme_author\">作者</string>\n    <string name=\"theme_description\">説明</string>\n    <string name=\"theme_export_action\">エクスポート</string>\n    <string name=\"theme_import_action\">インポート</string>\n    <string name=\"theme_import_confirm\">このテーマをインポートしてもよろしいですか？</string>\n    <string name=\"theme_info\">テーマ情報</string>\n    <!-- Grid Working Card Background -->\n    <string name=\"settings_grid_working_card_background\">動作中カードの背景</string>\n    <string name=\"settings_grid_working_card_background_enabled\">カスタムカード背景が有効化されています</string>\n    <string name=\"settings_grid_working_card_background_summary\">動作中カードのカスタム背景画像です。</string>\n    <string name=\"settings_grid_working_card_background_selected\">背景が選択されました。</string>\n    <string name=\"settings_grid_working_card_dual_opacity\">日中/夜間でのデュアル透過を有効化する</string>\n    <string name=\"settings_grid_working_card_dual_opacity_desc\">ライトテーマとダークテーマに合わせてカードの透明度を自動調整</string>\n    <string name=\"settings_grid_working_card_day_opacity\">日中モードのカード透過</string>\n    <string name=\"settings_grid_working_card_night_opacity\">夜間モードのカード透過</string>\n    <string name=\"settings_clear_grid_working_card_background\">カードの背景を消去する</string>\n    <string name=\"settings_clear_grid_working_card_background_confirm\">カードの背景画像を消去してもよろしいですか？</string>\n    <string name=\"settings_grid_working_card_background_saved\">カードの背景の保存に成功しました。</string>\n    <string name=\"settings_grid_working_card_background_error\">カードの背景の保存に失敗しました。</string>\n    <string name=\"settings_grid_working_card_background_cleared\">カードの背景を消去しました。</string>\n    <!-- Biometric Strings -->\n    <string name=\"action_biometric\">生体認証</string>\n    <string name=\"msg_biometric\">生体認証で確認してください</string>\n    <string name=\"settings_biometric_login\">生体認証</string>\n    <string name=\"settings_biometric_login_summary\">アプリを開くときに生体認証を要求します。</string>\n    <string name=\"settings_strong_biometric\">強力な生体認証</string>\n    <string name=\"settings_strong_biometric_summary\">モジュールのインストール/アンインストール/無効化で認証を要求します。</string>\n    <!-- Theme Store Strings -->\n    <string name=\"theme_store_title\">テーマストア</string>\n    <string name=\"theme_source_official\">公式</string>\n    <string name=\"theme_source_third_party\">サードパーティー</string>\n    <string name=\"theme_source_local\">ローカル</string>\n    <string name=\"theme_store_author\">作者: %s</string>\n    <string name=\"theme_store_version\">バージョン: %s</string>\n    <string name=\"theme_source\">ソース</string>\n    <string name=\"theme_store_download_failed\">ダウンロードに失敗しました。</string>\n    <string name=\"theme_store_download\">ダウンロード</string>\n    <string name=\"theme_store_search_hint\">テーマを検索...</string>\n    <string name=\"search_modules\">モジュールを検索...</string>\n    <string name=\"search_scripts\">スクリプトを検索...</string>\n    <!-- Update Check Strings -->\n    <string name=\"settings_auto_update_check\">自動で更新を確認する</string>\n    <string name=\"settings_auto_update_check_summary\">アプリの開始時に更新を自動で確認します。</string>\n    <string name=\"settings_check_update\">更新を確認</string>\n    <string name=\"update_available_title\">更新が利用可能です</string>\n    <string name=\"update_available_message\">バージョンが低すぎることが検出されました。新しいバージョンをダウンロードしますか？</string>\n    <string name=\"update_action\">更新</string>\n    <string name=\"update_close\">閉じる</string>\n    <string name=\"update_latest\">最新のバージョンを使用しています</string>\n    <string name=\"update_error\">更新の確認でエラーが発生しました。</string>\n    <string name=\"settings_category_general\">一般</string>\n    <string name=\"settings_app_list_loading_scheme\">アプリリストの読み込み方式</string>\n    <string name=\"settings_app_list_loading_scheme_summary\">アプリリストの読み込み方式を選択します。</string>\n    <string name=\"app_list_loading_scheme_root_service\">root サービス (デフォルト)</string>\n    <string name=\"app_list_loading_scheme_package_manager\">パッケージマネージャー (システム API)</string>\n    <string name=\"app_list_loading_scheme_dialog_title\">スキームを読み込み中</string>\n    <string name=\"superuser\">スーパーユーザー</string>\n    <string name=\"module\">モジュール</string>\n    <string name=\"settings_category_appearance\">外観</string>\n    <string name=\"settings_appearance_font\">フォント設定</string>\n    <string name=\"settings_appearance_font_summary\">カスタムフォントの設定</string>\n    <string name=\"settings_appearance_theme\">テーマ設定</string>\n    <string name=\"settings_appearance_theme_summary\">テーマストア、保存、インポート、リセット</string>\n    <string name=\"settings_appearance_banner\">バナー設定</string>\n    <string name=\"settings_appearance_banner_summary\">モジュールバナーの設定</string>\n    <string name=\"settings_appearance_layout\">レイアウト設定</string>\n    <string name=\"settings_appearance_layout_summary\">ホームレイアウト、ナビゲーション、カードのカスタマイズ</string>\n    <string name=\"settings_appearance_background\">背景設定</string>\n    <string name=\"settings_appearance_background_summary\">カスタム背景、動画、マルチ背景</string>\n    <string name=\"settings_appearance_night_mode\">ナイトモード設定</string>\n    <string name=\"settings_appearance_night_mode_summary\">ダークテーマと色設定</string>\n    <string name=\"settings_amoled_theme\">AMOLEDブラックテーマ</string>\n    <string name=\"settings_amoled_theme_desc\">ダークモードで完全な黒い背景を使用</string>\n    <string name=\"settings_switch_icon\">ボタンインジケーターを有効化</string>\n    <string name=\"settings_switch_icon_desc\">スイッチボタンにステータスアイコンを表示</string>\n    <string name=\"settings_category_behavior\">動作</string>\n    <string name=\"settings_category_function\">機能</string>\n    <string name=\"settings_category_module\">モジュール</string>\n    <string name=\"settings_category_security\">セキュリティ</string>\n    <!-- Background Music Strings -->\n    <string name=\"settings_background_music\">BGM</string>\n    <string name=\"settings_background_music_summary\">アプリがフォアグラウンド時に BGM を再生します。</string>\n    <string name=\"settings_background_music_playing\">再生中: %s</string>\n    <string name=\"settings_background_music_enabled\">BGM が有効化されています</string>\n    <string name=\"settings_select_music_file\">音楽ファイルを選択</string>\n    <string name=\"settings_music_selected\">音楽が選択されました。</string>\n    <string name=\"settings_clear_music\">音楽を消去</string>\n    <string name=\"settings_clear_music_confirm\">BGM を消去しますか？</string>\n    <string name=\"settings_music_auto_play\">自動再生</string>\n    <string name=\"settings_music_auto_play_summary\">アプリを開いたときに音楽を自動再生します。</string>\n    <string name=\"settings_music_looping\">ループ再生</string>\n    <string name=\"settings_music_looping_summary\">現在の曲をループ再生します。</string>\n    <string name=\"settings_music_volume\">音量</string>\n    <string name=\"settings_music_saved\">音楽の保存に成功しました。</string>\n    <string name=\"settings_music_save_error\">音楽の保存に失敗しました。</string>\n    <string name=\"settings_music_cleared\">音楽を消去しました。</string>\n    <string name=\"settings_category_multimedia\">マルチメディア</string>\n    <string name=\"settings_category_general_summary\">言語、アップデート、SELinux、システム調整</string>\n    <string name=\"settings_category_appearance_summary\">テーマ、色、レイアウト、背景、フォント</string>\n    <string name=\"settings_category_behavior_summary\">Webデバッグ、インストール動作、ホーム表示</string>\n    <string name=\"settings_category_security_summary\">生体認証、スーパーキー管理</string>\n    <string name=\"settings_category_backup_summary\">ローカルバックアップ、クラウドバックアップ、WebDAV</string>\n    <string name=\"settings_category_module_summary\">モジュール情報、並べ替え、一括インストール</string>\n    <string name=\"settings_category_function_summary\">FolkPatch非表示、Umountサービス</string>\n    <string name=\"settings_category_multimedia_summary\">BGM、効果音、バイブレーション</string>\n    <string name=\"settings_use_legacy_su_page\">単一ページのスーパーユーザー認証</string>\n    <string name=\"settings_use_legacy_su_page_summary\">スーパーユーザーページを単一ページな認証に切り替えます。</string>\n    <string name=\"settings_music_playback_control\">再生のコントロール</string>\n    <!-- Theme Store Filter -->\n    <string name=\"theme_store_filter_title\">テーマを絞り込む</string>\n    <string name=\"theme_store_filter_author\">作者</string>\n    <string name=\"theme_store_filter_author_hint\">作者の名前を入力</string>\n    <string name=\"theme_store_filter_source\">ソース</string>\n    <string name=\"theme_store_filter_source_all\">すべて</string>\n    <string name=\"theme_store_filter_type\">デバイスタイプ</string>\n    <string name=\"theme_store_filter_apply\">適用</string>\n    <string name=\"theme_store_filter_reset\">リセット</string>\n    <!-- My Themes Page -->\n    <string name=\"my_themes_title\">マイテーマ</string>\n    <string name=\"my_themes_empty\">テーマはまだありません</string>\n    <string name=\"my_themes_empty_action\">テーマストアを参照</string>\n    <string name=\"my_themes_apply\">テーマを適用</string>\n    <string name=\"my_themes_delete\">テーマを削除</string>\n    <string name=\"my_themes_delete_confirm\">このテーマを削除してもよろしいですか？</string>\n    <string name=\"my_themes_deleted\">テーマを削除しました。</string>\n    <string name=\"my_themes_applied\">テーマを適用しました。</string>\n    <string name=\"my_themes_apply_failed\">テーマの適用に失敗しました。</string>\n    <string name=\"my_themes_details\">テーマの詳細</string>\n    <!-- Download Dialog -->\n    <string name=\"theme_download_title\">テーマをダウンロード中</string>\n    <string name=\"theme_download_completed\">ダウンロード完了</string>\n    <string name=\"theme_download_failed\">ダウンロード失敗</string>\n    <string name=\"theme_download_progress\">進捗</string>\n    <string name=\"theme_download_file\">テーマファイル</string>\n    <string name=\"theme_download_image\">画像のプレビュー</string>\n    <string name=\"theme_download_cancel\">キャンセル</string>\n    <string name=\"theme_download_pause\">一時停止</string>\n    <string name=\"theme_download_resume\">再開</string>\n    <string name=\"theme_download_retry\">再試行</string>\n    <string name=\"theme_download_apply\">テーマを適用</string>\n    <string name=\"theme_download_go_to_my_themes\">マイテーマ</string>\n    <string name=\"theme_download_retrying\">再試行中 (%d/3)...</string>\n    <string name=\"theme_download_preparing\">ダウンロードを準備中...</string>\n    <string name=\"theme_download_downloading_file\">テーマファイルをダウンロード中...</string>\n    <string name=\"theme_download_downloading_image\">プレビュー画像をダウンロード中...</string>\n    <string name=\"theme_download_finalizing\">ファイナライズ中...</string>\n    <!-- File Picker -->\n    <string name=\"file_picker_permission_required\">権限が必要です</string>\n    <string name=\"file_picker_permission_desc\">ファイルを参照するには「すべてのファイルへのアクセス」の権限を許可してください。</string>\n    <string name=\"file_picker_grant_permission\">権限を許可</string>\n    <string name=\"file_picker_cancel\">キャンセル</string>\n    <string name=\"file_picker_internal_storage\">内部ストレージ</string>\n    <string name=\"file_picker_no_files\">ファイルなし</string>\n    <!-- HomeV3 Strings -->\n    <string name=\"home_device_status_title\">デバイスのステータス</string>\n    <string name=\"home_device_status_battery_temp\">バッテリー温度</string>\n    <string name=\"home_device_status_cpu_load\">CPU の負荷</string>\n    <string name=\"home_device_status_battery_level\">バッテリーレベル</string>\n    <string name=\"home_storage_title\">ストレージ</string>\n    <string name=\"home_storage_internal\">内部ストレージ</string>\n    <string name=\"home_storage_ram\">RAM</string>\n    <string name=\"home_storage_zram\">ZRAM</string>\n    <string name=\"home_storage_swap\">スワップファイル</string>\n    <string name=\"home_info_kernel\">カーネル</string>\n    <string name=\"home_info_superkey\">SuperKey</string>\n    <string name=\"home_info_auth_auth\">認証</string>\n    <string name=\"home_info_auth_na\">N/A</string>\n    <string name=\"home_info_device_slot\">デバイススロット</string>\n    <string name=\"home_info_device_model\">デバイスモデル</string>\n    <string name=\"home_info_running_mode\">実行モード</string>\n    <string name=\"home_info_mode_full\">完全</string>\n    <string name=\"home_info_mode_half\">一部</string>\n    <string name=\"home_zygisk_implement\">Zygisk の実装</string>\n    <string name=\"home_mount_implement\">マウントの実装</string>\n    <string name=\"settings_list_card_hide_status_badge\">クラシックの絵文字を使用する</string>\n    <string name=\"settings_list_card_hide_status_badge_summary\">ホームのステータスバッジにクラシックの絵文字を使用します。</string>\n    <string name=\"settings_custom_badge_text\">カスタムバッジテキスト</string>\n    <string name=\"settings_custom_badge_text_summary\">バッジのテキスト表示を変更します。</string>\n    <string name=\"settings_custom_badge_text_full_half\">完全/一部 (デフォルト)</string>\n    <string name=\"settings_custom_badge_text_lkm\">LKM</string>\n    <string name=\"settings_custom_badge_text_gki\">GKI</string>\n    <string name=\"settings_custom_badge_text_n_gki\">非 GKI</string>\n    <string name=\"settings_custom_badge_text_oki\">OKI</string>\n    <string name=\"settings_custom_badge_text_built_in\">内蔵</string>\n    <!-- Install Mode Select -->\n    <string name=\"kp_install_methods\">KernelPatch をパッチ/インストール</string>\n    <string name=\"restore_boot_methods\">boot パーティションを復元する boot を選択してください</string>\n    <string name=\"su_backup_list\">リストをバックアップ</string>\n    <string name=\"su_restore_list\">リストを復元</string>\n    <string name=\"backup_success\">バックアップが成功しました。</string>\n    <string name=\"restore_success\">復元が成功しました。</string>\n    <!-- SuperUser App Action Dialog -->\n    <string name=\"su_app_action_title\">アプリのアクション</string>\n    <string name=\"su_app_action_content\">アプリで実行するアクションを選択してください</string>\n    <string name=\"su_app_action_launch\">アプリを起動</string>\n    <string name=\"su_app_action_force_stop\">強制停止</string>\n    <string name=\"su_app_action_launch_success\">「%s」を起動中</string>\n    <string name=\"su_app_action_force_stop_success\">「%s」を強制停止しました。</string>\n    <string name=\"su_app_action_failed\">操作に失敗: %s</string>\n\n    <!-- SU Audit Log -->\n    <string name=\"su_audit_log_title\">認証ログ</string>\n    <string name=\"su_audit_log_empty\">認証記録がありません</string>\n    <string name=\"su_audit_log_clear\">ログを消去</string>\n    <string name=\"su_audit_log_clear_confirm\">すべての認証記録を消去しますか？</string>\n    <string name=\"su_audit_action_grant\">許可済み</string>\n    <string name=\"su_audit_action_revoke\">取り消し済み</string>\n    <string name=\"su_audit_action_exclude\">除外済み</string>\n    <string name=\"su_audit_tab_usage\">使用履歴</string>\n    <string name=\"su_audit_tab_operations\">操作履歴</string>\n\n    <string name=\"patch_output_written_to\">出力ファイル先</string>\n    <string name=\"patch_write_failed\">パッチ済みの boot.img のフラッシュに失敗しました。</string>\n    <!-- Badge Count Settings -->\n    <string name=\"enable_badge_count\">バッジカウントの設定</string>\n    <string name=\"enable_badge_count_summary\">ナビゲーション項目のバッジカウント表示を設定します。</string>\n    <string name=\"badge_superuser\">スーパーユーザーバッジを表示</string>\n    <string name=\"badge_apm\">システムモジュールバッジを表示</string>\n    <string name=\"badge_kernel\">カーネルモジュールバッジを表示</string>\n    <string name=\"settings_sound_effect\">サウンド効果</string>\n    <string name=\"settings_sound_effect_summary\">タップ時にサウンド効果を再生します。</string>\n    <string name=\"settings_sound_effect_enabled\">有効</string>\n    <string name=\"settings_sound_effect_playing\">選択済み: %s</string>\n    <string name=\"settings_sound_effect_source\">サウンドソース</string>\n    <string name=\"settings_sound_effect_source_local\">ローカルファイル</string>\n    <string name=\"settings_sound_effect_source_preset\">プリセット</string>\n    <string name=\"settings_sound_effect_preset_title\">プリセットのサウンド</string>\n    <string name=\"settings_select_sound_effect\">サウンド効果を選択</string>\n    <string name=\"settings_sound_effect_selected\">サウンド効果が選択されました。</string>\n    <string name=\"settings_sound_effect_scope\">効果のスコープ</string>\n    <string name=\"settings_sound_effect_scope_global\">グローバル (常に再生)</string>\n    <string name=\"settings_sound_effect_scope_bottom_bar\">ボトムバーのみ</string>\n    <string name=\"settings_clear_sound_effect\">サウンド効果を消去</string>\n    <string name=\"settings_clear_sound_effect_confirm\">サウンド効果を消去してもよろしいですか？</string>\n    <string name=\"settings_sound_effect_cleared\">サウンド効果を消去しました。</string>\n    <string name=\"settings_startup_sound\">起動時のサウンド</string>\n    <string name=\"settings_startup_sound_summary\">アプリの起動時にサウンドを再生します。</string>\n    <string name=\"settings_startup_sound_enabled\">起動時のサウンドが有効化されています</string>\n    <string name=\"settings_startup_sound_playing\">再生中: %s</string>\n    <string name=\"settings_select_startup_sound\">起動時のサウンドを選択</string>\n    <string name=\"settings_startup_sound_selected\">起動時のサウンドが選択されました。</string>\n    <string name=\"settings_clear_startup_sound\">起動時のサウンドを消去</string>\n    <string name=\"settings_clear_startup_sound_confirm\">起動時のサウンドを消去してもよろしいですか？</string>\n    <string name=\"settings_startup_sound_cleared\">起動時のサウンドを消去しました。</string>\n    <string name=\"settings_vibration\">振動フィードバック</string>\n    <string name=\"settings_vibration_summary\">タッチイベント時に振動させます。</string>\n    <string name=\"settings_vibration_enabled\">振動を有効化</string>\n    <string name=\"settings_vibration_intensity\">振動の強さ</string>\n    <string name=\"settings_vibration_scope\">振動スコープ</string>\n    <string name=\"settings_vibration_scope_global\">グローバル (常に)</string>\n    <string name=\"settings_vibration_scope_bottom_bar\">ボトムバーのみ</string>\n    <!-- Backup Settings -->\n    <string name=\"settings_category_backup\">バックアップ</string>\n    <string name=\"settings_enable_cloud_backup\">クラウドバックアップを有効化する</string>\n    <string name=\"settings_enable_cloud_backup_summary\">フラッシュされたモジュールをクラウドサービスに自動でバックアップします。</string>\n    <string name=\"settings_configure_webdav\">WebDAV サービスを構成</string>\n    <string name=\"webdav_config_title\">WebDAV の構成</string>\n    <string name=\"webdav_url\">WebDAV の URL</string>\n    <string name=\"webdav_username\">ユーザー名</string>\n    <string name=\"webdav_password\">パスワード</string>\n    <string name=\"webdav_test_success\">テストが成功しました。</string>\n    <string name=\"webdav_test_failed\">テストに失敗: %s</string>\n    <string name=\"webdav_uploading\">WebDAV にアップロード中...</string>\n    <string name=\"webdav_backup_success\">WebDAV バックアップに成功しました。</string>\n    <string name=\"webdav_backup_failed\">WebDAV バックアップに失敗: %s</string>\n    <string name=\"save\">保存</string>\n    <string name=\"test\">テスト</string>\n    <string name=\"webdav_path_label\">パス (例:/Backup)</string>\n    <string name=\"webdav_view_logs\">ログを表示</string>\n    <string name=\"webdav_backup_logs_title\">ログをバックアップ</string>\n    <string name=\"webdav_no_logs\">ログは見つかりません</string>\n    <string name=\"webdav_clear_logs\">消去</string>\n    <string name=\"settings_enable_local_backup\">ローカルバックアップを有効化する</string>\n    <string name=\"settings_enable_local_backup_summary\">ローカルストレージにバックアップします (Downloads/FolkPatch/ModuleBackups)。</string>\n    <string name=\"close\">閉じる</string>\n    <string name=\"script_library\">スクリプトライブラリ</string>\n    <string name=\"script_library_title\">スクリプトライブラリ</string>\n    <string name=\"script_library_empty\">スクリプトがありません、タップで追加します</string>\n    <string name=\"script_library_add\">スクリプトを追加</string>\n    <string name=\"script_library_add_title\">スクリプトを追加</string>\n    <string name=\"script_library_select_file\">スクリプトファイルを選択</string>\n    <string name=\"script_library_alias\">エイリアス</string>\n    <string name=\"script_library_alias_hint\">スクリプトエイリアスを入力 (任意)</string>\n    <string name=\"script_library_run\">実行</string>\n    <string name=\"script_library_delete\">削除</string>\n    <string name=\"script_library_path\">パス: %s</string>\n    <string name=\"script_library_confirm_delete\">スクリプトを削除してもよろしいですか？</string>\n    <string name=\"script_library_delete_success\">スクリプトの削除に成功しました。</string>\n    <string name=\"script_library_delete_failed\">スクリプトの削除に失敗しました。</string>\n    <string name=\"script_library_add_success\">スクリプトを追加しました。</string>\n    <string name=\"script_library_add_failed\">スクリプトの追加に失敗: %s</string>\n    <string name=\"script_library_load_failed\">スクリプトライブラリの読み込みに失敗しました。</string>\n    <string name=\"script_library_output\">出力を実行</string>\n    <string name=\"script_library_no_output\">出力なし</string>\n    <string name=\"script_library_save_log\">ログを保存</string>\n    <string name=\"script_library_log_saved\">「%s」にログを保存しました。</string>\n    <string name=\"script_library_log_save_failed\">ログの保存に失敗: %s</string>\n    <string name=\"settings_predictive_back\">予測バックジェスチャー</string>\n    <string name=\"settings_predictive_back_summary\">Android 14+ の予測バックジェスチャーアニメーションを有効にする</string>\n\n    <string name=\"home_device_status_battery_charging\">充電中</string>\n    <string name=\"home_device_status_cpu_temp\">CPU 温度</string>\n    <string name=\"home_device_status_memory_trend\">メモリ推移</string>\n    <string name=\"home_storage_partitions\">ストレージパーティション</string>\n    <string name=\"home_device_status_cpu_freq\">CPU 周波数</string>\n    <string name=\"home_network_rx\">ダウンロード</string>\n    <string name=\"home_network_tx\">アップロード</string>\n    <string name=\"settings_home_layout_stats\">統計UI</string>\n    <string name=\"home_stats_more_options\">その他のオプション</string>\n    <string name=\"home_stats_kp_label\">KP</string>\n    <string name=\"home_stats_manager_label\">マネージャー</string>\n    <string name=\"home_device_status_gpu_load\">GPU</string>\n    <string name=\"home_stats_kernel_modules\">カーネルモジュール</string>\n    <string name=\"home_stats_apm_modules\">システムモジュール</string>\n    <string name=\"home_stats_superusers\">スーパーユーザー</string>\n    <string name=\"settings_kernel_spoof\">カーネル偽装設定</string>\n    <string name=\"settings_kernel_spoof_summary\">カーネルバージョンとビルド時間を偽装</string>\n    <string name=\"settings_kernel_spoof_version\">カーネルバージョン</string>\n    <string name=\"settings_kernel_spoof_build_time\">カーネルビルド時間</string>\n    <string name=\"settings_kernel_spoof_restore\">復元</string>\n\n    <string name=\"kernel_spoof_enabled\">カーネルスプーフィングが有効になりました</string>\n    <string name=\"kernel_spoof_disabled_restored\">カーネルスプーフィングが無効になり、復元されました</string>\n    <string name=\"kernel_spoof_failed\">カーネルスプーフィングに失敗しました: %d</string>\n    <string name=\"kernel_spoof_applied\">カーネルスプーフィングが適用されました</string>\n\n    <string name=\"settings_path_hide\">パス非表示</string>\n    <string name=\"settings_path_hide_summary\">カーネルレベルでアプリからファイルやディレクトリを隠す</string>\n    <string name=\"path_hide_paths_label\">非表示パス（1行に1つ）</string>\n\n    <string name=\"path_hide_paths_helper\">1行に1つのパスを入力してください。一致するパスは ENOENT を返します。</string>\n    <string name=\"path_hide_save\">保存</string>\n    <string name=\"path_hide_enabled\">パス非表示が有効になりました</string>\n    <string name=\"path_hide_disabled\">パス非表示が無効になりました</string>\n    <string name=\"path_hide_applied\">パス非表示設定が適用されました</string>\n    <string name=\"path_hide_failed\">パス非表示操作に失敗しました: %d</string>\n    <string name=\"path_hide_uid_mode\">UID 実行モード</string>\n    <string name=\"path_hide_uid_mode_summary\">指定したアプリの UID のみパスを非表示にします</string>\n    <string name=\"path_hide_uids_label\">対象 UID（1行に1つ）</string>\n    <string name=\"path_hide_uids_helper\">アプリの UID を入力してください。これらのアプリのみパスが非表示になります。</string>\n    <string name=\"path_hide_uid_save\">UID を保存</string>\n    <string name=\"path_hide_uid_mode_enabled\">UID 実行モードが有効になりました</string>\n    <string name=\"path_hide_uid_mode_disabled\">UID 実行モードが無効になりました</string>\n    <string name=\"path_hide_filter_system\">システム UID をフィルター</string>\n    <string name=\"path_hide_filter_system_summary\">root およびシステムプロセス（UID &lt; 10000）からもパスを隠す</string>\n    <string name=\"path_hide_filter_system_enabled\">システム UID フィルタリングが有効になりました</string>\n    <string name=\"path_hide_filter_system_disabled\">システム UID フィルタリングが無効になりました</string>\n    <string name=\"path_hide_filter_system_warning_title\">警告</string>\n    <string name=\"path_hide_filter_system_warning_message\">有効にすると、root およびシステムプロセス（UID &lt; 10000）からもパスが隠されます。一部のシステム機能が正常に動作しなくなる可能性があります。慎重に操作してください。</string>\n    <string name=\"path_hide_uid_applied\">UID ホワイトリストが適用されました</string>\n    <string name=\"path_hide_select_apps\">アプリを選択</string>\n    <string name=\"path_hide_no_apps_selected\">アプリが選択されていません</string>\n    <string name=\"path_hide_app_removed\">アンインストール済みのアプリ %d 件の設定を削除しました</string>\n    <string name=\"path_hide_search_apps\">アプリを検索…</string>\n    <string name=\"path_hide_show_system\">システムアプリを表示</string>\n    <string name=\"netisolate_title\">ネットワーク分離</string>\n    <string name=\"netisolate_enable\">ネットワーク分離が有効になりました</string>\n    <string name=\"netisolate_disable\">ネットワーク分離が無効になりました</string>\n    <string name=\"netisolate_enable_summary\">カーネルレベルで選択したアプリのネットワークアクセスをブロック</string>\n    <string name=\"netisolate_uids_label\">ブロック済みアプリ</string>\n    <string name=\"netisolate_uids_hint\">ブロックする UID を入力（1行に1つ）</string>\n    <string name=\"netisolate_no_uids\">ブロックされたアプリはありません</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-ko/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\" translatable=\"false\">FolkPatch</string>\n    <string name=\"app_name_fpatch\">FPatch</string>\n\n    <string name=\"desktop_app_name\">홈 화면 앱 이름</string>\n\n    <string name=\"home\">홈</string>\n\n    <string name=\"success\">성공</string>\n    <string name=\"failure\">실패</string>\n\n    <string name=\"patch_warnning\">설치에는 위험이 따릅니다. 데이터를 백업했는지 확인하세요.</string>\n    <string name=\"patch\">패치</string>\n\n    <string name=\"kernel_patch\">KernelPatch</string>\n    <string name=\"android_patch\">AndroidPatch</string>\n\n    <string name=\"settings_nav_layout_title\">내비게이션 레이아웃 설정</string>\n    <string name=\"settings_nav_layout_summary\">일부 내비게이션 구성 요소를 숨기거나 표시합니다.</string>\n    <string name=\"settings_nav_scheme\">내비게이션 구성</string>\n    <string name=\"settings_nav_mode\">내비게이션 바 모드</string>\n    <string name=\"settings_nav_mode_summary\">내비게이션 바 표시 방식을 선택하세요.</string>\n    <string name=\"settings_nav_mode_auto\">자동 전통 바</string>\n    <string name=\"settings_nav_mode_bottom\">항상 하단 바</string>\n    <string name=\"settings_nav_mode_rail\">항상 측면 레일</string>\n    <string name=\"settings_nav_mode_floating\">플로팅 바텀 바</string>\n    <string name=\"settings_show_apm\">시스템 모듈 표시</string>\n    <string name=\"settings_show_kpm\">커널 모듈 표시</string>\n    <string name=\"settings_show_superuser\">Superuser 표시</string>\n    <string name=\"settings_floating_auto_hide\">자동 숨기기</string>\n    <string name=\"settings_floating_auto_hide_summary\">3초 동작 없이 플로팅 바 자동 숨기기</string>\n    <string name=\"settings_floating_swipe_hide\">스와이프 숨기기</string>\n    <string name=\"settings_floating_swipe_hide_summary\">아래로 스와이프 시 숨기기, 위로 스와이프 시 표시</string>\n    <string name=\"settings_navbar_glass_effect\">글래스 효과</string>\n    <string name=\"settings_navbar_glass_effect_summary\">플로팅 내비게이션 바에 프로스트 글래스 효과 적용</string>\n    <string name=\"settings_navbar_glass_blur_strength\">블러 강도</string>\n    <string name=\"settings_navbar_glass_transparency\">배경 투명도</string>\n    <string name=\"settings_navbar_glass_highlight_strength\">하이라이트 강도</string>\n    <string name=\"settings_navbar_glass_specular\">거울 반사</string>\n    <string name=\"settings_navbar_glass_specular_summary\">내비게이션 바 상단에 거울 같은 하이라이트 활성화</string>\n    <string name=\"settings_navbar_glass_inner_glow\">내부 발광</string>\n    <string name=\"settings_navbar_glass_inner_glow_summary\">내비게이션 바 하단에 은은한 발광 효과 활성화</string>\n    <string name=\"settings_navbar_glass_border\">글래스 테두리</string>\n    <string name=\"settings_navbar_glass_border_summary\">내비게이션 바 주위에 은은한 테두리 활성화</string>\n    <string name=\"settings_stats_top_layout\">상단 레이아웃</string>\n    <string name=\"settings_stats_top_layout_summary\">상단 정보 카드 스타일 선택</string>\n    <string name=\"settings_stats_top_layout_list\">리스트</string>\n    <string name=\"settings_stats_top_layout_grid\">그리드</string>\n    <string name=\"settings_block_kernelpatch_update\">KernelPatch 업데이트 알림 차단</string>\n    <string name=\"settings_block_kernelpatch_update_summary\">KernelPatch 업데이트 알림을 표시하지 않습니다.</string>\n    <string name=\"settings_block_androidpatch_update\">시스템 패치 업데이트 알림 차단</string>\n    <string name=\"settings_block_androidpatch_update_summary\">시스템 패치(APD) 업데이트 알림을 표시하지 않습니다.</string>\n\n    <string name=\"reboot\">다시 시작</string>\n    <string name=\"settings\">설정</string>\n    <string name=\"reboot_recovery\">복구 모드로 다시 시작</string>\n    <string name=\"reboot_bootloader\">부트로더로 다시 시작</string>\n    <string name=\"reboot_download\">다운로드 모드로 다시 시작</string>\n    <string name=\"reboot_edl\">EDL로 다시 시작</string>\n    <string name=\"reboot_fastbootd\">FastbootD로 다시 시작</string>\n    <string name=\"about\">정보</string>\n    <string name=\"developer_and_maintainer\">개발자 | 유지보수자</string>\n    <string name=\"settings_app_language\">언어</string>\n    <string name=\"system_default\">시스템 기본값</string>\n    <string name=\"settings_global_namespace_mode\">전역 네임스페이스 모드</string>\n    <string name=\"settings_global_namespace_mode_summary\">모든 Root 세션이 전역 마운트 네임스페이스를 사용합니다.</string>\n    <string name=\"settings_clear_super_key_dialog\">계속할까요?</string>\n    <string name=\"settings_grid_working_card_hide_check\">상태 아이콘 숨김</string>\n    <string name=\"settings_grid_working_card_hide_check_summary\">작업 카드에서 확인 또는 경고 아이콘을 숨깁니다.</string>\n    <string name=\"settings_grid_working_card_hide_text\">상태 텍스트 숨김</string>\n    <string name=\"settings_grid_working_card_hide_text_summary\">작업 카드에서 \\'작업 중\\' 또는 \\'설치되지 않음\\' 텍스트를 숨깁니다.</string>\n    <string name=\"settings_grid_working_card_hide_mode\">작업 모드 숨김</string>\n    <string name=\"settings_grid_working_card_hide_mode_summary\">작업 카드에서 \\'Full\\' 또는 \\'Half\\' 텍스트를 숨깁니다.</string>\n\n\n    <string name=\"settings_folkx_engine_title\">FolkX 애니메이션 엔진</string>\n    <string name=\"settings_folkx_engine_summary\">최상위 페이지 전환 시 부드러운 물리 기반 스프링 애니메이션을 사용합니다.</string>\n    <string name=\"settings_folkx_animation_type\">애니메이션 유형</string>\n    <string name=\"settings_folkx_animation_speed\">애니메이션 속도</string>\n    <string name=\"settings_folkx_animation_linear\">선형 이동</string>\n    <string name=\"settings_folkx_animation_spatial\">공간 이동</string>\n    <string name=\"settings_folkx_animation_fade\">페이드 인/아웃</string>\n    <string name=\"settings_folkx_animation_vertical\">수직 슬라이드</string>\n    <string name=\"settings_folkx_animation_diagonal\">대각선 슬라이드</string>\n\n    <string name=\"su_exclude_all_title\">모두 제외</string>\n    <string name=\"su_exclude_all_confirm\">Root 권한이 없는 모든 앱을 제외할까요?</string>\n    <string name=\"su_batch_exclude_title\">일괄 제외</string>\n    <string name=\"su_batch_exclude_content\">모든 비 Root 앱에 대한 인젝션을 제외합니다. 작업을 선택하세요.</string>\n    <string name=\"su_exclude_btn\">제외</string>\n    <string name=\"su_exclude_reverse_btn\">제외 취소</string>\n\n    <string name=\"home_learn_apatch\">FolkPatch 알아보기</string>\n    <string name=\"home_click_to_learn_apatch\">FolkPatch의 기능과 사용 방법을 알아보세요.</string>\n    <string name=\"settings_hide_apatch_card\">FolkPatch 알아보기 카드 숨김</string>\n    <string name=\"settings_hide_apatch_card_summary\">홈 화면에서 FolkPatch 알아보기 카드를 숨깁니다.</string>\n\n    <string name=\"about_source_code\"><![CDATA[<p>%1$s에서 소스 코드를 확인하세요<p/>%2$s 채널에 가입하세요<p/>%3$s 그룹에 가입하세요]]></string>\n    <string name=\"send_log\">로그 보내기</string>\n    <string name=\"save_log\">로그 저장</string>\n    <string name=\"log_saved\">로그 저장됨</string>\n    <string name=\"safe_mode\">안전 모드</string>\n    <string name=\"support_or_donate\">지원 / 후원</string>\n\n    <string name=\"super_key\">SuperKey</string>\n    <string name=\"clear_super_key\">SuperKey 지우기</string>\n    <string name=\"patch_set_superkey\">SuperKey 설정</string>\n    <string name=\"home_patch_set_key_desc\">KernelPatch를 위한 유일한 자격 증명입니다.</string>\n    <string name=\"home_patch_next_step\">다음 단계</string>\n\n    <string name=\"home_not_installed\">설치되지 않음</string>\n    <string name=\"home_install_unknown\">설치되지 않았거나 인증되지 않음</string>\n    <string name=\"home_install_unknown_summary\">눌러서 설치</string>\n    <string name=\"home_click_to_install\">눌러서 설치</string>\n    <string name=\"home_working\">작동 중</string>\n    <string name=\"home_version\">버전: %s</string>\n    <string name=\"home_kp_need_update\">새 버전 사용 가능</string>\n    <string name=\"home_kp_cando_update\">업데이트</string>\n\n    <string name=\"home_installing\">설치 중</string>\n    <string name=\"kpatch_version\">버전: %s</string>\n    <string name=\"apatch_version\">버전: %s</string>\n\n    <string name=\"kpatch_version_update\" formatted=\"false\">버전: %s -&gt; %s</string>\n    <string name=\"kpatch_shadow_path_title\">kpatch</string>\n    <string name=\"home_auth_key_title\">SuperKey 입력</string>\n    <string name=\"home_auth_key_desc\">인증 후 시작합니다.</string>\n    <string name=\"home_kpatch_info_title\">정보</string>\n\n    <string name=\"home_ap_cando_install\">설치</string>\n\n    <string name=\"home_ap_cando_uninstall\">삭제</string>\n    <string name=\"home_ap_cando_reboot\">다시 시작</string>\n\n    <string name=\"patch_title\">패치</string>\n\n    <string name=\"patch_config_title\">패치</string>\n    <string name=\"patch_mode_bootimg_patch\">모드: 패치</string>\n    <string name=\"patch_mode_patch_and_install\">모드: 패치 및 설치</string>\n    <string name=\"patch_mode_install_to_next_slot\">모드: 비활성 슬롯에 설치 (OTA 이후)</string>\n    <string name=\"patch_mode_restore\">모드: 복원</string>\n    <string name=\"patch_mode_uninstall_patch\">모드: KPatch 삭제</string>\n    <string name=\"patch_select_bootimg_btn\">Boot 선택</string>\n    <string name=\"patch_embed_kpm_btn\">KPM 삽입</string>\n    <string name=\"patch_start_patch_btn\">시작</string>\n    <string name=\"patch_start_unpatch_btn\">패치 취소</string>\n    <string name=\"patch_item_error\">!!오류!!</string>\n    <string name=\"patch_item_bootimg\">bootimg</string>\n    <string name=\"patch_item_bootimg_slot\">슬롯:</string>\n    <string name=\"patch_item_bootimg_dev\">기기:</string>\n    <string name=\"patch_item_kernel\">커널</string>\n    <string name=\"patch_item_kpimg\">kpimg</string>\n    <string name=\"patch_item_kpimg_version\">버전:</string>\n    <string name=\"patch_item_kpimg_comile_time\">시간:</string>\n    <string name=\"patch_item_kpimg_config\">설정:</string>\n    <string name=\"patch_item_new_extra_kpm\">새 모듈 삽입</string>\n    <string name=\"patch_item_existed_extra_kpm\">기존 모듈</string>\n    <string name=\"patch_item_extra_name\">이름:</string>\n    <string name=\"patch_item_extra_version\">버전:</string>\n    <string name=\"patch_item_extra_author\">개발자:</string>\n    <string name=\"patch_item_extra_kpm_license\">라이선스:</string>\n    <string name=\"patch_item_extra_kpm_desciption\">설명:</string>\n    <string name=\"patch_item_extra_args\">인수:</string>\n    <string name=\"patch_item_extra_event\">이벤트:</string>\n    <string name=\"patch_item_skey\">SuperKey</string>\n    <string name=\"patch_custom_superkey\">사용자 지정 SuperKey</string>\n    <string name=\"patch_custom_superkey_summary\">여전히 Root 및 커널 관리를 위해 SuperKey를 설정할 수 있으며, 관리자는 커널의 내장 서명으로 인증됩니다</string>\n    <string name=\"patch_item_set_skey_label\">SuperKey는 8~63자 사이여야 하며 숫자와 영문자를 포함해야 합니다. 특수 문자는 사용할 수 없습니다.</string>\n    <string name=\"patch_confirm_superkey\">SuperKey 확인</string>\n    <string name=\"patch_skey_mismatch\">SuperKey가 일치하지 않습니다</string>\n\n    <string name=\"home_kernel\">커널 버전</string>\n    <string name=\"home_manager_version\">매니저 버전</string>\n    <string name=\"home_fingerprint\">지문</string>\n\n    <string name=\"home_selinux_status\">SELinux 상태</string>\n    <string name=\"home_selinux_status_disabled\">비활성화</string>\n    <string name=\"home_selinux_status_enforcing\">강제</string>\n    <string name=\"home_selinux_status_permissive\">허용</string>\n    <string name=\"home_selinux_status_unknown\">알 수 없음</string>\n\n    <string name=\"settings_selinux_mode\">SELinux 모드</string>\n    <string name=\"settings_selinux_mode_summary\">SELinux 강제 모드를 선택하세요.</string>\n    <string name=\"settings_selinux_mode_enforcing\">강제 (엄격)</string>\n    <string name=\"settings_selinux_mode_permissive\">허용 (관대)</string>\n    <string name=\"settings_selinux_current_mode\">현재: %s</string>\n    <string name=\"settings_selinux_mode_enforcing_summary\">SELinux가 액세스 규칙을 완전히 강제합니다.</string>\n    <string name=\"settings_selinux_mode_permissive_summary\">SELinux가 위반 사항만 기록하며 거부하지 않습니다.</string>\n\n    <string name=\"home_device_info\">기기</string>\n    <string name=\"home_system_version\">시스템 버전</string>\n    <string name=\"home_kpatch_version\">KernelPatch 버전</string>\n    <string name=\"home_su_path\">실행 가능한 su</string>\n    <string name=\"home_apatch_version\">FolkPatch 버전</string>\n\n    <string name=\"kpm\">KPM</string>\n    <string name=\"kpm_kp_not_installed\">KernelPatch가 설치되지 않음</string>\n    <string name=\"kpm_add_kpm\">KPM 추가</string>\n    <string name=\"kpm_load\">로드</string>\n    <string name=\"kpm_install\">설치</string>\n    <string name=\"kpm_embed\">삽입</string>\n    <string name=\"kpm_load_toast_succ\">로드됨</string>\n    <string name=\"kpm_load_toast_failed\">로드 실패</string>\n    <string name=\"kpm_unload_confirm\">%s 모듈을 언로드할까요?</string>\n    <string name=\"kpm_unload\">언로드</string>\n    <string name=\"kpm_control\">제어</string>\n    <string name=\"kpm_apm_empty\">로드된 모듈 없음</string>\n    <string name=\"kpm_version\">버전</string>\n    <string name=\"kpm_license\">라이선스</string>\n    <string name=\"kpm_author\">개발자</string>\n    <string name=\"kpm_desc\">설명</string>\n    <string name=\"kpm_args\">인수</string>\n\n    <string name=\"su_title\">Superuser</string>\n    <string name=\"su_selinux_via_hook\">후킹을 통한 우회</string>\n    <string name=\"su_pkg_excluded_label\">제외</string>\n    <string name=\"su_pkg_excluded_setting_title\">수정 제외</string>\n    <string name=\"su_pkg_excluded_setting_summary\">이 옵션을 활성화하면 이 앱의 모듈에 의해 수정된 모든 파일을 FolkPatch가 복원할 수 있습니다.</string>\n    <string name=\"su_pkg_root_setting_title\">Superuser</string>\n    <string name=\"su_pkg_root_setting_summary\">이 옵션을 활성화하면 앱에 Superuser 권한을 부여하여 su 명령을 사용할 수 있습니다.</string>\n    <string name=\"su_pkg_normal_setting_title\">일반 모드</string>\n    <string name=\"su_pkg_normal_setting_summary\">기본 상태입니다. 특별한 처리를 하지 않습니다.</string>\n    <string name=\"su_show_system_apps\">시스템 앱 표시</string>\n    <string name=\"su_hide_system_apps\">시스템 앱 숨김</string>\n    <string name=\"su_refresh\">새로고침</string>\n    <string name=\"apm\">시스템 모듈</string>\n\n\n    <string name=\"apm_not_installed\">AndroidPatch가 설치되지 않음</string>\n    <string name=\"apm_failed_to_enable\">모듈 활성화 실패: %s</string>\n    <string name=\"apm_failed_to_disable\">모듈 비활성화 실패: %s</string>\n    <string name=\"apm_empty\">설치된 모듈이 없음</string>\n    <string name=\"apm_remove\">제거</string>\n    <string name=\"apm_undo\">실행 취소</string>\n    <string name=\"apm_install\">설치</string>\n    <string name=\"apm_uninstall_confirm\">%s 모듈을 삭제할까요?</string>\n    <string name=\"apm_uninstall_success\">%s 모듈 삭제됨</string>\n    <string name=\"apm_uninstall_failed\">삭제 실패: %s</string>\n    <string name=\"apm_undo_uninstall_success\">%s 모듈 복원됨</string>\n    <string name=\"apm_undo_uninstall_failed\">복원 실패: %s</string>\n    <string name=\"apm_version\">버전</string>\n    <string name=\"apm_author\">개발자</string>\n    <string name=\"apm_desc\">설명</string>\n    <string name=\"apm_overlay_fs_not_available\">커널에서 OverlayFS가 비활성화되어 모듈을 사용할 수 없습니다!</string>\n    <string name=\"apm_magisk_conflict\">Magisk와의 충돌로 인해 모듈을 사용할 수 없습니다!</string>\n    <string name=\"apm_mount_warning_title\">중요 알림</string>\n    <string name=\"apm_mount_warning_message\">모듈은 기본적으로 마운트되지 않습니다. 내장 마운트 시스템이나 메타 모듈을 사용하세요.</string>\n    <string name=\"apm_mount_warning_button\">확인</string>\n    <string name=\"apm_reboot_to_apply\">적용하려면 다시 시작하세요.</string>\n    <string name=\"apm_changelog\">변경 내역</string>\n    <string name=\"apm_update\">업데이트</string>\n    <string name=\"apm_downloading\">모듈 다운로드 중: %s</string>\n    <string name=\"apm_start_downloading\">다운로드 시작: %s</string>\n    <string name=\"apm_new_version_available\">새 버전 %s을(를) 사용할 수 있습니다. 눌러서 업그레이드하세요.</string>\n\n    <string name=\"hide_apatch_manager\">APatch 매니저 숨김</string>\n    <string name=\"hide_apatch_manager_summary\">임의의 패키지 ID와 맞춤 앱 라벨을 가진 프록시 앱을 설치합니다.</string>\n    <string name=\"hide_apatch_dialog_new_manager_name\">새 매니저 이름</string>\n    <string name=\"hide_apatch_dialog_summary\">런처에 표시될 새로운 앱 라벨로 사용됩니다.</string>\n    <string name=\"hide_apatch_manager_failure\">숨김 실패. 버그를 제보해 주세요!</string>\n\n    <string name=\"setting_reset_su_path\">su 경로 초기화</string>\n    <string name=\"setting_reset_su_new_path\">새 전체 경로</string>\n\n\n    <string name=\"apm_webui_open\">열기</string>\n    <string name=\"apm_action\">작업</string>\n    <string name=\"module_shortcut_add\">바로가기</string>\n    <string name=\"module_shortcut_not_supported\">런처가 바로가기를 지원하지 않습니다.</string>\n    <string name=\"module_shortcut_created\">홈 화면에 바로가기 생성됨</string>\n    <string name=\"module_shortcut_updated\">바로가기 업데이트됨</string>\n    <string name=\"module_shortcut_permission_tip_xiaomi\">Xiaomi 설정에서 이 앱의 \\\"홈 화면 바로가기 만들기\\\" 권한을 활성화하세요.</string>\n    <string name=\"module_shortcut_permission_tip_oppo\">OPPO 설정에서 이 앱의 \\\"홈 화면 바로가기\\\" 권한을 활성화하세요.</string>\n    <string name=\"module_shortcut_permission_tip_default\">바로가기 만들기에 실패한 경우, 시스템 설정에서 이 앱의 홈 화면 바로가기 권한을 활성화하세요.</string>\n    <string name=\"enable_web_debugging\">WebView 디버깅 활성화</string>\n    <string name=\"enable_web_debugging_summary\">WebUI를 디버깅할 때 사용할 수 있습니다. 필요한 경우에만 활성화하세요.</string>\n    <string name=\"settings_apm_install_confirm\">설치 전 확인</string>\n    <string name=\"settings_apm_install_confirm_summary\">모듈을 설치하기 전에 확인 대화상자를 표시합니다.</string>\n    <string name=\"settings_show_more_module_info\">모듈 상세 정보 표시</string>\n    <string name=\"settings_show_more_module_info_summary\">모듈 목록에 모듈 ID와 크기를 표시합니다.</string>\n    <string name=\"settings_apm_stay_on_page\">작업 페이지에 머무르기</string>\n    <string name=\"settings_apm_stay_on_page_summary\">모듈 작업 실행 후 자동으로 돌아가지 않습니다.</string>\n    <string name=\"settings_enable_module_shortcut_add\">바로가기 추가 버튼 활성화</string>\n    <string name=\"settings_enable_module_shortcut_add_summary\">WebUI 바로가기 추가 버튼을 표시합니다.</string>\n    <string name=\"settings_disable_module_update_check\">모듈 업데이트 확인 비활성화</string>\n    <string name=\"settings_disable_module_update_check_summary\">시스템 모듈의 자동 업데이트 확인을 비활성화합니다.</string>\n    <string name=\"settings_module_sort_optimization\">모듈 정렬 최적화</string>\n    <string name=\"settings_module_sort_optimization_summary\">WebUI 및 Action이 있는 시스템 모듈을 최상단에 표시합니다.</string>\n    <string name=\"settings_fold_system_module\">시스템 모듈 접기</string>\n    <string name=\"settings_fold_system_module_summary\">모듈 카드를 눌러 작업을 확장하거나 접습니다.</string>\n    <string name=\"settings_simple_list_bottom_bar\">간결한 목록 하단 바</string>\n    <string name=\"settings_simple_list_bottom_bar_summary\">APatch에서 영감을 받아 모듈 작업에 아이콘 전용 버튼 스타일을 사용합니다.</string>\n    <string name=\"settings_spliced_card_group\">Spliced Card Group</string>\n    <string name=\"settings_spliced_card_group_summary\">Use M3E continuous card group style for module list</string>\n    <string name=\"apm_install_confirm_title\">모듈 설치</string>\n    <string name=\"apm_install_confirm_content\">%s 모듈을 설치할까요?</string>\n    <string name=\"apm_disable_all_title\">모든 모듈 비활성화</string>\n    <string name=\"module_shortcut_name\">바로가기 이름</string>\n    <string name=\"module_shortcut_icon\">바로가기 아이콘</string>\n    <string name=\"module_shortcut_type\">바로가기 유형</string>\n    <string name=\"module_shortcut_type_webui\">WebUI</string>\n    <string name=\"module_shortcut_type_action\">Action</string>\n    <string name=\"module_shortcut_icon_default\">기본 아이콘 사용</string>\n    <string name=\"module_shortcut_icon_select\">아이콘 선택</string>\n    <string name=\"apm_disable_all_confirm\">모든 모듈을 비활성화할까요? 설치된 모든 모듈이 비활성화됩니다.</string>\n    <string name=\"apm_enable_module_banner\">모듈 배너 활성화</string>\n    <string name=\"apm_enable_module_banner_summary\">사용 가능한 경우 모듈용 배너 이미지를 표시합니다.</string>\n    <string name=\"apm_enable_folk_banner\">모듈 배너 사용자 지정</string>\n    <string name=\"apm_enable_folk_banner_summary\">모듈 카드를 길게 눌러 배너 이미지를 선택하세요.</string>\n    <string name=\"apm_folk_banner_title\">FolkBanner</string>\n    <string name=\"apm_folk_banner_select\">이미지 선택</string>\n    <string name=\"apm_folk_banner_clear\">FolkBanner 지우기</string>\n    <string name=\"apm_folk_banner_saved\">%s에 FolkBanner 삽입됨</string>\n    <string name=\"apm_folk_banner_cleared\">%s의 FolkBanner 삭제됨</string>\n    <string name=\"apm_folk_banner_failed\">%s의 FolkBanner 업데이트 실패</string>\n    <string name=\"apm_banner_api_mode\">API 모드</string>\n    <string name=\"apm_banner_api_mode_summary\">모듈 배너에 무작위 이미지 API 또는 로컬 디렉터리를 사용합니다.</string>\n    <string name=\"apm_banner_api_source\">API 소스 구성</string>\n    <string name=\"apm_banner_api_source_hint\">API URL 또는 로컬 경로를 입력하세요.</string>\n    <string name=\"apm_banner_api_source_saved\">API 소스 저장됨</string>\n    <string name=\"apm_banner_api_source_not_configured\">구성되지 않음</string>\n    <string name=\"apm_banner_api_url_configured\">API URL 구성됨</string>\n    <string name=\"apm_banner_local_dir_configured\">로컬 디렉터리 구성됨</string>\n    <string name=\"apm_banner_clear_cache\">캐시 지우기</string>\n    <string name=\"apm_banner_cache_cleared\">배너 캐시 삭제됨</string>\n    <string name=\"apm_banner_api_config_title\">API 소스 구성</string>\n    <string name=\"apm_banner_api_config_desc\">무작위 이미지 API URL 또는 로컬 디렉터리 경로를 입력하세요. 각 모듈에 고유한 배너 이미지가 제공됩니다.</string>\n    <string name=\"apm_banner_api_examples_title\">예시:</string>\n    <string name=\"apm_banner_api_examples\">API: https://picsum.photos/800/400\\n로컬: /sdcard/Pictures/Banners</string>\n    <!-- API Marketplace -->\n    <string name=\"apm_api_marketplace_title\">API 마켓플레이스</string>\n    <string name=\"apm_api_preview\">미리보기</string>\n    <string name=\"apm_api_apply\">적용</string>\n    <string name=\"apm_api_preview_title\">API 미리보기</string>\n    <string name=\"apm_api_preview_failed\">미리보기 로드 실패</string>\n    <string name=\"apm_api_url_label\">API URL:</string>\n    <string name=\"apm_api_apply_success\">API 소스가 적용됨</string>\n    <string name=\"apm_api_verifying\">확인 중…</string>\n    <string name=\"apm_api_retry\">다시 시도</string>\n    <string name=\"apm_api_empty\">사용 가능한 API 소스 없음</string>\n    <string name=\"settings_banner_custom_opacity\">배너 투명도</string>\n    <string name=\"settings_banner_custom_opacity_summary\">배경화면 모드를 따르지 않고 배너에 사용자 지정 투명도를 사용합니다.</string>\n    <string name=\"settings_banner_opacity\">배너 불투명도</string>\n\n    <string name=\"settings_donot_store_superkey\">로컬에 SuperKey를 저장하지 않음</string>\n    <string name=\"settings_donot_store_superkey_summary\">매니저를 시작할 때마다 SuperKey를 인증합니다.</string>\n\n\n\n    <string name=\"mode_select_page_title\">설치</string>\n    <string name=\"mode_select_page_patch_and_install\">패치 및 설치</string>\n    <string name=\"mode_select_page_select_file\">패치할 Boot 이미지 선택</string>\n    <string name=\"restore_select_file\">복원할 Boot 파티션 파일 선택</string>\n    <string name=\"mode_select_page_install_inactive_slot\">비활성 슬롯에 설치 (OTA 이후)</string>\n    <string name=\"mode_select_page_install_inactive_slot_warning\">다시 시작한 후 기기가 현재 비활성 슬롯으로 **강제** 부팅됩니다!\\nOTA가 완료된 후에만 이 옵션을 사용하세요.\\n계속할까요?</string>\n    <string name=\"mode_select_page_select_kpimg\">로컬 패치 파일 사용 (KPimg)</string>\n    <string name=\"patch_custom_kpimg_label\">사용자 지정 KPimg</string>\n    <string name=\"patch_custom_kpimg_file\">파일: %s</string>\n    <string name=\"patch_select_kpimg_btn\">KPimg 선택</string>\n\n    <string name=\"home_dialog_auth_fail_title\">인증 실패</string>\n    <string name=\"home_dialog_auth_fail_content\">SuperKey를 인증할 수 없어 FolkPatch 활성화에 실패했습니다.\n가능한 원인은 다음과 같습니다. 해당되는 사항이 있는지 확인하세요:\n\n\\n1. KernelPatch를 사용하여 boot.img를 패치하지 않았거나, 무엇을 했는지 잊어버렸습니다.\n\\n2. 패치된 boot.img가 기기에 플래싱되지 않고 여전히 컴퓨터에 있습니다.\n\\n3. SuperKey를 잘못 입력했거나, 외계어와 같은 기이한 기호가 포함되어 있습니다.\n\\n4. 사용 중인 기기가 FolkPatch 및 KernelPatch와 호환되지 않을 수 있습니다. 억지로 시도해도 소용없습니다.\n\\n5. FolkPatch를 차단하는 특정 모듈을 사용하여 스스로를 추방하는 등의 이상한 작업을 수행했을 수 있습니다.\n\n</string>\n\n    <string name=\"home_more_menu_feedback_or_suggestion\">피드백 또는 제안</string>\n    <string name=\"home_more_menu_about\">정보</string>\n    <string name=\"home_more_menu_document\">문서</string>\n\n    <string name=\"home_dialog_uninstall_title\">삭제</string>\n    \n    <string name=\"home_dialog_uninstall_ap_only\">패치만 삭제</string>\n    <string name=\"home_dialog_uninstall_all\">완전히 삭제</string>\n    <string name=\"home_dialog_uninstall_ap_only_desc\">AndroidPatch만 제거하고 매니저는 유지합니다.</string>\n    <string name=\"home_dialog_uninstall_all_desc\">AndroidPatch를 제거하고 전체 삭제 과정을 시작합니다.</string>\n\n    <string name=\"kpm_control_dialog_title\">KPM 제어</string>\n    <string name=\"kpm_control_dialog_content\">제어 매개변수를 입력하세요:</string>\n    <string name=\"kpm_control_paramters\">매개변수</string>\n    <string name=\"kpm_control_outMsg\">출력</string>\n    <string name=\"kpm_control_ok\">성공!</string>\n    <string name=\"kpm_control_failed\">실패!</string>\n    \n    <string name=\"settings_night_mode_follow_sys\">다크 테마 시스템 설정 따름</string>\n    <string name=\"settings_night_mode_follow_sys_summary\">시스템 설정에 따라 다크 테마를 자동으로 전환합니다.</string>\n    <string name=\"settings_night_theme_enabled\">다크 테마</string>\n    \n    <string name=\"settings_use_system_color_theme\">시스템 색상 테마</string>\n    <string name=\"settings_use_system_color_theme_summary\">시스템에서 배경화면을 기반으로 생성한 색상 테마를 사용합니다.</string>\n    <string name=\"settings_custom_color_theme\">색상 테마</string>\n\n    <string name=\"amber_theme\">앰버</string>\n    <string name=\"blue_theme\">블루</string>\n    <string name=\"blue_grey_theme\">블루 그레이</string>\n    <string name=\"brown_theme\">브라운</string>\n    <string name=\"cyan_theme\">시안</string>\n    <string name=\"deep_orange_theme\">딥 오렌지</string>\n    <string name=\"deep_purple_theme\">딥 퍼플</string>\n    <string name=\"green_theme\">그린</string>\n    <string name=\"indigo_theme\">인디고</string>\n    <string name=\"light_blue_theme\">라이트 블루</string>\n    <string name=\"light_green_theme\">라이트 그린</string>\n    <string name=\"lime_theme\">라임</string>\n    <string name=\"orange_theme\">오렌지</string>\n    <string name=\"pink_theme\">핑크</string>\n    <string name=\"purple_theme\">퍼플</string>\n    <string name=\"red_theme\">레드</string>\n    <string name=\"sakura_theme\">사쿠라</string>\n    <string name=\"teal_theme\">틸</string>\n    <string name=\"yellow_theme\">옐로</string>\n    <string name=\"ink_wash_theme\">잉크 워시</string>\n    <string name=\"theme_color\">테마 색상</string>\n    <string name=\"theme_light\">라이트</string>\n    <string name=\"theme_dark\">다크</string>\n    <string name=\"theme_system\">시스템</string>\n    <string name=\"loading_modules\">모듈 불러오는 중…</string>\n    <string name=\"loading_scripts\">스크립트 검색 중…</string>\n    <string name=\"loading_themes\">테마 불러오는 중…</string>\n    <string name=\"loading_apis\">API 소스 불러오는 중…</string>\n\n    <string name=\"about_app_desc\">KernelPatch를 기반으로 한 Root 구현체로, 커널을 다시 컴파일할 필요 없이 커널 함수 후킹을 허용합니다.</string>\n    <string name=\"about_powered_by\">%1$s 제공</string>\n    <string name=\"about_telegram_group\">텔레그램 그룹</string>\n    <string name=\"github\">GitHub</string>\n    <string name=\"telegram\">텔레그램</string>\n    \n    <!-- Online Module Strings -->\n    <string name=\"online_module_title\">온라인 시스템 모듈</string>\n    <string name=\"online_module_download_start\">다운로드 시작: %s</string>\n    <string name=\"online_module_download_complete\">다운로드 완료: %s</string>\n    <string name=\"online_module_download_notification\">%s을(를) 다운로드하는 중입니다. 진행 상황은 알림 패널에서, 완료된 파일은 Download 폴더에서 확인하세요.</string>\n\n    <!-- Online KPM Strings -->\n    <string name=\"online_kpm_title\">온라인 커널 모듈</string>\n    <string name=\"online_kpm_download_start\">다운로드 시작: %s</string>\n    <string name=\"online_kpm_download_complete\">다운로드 완료: %s</string>\n    <string name=\"online_kpm_download_notification\">%s을(를) 다운로드하는 중입니다. 진행 상황은 알림 패널에서, 완료된 파일은 Download 폴더에서 확인하세요.</string>\n\n    <!-- Online Script Strings -->\n    <string name=\"online_script_title\">온라인 스크립트</string>\n    <string name=\"online_script_download_start\">다운로드 시작: %s</string>\n    <string name=\"online_script_download_complete\">다운로드 완료: %s</string>\n    <string name=\"online_script_download_notification\">%s 다운로드 중</string>\n\n    <!-- Crash Handle Strings -->\n    <string name=\"crash_handle_title\">충돌 보고서</string>\n    <string name=\"crash_handle_copy\">복사</string>\n\n    <!-- About Screen Strings -->\n    <string name=\"about_app_version\">앱 버전: %s</string>\n    <string name=\"about_github\">GitHub</string>\n    <string name=\"about_telegram_channel\">텔레그램 채널</string>\n\n    <!-- App Title Strings -->\n    <string name=\"app_title_fpatch\">FPatch</string>\n    <string name=\"app_title_apatch_folk\">APatch Folk</string>\n    <string name=\"app_title_apatchx\">APatch X</string>\n    <string name=\"app_title_apatch\">APatch</string>\n    <string name=\"app_title_folkpatch\">FolkPatch</string>\n    <string name=\"app_title_kernelpatch\">KernelPatch</string>\n    <string name=\"app_title_kernelsu\">KernelSU</string>\n    <string name=\"app_title_supersu\">SuperSU</string>\n    <string name=\"app_title_superuser\">Superuser</string>\n    <string name=\"app_title_superpatch\">SuperPatch</string>\n    <string name=\"app_title_magicpatch\">MagicPatch</string>\n\n    <string name=\"settings_app_dpi\">앱 DPI</string>\n    <string name=\"dpi_apply_settings\">적용</string>\n    <string name=\"dpi_confirm_title\">DPI 변경 확인</string>\n    <string name=\"dpi_confirm_message\">앱 DPI를 %1$s에서 %2$s(으)로 변경하시겠습니까?</string>\n    <string name=\"settings_magic_mount\">Folk 마운트 API</string>\n    <string name=\"settings_magic_mount_summary\">내장된 모듈 마운트 시스템을 활성화합니다.</string>\n    <string name=\"settings_new_app_profile_mode\">새 앱의 기본 모드</string>\n    <string name=\"settings_new_app_profile_normal\">루트 없음</string>\n    <string name=\"settings_new_app_profile_root\">루트</string>\n    <string name=\"settings_new_app_profile_exclude\">제외</string>\n    <string name=\"settings_hide_service\">FolkPatch 숨김</string>\n    <string name=\"settings_hide_service_summary\">부트로더 잠금 해제 상태를 어느 정도 숨깁니다.</string>\n    <!-- Umount Service -->\n    <string name=\"settings_umount_service\">Umount Service</string>\n    <string name=\"settings_umount_service_summary\">부팅 시 자동으로 마운트 해제할 마운트 지점을 구성합니다.</string>\n    <string name=\"umount_config_title\">Umount 구성</string>\n    <string name=\"umount_config_enabled\">Umount 활성화</string>\n    <string name=\"umount_config_enabled_summary\">부팅 시 지정된 마운트 지점을 자동으로 마운트 해제합니다.</string>\n    <string name=\"umount_config_paths_label\">마운트 지점 경로 (한 줄에 하나씩)</string>\n    <string name=\"umount_config_paths_placeholder\">/system/vendor/app\\n/system/vendor/priv-app</string>\n    <string name=\"umount_config_paths_helper\">마운트 해제할 경로를 한 줄에 하나씩 입력하세요.</string>\n    <string name=\"umount_config_save\">저장</string>\n    <string name=\"umount_config_save_confirm\">구성을 저장할까요?</string>\n    <string name=\"umount_config_save_success\">구성 저장됨</string>\n    <string name=\"umount_config_save_failed\">구성 저장 실패</string>\n    <string name=\"settings_app_title\">앱 제목</string>\n    <string name=\"app_title_custom\">사용자 지정</string>\n    <string name=\"settings_custom_app_title\">앱 이름 설정</string>\n    <string name=\"custom_app_title_dialog_title\">사용자 지정 앱 이름</string>\n    <string name=\"custom_app_title_dialog_hint\">앱 이름 입력</string>\n    <string name=\"custom_app_title_dialog_confirm\">확인</string>\n    <string name=\"custom_app_title_dialog_empty\">이름을 입력해 주세요</string>\n    <string name=\"cancel\">취소</string>\n    <string name=\"settings_custom_background\">사용자 지정 배경</string>\n    <string name=\"settings_custom_background_enabled\">사용자 지정 배경 활성화됨</string>\n    <string name=\"settings_custom_background_opacity\">카드 불투명도</string>\n    <string name=\"settings_custom_background_blur\">배경 블러</string>\n    <string name=\"settings_custom_background_dim\">배경 어둡게</string>\n    <string name=\"settings_custom_background_dual_dim\">주야간 이중 어둡게 적용 활성화</string>\n    <string name=\"settings_custom_background_dual_dim_desc\">라이트 및 다크 테마에 맞게 배경 어둡기를 자동 조정</string>\n    <string name=\"settings_custom_background_day_dim\">주간 모드 배경 어둡게</string>\n    <string name=\"settings_custom_background_night_dim\">야간 모드 배경 어둡게</string>\n    <string name=\"settings_multi_background_mode\">다중 배경 모드</string>\n    <string name=\"settings_multi_background_mode_summary\">페이지마다 다른 배경 이미지를 설정합니다.</string>\n    <string name=\"settings_select_home_background\">홈 화면 배경</string>\n    <string name=\"settings_select_kernel_background\">커널 모듈 배경</string>\n    <string name=\"settings_select_superuser_background\">Superuser 배경</string>\n    <string name=\"settings_select_system_module_background\">시스템 모듈 배경</string>\n    <string name=\"settings_select_settings_background\">설정 배경</string>\n\n    <string name=\"settings_advanced_title_style\">고급 제목 스타일</string>\n    <string name=\"settings_advanced_title_style_summary\">상단 바 제목을 사용자 지정 이미지로 바꿉니다.</string>\n    <string name=\"settings_advanced_title_style_enabled\">고급 제목 스타일 활성화됨</string>\n    <string name=\"settings_select_title_image\">제목 이미지 선택</string>\n    <string name=\"settings_title_image_selected\">제목 이미지 선택됨</string>\n    <string name=\"settings_title_image_saved\">제목 이미지 저장됨</string>\n    <string name=\"settings_title_image_error\">제목 이미지 저장 실패</string>\n    <string name=\"settings_clear_title_image\">제목 이미지 지우기</string>\n    <string name=\"settings_clear_title_image_confirm\">제목 이미지를 지울까요?</string>\n    <string name=\"settings_title_image_cleared\">제목 이미지 삭제됨</string>\n    <string name=\"settings_title_image_day_opacity\">주간 모드 불투명도</string>\n    <string name=\"settings_title_image_night_opacity\">야간 모드 불투명도</string>\n    <string name=\"settings_title_image_dim\">배경 어둡게</string>\n    <string name=\"settings_title_image_offset_x\">수평 위치</string>\n\n    \n    <!-- Font Settings -->\n    <string name=\"settings_custom_font\">사용자 지정 글꼴</string>\n    <string name=\"settings_select_font_file\">글꼴 파일 선택</string>\n    <string name=\"settings_custom_font_enabled\">사용자 지정 글꼴 활성화됨</string>\n    <string name=\"settings_custom_font_summary\">앱에 사용자 지정 TTF 글꼴을 사용합니다.</string>\n    <string name=\"settings_font_selected\">사용자 지정 글꼴 선택됨</string>\n    <string name=\"settings_custom_font_error\">글꼴 파일 저장 실패</string>\n    <string name=\"settings_custom_font_saved\">사용자 지정 글꼴 저장됨</string>\n    <string name=\"settings_clear_font\">기본 글꼴 복원</string>\n    <string name=\"settings_clear_font_confirm\">기본 글꼴로 복원할까요?</string>\n    <string name=\"settings_font_cleared\">기본 글꼴 복원됨</string>\n    <string name=\"settings_font_select_hint\">TTF 글꼴 파일 선택</string>\n    <string name=\"settings_select_background_image\">배경 이미지 선택</string>\n    <string name=\"settings_custom_background_summary\">사용자 지정 배경 이미지를 설정합니다.</string>\n    <string name=\"settings_custom_background_saved\">배경 저장됨</string>\n    <string name=\"settings_custom_background_error\">배경 저장 실패</string>\n    <string name=\"settings_background_selected\">배경 선택됨</string>\n    <string name=\"settings_background_image_cleared\">배경 이미지 삭제됨</string>\n    <string name=\"settings_clear_background\">배경 지우기</string>\n    <string name=\"settings_clear_background_confirm\">배경 이미지를 지울까요?</string>\n\n    <string name=\"settings_alt_icon\">대체 아이콘</string>\n    <string name=\"alt_icon_summary\">대체 런처 아이콘을 사용합니다.</string>\n\n    <!-- Video Background Settings -->\n    <string name=\"settings_video_background\">비디오 배경</string>\n    <string name=\"settings_video_background_summary\">비디오를 배경으로 사용합니다.</string>\n    <string name=\"settings_select_video\">비디오 선택</string>\n    <string name=\"settings_video_selected\">비디오 선택됨</string>\n    <string name=\"settings_clear_video_background\">비디오 배경 지우기</string>\n    <string name=\"settings_clear_video_background_confirm\">비디오 배경을 지울까요?</string>\n    <string name=\"settings_video_background_enabled\">비디오 배경 활성화됨</string>\n    <string name=\"settings_video_volume\">비디오 음량</string>\n    \n    <!-- KPM AutoLoad Strings -->\n    <string name=\"kpm_autoload_title\">KPM 자동 로드</string>\n    <string name=\"kpm_autoload_enabled\">자동 로드 활성화</string>\n    <string name=\"kpm_autoload_enabled_summary\">기기 부팅 시 자동으로 KPM 모듈을 로드합니다.</string>\n    <string name=\"kpm_autoload_json_config\">JSON 구성</string>\n    <string name=\"kpm_autoload_json_label\">구성 JSON</string>\n    <string name=\"kpm_autoload_json_placeholder\">{\n  \"enabled\": true,\n  \"kpmPaths\": [\n    \"/path/to/module1.kpm\",\n    \"/path/to/module2.kpm\"\n  ]\n}</string>\n    <string name=\"kpm_autoload_json_error\">잘못된 JSON 형식</string>\n    <string name=\"kpm_autoload_json_helper\">KPM 파일 경로 배열을 포함한 유효한 JSON을 입력하세요.</string>\n    <string name=\"kpm_autoload_save\">구성 저장</string>\n    <string name=\"kpm_autoload_save_confirm\">자동 로드 구성을 저장할까요?</string>\n    <string name=\"kpm_autoload_save_success\">구성 저장됨</string>\n    <string name=\"kpm_autoload_save_failed\">구성 저장 실패</string>\n    <string name=\"kpm_autoload_visual_mode\">시각 모드</string>\n    <string name=\"kpm_autoload_json_mode\">JSON 모드</string>\n    <string name=\"kpm_autoload_add_kpm\">KPM 추가</string>\n    <string name=\"kpm_autoload_remove_kpm\">제거</string>\n    <string name=\"kpm_autoload_edit_kpm\">편집</string>\n    <string name=\"kpm_autoload_edit_dialog_title\">KPM 편집</string>    <string name=\"kpm_autoload_event_label\">이벤트:</string>    <string name=\"kpm_autoload_args_label\">매개변수:</string>    <string name=\"kpm_autoload_event_service\">service</string>    <string name=\"kpm_autoload_event_post_fs_data\">post-fs-data</string>\n    \n    <!-- Module Backup/Restore Strings -->\n    <string name=\"apm_backup_title\">모듈 백업</string>\n    <string name=\"apm_restore_title\">모듈 복원</string>\n    <string name=\"apm_backup_success\">백업됨</string>\n    <string name=\"apm_restore_success\">복원됨</string>\n    <string name=\"apm_backup_failed\">백업 실패</string>\n    <string name=\"apm_restore_failed\">복원 실패</string>\n    <string name=\"apm_backup_failed_msg\">백업 실패: %s</string>\n    <string name=\"apm_restore_failed_msg\">복원 실패: %s</string>\n    <string name=\"apm_copy_list_title\">목록 복사</string>\n    <string name=\"apm_copy_list_success\">모듈 목록 복사됨</string>\n\n    <!-- APM Bulk Install Strings -->\n    <string name=\"apm_bulk_install_title\">시스템 모듈 일괄 설치</string>\n    <string name=\"apm_bulk_install_list_title\">모듈 목록</string>\n    <string name=\"apm_bulk_install_add\">모듈 추가</string>\n    <string name=\"apm_bulk_install_empty\">추가된 모듈 없음</string>\n    <string name=\"apm_bulk_install_action\">일괄 설치</string>\n    <string name=\"apm_bulk_install_log_title\">일괄 설치 로그</string>\n    <string name=\"apm_bulk_install_log_start\">일괄 설치를 시작합니다...</string>\n    <string name=\"apm_bulk_install_log_installing\">%1$s 설치 중</string>\n    <string name=\"apm_batch_install_full_process\">전체 과정 일괄 설치</string>\n    <string name=\"apm_batch_install_full_process_summary\">시스템 모듈 일괄 설치를 전체 과정으로 수행합니다.</string>\n    <string name=\"next_module\">다음 모듈</string>\n    <string name=\"apm_bulk_install_log_installed\">%s 모듈 설치됨</string>\n    <string name=\"apm_bulk_install_log_done\">모든 작업 완료</string>\n    <string name=\"apm_bulk_install_first_use_text\">여러 모듈을 한 번에 설치할 수 있는 기능입니다. 설치 중 버튼 조작이 필요 없는 모듈에 적합한 빠른 설치 방법입니다. 볼륨 버튼 조작이 필요한 모듈은 설정에서 전체 과정 설치 모드를 활성화하세요.</string>\n    <string name=\"apm_bulk_install_remove\">제거</string>\n    <string name=\"apm_first_use_title\">시스템 모듈 시작하기</string>\n    <string name=\"apm_first_use_text\">시스템 모듈에 오신 것을 환영합니다. 여기서는 Magisk 생태계와 호환되는 모듈을 사용합니다. 모듈을 설치하려면 우측 하단의 버튼을 누르세요. 상단의 설치 프로그램을 사용하여 모듈을 일괄 설치할 수도 있습니다. 한 번의 클릭으로 모든 모듈을 백업하는 기능도 제공되지만, 이 방법이 모든 모듈에 적합하지 않을 수 있으므로 개별적으로 백업하는 것을 권장합니다.</string>\n\n    <string name=\"kpm_autoload_kpm_list_title\">KPM 모듈</string>\n    <string name=\"kpm_autoload_no_kpm_added\">추가된 KPM 모듈 없음</string>\n    <string name=\"kpm_autoload_kpm_path\">KPM 경로</string>\n    <string name=\"kpm_autoload_file_not_found\">파일을 찾을 수 없음</string>\n    <string name=\"kpm_autoload_select_kpm_file\">KPM 파일 선택</string>\n    <string name=\"kpm_autoload_first_time_title\">KPM 자동 로드 정보</string>\n    <string name=\"kpm_autoload_first_time_message\">구성된 모든 KPM을 부팅 시 임시로 자동 로드할 수 있는 기능입니다. 이 방법은 커널에 직접 포함하는 것보다 편리합니다.\n\n예를 들어 파티션 수정을 방지하는 KPM은 임시로만 사용할 수 있으며, 삽입할 경우 부팅 오류가 발생할 수 있습니다. Boot 파티션을 수정하지 않으려면 이 구성을 사용하여 모듈을 빠르게 로드할 수 있습니다.\n\n명령이 실행되려면 앱을 완전히 닫았다가 다시 열어야 합니다. 일반적으로 부팅 시 로드됩니다. 매니저 화면이 잠시 검게 나타나는 것은 정상입니다! 이는 순수 백그라운드 로딩 방식을 사용하므로 모듈이 올바르게 로드되었는지 수동으로 아래로 스와이프하여 새로고침하세요!</string>\n    <string name=\"kpm_autoload_first_time_confirm\">확인</string>\n    <string name=\"kpm_autoload_do_not_show_again\">다시 보지 않기</string>\n\n    <!-- Hide Settings Strings -->\n    <string name=\"home_hide_su_path\">su 실행 파일 경로 숨김</string>\n    <string name=\"home_hide_kpatch_version\">KernelPatch 버전 숨김</string>\n    <string name=\"home_hide_fingerprint\">지문 숨김</string>\n    <string name=\"home_hide_zygisk\">Zygisk 구현 숨김</string>\n    <string name=\"home_hide_mount\">마운트 구현 숨김</string>\n    <string name=\"home_hide_su_path_summary\">정보 카드에서 su 실행 파일 경로를 숨깁니다.</string>\n    <string name=\"home_hide_kpatch_version_summary\">정보 카드에서 KernelPatch 버전을 숨깁니다.</string>\n    <string name=\"home_hide_fingerprint_summary\">정보 카드에서 지문을 숨깁니다.</string>\n    <string name=\"home_hide_zygisk_summary\">정보 카드에서 Zygisk 구현을 숨깁니다.</string>\n    <string name=\"home_hide_mount_summary\">정보 카드에서 마운트 구현을 숨깁니다.</string>\n\n    <string name=\"kpm_page_first_time_title\">경고</string>\n    <string name=\"kpm_page_first_time_message\">커널 모듈은 Boot 구현을 직접 수정합니다. 시스템 모듈과 달리 복구 메커니즘이 좋지 않습니다. 문제가 발생하면 Fastboot 모드에 진입해서만 해결할 수 있습니다. 모듈을 Boot에 포함하기 전에 먼저 로드하여 문제가 없는지 확인하는 것이 좋습니다. 로드해서만 사용할 수 있는 모듈이라면 자동 KPM 모듈 로드 기능을 사용해 보세요. 커널 모듈에 대해 잘 모른다면 이 기능을 사용하지 마세요!</string>\n    \n    \n    <!-- Auto Backup Strings -->\n    <string name=\"settings_auto_backup_module\">시스템 모듈 자동 백업</string>\n    <string name=\"settings_auto_backup_module_summary\">모듈을 설치할 때 비공개 디렉터리로 모듈 파일을 자동으로 백업합니다.</string>\n    <string name=\"settings_open_backup_dir\">백업 디렉터리 열기</string>\n    <string name=\"backup_dir_empty\">백업 디렉터리가 비어 있음</string>\n    <string name=\"backup_dir_open_failed\">백업 디렉터리를 열지 못함</string>\n    <string name=\"auto_backup_failed\">자동 백업 실패: %s</string>\n    <string name=\"auto_backup_success\">자동 백업됨: %s</string>\n    <string name=\"settings_auto_backup_boot\">Boot 자동 백업</string>\n    <string name=\"settings_auto_backup_boot_summary\">로컬 저장소로 Boot를 자동으로 백업합니다. (Downloads/FolkPatch/BootBackups)</string>\n\n    <!-- Home Layout Strings -->\n    <string name=\"settings_home_layout_style\">홈 레이아웃 스타일</string>\n    <string name=\"settings_home_layout_default\">ListUI</string>\n    <string name=\"settings_home_layout_grid\">GridUI</string>\n    <string name=\"settings_home_layout_focus\">FocusUI</string>\n    <string name=\"settings_home_layout_sign\">SignUI</string>\n    <string name=\"settings_home_layout_circle\">CircleUI</string>\n    <string name=\"settings_home_layout_dashboard_pro\">DashboardUI</string>\n    <string name=\"unofficial_version_title\">비공식 버전</string>\n    <string name=\"unofficial_version_message\">공식 앱이 아닌 FolkPatch를 사용 중입니다. 공식 앱을 다운로드하세요!</string>\n    <string name=\"go_to_github\">Github로 이동</string>\n    <string name=\"settings_save_theme\">테마 저장</string>\n    <string name=\"settings_import_theme\">테마 가져오기</string>\n    <string name=\"settings_theme_saved\">테마 저장됨</string>\n    <string name=\"settings_theme_imported\">테마를 가져옴</string>\n    <string name=\"settings_theme_save_failed\">테마 저장 실패</string>\n    <string name=\"settings_theme_import_failed\">테마 가져오기 실패</string>\n    <string name=\"settings_reset_theme\">테마 초기화</string>\n    <string name=\"settings_reset_theme_confirm\">모든 테마 설정을 기본값으로 초기화할까요? 사용자 지정 배경, 글꼴, 음악, 음향 효과가 모두 지워집니다.</string>\n    <string name=\"settings_theme_reset\">테마가 기본값으로 초기화됨</string>\n    <string name=\"settings_theme_reset_failed\">테마 초기화 실패</string>\n\n    <!-- Theme Strings -->\n    <string name=\"theme_export_title\">테마 내보내기</string>\n    <string name=\"theme_import_title\">테마 가져오기</string>\n    <string name=\"theme_name\">테마 이름</string>\n    <string name=\"theme_type\">테마 종류</string>\n    <string name=\"theme_type_phone\">휴대전화</string>\n    <string name=\"theme_type_tablet\">태블릿</string>\n    <string name=\"theme_version\">버전</string>\n    <string name=\"theme_author\">개발자</string>\n    <string name=\"theme_description\">설명</string>\n    <string name=\"theme_export_action\">내보내기</string>\n    <string name=\"theme_import_action\">가져오기</string>\n    <string name=\"theme_import_confirm\">이 테마를 가져올까요?</string>\n    <string name=\"theme_info\">테마 정보</string>\n\n    <!-- Grid Working Card Background -->\n    <string name=\"settings_grid_working_card_background\">작업 카드 배경</string>\n    <string name=\"settings_grid_working_card_background_enabled\">사용자 지정 카드 배경 활성화됨</string>\n    <string name=\"settings_grid_working_card_background_summary\">작업 카드에 사용자 지정 배경 이미지를 사용합니다.</string>\n    <string name=\"settings_grid_working_card_background_selected\">배경 선택됨</string>\n    <string name=\"settings_grid_working_card_dual_opacity\">주야간 이중 투명도 적용 활성화</string>\n    <string name=\"settings_grid_working_card_dual_opacity_desc\">라이트 및 다크 테마에 맞게 카드 투명도를 자동 조정</string>\n    <string name=\"settings_grid_working_card_day_opacity\">주간 모드 카드 투명도</string>\n    <string name=\"settings_grid_working_card_night_opacity\">야간 모드 카드 투명도</string>\n    <string name=\"settings_clear_grid_working_card_background\">카드 배경 지우기</string>\n    <string name=\"settings_clear_grid_working_card_background_confirm\">카드 배경 이미지를 지울까요?</string>\n    <string name=\"settings_grid_working_card_background_saved\">카드 배경 저장됨</string>\n    <string name=\"settings_grid_working_card_background_error\">카드 배경 저장 실패</string>\n    <string name=\"settings_grid_working_card_background_cleared\">카드 배경 삭제됨</string>\n\n    <!-- Biometric Strings -->\n    <string name=\"action_biometric\">생체 인식 인증</string>\n    <string name=\"msg_biometric\">생체 인식으로 본인 확인을 하세요.</string>\n    <string name=\"settings_biometric_login\">생체 인식 인증</string>\n    <string name=\"settings_biometric_login_summary\">앱을 열 때 생체 인식 인증을 요구합니다.</string>\n    <string name=\"settings_strong_biometric\">강력한 생체 인식</string>\n    <string name=\"settings_strong_biometric_summary\">모듈을 설치/제거/비활성화할 때 인증을 요구합니다.</string>\n\n    <!-- Theme Store Strings -->\n    <string name=\"theme_store_title\">테마 스토어</string>\n    <string name=\"theme_source_official\">공식</string>\n    <string name=\"theme_source_third_party\">서드파티</string>\n    <string name=\"theme_source_local\">로컬</string>\n    <string name=\"theme_store_author\">개발자: %s</string>\n    <string name=\"theme_store_version\">버전: %s</string>\n    <string name=\"theme_source\">출처</string>\n    <string name=\"theme_store_download_failed\">다운로드 실패</string>\n    <string name=\"theme_store_download\">다운로드</string>\n    <string name=\"theme_store_search_hint\">테마 검색...</string>\n    <string name=\"search_modules\">모듈 검색...</string>\n    <string name=\"search_scripts\">스크립트 검색...</string>\n\n    <!-- Update Check Strings -->\n    <string name=\"settings_auto_update_check\">자동 업데이트 확인</string>\n    <string name=\"settings_auto_update_check_summary\">앱 시작 시 업데이트를 자동으로 확인합니다.</string>\n    <string name=\"settings_check_update\">업데이트 확인</string>\n    <string name=\"update_available_title\">업데이트 가능</string>\n    <string name=\"update_available_message\">앱 버전이 너무 낮습니다. 새 버전을 다운로드할까요?</string>\n    <string name=\"update_action\">업데이트</string>\n    <string name=\"update_close\">닫기</string>\n    <string name=\"update_latest\">최신 버전을 사용 중입니다.</string>\n    <string name=\"update_error\">업데이트 확인 중 오류 발생</string>\n    <string name=\"settings_category_general\">일반</string>\n    <string name=\"settings_app_list_loading_scheme\">앱 목록 로드 방식</string>\n    <string name=\"settings_app_list_loading_scheme_summary\">애플리케이션 목록을 로드하는 방식을 선택하세요.</string>\n    <string name=\"app_list_loading_scheme_root_service\">RootService (기본값)</string>\n    <string name=\"app_list_loading_scheme_package_manager\">PackageManager (시스템 API)</string>\n    <string name=\"app_list_loading_scheme_dialog_title\">로드 방식</string>\n    <string name=\"superuser\">Superuser</string>\n    <string name=\"module\">모듈</string>\n    <string name=\"settings_category_appearance\">디스플레이</string>\n    <string name=\"settings_category_behavior\">작동</string>\n    \n    <!-- Appearance Sub-Categories -->\n    <string name=\"settings_appearance_font\">글꼴 설정</string>\n    <string name=\"settings_appearance_font_summary\">사용자 지정 글꼴을 구성합니다.</string>\n    <string name=\"settings_appearance_theme\">테마 설정</string>\n    <string name=\"settings_appearance_theme_summary\">테마 스토어, 저장, 가져오기 및 초기화</string>\n    <string name=\"settings_appearance_banner\">배너 설정</string>\n    <string name=\"settings_appearance_banner_summary\">모듈 배너를 구성합니다.</string>\n    <string name=\"settings_appearance_layout\">레이아웃 설정</string>\n    <string name=\"settings_appearance_layout_summary\">홈 화면 레이아웃, 내비게이션, 카드를 사용자 지정합니다.</string>\n    <string name=\"settings_appearance_background\">배경 설정</string>\n    <string name=\"settings_appearance_background_summary\">사용자 지정 배경, 비디오 및 다중 배경을 설정합니다.</string>\n    <string name=\"settings_appearance_night_mode\">야간 모드 설정</string>\n    <string name=\"settings_appearance_night_mode_summary\">다크 테마 및 색상을 구성합니다.</string>\n    <string name=\"settings_amoled_theme\">AMOLED 블랙 테마</string>\n    <string name=\"settings_amoled_theme_desc\">다크 모드에서 순정 블랙 배경 사용</string>\n    <string name=\"settings_switch_icon\">버튼 표시기 활성화</string>\n    <string name=\"settings_switch_icon_desc\">스위치 버튼에 상태 아이콘 표시</string>\n    <string name=\"settings_category_module\">모듈</string>\n    <string name=\"settings_category_security\">보안</string>\n    <string name=\"settings_category_function\">기능</string>\n    \n    <!-- Background Music Strings -->\n    <string name=\"settings_background_music\">배경 음악</string>\n    <string name=\"settings_background_music_summary\">앱이 포그라운드에 있을 때 배경 음악을 재생합니다.</string>\n    <string name=\"settings_background_music_playing\">재생 중: %s</string>\n    <string name=\"settings_background_music_enabled\">배경 음악 활성화됨</string>\n    <string name=\"settings_select_music_file\">음악 파일 선택</string>\n    <string name=\"settings_music_selected\">음악 선택됨</string>\n    <string name=\"settings_clear_music\">음악 지우기</string>\n    <string name=\"settings_clear_music_confirm\">배경 음악을 지울까요?</string>\n    <string name=\"settings_music_auto_play\">자동 재생</string>\n    <string name=\"settings_music_auto_play_summary\">앱이 열리면 음악을 자동으로 재생합니다.</string>\n    <string name=\"settings_music_looping\">반복 재생</string>\n    <string name=\"settings_music_looping_summary\">현재 곡을 반복 재생합니다.</string>\n    <string name=\"settings_music_volume\">음량</string>\n    <string name=\"settings_music_saved\">음악 파일 저장됨</string>\n    <string name=\"settings_music_save_error\">음악 파일 저장 실패</string>\n    <string name=\"settings_music_cleared\">음악 삭제됨</string>\n    <string name=\"settings_category_multimedia\">멀티미디어</string>\n    <string name=\"settings_category_general_summary\">언어, 업데이트, SELinux, 시스템 조정</string>\n    <string name=\"settings_category_appearance_summary\">테마, 색상, 레이아웃, 배경, 글꼴</string>\n    <string name=\"settings_category_behavior_summary\">웹 디버깅, 설치 동작, 홈 표시</string>\n    <string name=\"settings_category_security_summary\">생체 인식, 슈퍼키 관리</string>\n    <string name=\"settings_category_backup_summary\">로컬 백업, 클라우드 백업, WebDAV</string>\n    <string name=\"settings_category_module_summary\">모듈 정보, 정렬, 일괄 설치</string>\n    <string name=\"settings_category_function_summary\">FolkPatch 숨기기, Umount 서비스</string>\n    <string name=\"settings_category_multimedia_summary\">배경 음악, 소리, 진동</string>\n    <string name=\"settings_use_legacy_su_page\">단일 페이지 Superuser 인증</string>\n    <string name=\"settings_use_legacy_su_page_summary\">Superuser 페이지를 단일 페이지 인증 디자인으로 전환합니다.</string>\n    <string name=\"settings_music_playback_control\">재생 제어</string>\n\n    <!-- Theme Store Filter -->\n    <string name=\"theme_store_filter_title\">테마 필터링</string>\n    <string name=\"theme_store_filter_author\">개발자</string>\n    <string name=\"theme_store_filter_author_hint\">개발자 이름을 입력하세요.</string>\n    <string name=\"theme_store_filter_source\">출처</string>\n    <string name=\"theme_store_filter_source_all\">모두</string>\n    <string name=\"theme_store_filter_type\">기기 종류</string>\n    <string name=\"theme_store_filter_apply\">적용</string>\n    <string name=\"theme_store_filter_reset\">초기화</string>\n\n    <!-- My Themes Page -->\n    <string name=\"my_themes_title\">내 테마</string>\n    <string name=\"my_themes_empty\">테마 없음</string>\n    <string name=\"my_themes_empty_action\">테마 스토어 둘러보기</string>\n    <string name=\"my_themes_apply\">테마 적용</string>\n    <string name=\"my_themes_delete\">테마 삭제</string>\n    <string name=\"my_themes_delete_confirm\">이 테마를 삭제할까요?</string>\n    <string name=\"my_themes_deleted\">테마 삭제됨</string>\n    <string name=\"my_themes_applied\">테마 적용됨</string>\n    <string name=\"my_themes_apply_failed\">테마 적용 실패</string>\n    <string name=\"my_themes_details\">테마 세부정보</string>\n\n    <!-- Download Dialog -->\n    <string name=\"theme_download_title\">테마 다운로드 중</string>\n    <string name=\"theme_download_completed\">다운로드 완료</string>\n    <string name=\"theme_download_failed\">다운로드 실패</string>\n    <string name=\"theme_download_progress\">진행률</string>\n    <string name=\"theme_download_file\">테마 파일</string>\n    <string name=\"theme_download_image\">미리보기 이미지</string>\n    <string name=\"theme_download_cancel\">취소</string>\n    <string name=\"theme_download_pause\">일시정지</string>\n    <string name=\"theme_download_resume\">계속</string>\n    <string name=\"theme_download_retry\">다시 시도</string>\n    <string name=\"theme_download_apply\">테마 적용</string>\n    <string name=\"theme_download_go_to_my_themes\">내 테마</string>\n    <string name=\"theme_download_retrying\">다시 시도하는 중 (%d/3)...</string>\n    <string name=\"theme_download_preparing\">다운로드 준비 중...</string>\n    <string name=\"theme_download_downloading_file\">테마 파일 다운로드 중...</string>\n    <string name=\"theme_download_downloading_image\">미리보기 이미지 다운로드 중...</string>\n    <string name=\"theme_download_finalizing\">마무리 중...</string>\n\n    <!-- File Picker -->\n    <string name=\"file_picker_permission_required\">권한 필요</string>\n    <string name=\"file_picker_permission_desc\">파일을 탐색하려면 \\'모든 파일에 대한 접근\\' 권한을 부여하세요.</string>\n    <string name=\"file_picker_grant_permission\">권한 부여</string>\n    <string name=\"file_picker_cancel\">취소</string>\n    <string name=\"file_picker_internal_storage\">내부 저장소</string>\n    <string name=\"file_picker_no_files\">파일 없음</string>\n\n    <!-- HomeV3 Strings -->\n    <string name=\"home_device_status_title\">기기 상태</string>\n    <string name=\"home_device_status_battery_temp\">배터리 온도</string>\n    <string name=\"home_device_status_cpu_load\">CPU 부하</string>\n    <string name=\"home_device_status_battery_level\">배터리 잔량</string>\n    <string name=\"home_storage_title\">저장소</string>\n    <string name=\"home_storage_internal\">내부 저장소</string>\n    <string name=\"home_storage_ram\">RAM</string>\n    <string name=\"home_storage_zram\">ZRAM</string>\n    <string name=\"home_storage_swap\">스왑 파일</string>\n    <string name=\"home_info_kernel\">커널</string>\n    <string name=\"home_info_superkey\">SuperKey</string>\n    <string name=\"home_info_auth_auth\">인증됨</string>\n    <string name=\"home_info_auth_na\">해당 없음</string>\n    <string name=\"home_info_device_slot\">기기 슬롯</string>\n    <string name=\"home_info_device_model\">기기 모델</string>\n    <string name=\"home_info_running_mode\">실행 모드</string>\n    <string name=\"home_info_mode_full\">Full</string>\n    <string name=\"home_info_mode_half\">Half</string>\n    <string name=\"home_zygisk_implement\">Zygisk 구현</string>\n    <string name=\"home_mount_implement\">마운트 구현</string>\n\n    <string name=\"settings_list_card_hide_status_badge\">기본 이모지 사용</string>\n    <string name=\"settings_list_card_hide_status_badge_summary\">홈 상태 배지를 기본 이모지로 변경합니다.</string>\n    <string name=\"settings_custom_badge_text\">사용자 지정 배지 텍스트</string>\n    <string name=\"settings_custom_badge_text_summary\">배지의 텍스트 표시를 재미로 수정합니다.</string>\n    <string name=\"settings_custom_badge_text_full_half\">Full/Half (기본값)</string>\n    <string name=\"settings_custom_badge_text_lkm\">LKM</string>\n    <string name=\"settings_custom_badge_text_gki\">GKI</string>\n    <string name=\"settings_custom_badge_text_n_gki\">N-GKI</string>\n    <string name=\"settings_custom_badge_text_oki\">OKI</string>\n    <string name=\"settings_custom_badge_text_built_in\">Built-in</string>\n\n    <!-- Install Mode Select -->\n    <string name=\"kp_install_methods\">KernelPatch 패치/설치</string>\n    <string name=\"restore_boot_methods\">boot 파티션으로 복원할 Boot 이미지를 선택하세요.</string>\n\n\n    <string name=\"su_backup_list\">백업 목록</string>\n    <string name=\"su_restore_list\">복원 목록</string>\n    <string name=\"backup_success\">백업 성공</string>\n    <string name=\"restore_success\">복원 성공</string>\n\n    <!-- SuperUser App Action Dialog -->\n    <string name=\"su_app_action_title\">앱 작업</string>\n    <string name=\"su_app_action_content\">앱에서 수행할 작업을 선택하세요.</string>\n    <string name=\"su_app_action_launch\">앱 실행</string>\n    <string name=\"su_app_action_force_stop\">강제 중지</string>\n    <string name=\"su_app_action_launch_success\">%s 실행 중</string>\n    <string name=\"su_app_action_force_stop_success\">%s이(가) 강제로 중지됨</string>\n    <string name=\"su_app_action_failed\">작업 실패: %s</string>\n\n    <!-- SU Audit Log -->\n    <string name=\"su_audit_log_title\">권한 로그</string>\n    <string name=\"su_audit_log_empty\">권한 기록이 없습니다</string>\n    <string name=\"su_audit_log_clear\">로그 지우기</string>\n    <string name=\"su_audit_log_clear_confirm\">모든 권한 기록을 삭제하시겠습니까?</string>\n    <string name=\"su_audit_action_grant\">허용됨</string>\n    <string name=\"su_audit_action_revoke\">취소됨</string>\n    <string name=\"su_audit_action_exclude\">제외됨</string>\n    <string name=\"su_audit_tab_usage\">사용 기록</string>\n    <string name=\"su_audit_tab_operations\">작업 기록</string>\n\n    <string name=\"patch_output_written_to\"> 출력 파일이 저장된 위치: </string>\n    <string name=\"patch_write_failed\"> 패치된 boot.img 작성 실패</string>\n\n    <!-- Badge Count Settings -->\n    <string name=\"enable_badge_count\">배지 개수 설정</string>\n    <string name=\"enable_badge_count_summary\">내비게이션 항목의 배지 개수 표시를 구성합니다.</string>\n    <string name=\"badge_superuser\">Superuser 배지 표시</string>\n    <string name=\"badge_apm\">시스템 모듈 배지 표시</string>\n    <string name=\"badge_kernel\">커널 모듈 배지 표시</string>\n    <string name=\"settings_sound_effect\">음향 효과</string>\n    <string name=\"settings_sound_effect_summary\">터치 시 음향 효과를 재생합니다.</string>\n    <string name=\"settings_sound_effect_enabled\">활성화됨</string>\n    <string name=\"settings_sound_effect_playing\">선택됨: %s</string>\n    <string name=\"settings_sound_effect_source\">음원</string>\n    <string name=\"settings_sound_effect_source_local\">로컬 파일</string>\n    <string name=\"settings_sound_effect_source_preset\">프리셋</string>\n    <string name=\"settings_sound_effect_preset_title\">프리셋 음향</string>\n    <string name=\"settings_select_sound_effect\">음향 효과 선택</string>\n    <string name=\"settings_sound_effect_selected\">음향 효과 파일 선택됨</string>\n    <string name=\"settings_sound_effect_scope\">효과 범위</string>\n    <string name=\"settings_sound_effect_scope_global\">전역 (어디서나)</string>\n    <string name=\"settings_sound_effect_scope_bottom_bar\">하단 바만</string>\n    <string name=\"settings_clear_sound_effect\">음향 효과 지우기</string>\n    <string name=\"settings_clear_sound_effect_confirm\">음향 효과를 지울까요?</string>\n    <string name=\"settings_sound_effect_cleared\">음향 효과 삭제됨</string>\n\n    <string name=\"settings_startup_sound\">시작 음향</string>\n    <string name=\"settings_startup_sound_summary\">앱이 시작될 때 음향을 재생합니다.</string>\n    <string name=\"settings_startup_sound_enabled\">시작 음향 활성화됨</string>\n    <string name=\"settings_startup_sound_playing\">재생 중: %s</string>\n    <string name=\"settings_select_startup_sound\">시작 음향 선택</string>\n    <string name=\"settings_startup_sound_selected\">시작 음향 선택됨</string>\n    <string name=\"settings_clear_startup_sound\">시작 음향 지우기</string>\n    <string name=\"settings_clear_startup_sound_confirm\">시작 음향을 지울까요?</string>\n    <string name=\"settings_startup_sound_cleared\">시작 음향 삭제됨</string>\n\n    <string name=\"settings_vibration\">진동 피드백</string>\n    <string name=\"settings_vibration_summary\">터치 이벤트 시 진동합니다.</string>\n    <string name=\"settings_vibration_enabled\">진동 활성화</string>\n    <string name=\"settings_vibration_intensity\">진동 세기</string>\n    <string name=\"settings_vibration_scope\">진동 범위</string>\n    <string name=\"settings_vibration_scope_global\">전역 (어디서나)</string>\n    <string name=\"settings_vibration_scope_bottom_bar\">하단 바만</string>\n\n    <!-- Backup Settings -->\n    <string name=\"settings_category_backup\">백업</string>\n    <string name=\"settings_enable_cloud_backup\">클라우드 백업 활성화</string>\n    <string name=\"settings_enable_cloud_backup_summary\">플래싱된 모듈을 클라우드 서비스에 자동으로 백업합니다.</string>\n    <string name=\"settings_configure_webdav\">WebDAV 서비스 구성</string>\n    <string name=\"webdav_config_title\">WebDAV 구성</string>\n    <string name=\"webdav_url\">WebDAV URL</string>\n    <string name=\"webdav_username\">사용자 이름</string>\n    <string name=\"webdav_password\">비밀번호</string>\n    <string name=\"webdav_test_success\">테스트 성공</string>\n    <string name=\"webdav_test_failed\">테스트 실패: %s</string>\n    <string name=\"webdav_uploading\">WebDAV에 업로드하는 중...</string>\n    <string name=\"webdav_backup_success\">WebDAV 백업됨</string>\n    <string name=\"webdav_backup_failed\">WebDAV 백업 실패: %s</string>\n    <string name=\"save\">저장</string>\n    <string name=\"test\">테스트</string>\n    <string name=\"webdav_path_label\">경로 (예: /Backup)</string>\n    <string name=\"webdav_view_logs\">로그 보기</string>\n    <string name=\"webdav_backup_logs_title\">백업 로그</string>\n    <string name=\"webdav_no_logs\">로그를 찾을 수 없음</string>\n    <string name=\"webdav_clear_logs\">지우기</string>\n    <string name=\"settings_enable_local_backup\">로컬 백업 활성화</string>\n    <string name=\"settings_enable_local_backup_summary\">로컬 저장소로 모듈을 백업합니다. (Downloads/FolkPatch/ModuleBackups)</string>\n    <string name=\"close\">닫기</string>\n\n\n\n    <string name=\"script_library\">스크립트 라이브러리</string>\n    <string name=\"script_library_title\">스크립트 라이브러리</string>\n    <string name=\"script_library_empty\">스크립트가 없습니다. 추가 버튼을 눌러 추가하세요.</string>\n    <string name=\"script_library_add\">스크립트 추가</string>\n    <string name=\"script_library_add_title\">스크립트 추가</string>\n    <string name=\"script_library_select_file\">스크립트 파일 선택</string>\n    <string name=\"script_library_alias\">별칭</string>\n    <string name=\"script_library_alias_hint\">스크립트 별칭을 입력하세요. (선택사항)</string>\n    <string name=\"script_library_run\">실행</string>\n    <string name=\"script_library_delete\">삭제</string>\n    <string name=\"script_library_path\">경로: %s</string>\n    <string name=\"script_library_confirm_delete\">스크립트를 삭제할까요?</string>\n    <string name=\"script_library_delete_success\">스크립트 삭제됨</string>\n    <string name=\"script_library_delete_failed\">스크립트 삭제 실패</string>\n    <string name=\"script_library_add_success\">스크립트 추가됨</string>\n    <string name=\"script_library_add_failed\">스크립트 추가 실패: %s</string>\n    <string name=\"script_library_load_failed\">스크립트 라이브러리 로드 실패</string>\n    <string name=\"script_library_output\">실행 출력</string>\n    <string name=\"script_library_no_output\">출력 없음</string>\n    <string name=\"script_library_save_log\">로그 저장</string>\n    <string name=\"script_library_log_saved\">로그가 %s에 저장됨</string>\n    <string name=\"script_library_log_save_failed\">로그 저장 실패: %s</string>\n    <string name=\"settings_predictive_back\">예측 백 제스처</string>\n    <string name=\"settings_predictive_back_summary\">Android 14+ 예측 백 제스처 애니메이션 활성화</string>\n\n    <string name=\"home_device_status_battery_charging\">충전 중</string>\n    <string name=\"home_device_status_cpu_temp\">CPU 온도</string>\n    <string name=\"home_device_status_memory_trend\">메모리 트렌드</string>\n    <string name=\"home_storage_partitions\">저장소 파티션</string>\n    <string name=\"home_device_status_cpu_freq\">CPU 주파수</string>\n    <string name=\"home_network_rx\">다운로드</string>\n    <string name=\"home_network_tx\">업로드</string>\n    <string name=\"settings_home_layout_stats\">StatsUI</string>\n    <string name=\"home_stats_more_options\">더보기</string>\n    <string name=\"home_stats_kp_label\">KP</string>\n    <string name=\"home_stats_manager_label\">관리자</string>\n    <string name=\"home_device_status_gpu_load\">GPU</string>\n    <string name=\"home_stats_kernel_modules\">커널 모듈</string>\n    <string name=\"home_stats_apm_modules\">시스템 모듈</string>\n    <string name=\"home_stats_superusers\">슈퍼유저</string>\n    <string name=\"settings_kernel_spoof\">커널 위장 설정</string>\n    <string name=\"settings_kernel_spoof_summary\">커널 버전 및 빌드 시간 위장</string>\n    <string name=\"settings_kernel_spoof_version\">커널 버전</string>\n    <string name=\"settings_kernel_spoof_build_time\">커널 빌드 시간</string>\n    <string name=\"settings_kernel_spoof_restore\">복원</string>\n\n    <string name=\"kernel_spoof_enabled\">커널 스푸핑 활성화됨</string>\n    <string name=\"kernel_spoof_disabled_restored\">커널 스푸핑 비활성화 및 복원됨</string>\n    <string name=\"kernel_spoof_failed\">커널 스푸핑 실패: %d</string>\n    <string name=\"kernel_spoof_applied\">커널 스푸핑 적용됨</string>\n\n    <string name=\"settings_path_hide\">경로 숨기기</string>\n    <string name=\"settings_path_hide_summary\">커널 수준에서 앱에 파일 및 디렉터리 숨기기</string>\n    <string name=\"path_hide_paths_label\">숨길 경로 (한 줄에 하나씩)</string>\n\n    <string name=\"path_hide_paths_helper\">한 줄에 하나의 경로를 입력하세요. 일치하는 경로는 ENOENT를 반환합니다.</string>\n    <string name=\"path_hide_save\">저장</string>\n    <string name=\"path_hide_enabled\">경로 숨기기 활성화됨</string>\n    <string name=\"path_hide_disabled\">경로 숨기기 비활성화됨</string>\n    <string name=\"path_hide_applied\">경로 숨기기 설정이 적용됨</string>\n    <string name=\"path_hide_failed\">경로 숨기기 작업 실패: %d</string>\n    <string name=\"path_hide_uid_mode\">UID 실행 모드</string>\n    <string name=\"path_hide_uid_mode_summary\">지정한 앱 UID에 대해서만 경로를 숨깁니다</string>\n    <string name=\"path_hide_uids_label\">대상 UID (한 줄에 하나)</string>\n    <string name=\"path_hide_uids_helper\">앱 UID를 입력하세요. 이 앱들만 경로가 숨겨집니다.</string>\n    <string name=\"path_hide_uid_save\">UID 저장</string>\n    <string name=\"path_hide_uid_mode_enabled\">UID 실행 모드가 활성화되었습니다</string>\n    <string name=\"path_hide_uid_mode_disabled\">UID 실행 모드가 비활성화되었습니다</string>\n    <string name=\"path_hide_filter_system\">시스템 UID 필터링</string>\n    <string name=\"path_hide_filter_system_summary\">root 및 시스템 프로세스(UID &lt; 10000)에서도 경로 숨기기</string>\n    <string name=\"path_hide_filter_system_enabled\">시스템 UID 필터링이 활성화되었습니다</string>\n    <string name=\"path_hide_filter_system_disabled\">시스템 UID 필터링이 비활성화되었습니다</string>\n    <string name=\"path_hide_filter_system_warning_title\">경고</string>\n    <string name=\"path_hide_filter_system_warning_message\">활성화하면 root 및 시스템 프로세스(UID &lt; 10000)에서도 경로가 숨겨집니다. 일부 시스템 기능이 정상적으로 작동하지 않을 수 있습니다. 주의하여 진행하세요.</string>\n    <string name=\"path_hide_uid_applied\">UID 허용 목록이 적용되었습니다</string>\n    <string name=\"path_hide_select_apps\">앱 선택</string>\n    <string name=\"path_hide_no_apps_selected\">선택된 앱 없음</string>\n    <string name=\"path_hide_app_removed\">설치 제거된 %d개 앱의 설정을 제거했습니다</string>\n    <string name=\"path_hide_search_apps\">앱 검색…</string>\n    <string name=\"path_hide_show_system\">시스템 앱 표시</string>\n    <string name=\"netisolate_title\">네트워크 격리</string>\n    <string name=\"netisolate_enable\">네트워크 격리 활성화됨</string>\n    <string name=\"netisolate_disable\">네트워크 격리 비활성화됨</string>\n    <string name=\"netisolate_enable_summary\">커널 수준에서 선택한 앱의 네트워크 접근 차단</string>\n    <string name=\"netisolate_uids_label\">차단된 앱</string>\n    <string name=\"netisolate_uids_hint\">차단할 UID 입력 (한 줄에 하나)</string>\n    <string name=\"netisolate_no_uids\">차단된 앱이 없습니다</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-mgl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\" translatable=\"false\">FolkPatch</string>\n    <string name=\"app_name_fpatch\">FPatch</string>\n\n    <string name=\"desktop_app_name\">魔法应用名称</string>\n\n    <string name=\"home\">魔法大厅</string>\n\n    <string name=\"success\">变身成功</string>\n    <string name=\"failure\">变身失败</string>\n\n    <string name=\"patch_warnning\">施展魔法有风险，请确保已保存珍贵的回忆。</string>\n    <string name=\"patch\">施展魔法</string>\n\n    <string name=\"kernel_patch\">心之契约</string>\n    <string name=\"android_patch\">星之守护</string>\n\n    <string name=\"settings_nav_layout_title\">星图导航设定</string>\n    <string name=\"settings_nav_layout_summary\">隐藏或显示星图导航的部分组件</string>\n    <string name=\"settings_nav_scheme\">星图导航方案</string>\n    <string name=\"settings_nav_mode\">星图导航栏模式</string>\n    <string name=\"settings_nav_mode_summary\">选择星图导航栏的显示方式</string>\n    <string name=\"settings_nav_mode_auto\">魔法自动传统</string>\n    <string name=\"settings_nav_mode_bottom\">始终底部星图导航栏</string>\n    <string name=\"settings_nav_mode_rail\">始终侧边星图导航栏</string>\n    <string name=\"settings_nav_mode_floating\">悬浮底部星图导航栏</string>\n    <string name=\"settings_show_apm\">显示星之魔法阵</string>\n    <string name=\"settings_show_kpm\">显示心之结晶</string>\n    <string name=\"settings_show_superuser\">显示魔法使</string>\n    <string name=\"settings_floating_auto_hide\">魔法底栏自动隐遁</string>\n    <string name=\"settings_floating_auto_hide_summary\">3秒无魔法操作后底栏自动隐遁</string>\n    <string name=\"settings_floating_swipe_hide\">魔法底栏滑动隐遁</string>\n    <string name=\"settings_floating_swipe_hide_summary\">向下划动隐遁底栏，向上划动现出底栏</string>\n    <string name=\"settings_navbar_glass_effect\">幻彩毛玻璃效果</string>\n    <string name=\"settings_navbar_glass_effect_summary\">为悬浮星图导航栏施加幻彩毛玻璃魔法视觉效果</string>\n    <string name=\"settings_navbar_glass_blur_strength\">魔法模糊强度</string>\n    <string name=\"settings_navbar_glass_transparency\">魔法背景透明度</string>\n    <string name=\"settings_navbar_glass_highlight_strength\">星光高光强度</string>\n    <string name=\"settings_navbar_glass_specular\">魔法镜面反射</string>\n    <string name=\"settings_navbar_glass_specular_summary\">在星图导航栏顶部施展镜面高光魔法效果</string>\n    <string name=\"settings_navbar_glass_inner_glow\">魔法内发光</string>\n    <string name=\"settings_navbar_glass_inner_glow_summary\">在星图导航栏底部施展微光魔法效果</string>\n    <string name=\"settings_navbar_glass_border\">魔法玻璃边框</string>\n    <string name=\"settings_navbar_glass_border_summary\">在星图导航栏周围施展微妙的魔法边框描边</string>\n    <string name=\"settings_stats_top_layout\">顶部魔法阵</string>\n    <string name=\"settings_stats_top_layout_summary\">选择顶部信息魔法卡样式</string>\n    <string name=\"settings_stats_top_layout_list\">列表魔法卡</string>\n    <string name=\"settings_stats_top_layout_grid\">网格魔法卡</string>\n    <string name=\"settings_block_kernelpatch_update\">封印心之契约更新感应</string>\n    <string name=\"settings_block_kernelpatch_update_summary\">不再接收来自心之契约的更新讯号</string>\n    <string name=\"settings_block_androidpatch_update\">封印魔法阵法更新感应</string>\n    <string name=\"settings_block_androidpatch_update_summary\">不再接收来自魔法阵法的更新讯号</string>\n    <string name=\"settings_disable_module_update_check\">禁用魔法阵更新检查</string>\n    <string name=\"settings_disable_module_update_check_summary\">禁用星之魔法阵的自动更新检查</string>\n\n    <string name=\"reboot\">重新觉醒</string>\n    <string name=\"settings\">魔法设定</string>\n    <string name=\"reboot_recovery\">进入治愈法阵</string>\n    <string name=\"reboot_bootloader\">前往召唤阵</string>\n    <string name=\"reboot_download\">进入星之回廊</string>\n    <string name=\"reboot_edl\">进入梦境之门</string>\n    <string name=\"reboot_fastbootd\">前往星界传送阵</string>\n    <string name=\"about\">我来自哪?</string>\n    <string name=\"developer_and_maintainer\">施法者 | 守护者</string>\n    <string name=\"settings_app_language\">魔法语言</string>\n    <string name=\"system_default\">默认魔法</string>\n    <string name=\"settings_global_namespace_mode\">全域魔法领域</string>\n    <string name=\"settings_global_namespace_mode_summary\">所有变身仪式共享同一个魔法空间</string>\n    <string name=\"settings_clear_super_key_dialog\">确定要继续施展这个魔法吗？</string>\n\n    <string name=\"home_learn_apatch\">了解心之契约</string>\n    <string name=\"home_click_to_learn_apatch\">了解心之契约的魔力及使用方法</string>\n    <string name=\"settings_hide_apatch_card\">隐藏\"了解心之契约\"魔法阵</string>\n    <string name=\"settings_hide_apatch_card_summary\">隐藏魔法大厅的\"了解心之契约\"魔法阵</string>\n\n    <string name=\"about_source_code\"><![CDATA[<p>在 %1$s 查看魔法书<p/>加入我们的 %2$s 魔法学院<p/>加入我们的 %3$s 魔法少女公会]]></string>\n    <string name=\"send_log\">发送魔法记录</string>\n    <string name=\"save_log\">保存魔法记录</string>\n    <string name=\"log_saved\">魔法记录已保存</string>\n    <string name=\"safe_mode\">安全变身模式</string>\n    <string name=\"github\">GitHub</string>\n    <string name=\"telegram\">Telegram</string>\n    <string name=\"support_or_donate\">支持 / 捐赠魔法</string>\n\n    <string name=\"super_key\">心之密钥</string>\n    <string name=\"clear_super_key\">清除心之密钥</string>\n    <string name=\"patch_set_superkey\">设定心之密钥</string>\n    <string name=\"home_patch_set_key_desc\">心之契约的唯一认证咒语</string>\n    <string name=\"home_patch_next_step\">下一步咒语</string>\n\n    <string name=\"home_not_installed\">尚未觉醒</string>\n    <string name=\"home_install_unknown\">未觉醒或未认证</string>\n    <string name=\"home_install_unknown_summary\">点击觉醒魔法</string>\n    <string name=\"home_click_to_install\">点击觉醒</string>\n    <string name=\"home_working\">神域展开</string>\n    <string name=\"home_kp_need_update\">发现新的魔法咒语</string>\n    <string name=\"home_kp_cando_update\">更新咒语</string>\n\n    <string name=\"home_installing\">正在觉醒中</string>\n\n    <string name=\"kpatch_version\">魔法等级: %s</string>\n    <string name=\"apatch_version\">契约等级: %s</string>\n\n    <string name=\"kpatch_version_update\" formatted=\"false\">魔法等级: %s -> %s</string>\n    <string name=\"kpatch_shadow_path_title\">心之契约</string>\n    <string name=\"home_auth_key_title\">吟唱心之密钥</string>\n    <string name=\"home_auth_key_desc\">认证通过后开始使用魔法</string>\n    <string name=\"home_kpatch_info_title\">魔法情报</string>\n\n    <string name=\"home_ap_cando_install\">觉醒</string>\n\n    <string name=\"home_ap_cando_uninstall\">解除契约</string>\n    <string name=\"home_ap_cando_reboot\">重新觉醒</string>\n\n    <string name=\"patch_title\">施展魔法</string>\n\n    <string name=\"patch_config_title\">魔法阵</string>\n    <string name=\"patch_mode_bootimg_patch\">模式：魔法附魔</string>\n    <string name=\"patch_mode_patch_and_install\">模式：附魔并觉醒</string>\n    <string name=\"patch_mode_install_to_next_slot\">模式：转移到备用星之容器 (OTA仪式后)</string>\n    <string name=\"patch_mode_uninstall_patch\">模式：解除心之契约</string>\n    <string name=\"patch_select_bootimg_btn\">选择魔法容器</string>\n    <string name=\"patch_embed_kpm_btn\">镶嵌星之魔法阵</string>\n    <string name=\"patch_start_patch_btn\">开始施法</string>\n    <string name=\"patch_start_unpatch_btn\">解除附魔</string>\n    <string name=\"patch_item_error\">!!魔法反噬!!</string>\n    <string name=\"patch_item_bootimg\">魔法容器</string>\n    <string name=\"patch_item_bootimg_slot\">星之位置:</string>\n    <string name=\"patch_item_bootimg_dev\">魔法道具:</string>\n    <string name=\"patch_item_kernel\">心之核心</string>\n    <string name=\"patch_item_kpimg\">契约印记</string>\n    <string name=\"patch_item_kpimg_version\">魔法等级:</string>\n    <string name=\"patch_item_kpimg_comile_time\">施法时间:</string>\n    <string name=\"patch_item_kpimg_config\">魔法配置:</string>\n    <string name=\"patch_item_new_extra_kpm\">镶嵌新星之魔法阵</string>\n    <string name=\"patch_item_existed_extra_kpm\">已存在</string>\n    <string name=\"patch_item_extra_name\">魔法名称:</string>\n    <string name=\"patch_item_extra_version\">魔法等级:</string>\n    <string name=\"patch_item_extra_author\">创造者:</string>\n    <string name=\"patch_item_extra_kpm_license\">魔法契约:</string>\n    <string name=\"patch_item_extra_kpm_desciption\">魔法描述:</string>\n    <string name=\"patch_item_extra_args\">咒语参数:</string>\n    <string name=\"patch_item_extra_event\">触发事件:</string>\n    <string name=\"patch_item_skey\">心之密钥</string>\n    <string name=\"patch_custom_superkey\">自定义心之密钥</string>\n    <string name=\"patch_custom_superkey_summary\">你仍可设心之密钥来管理魔法，魔法书已获内印授权</string>\n    <string name=\"patch_item_set_skey_label\">心之密钥长度应为8-63个字符，包含星光和月辉，不能含有混沌之力。</string>\n    <string name=\"patch_confirm_superkey\">再次凝炼心之密钥</string>\n    <string name=\"patch_skey_mismatch\">两次心之密钥不契合</string>\n\n    <string name=\"home_kernel\">心之核心等级</string>\n    <string name=\"home_manager_version\">契约管理器等级</string>\n    <string name=\"home_fingerprint\">魔法指纹</string>\n\n    <string name=\"home_selinux_status\">魔法结界状态</string>\n    <string name=\"home_selinux_status_disabled\">结界已解除</string>\n    <string name=\"home_selinux_status_enforcing\">结界强制执行中</string>\n    <string name=\"home_selinux_status_permissive\">结界宽容模式</string>\n    <string name=\"home_selinux_status_unknown\">结界状态未知</string>\n\n    <string name=\"settings_selinux_mode\">魔法结界模式</string>\n    <string name=\"settings_selinux_mode_summary\">选择魔法结界的执行模式</string>\n    <string name=\"settings_selinux_mode_enforcing\">强制执行 (严格)</string>\n    <string name=\"settings_selinux_mode_permissive\">宽容模式 (宽松)</string>\n    <string name=\"settings_selinux_current_mode\">当前: %s</string>\n    <string name=\"settings_selinux_mode_enforcing_summary\">魔法结界将严格执行所有魔法规则</string>\n    <string name=\"settings_selinux_mode_permissive_summary\">魔法结界仅记录违规行为，不会阻止</string>\n\n    <string name=\"home_device_info\">魔法道具</string>\n    <string name=\"home_system_version\">星界系统等级</string>\n    <string name=\"home_kpatch_version\">心之契约等级</string>\n    <string name=\"home_su_path\">魔法使路径</string>\n    <string name=\"home_apatch_version\">魔法少女工坊版本</string>\n\n    <string name=\"kpm\">心之结晶</string>\n    <string name=\"kpm_kp_not_installed\">心之契约尚未觉醒</string>\n    <string name=\"kpm_add_kpm\">添加心之结晶</string>\n    <string name=\"kpm_load\">激活</string>\n    <string name=\"kpm_install\">契约</string>\n    <string name=\"kpm_embed\">融合</string>\n    <string name=\"kpm_load_toast_succ\">激活成功</string>\n    <string name=\"kpm_load_toast_failed\">激活失败</string>\n    <string name=\"kpm_unload_confirm\">卸载%s心之结晶？</string>\n    <string name=\"kpm_unload\">解除契约</string>\n    <string name=\"kpm_control\">操控</string>\n    <string name=\"kpm_apm_empty\">未激活心之结晶</string>\n    <string name=\"kpm_version\">魔法等级</string>\n    <string name=\"kpm_license\">魔法契约</string>\n    <string name=\"kpm_author\">创造者</string>\n    <string name=\"kpm_desc\">魔法描述</string>\n    <string name=\"kpm_args\">咒语参数</string>\n\n    <string name=\"su_title\">魔法使</string>\n    <string name=\"su_selinux_via_hook\">通过魔法阵绕过结界</string>\n    <string name=\"su_pkg_excluded_label\">魔法免疫</string>\n    <string name=\"su_batch_exclude_title\">批量免疫</string>\n    <string name=\"su_batch_exclude_content\">为全部非魔法使施加免疫咒语,请选择一个魔法</string>\n    <string name=\"su_exclude_btn\">施加免疫</string>\n    <string name=\"su_exclude_reverse_btn\">解除免疫</string>\n\n    <!-- 超级用户应用操作弹窗 -->\n    <string name=\"su_app_action_title\">魔法操作</string>\n    <string name=\"su_app_action_content\">请选择一个魔法来对应用施放</string>\n    <string name=\"su_app_action_launch\">召唤应用</string>\n    <string name=\"su_app_action_force_stop\">封印应用</string>\n    <string name=\"su_app_action_launch_success\">正在召唤 %s</string>\n    <string name=\"su_app_action_force_stop_success\">已封印 %s</string>\n    <string name=\"su_app_action_failed\">魔法失效: %s</string>\n\n    <!-- SU Audit Log -->\n    <string name=\"su_audit_log_title\">授权日志</string>\n    <string name=\"su_audit_log_empty\">暂无授权记录</string>\n    <string name=\"su_audit_log_clear\">清除日志</string>\n    <string name=\"su_audit_log_clear_confirm\">确认清除所有授权记录？</string>\n    <string name=\"su_audit_action_grant\">已授权</string>\n    <string name=\"su_audit_action_revoke\">已撤销</string>\n    <string name=\"su_audit_action_exclude\">已排除</string>\n    <string name=\"su_audit_tab_usage\">使用记录</string>\n    <string name=\"su_audit_tab_operations\">操作记录</string>\n\n    <string name=\"su_pkg_excluded_setting_title\">免疫设置</string>\n    <string name=\"su_pkg_excluded_setting_summary\">启用此选项将允许魔法工坊恢复此应用所受的魔法影响。</string>\n    <string name=\"su_pkg_root_setting_title\">魔法使</string>\n    <string name=\"su_pkg_root_setting_summary\">启用此选项为你的应用启动魔法少女之力，应用能够使用魔法咒语</string>\n    <string name=\"su_pkg_normal_setting_title\">凡人模式</string>\n    <string name=\"su_pkg_normal_setting_summary\">凡人状态，无魔法少女之力加持，不能使用魔法咒语。</string>\n    <string name=\"su_show_system_apps\">显示系统魔法使</string>\n    <string name=\"su_hide_system_apps\">隐藏系统魔法使</string>\n    <string name=\"su_refresh\">刷新魔法阵</string>\n\n    <string name=\"apm\">星之魔法阵</string>\n    <string name=\"apm_not_installed\">星之守护尚未觉醒</string>\n    <string name=\"apm_failed_to_enable\">激活魔法阵失败: %s</string>\n    <string name=\"apm_failed_to_disable\">解除魔法阵失败: %s</string>\n    <string name=\"apm_empty\">未契约魔法阵</string>\n    <string name=\"apm_remove\">移除</string>\n    <string name=\"apm_undo\">恢复契约</string>\n    <string name=\"apm_install\">契约</string>\n    <string name=\"apm_uninstall_confirm\">解除%s魔法阵契约？</string>\n    <string name=\"apm_uninstall_success\">%s已解除契约</string>\n    <string name=\"apm_uninstall_failed\">解除契约失败: %s</string>\n    <string name=\"apm_undo_uninstall_success\">%s契约恢复成功</string>\n    <string name=\"apm_undo_uninstall_failed\">契约恢复失败：%s</string>\n    <string name=\"apm_version\">魔法等级</string>\n    <string name=\"apm_author\">创造者</string>\n    <string name=\"apm_desc\">魔法描述</string>\n    <string name=\"apm_overlay_fs_not_available\">星之回廊被心之核心封印，魔法阵不可用！</string>\n    <string name=\"apm_magisk_conflict\">由于与Magisk魔法阵冲突，此魔法阵不可用！</string>\n    <string name=\"apm_mount_warning_title\">重要提示</string>\n    <string name=\"apm_mount_warning_message\">默认不挂载魔法阵，请使用内置挂载系统或者元魔法阵</string>\n    <string name=\"apm_mount_warning_button\">我知道了</string>\n    <string name=\"apm_reboot_to_apply\">重新觉醒后生效</string>\n    <string name=\"apm_changelog\">魔法更新记录</string>\n    <string name=\"apm_update\">更新魔法</string>\n    <string name=\"apm_downloading\">正在下载魔法阵: %s</string>\n    <string name=\"apm_start_downloading\">开始下载: %s</string>\n    <string name=\"apm_new_version_available\">发现新版本%s魔法阵，点击升级。</string>\n\n    <string name=\"hide_apatch_manager\">隐藏魔法工坊</string>\n    <string name=\"hide_apatch_manager_summary\">安装一个随机包名和自定义魔法名的代理魔法使</string>\n    <string name=\"hide_apatch_dialog_new_manager_name\">新魔法使名称</string>\n    <string name=\"hide_apatch_dialog_summary\">这将作为显示在星界中的新魔法使名称</string>\n    <string name=\"hide_apatch_manager_failure\">隐藏失败。请报告此魔法错误！</string>\n\n    <string name=\"setting_reset_su_path\">重置魔法使路径</string>\n    <string name=\"setting_reset_su_new_path\">新完整魔法路径</string>\n\n    <string name=\"settings_folkx_engine_title\">FolkX魔法动画引擎</string>\n    <string name=\"settings_folkx_engine_summary\">在顶层魔法页面切换时使用流畅的物理弹簧动画</string>\n    <string name=\"settings_folkx_animation_type\">魔法动画类型</string>\n    <string name=\"settings_folkx_animation_speed\">异次元速度</string>\n    <string name=\"settings_folkx_animation_linear\">魔法线性运动</string>\n    <string name=\"settings_folkx_animation_spatial\">魔法空间运动</string>\n    <string name=\"settings_folkx_animation_fade\">魔法渐隐渐显</string>\n    <string name=\"settings_folkx_animation_vertical\">魔法垂直滑动</string>\n    <string name=\"settings_folkx_animation_diagonal\">魔法对角滑动</string>\n\n    <string name=\"apm_webui_open\">开启</string>\n    <string name=\"apm_action\">魔法操作</string>\n    <string name=\"module_shortcut_add\">魔法传送门</string>\n    <string name=\"module_shortcut_not_supported\">魔法使召唤界面不支持桌面魔法传送门。</string>\n    <string name=\"module_shortcut_created\">已在桌面创建魔法传送门。</string>\n    <string name=\"module_shortcut_updated\">魔法传送门已更新。</string>\n    <string name=\"module_shortcut_permission_tip_xiaomi\">请在小米魔法界面的设置中为本魔法使启用\"创建桌面魔法传送门\"权限。</string>\n    <string name=\"module_shortcut_permission_tip_oppo\">请在OPPO魔法界面的设置中为本魔法使启用\"桌面魔法传送门\"权限。</string>\n    <string name=\"module_shortcut_permission_tip_default\">若创建魔法传送门失败，请在魔法阵设置中为本魔法使启用桌面魔法传送门权限。</string>\n    <string name=\"module_shortcut_name\">魔法传送门名称</string>\n    <string name=\"module_shortcut_icon\">魔法传送门图标</string>\n    <string name=\"module_shortcut_type\">魔法传送门类型</string>\n    <string name=\"module_shortcut_type_webui\">WebUI</string>\n    <string name=\"module_shortcut_type_action\">Action</string>\n    <string name=\"module_shortcut_icon_default\">使用默认图标</string>\n    <string name=\"module_shortcut_icon_select\">选择图标</string>\n    <string name=\"enable_web_debugging\">启用魔法阵调试</string>\n    <string name=\"enable_web_debugging_summary\">可用于调试星界魔法阵。请仅在需要时启用。</string>\n    <string name=\"settings_apm_install_confirm\">安装魔法阵确认</string>\n    <string name=\"settings_apm_install_confirm_summary\">安装魔法阵前显示确认魔法阵</string>\n    <string name=\"settings_apm_stay_on_page\">留在魔法阵页面</string>\n    <string name=\"settings_apm_stay_on_page_summary\">星之魔法阵执行完魔法后不自动返回</string>\n    <string name=\"settings_enable_module_shortcut_add\">启用魔法传送门添加按钮</string>\n    <string name=\"settings_enable_module_shortcut_add_summary\">在系统魔法阵页面显示 WebUI 魔法传送门添加按钮</string>\n    <string name=\"settings_module_sort_optimization\">魔法阵排序优化</string>\n    <string name=\"settings_module_sort_optimization_summary\">让系统魔法阵带有WebUI和Action的魔法阵排在前面</string>\n    <string name=\"settings_fold_system_module\">折叠系统魔法阵</string>\n    <string name=\"settings_fold_system_module_summary\">点击魔法阵卡片展开/折叠操作按钮</string>\n    <string name=\"settings_simple_list_bottom_bar\">简约魔法底栏</string>\n    <string name=\"settings_simple_list_bottom_bar_summary\">魔法阵符法使用纯图标按钮样式，灵感来自APatch</string>\n    <string name=\"settings_spliced_card_group\">Spliced Card Group</string>\n    <string name=\"settings_spliced_card_group_summary\">Use M3E continuous card group style for module list</string>\n    <string name=\"settings_show_more_module_info\">显示魔法阵详细信息</string>\n    <string name=\"settings_show_more_module_info_summary\">在魔法阵列表中显示魔法阵 ID 和大小</string>\n    <string name=\"apm_install_confirm_title\">安装魔法阵</string>\n    <string name=\"apm_install_confirm_content\">确定要契约%s魔法阵吗？</string>\n    <string name=\"apm_disable_all_title\">禁用所有魔法阵</string>\n    <string name=\"apm_disable_all_confirm\">确定禁用全部魔法阵吗？此操作将停用所有已安装的魔法阵</string>\n    <string name=\"apm_enable_module_banner\">启用魔法阵横幅</string>\n    <string name=\"apm_enable_module_banner_summary\">为支持的魔法阵显示横幅图片</string>\n    <string name=\"apm_enable_folk_banner\">自定义魔法阵横幅</string>\n    <string name=\"apm_enable_folk_banner_summary\">长按魔法阵卡片自选横幅图片</string>\n    <string name=\"apm_folk_banner_title\">FolkBanner</string>\n    <string name=\"apm_folk_banner_select\">选择图片</string>\n    <string name=\"apm_folk_banner_clear\">清除 FolkBanner</string>\n    <string name=\"apm_folk_banner_saved\">已为 %s 注入 FolkBanner。</string>\n    <string name=\"apm_folk_banner_cleared\">已为 %s 清除 FolkBanner。</string>\n    <string name=\"apm_folk_banner_failed\">为 %s 更新 FolkBanner 失败。</string>\n    <string name=\"apm_banner_api_mode\">API模式</string>\n    <string name=\"apm_banner_api_mode_summary\">使用随机图API或本地目录为模块提供横幅</string>\n    <string name=\"apm_banner_api_source\">配置API源</string>\n    <string name=\"apm_banner_api_source_hint\">输入API地址或本地路径</string>\n    <string name=\"apm_banner_api_source_saved\">API源已保存</string>\n    <string name=\"apm_banner_api_source_not_configured\">未配置</string>\n    <string name=\"apm_banner_api_url_configured\">API地址已配置</string>\n    <string name=\"apm_banner_local_dir_configured\">本地目录已配置</string>\n    <string name=\"apm_banner_clear_cache\">清除缓存</string>\n    <string name=\"apm_banner_cache_cleared\">横幅缓存已清除</string>\n    <string name=\"apm_banner_api_config_title\">配置API源</string>\n    <string name=\"apm_banner_api_config_desc\">输入随机图API地址或本地目录路径，每个模块将获得唯一的横幅图片。</string>\n    <string name=\"apm_banner_api_examples_title\">示例：</string>\n    <string name=\"apm_banner_api_examples\">API: https://picsum.photos/800/400\\n本地: /sdcard/Pictures/Banners</string>\n    <!-- API 魔法集市 -->\n    <string name=\"apm_api_marketplace_title\">API 魔法集市</string>\n    <string name=\"apm_api_preview\">预见</string>\n    <string name=\"apm_api_apply\">施展</string>\n    <string name=\"apm_api_preview_title\">API 魔法预见</string>\n    <string name=\"apm_api_preview_failed\">魔法预见失败</string>\n    <string name=\"apm_api_url_label\">API 魔法阵地址：</string>\n    <string name=\"apm_api_apply_success\">魔法源施展成功</string>\n    <string name=\"apm_api_verifying\">魔法鉴定中…</string>\n    <string name=\"apm_api_retry\">重新施展</string>\n    <string name=\"apm_api_empty\">暂无魔法源</string>\n    <string name=\"settings_banner_custom_opacity\">横幅透光率</string>\n    <string name=\"settings_banner_custom_opacity_summary\">自定义横幅透光率，不再跟随桌布模式</string>\n    <string name=\"settings_banner_opacity\">横幅不透光率</string>\n\n    <string name=\"settings_donot_store_superkey\">不在本地存储心之密钥</string>\n    <string name=\"settings_donot_store_superkey_summary\">每次启动魔法工坊时验证心之密钥</string>\n\n    <string name=\"mode_select_page_title\">觉醒</string>\n    <string name=\"mode_select_page_patch_and_install\">附魔并觉醒</string>\n    <string name=\"mode_select_page_select_file\">选择要附魔的魔法容器</string>\n    <string name=\"mode_select_page_install_inactive_slot\">契约到备用星之容器 (OTA仪式后)</string>\n    <string name=\"mode_select_page_install_inactive_slot_warning\">你的魔法道具将在重新觉醒后**强制**启动到当前未激活的星之位置！\\n仅在OTA仪式完成后使用此选项。\\n继续吗？</string>\n    <string name=\"mode_select_page_select_kpimg\">使用本地魔法补丁 (KPimg)</string>\n    <string name=\"patch_custom_kpimg_label\">自定义 KPimg</string>\n    <string name=\"patch_custom_kpimg_file\">文件: %s</string>\n    <string name=\"patch_select_kpimg_btn\">选择 KPimg</string>\n\n    <string name=\"home_dialog_auth_fail_title\">认证失败</string>\n    <string name=\"home_dialog_auth_fail_content\">无法认证心之密钥，导致 FolkPatch 激活失败。\n以下是认证失败的一些可能原因——请对号入座：\n\n\n\\n1. 你根本没使用心之契约来附魔 boot.img，或者忘了自己到底做了啥。\n\\n2. 附魔完的 boot.img 还躺在电脑里睡觉，根本没激活进魔法道具。\n\\n3. 心之密钥输错了，或者夹杂了混沌符号，比如来自异界符文的字符。\n\\n4. 你的魔法道具可能并不兼容 FolkPatch 和心之契约，强行施法也是徒勞。\n\\n5. 你可能搞了一些神秘操作——比如用了某些排除咒语的魔法把 FolkPatch 给屏蔽了，结果把自己整出局了。\n\\n请先好好检查一遍，再试一次。如果问题依旧，欢迎到官方魔法塔的 issue 页面提问。\n毕竟，有些问题可能纯粹是您自己亲手造成的！\n祝你好运啊！顺带一提，你点击下面的魔导书按钮准没错！</string>\n\n    <string name=\"home_more_menu_feedback_or_suggestion\">反馈或建议</string>\n    <string name=\"home_more_menu_about\">关于</string>\n    <string name=\"home_more_menu_document\">魔导书</string>\n\n    <string name=\"home_dialog_uninstall_title\">解除契约</string>\n    \n    <string name=\"home_dialog_uninstall_ap_only\">仅解除魔法契约</string>\n    <string name=\"home_dialog_uninstall_ap_only_desc\">仅移除魔法契约，保留部分魔力</string>\n    <string name=\"home_dialog_uninstall_all\">完全解除契约</string>\n    <string name=\"home_dialog_uninstall_all_desc\">移除魔法契约并进入完整解除流程。</string>\n    \n    <!-- Install Mode Select -->\n    <string name=\"kp_install_methods\">魔法契约修补/安装</string>\n    <string name=\"restore_boot_methods\">选择一个启动镜像来恢复到引导分区</string>\n    <string name=\"restore_select_file\">选择一个要还原的分区引导文件</string>\n\n    <string name=\"kpm_control_dialog_title\">操控心之结晶</string>\n    <string name=\"kpm_control_dialog_content\">请输入操控咒语：</string>\n    <string name=\"kpm_control_paramters\">咒语参数</string>\n    <string name=\"kpm_control_outMsg\">魔法输出</string>\n    <string name=\"kpm_control_ok\">魔法成功！</string>\n    <string name=\"kpm_control_failed\">魔法失败！</string>\n    \n    <string name=\"settings_night_mode_follow_sys\">星夜模式跟随星界</string>\n    <string name=\"settings_night_mode_follow_sys_summary\">根据星界设置自动切换星夜模式</string>\n    <string name=\"settings_night_theme_enabled\">星夜模式</string>\n    \n    <string name=\"settings_use_system_color_theme\">星界颜色主题</string>\n    <string name=\"settings_use_system_color_theme_summary\">使用星界根据魔法阵生成的颜色主题</string>\n    <string name=\"settings_custom_color_theme\">颜色魔法主题</string>\n\n    <string name=\"amber_theme\">琥珀之光</string>\n    <string name=\"blue_theme\">星空之蓝</string>\n    <string name=\"blue_grey_theme\">月影之灰</string>\n    <string name=\"brown_theme\">大地之棕</string>\n    <string name=\"cyan_theme\">水之青</string>\n    <string name=\"deep_orange_theme\">夕阳之橙</string>\n    <string name=\"deep_purple_theme\">神秘之紫</string>\n    <string name=\"green_theme\">森林之绿</string>\n    <string name=\"indigo_theme\">靛蓝之夜</string>\n    <string name=\"light_blue_theme\">天空之蓝</string>\n    <string name=\"light_green_theme\">嫩芽之绿</string>\n    <string name=\"lime_theme\">酸橙之光</string>\n    <string name=\"orange_theme\">晨光之橙</string>\n    <string name=\"pink_theme\">樱花之粉</string>\n    <string name=\"purple_theme\">紫罗兰之紫</string>\n    <string name=\"red_theme\">火焰之红</string>\n    <string name=\"sakura_theme\">樱花飞舞</string>\n    <string name=\"teal_theme\">蓝绿之韵</string>\n    <string name=\"yellow_theme\">阳光之黄</string>\n    <string name=\"theme_color\">魔法主题颜色</string>\n    <string name=\"theme_light\">光之形态</string>\n    <string name=\"theme_dark\">暗之形态</string>\n    <string name=\"theme_system\">魔法世界</string>\n    <string name=\"loading_modules\">正在召唤魔法阵…</string>\n    <string name=\"loading_scripts\">正在搜寻魔法咒语…</string>\n    <string name=\"loading_themes\">正在加载魔法主题…</string>\n    <string name=\"loading_apis\">正在加载魔法源…</string>\n\n    <string name=\"about_app_desc\">一个基于心之核心的魔法变身解决方案，支持心之结晶和心之函数hook，无需重新锻造心之核心。</string>\n    <string name=\"about_powered_by\">由%1$s提供魔力</string>\n    <string name=\"about_telegram_group\">魔法少女通讯群组</string>\n    \n    <!-- Online Module Strings -->\n    <string name=\"online_module_title\">在线星之魔法阵</string>\n    <string name=\"online_module_download_start\">开始召唤: %s</string>\n    <string name=\"online_module_download_complete\">召唤完成: %s</string>\n    <string name=\"online_module_download_notification\">正在召唤%s。请查看魔法水晶球了解进度，召唤完成后请查看魔法仓库。</string>\n\n    <!-- Online KPM Strings -->\n    <string name=\"online_kpm_title\">在线心之结晶</string>\n    <string name=\"online_kpm_download_start\">开始召唤: %s</string>\n    <string name=\"online_kpm_download_complete\">召唤完成: %s</string>\n    <string name=\"online_kpm_download_notification\">正在召唤%s。请查看魔法水晶球了解进度，召唤完成后请查看魔法仓库。</string>\n\n    <!-- Online Script Strings -->\n    <string name=\"online_script_title\">在线魔法咒语</string>\n    <string name=\"online_script_download_start\">开始召唤: %s</string>\n    <string name=\"online_script_download_complete\">召唤完成: %s</string>\n    <string name=\"online_script_download_notification\">正在召唤%s</string>\n\n    <!-- Crash Handle Strings -->\n    <string name=\"crash_handle_title\">魔法事故报告</string>\n    <string name=\"crash_handle_copy\">复制</string>\n\n    <!-- About Screen Strings -->\n    <string name=\"about_app_version\">应用魔法等级: %s</string>\n    <string name=\"about_github\">魔法书仓库</string>\n    <string name=\"about_telegram_channel\">魔法少女通讯频道</string>\n\n    <string name=\"settings_app_dpi\">魔法使清晰度</string>\n    <string name=\"dpi_apply_settings\">适月</string>\n    <string name=\"dpi_confirm_title\">确认更改DPI</string>\n    <string name=\"dpi_confirm_message\">将应用DPI从%1$s更改为%2$s？</string>\n    <string name=\"settings_magic_mount\">Folk Mount API</string>\n    <string name=\"settings_magic_mount_summary\">启用内置的魔法阵挂载系统</string>\n    <string name=\"settings_new_app_profile_mode\">新魔法使默认形态</string>\n    <string name=\"settings_new_app_profile_normal\">日常形态</string>\n    <string name=\"settings_new_app_profile_root\">魔法形态</string>\n    <string name=\"settings_new_app_profile_exclude\">驱逐</string>\n    <string name=\"settings_hide_service\">FolkPatch Hide</string>\n    <string name=\"settings_hide_service_summary\">用魔法阵隐藏Bootloader解锁状态</string>\n    <string name=\"settings_app_title\">魔法使名称</string>\n    <string name=\"app_title_custom\">自定义魔法名</string>\n    <string name=\"settings_custom_app_title\">设置魔法使名称</string>\n    <string name=\"custom_app_title_dialog_title\">自定义魔法使名称</string>\n    <string name=\"custom_app_title_dialog_hint\">请输入魔法使名称</string>\n    <string name=\"custom_app_title_dialog_confirm\">确认</string>\n    <string name=\"custom_app_title_dialog_empty\">名称不能为空</string>\n    <string name=\"cancel\">取消</string>\n    <string name=\"settings_custom_background\">自定义魔法背景</string>\n    <string name=\"settings_custom_background_enabled\">已启用自定义魔法背景</string>\n    <string name=\"settings_custom_background_opacity\">魔法阵透明度</string>\n    <string name=\"settings_alt_icon\">备用魔法图标</string>\n    <string name=\"alt_icon_summary\">使用备选魔法启动器图标</string>\n    <string name=\"settings_custom_background_blur\">魔法背景模糊</string>\n    <string name=\"settings_custom_background_dim\">背景暗度</string>\n    <string name=\"settings_select_background_image\">选择魔法背景图片</string>\n    <string name=\"settings_custom_background_summary\">设置自定义魔法背景图片</string>\n    <string name=\"settings_custom_background_saved\">魔法背景保存成功</string>\n    <string name=\"settings_custom_background_error\">保存魔法背景失败</string>\n    <string name=\"settings_background_selected\">已选择魔法背景</string>\n    <string name=\"settings_background_image_cleared\">魔法背景已清除</string>\n    <string name=\"settings_clear_background\">清除魔法背景</string>\n    <string name=\"settings_clear_background_confirm\">确定要清除魔法背景图片吗？</string>\n    <string name=\"settings_multi_background_mode\">次元多魔法</string>\n    <string name=\"settings_multi_background_mode_summary\">为不同页面设置不同的魔法背景图片</string>\n    <string name=\"settings_select_home_background\">首页魔法背景</string>\n    <string name=\"settings_select_kernel_background\">内核模块魔法背景</string>\n    <string name=\"settings_select_superuser_background\">超级用户魔法背景</string>\n    <string name=\"settings_select_system_module_background\">系统模块魔法背景</string>\n    <string name=\"settings_select_settings_background\">设置页面魔法背景</string>\n\n    <string name=\"settings_advanced_title_style\">高级魔法标题样式</string>\n    <string name=\"settings_advanced_title_style_summary\">使用魔法图片替换顶部标题</string>\n    <string name=\"settings_advanced_title_style_enabled\">高级魔法标题样式已开启</string>\n    <string name=\"settings_select_title_image\">选择魔法标题图片</string>\n    <string name=\"settings_title_image_selected\">已选择魔法标题图片</string>\n    <string name=\"settings_title_image_saved\">魔法标题图片保存成功</string>\n    <string name=\"settings_title_image_error\">保存魔法标题图片失败</string>\n    <string name=\"settings_clear_title_image\">清除魔法标题图片</string>\n    <string name=\"settings_clear_title_image_confirm\">确定要清除魔法标题图片吗？</string>\n    <string name=\"settings_title_image_cleared\">魔法标题图片已清除</string>\n    <string name=\"settings_title_image_day_opacity\">白天模式透明度</string>\n    <string name=\"settings_title_image_night_opacity\">夜间模式透明度</string>\n    <string name=\"settings_title_image_dim\">背景魔法暗度</string>\n    <string name=\"settings_title_image_offset_x\">水平位置</string>\n\n    <string name=\"settings_launcher_icon\">魔法使徽章</string>\n    <string name=\"su_exclude_all_title\">批量免疫</string>\n    <string name=\"su_exclude_all_confirm\">确定要将所有未觉醒的魔法使施加免疫咒语吗？</string>\n\n    <!-- 隐藏设定字符串 -->\n    <string name=\"home_hide_su_path\">隐藏魔法使路径</string>\n    <string name=\"home_hide_kpatch_version\">隐藏心之核心契约等级</string>\n    <string name=\"home_hide_fingerprint\">隐藏魔法指纹</string>\n    <string name=\"home_hide_zygisk\">隐藏 Zygisk 魔法实现</string>\n    <string name=\"home_hide_mount\">隐藏魔法结界</string>\n    <string name=\"home_hide_su_path_summary\">在魔法情报卡片中隐藏魔法使路径</string>\n    <string name=\"home_hide_kpatch_version_summary\">在魔法情报卡片中隐藏心之核心契约等级信息</string>\n    <string name=\"home_hide_zygisk_summary\">在魔法情报卡片中隐藏 Zygisk 魔法实现信息</string>\n    <string name=\"home_hide_mount_summary\">在魔法情报卡片中隐藏魔法结界信息</string>\n    <string name=\"home_hide_fingerprint_summary\">在魔法情报卡片中隐藏魔法指纹信息</string>\n    <string name=\"settings_grid_working_card_hide_check\">魔法少女隐身术</string>\n    <string name=\"settings_grid_working_card_hide_check_summary\">隐藏魔法少女卡片上的魔法符印或警示标记</string>\n    <string name=\"settings_grid_working_card_hide_text\">魔法少女文字隐身术</string>\n    <string name=\"settings_grid_working_card_hide_text_summary\">隐藏魔法少女卡片上的\"神域展开\"或\"未觉醒\"文字</string>\n    <string name=\"settings_grid_working_card_hide_mode\">魔法少女模式隐身术</string>\n    <string name=\"settings_grid_working_card_hide_mode_summary\">隐藏魔法少女卡片上的\"Full\"或\"Half\"文字</string>\n    <string name=\"settings_list_card_hide_status_badge\">使用经典表情</string>\n    <string name=\"settings_list_card_hide_status_badge_summary\">首页的魔力印记即刻变身经典表情</string>\n    <string name=\"settings_custom_badge_text\">自选魔力印记文字</string>\n    <string name=\"settings_custom_badge_text_summary\">修改魔力印记的文字显示，仅供魔法少女娱乐</string>\n    <string name=\"settings_custom_background_dual_dim\">双向契约</string>\n    <string name=\"settings_custom_background_dual_dim_desc\">根据光之形态和暗之形态自动调整魔法背景暗度</string>\n    <string name=\"settings_custom_background_day_dim\">星之契约魔力</string>\n    <string name=\"settings_custom_background_night_dim\">月之契约魔力</string>\n    <string name=\"settings_grid_working_card_dual_opacity\">双向契约透明</string>\n    <string name=\"settings_grid_working_card_dual_opacity_desc\">根据光之形态和暗之形态自动调整魔法卡片透明度</string>\n    <string name=\"settings_grid_working_card_day_opacity\">日间魔力透明</string>\n    <string name=\"settings_grid_working_card_night_opacity\">月间魔力透明</string>\n    <!-- KPM AutoLoad Strings -->\n    <string name=\"kpm_autoload_title\">心之结晶自动觉醒</string>\n    <string name=\"kpm_autoload_enabled\">启用结晶自主觉醒</string>\n    <string name=\"kpm_autoload_enabled_summary\">开机时自动激活心之结晶</string>\n    <string name=\"kpm_autoload_json_config\">星界咒语配置</string>\n    <string name=\"kpm_autoload_json_label\">星界咒语</string>\n    <string name=\"kpm_autoload_json_placeholder\">{\n  \"enabled\": true,\n  \"kpmPaths\": [\n    \"/path/to/crystal1.kpm\",\n    \"/path/to/crystal2.kpm\"\n  ]\n}</string>\n    <string name=\"kpm_autoload_json_error\">无效的星界咒语格式</string>\n    <string name=\"kpm_autoload_json_helper\">输入有效的星界咒语，包含心之结晶文件路径数组</string>\n    <string name=\"kpm_autoload_save\">保存咒语配置</string>\n    <string name=\"kpm_autoload_save_confirm\">保存自动激活咒语配置？</string>\n    <string name=\"kpm_autoload_save_success\">咒语配置保存成功</string>\n    <string name=\"kpm_autoload_save_failed\">咒语配置保存失败</string>\n    <string name=\"kpm_autoload_visual_mode\">可视化咒语模式</string>\n    <string name=\"kpm_autoload_json_mode\">星界咒语模式</string>\n\n    <!-- APM Bulk Install Strings -->\n    <string name=\"apm_bulk_install_title\">星之魔法阵批量契约</string>\n    <string name=\"apm_bulk_install_list_title\">魔法阵列表</string>\n    <string name=\"apm_bulk_install_add\">添加魔法阵</string>\n    <string name=\"apm_bulk_install_empty\">未添加魔法阵</string>\n    <string name=\"apm_bulk_install_action\">批量契约</string>\n    <string name=\"apm_bulk_install_log_title\">批量契约记录</string>\n    <string name=\"apm_bulk_install_log_start\">开始批量契约...</string>\n    <string name=\"apm_bulk_install_log_installing\">正在契约 %1$s</string>\n    <string name=\"apm_batch_install_full_process\">完整批量契约仪式</string>\n    <string name=\"apm_batch_install_full_process_summary\">使用完整仪式进行魔法阵批量契约</string>\n    <string name=\"next_module\">下一个魔法阵</string>\n    <string name=\"apm_bulk_install_log_installed\">魔法阵 %s 契约完成。</string>\n    <string name=\"apm_bulk_install_log_done\">所有魔法仪式已完成。</string>\n    <string name=\"apm_bulk_install_first_use_text\">这个功能允许你一次性施展多个魔法阵，属于快速施法，适合施展一些过程中没有涉及咒语咏唱的魔法阵，涉及咏唱古代咒语的请去设置启用完整仪式模式</string>\n    <string name=\"apm_bulk_install_remove\">移除</string>\n    <string name=\"apm_first_use_title\">欢迎使用星之魔法阵</string>\n    <string name=\"apm_first_use_text\">欢迎使用星之魔法阵，这里使用的是兼容Magisk生态的魔法阵，点击右下角的按钮可以施展魔法阵，你也可以使用顶部的施法器批量施展魔法阵，还提供了一个功能一键备份全部魔法阵，但是注意这种方案不一定适用全部魔法阵，还是自己多加备份</string>\n    <string name=\"kpm_autoload_add_kpm\">添加心之结晶</string>\n    <string name=\"kpm_autoload_remove_kpm\">解除</string>\n    <string name=\"kpm_autoload_edit_kpm\">编辑魔法咒语</string>\n    <string name=\"kpm_autoload_edit_dialog_title\">编辑心之结晶</string>    <string name=\"kpm_autoload_event_label\">触发事件:</string>    <string name=\"kpm_autoload_args_label\">咒语参数:</string>    <string name=\"kpm_autoload_event_service\">service</string>    <string name=\"kpm_autoload_event_post_fs_data\">post-fs-data</string>\n    <string name=\"kpm_autoload_kpm_list_title\">心之结晶列表</string>\n    <string name=\"kpm_autoload_no_kpm_added\">未添加心之结晶</string>\n    <string name=\"kpm_autoload_kpm_path\">心之结晶路径</string>\n    <string name=\"kpm_autoload_file_not_found\">未找到魔法文件</string>\n    <string name=\"kpm_autoload_select_kpm_file\">选择心之结晶文件</string>\n    <string name=\"kpm_autoload_first_time_title\">关于心之结晶自动觉醒</string>\n    <string name=\"kpm_autoload_first_time_message\">这个功能可以让你觉醒时自动临时激活咒语配置文件的全部心之结晶，这种方案相比于直接融合到心之核心更加方便。\n\n例如，功能为防止魔法容器被修改的心之结晶，这种心之结晶只能临时激活使用，但是融合会导致无法觉醒。或者你不想破坏心之核心就可以使用这个咒语配置快速激活结晶。\n\n需要确保魔法工坊完全退出进入才会执行咒语，一般来说觉醒进入也是会激活的，工坊黑屏一段时间属于正常现象，使用的是纯后台的咒语激活方案，记得手动刷新看看是否正确激活！</string>\n    <string name=\"kpm_autoload_first_time_confirm\">了解了</string>\n    <string name=\"kpm_autoload_do_not_show_again\">不再显示</string>\n\n    <string name=\"kpm_page_first_time_title\">魔法警告</string>\n    <string name=\"kpm_page_first_time_message\">心之结晶是直接修改心之核心的实现，它不像星之魔法阵一样有很好的恢复机制，出问题只能进入召唤阵去修复。建议先激活结晶没有问题再融合到心之核心。如果是只能激活使用的结晶，可以尝试使用自动心之结晶激活功能。如果你不了解心之结晶，请不要使用本功能！</string>\n    <!-- Module Backup/Restore Strings -->\n    <string name=\"apm_backup_title\">封印魔法记忆</string>\n    <string name=\"apm_restore_title\">唤醒魔法记忆</string>\n    <string name=\"apm_backup_success\">记忆封印成功</string>\n    <string name=\"apm_restore_success\">记忆唤醒成功</string>\n    <string name=\"apm_backup_failed\">记忆封印失败</string>\n    <string name=\"apm_restore_failed\">记忆唤醒失败</string>\n    <string name=\"apm_backup_failed_msg\">记忆封印失败：%s</string>\n    <string name=\"apm_restore_failed_msg\">记忆唤醒失败：%s</string>\n    <string name=\"apm_copy_list_title\">抄录魔法名录</string>\n    <string name=\"apm_copy_list_success\">名录已抄录完毕</string>\n\n    <!-- Font Settings -->\n    <string name=\"settings_custom_font\">魔法少女变身</string>\n    <string name=\"settings_select_font_file\">选择星尘卷轴(TTF)</string>\n    <string name=\"settings_custom_font_enabled\">已使用星尘卷轴</string>\n    <string name=\"settings_custom_font_summary\">使用星尘卷轴</string>\n    <string name=\"settings_font_selected\">已选择星尘卷轴</string>\n    <string name=\"settings_custom_font_error\">使用星尘卷轴失败</string>\n    <string name=\"settings_custom_font_saved\">星尘卷轴召唤成功</string>\n    <string name=\"settings_clear_font\">恢复变身前</string>\n    <string name=\"settings_clear_font_confirm\">确定要解除变身吗？</string>\n    <string name=\"settings_font_cleared\">已恢复变身前的状态</string>\n    <string name=\"settings_font_select_hint\">请选择一个星尘卷轴(TTF)</string>\n\n    <!-- Auto Backup Strings -->\n    <string name=\"settings_auto_backup_module\">魔法星阵自动护存</string>\n    <string name=\"settings_auto_backup_module_summary\">觉醒时自动将星之魔法阵保存到私人魔法宝箱</string>\n    <string name=\"settings_open_backup_dir\">打开魔法宝箱</string>\n    <string name=\"backup_dir_empty\">魔法宝箱空空如也</string>\n    <string name=\"backup_dir_open_failed\">无法打开魔法宝箱</string>\n    <string name=\"auto_backup_failed\">星之魔法阵保存失败: %s</string>\n    <string name=\"auto_backup_success\">星之魔法阵保存成功: %s</string>\n    <string name=\"settings_auto_backup_boot\">自动封印Boot圣物</string>\n    <string name=\"settings_auto_backup_boot_summary\">自动将Boot圣物封印至魔法宝箱 (Downloads/FolkPatch/BootBackups)</string>\n\n    <!-- Auto-added missing strings -->\n\n    <!-- Home Layout Strings -->\n    <string name=\"settings_home_layout_style\">魔法大厅布局样式</string>\n    <string name=\"settings_home_layout_default\">ListUI</string>\n    <string name=\"settings_home_layout_grid\">GridUI</string>\n    <string name=\"settings_home_layout_focus\">FocusUI</string>\n    <string name=\"settings_home_layout_sign\">SignUI</string>\n    <string name=\"settings_home_layout_circle\">CircleUI</string>\n    <string name=\"settings_home_layout_dashboard_pro\">DashboardUI</string>\n\n    <!-- Unofficial Version Strings -->\n    <string name=\"unofficial_version_title\">非正统魔法工坊✨</string>\n    <string name=\"unofficial_version_message\">你所使用的FolkPatch并非星界认证的正统魔法工坊~ 请前往官方魔法屋获取正版工坊，以避免魔法反噬或契约失效！</string>\n    <string name=\"go_to_github\">前往Github星界魔法屋~☆</string>\n    <string name=\"settings_save_theme\">封印魔法主题</string>\n    <string name=\"settings_import_theme\">唤醒魔法主题</string>\n    <string name=\"settings_theme_saved\">魔法主题已封印</string>\n    <string name=\"settings_theme_imported\">魔法主题已唤醒</string>\n    <string name=\"settings_theme_save_failed\">封印魔法主题失败</string>\n    <string name=\"settings_theme_import_failed\">唤醒魔法主题失败</string>\n    <string name=\"settings_reset_theme\">重置魔法主题</string>\n    <string name=\"settings_reset_theme_confirm\">确定要将所有魔法主题设置重置为默认吗？这将清除所有自定义背景、字体、音乐和音效。</string>\n    <string name=\"settings_theme_reset\">魔法主题已重置</string>\n    <string name=\"settings_theme_reset_failed\">重置魔法主题失败</string>\n\n    <!-- Theme Strings -->\n    <string name=\"theme_export_title\">封印魔法主题</string>\n    <string name=\"theme_import_title\">唤醒魔法主题</string>\n    <string name=\"theme_name\">魔法主题真名</string>\n    <string name=\"theme_type\">魔法类型</string>\n    <string name=\"theme_type_phone\">魔导手机</string>\n    <string name=\"theme_type_tablet\">魔导石板</string>\n    <string name=\"theme_version\">魔法等级</string>\n    <string name=\"theme_author\">创造者</string>\n    <string name=\"theme_description\">魔法描述</string>\n    <string name=\"theme_export_action\">封印</string>\n    <string name=\"theme_import_action\">唤醒</string>\n    <string name=\"theme_import_confirm\">确定要唤醒此魔法主题吗？</string>\n    <string name=\"theme_info\">魔法主题情报</string>\n\n    <!-- Grid Working Card Background -->\n    <string name=\"settings_grid_working_card_background\">魔法契约卡片背景</string>\n    <string name=\"settings_grid_working_card_background_enabled\">魔法卡片背景已生效</string>\n    <string name=\"settings_grid_working_card_background_summary\">为契约卡片施加自定义幻象</string>\n    <string name=\"settings_grid_working_card_background_selected\">幻象已选定</string>\n    <string name=\"settings_clear_grid_working_card_background\">解除卡片幻象</string>\n    <string name=\"settings_clear_grid_working_card_background_confirm\">确定要解除卡片上的幻象吗？</string>\n    <string name=\"settings_grid_working_card_background_saved\">卡片幻象保存成功</string>\n    <string name=\"settings_grid_working_card_background_error\">卡片幻象保存失败</string>\n    <string name=\"settings_grid_working_card_background_cleared\">卡片幻象已解除</string>\n\n    <!-- Biometric Strings -->\n    <string name=\"action_biometric\">魔法印鉴认证</string>\n    <string name=\"msg_biometric\">请验证您的魔法印鉴</string>\n    <string name=\"settings_biometric_login\">魔法印鉴认证</string>\n    <string name=\"settings_biometric_login_summary\">开启魔法工坊时需要进行魔法印鉴认证</string>\n    <string name=\"settings_strong_biometric\">强力魔法印鉴</string>\n    <string name=\"settings_strong_biometric_summary\">施展、解除或封印魔法阵时需要认证</string>\n\n    <!-- Theme Store Strings -->\n    <string name=\"theme_store_title\">绮幻衣装魔盒</string>\n    <string name=\"theme_store_search_hint\">搜索魔法主题...</string>\n    <string name=\"theme_source_official\">魔女工坊直供</string>\n    <string name=\"theme_source_third_party\">同好魔法分享</string>\n    <string name=\"theme_source_local\">本地</string>\n    <string name=\"theme_store_author\">编织师: %s</string>\n    <string name=\"theme_store_version\">咒语版本: %s</string>\n    <string name=\"theme_source\">魔力源流</string>\n    <string name=\"theme_store_download_failed\">呜哇！魔力传输失败啦</string>\n<string name=\"theme_store_download\">点亮魔法下载</string>\n\n    <!-- Update Check Strings -->\n    <string name=\"settings_auto_update_check\">自动魔法升级检查</string>\n    <string name=\"settings_auto_update_check_summary\">魔法工坊启动时自动检查新魔法咒语</string>\n    <string name=\"settings_check_update\">检查魔法升级</string>\n    <string name=\"update_available_title\">发现新魔法</string>\n    <string name=\"update_available_message\">检测到您的魔法等级过低，是否要下载新版本的魔法？</string>\n    <string name=\"update_action\">升级魔法</string>\n    <string name=\"update_close\">关闭</string>\n    <string name=\"update_latest\">您已掌握最新的魔法</string>\n    <string name=\"update_error\">检查魔法升级时出错</string>\n    <!--折叠选项-->\n    <string name=\"settings_category_general\">魔法基础</string>\n    <string name=\"settings_category_appearance\">变身装束</string>\n    <string name=\"settings_appearance_font\">字体魔法</string>\n    <string name=\"settings_appearance_font_summary\">自定义字体魔法配置</string>\n    <string name=\"settings_appearance_theme\">星之主题</string>\n    <string name=\"settings_appearance_theme_summary\">主题商店、保存、导入和重置</string>\n    <string name=\"settings_appearance_banner\">魔法横幅</string>\n    <string name=\"settings_appearance_banner_summary\">模块横幅配置</string>\n    <string name=\"settings_appearance_layout\">星之布局</string>\n    <string name=\"settings_appearance_layout_summary\">首页布局、导航和卡片自定义</string>\n    <string name=\"settings_appearance_background\">星之背景</string>\n    <string name=\"settings_appearance_background_summary\">自定义背景、视频和多背景</string>\n    <string name=\"settings_appearance_night_mode\">暗夜模式</string>\n    <string name=\"settings_appearance_night_mode_summary\">深色主题和颜色配置</string>\n    <string name=\"settings_amoled_theme\">AMOLED 纯黑主题</string>\n    <string name=\"settings_amoled_theme_desc\">为深色模式使用纯黑背景</string>\n    <string name=\"settings_switch_icon\">启用魔法按钮指示器</string>\n    <string name=\"settings_switch_icon_desc\">为魔法开关按钮添加状态指示图标</string>\n    <string name=\"settings_category_behavior\">咒语指令</string>\n    <string name=\"settings_category_function\">魔法功能</string>\n    <string name=\"settings_use_legacy_su_page\">单页超级用户授权喵</string>\n    <string name=\"settings_use_legacy_su_page_summary\">超级用户页面改用单页授权设计喵</string>\n    <string name=\"settings_category_module\">魔法模组</string>\n    <string name=\"settings_category_security\">结界守护</string>\n    \n    <!-- 背景音乐字符串（魔法少女风格版） -->\n    <string name=\"settings_background_music\">魔法颂歌</string>\n    <string name=\"settings_background_music_summary\">少女开启魔法模式时，奏响魔法颂歌</string>\n    <string name=\"settings_background_music_playing\">颂歌吟唱中: %s</string>\n    <string name=\"settings_background_music_enabled\">魔法颂歌已激活</string>\n    <string name=\"settings_select_music_file\">挑选魔法旋律水晶</string>\n    <string name=\"settings_music_selected\">魔法旋律水晶已选定</string>\n    <string name=\"settings_clear_music\">清空魔法颂歌</string>\n    <string name=\"settings_clear_music_confirm\">确定要清空当前的魔法颂歌吗？</string>\n    <string name=\"settings_music_auto_play\">颂歌自动吟唱</string>\n    <string name=\"settings_music_auto_play_summary\">启动魔法终端时，颂歌自动吟唱</string>\n    <string name=\"settings_music_volume\">颂歌音量</string>\n    <string name=\"settings_music_saved\">魔法旋律水晶已封存</string>\n    <string name=\"settings_music_save_error\">魔法旋律水晶封存失败</string>\n    <string name=\"settings_music_cleared\">魔法颂歌已清空</string>\n    <string name=\"settings_category_multimedia\">魔法幻音</string>\n    <string name=\"settings_category_general_summary\">魔法语韵、魔法更新、结界律令、魔法世界调整</string>\n    <string name=\"settings_category_appearance_summary\">魔法主题、星光色彩、魔法阵布局、梦境背景、魔法符文字体</string>\n    <string name=\"settings_category_behavior_summary\">万维除错、魔法植入、魔法殿堂展示</string>\n    <string name=\"settings_category_security_summary\">魔法印记、万法密钥管理</string>\n    <string name=\"settings_category_backup_summary\">本地魔法书、云端魔法书、WebDAV</string>\n    <string name=\"settings_category_module_summary\">魔法信息、星序排列、批量施法</string>\n    <string name=\"settings_category_function_summary\">FolkPatch隐匿、卸载魔法侍从</string>\n    <string name=\"settings_category_multimedia_summary\">背景魔法颂歌、魔法音效、魔力振动</string>\n    <string name=\"settings_music_playback_control\">颂歌控法阵</string>\n    <string name=\"settings_music_looping\">无限吟唱</string>\n    <string name=\"settings_music_looping_summary\">无限循环的魔法咏唱</string>\n    <!-- Theme Store Filter -->\n    <string name=\"theme_store_filter_title\">筛选魔法主题</string>\n    <string name=\"theme_store_filter_author\">魔法师</string>\n    <string name=\"theme_store_filter_author_hint\">输入魔法师名称</string>\n    <string name=\"theme_store_filter_source\">魔法来源</string>\n    <string name=\"theme_store_filter_source_all\">全部</string>\n    <string name=\"theme_store_filter_type\">魔法设备类型</string>\n    <string name=\"theme_store_filter_apply\">施法</string>\n    <string name=\"theme_store_filter_reset\">重置</string>\n\n    <!-- Video Background Settings -->\n    <string name=\"settings_video_background\">魔法影像背景</string>\n    <string name=\"settings_video_background_summary\">使用魔法影像作为背景</string>\n    <string name=\"settings_select_video\">选择魔法影像</string>\n    <string name=\"settings_video_selected\">已选择魔法影像</string>\n    <string name=\"settings_video_background_enabled\">魔法影像背景已激活</string>\n    <string name=\"settings_clear_video_background\">清除魔法影像</string>\n    <string name=\"settings_clear_video_background_confirm\">确定要清除魔法影像吗？</string>\n    <string name=\"settings_video_volume\">魔法影像音量</string>\n    <!-- File Picker -->\n    <string name=\"file_picker_permission_required\">需要魔法权限</string>\n    <string name=\"file_picker_permission_desc\">为了查阅魔法书，请授予\\'所有文件访问\\'魔法权限。</string>\n    <string name=\"file_picker_grant_permission\">授予魔法</string>\n    <string name=\"file_picker_cancel\">魔法取消</string>\n    <string name=\"file_picker_internal_storage\">魔法储物袋</string>\n    <string name=\"file_picker_no_files\">魔法书空空如也</string>\n    <!-- HomeV3 Strings -->\n    <string name=\"home_device_status_title\">魔法状态</string>\n    <string name=\"home_device_status_battery_temp\">魔力温度</string>\n    <string name=\"home_device_status_cpu_load\">魔法负载</string>\n    <string name=\"home_device_status_battery_level\">魔力值</string>\n    <string name=\"home_storage_title\">魔法储物</string>\n    <string name=\"home_storage_internal\">魔法储物袋</string>\n    <string name=\"home_storage_ram\">魔法记忆</string>\n    <string name=\"home_storage_zram\">魔法压缩</string>\n    <string name=\"home_storage_swap\">魔法交换</string>\n    <string name=\"home_info_kernel\">魔法核心</string>\n    <string name=\"home_info_superkey\">心之密钥</string>\n    <string name=\"home_info_auth_auth\">已认证</string>\n    <string name=\"home_info_auth_na\">不可用</string>\n    <string name=\"home_info_device_slot\">魔法槽位</string>\n    <string name=\"home_info_device_model\">魔法形态</string>\n    <string name=\"home_info_running_mode\">运行模式</string>\n    <string name=\"home_info_mode_full\">完整</string>\n    <string name=\"home_info_mode_half\">不完整</string>\n    <string name=\"home_version\">魔法: %s</string>\n    <string name=\"home_zygisk_implement\">Zygisk 魔法实现</string>\n    <string name=\"home_mount_implement\">魔法结界</string>\n\n    <string name=\"settings_app_list_loading_scheme\">魔法使召唤方案</string>\n    <string name=\"settings_app_list_loading_scheme_summary\">选择如何召唤魔法使列表</string>\n    <string name=\"app_list_loading_scheme_root_service\">魔法阵召唤 (默认)</string>\n    <string name=\"app_list_loading_scheme_package_manager\">星界召唤 (星界 API)</string>\n    <string name=\"app_list_loading_scheme_dialog_title\">召唤方案</string>\n    <string name=\"su_backup_list\">封印魔法使</string>\n    <string name=\"su_restore_list\">解封魔法使</string>\n    <string name=\"backup_success\">封印成功</string>\n    <string name=\"restore_success\">唤醒成功</string>\n    <!-- Badge Count Settings -->\n    <string name=\"enable_badge_count\">魔法印记设定</string>\n    <string name=\"enable_badge_count_summary\">配置星图导航的魔法印记显示</string>\n    <string name=\"badge_superuser\">显示魔法使印记</string>\n    <string name=\"badge_apm\">显示星之魔法阵印记</string>\n    <string name=\"badge_kernel\">显示心之结晶印记</string>\n    <string name=\"settings_sound_effect\">魔法音效</string>\n    <string name=\"settings_sound_effect_summary\">施法（点击）时回响魔法音效</string>\n    <string name=\"settings_sound_effect_enabled\">魔法音效已激活</string>\n    <string name=\"settings_sound_effect_playing\">当前音效: %s</string>\n    <string name=\"settings_sound_effect_source\">魔法音源</string>\n    <string name=\"settings_sound_effect_source_local\">现世音卷</string>\n    <string name=\"settings_sound_effect_source_preset\">预设魔法</string>\n    <string name=\"settings_sound_effect_preset_title\">魔法音效典藏</string>\n    <string name=\"settings_select_sound_effect\">挑选魔法音效</string>\n    <string name=\"settings_sound_effect_selected\">魔法音效已选定</string>\n    <string name=\"settings_sound_effect_scope\">音效生效结界</string>\n    <string name=\"settings_sound_effect_scope_global\">全域 (魔法世界)</string>\n    <string name=\"settings_sound_effect_scope_bottom_bar\">仅基座法阵</string>\n    <string name=\"settings_clear_sound_effect\">消除魔法音效</string>\n    <string name=\"settings_clear_sound_effect_confirm\">确定要消除此魔法音效吗？</string>\n    <string name=\"settings_sound_effect_cleared\">声音魔法已消散</string>\n\n    <string name=\"settings_startup_sound\">唤醒魔法音</string>\n    <string name=\"settings_startup_sound_summary\">启动魔法书时奏响魔法音</string>\n    <string name=\"settings_startup_sound_enabled\">唤醒音已激活</string>\n    <string name=\"settings_startup_sound_playing\">正在播放: %s</string>\n    <string name=\"settings_select_startup_sound\">选择唤醒魔法音</string>\n    <string name=\"settings_startup_sound_selected\">已选定唤醒魔法音</string>\n    <string name=\"settings_clear_startup_sound\">消除唤醒音</string>\n    <string name=\"settings_clear_startup_sound_confirm\">确定要消除唤醒魔法音吗？</string>\n    <string name=\"settings_startup_sound_cleared\">唤醒音已消散</string>\n\n    <!-- Backup Settings -->\n    <string name=\"settings_category_backup\">记忆回廊</string>\n    <string name=\"settings_enable_cloud_backup\">开启异界投影</string>\n    <string name=\"settings_enable_cloud_backup_summary\">自动将变身道具投影到异世界</string>\n    <string name=\"settings_configure_webdav\">建立异界连接</string>\n    <string name=\"webdav_config_title\">异界连接仪式</string>\n    <string name=\"webdav_url\">异界坐标</string>\n    <string name=\"webdav_username\">魔女之名</string>\n    <string name=\"webdav_password\">魔力印记</string>\n    <string name=\"webdav_test_success\">契约缔结成功</string>\n    <string name=\"webdav_test_failed\">契约缔结失败: %s</string>\n    <string name=\"webdav_uploading\">正在传送魔法...</string>\n    <string name=\"webdav_backup_success\">异界投影成功</string>\n    <string name=\"webdav_backup_failed\">异界投影失败: %s</string>\n    <string name=\"save\">缔结</string>\n    <string name=\"test\">试炼</string>\n    <string name=\"webdav_path_label\">次源路径 (如 /Backup)</string>\n    <string name=\"webdav_view_logs\">查看魔法书</string>\n    <string name=\"webdav_backup_logs_title\">投影记录</string>\n    <string name=\"webdav_no_logs\">魔法书是空的</string>\n    <string name=\"webdav_clear_logs\">遗忘</string>\n    <string name=\"settings_enable_local_backup\">开启现世留影</string>\n    <string name=\"settings_enable_local_backup_summary\">将道具留影在现世 (Downloads/FolkPatch/ModuleBackups)</string>\n    <string name=\"close\">合上魔法书</string>\n    <string name=\"settings_backup_password\">守护咒语</string>\n    <string name=\"settings_backup_password_hint\">吟唱守护咒语</string>\n\n    <string name=\"patch_output_written_to\"> 魔法炼成成功，魔法书书写至 </string>\n    <string name=\"patch_write_failed\"> 魔法炼成失败，魔力反噬</string>\n\n    <!-- Script Library Strings -->\n    <string name=\"script_library\">魔法咒语书架</string>\n    <string name=\"script_library_title\">魔法咒语书架</string>\n    <string name=\"script_library_empty\">书架空空如也，点击添加咒语</string>\n    <string name=\"script_library_add\">添加咒语</string>\n    <string name=\"script_library_add_title\">添加咒语</string>\n    <string name=\"script_library_select_file\">选择咒语卷轴</string>\n    <string name=\"script_library_alias\">咒名</string>\n    <string name=\"script_library_alias_hint\">输入咒语名称（可选）</string>\n    <string name=\"script_library_run\">施展</string>\n    <string name=\"script_library_delete\">遗忘</string>\n    <string name=\"script_library_path\">魔法路径：%s</string>\n    <string name=\"script_library_confirm_delete\">真的要遗忘这个咒语吗？</string>\n    <string name=\"script_library_delete_success\">咒语已遗忘</string>\n    <string name=\"script_library_delete_failed\">遗忘咒语失败</string>\n    <string name=\"script_library_add_success\">咒语已添加</string>\n    <string name=\"script_library_add_failed\">添加咒语失败：%s</string>\n    <string name=\"script_library_load_failed\">打开魔法书失败</string>\n    <string name=\"script_library_output\">魔法显现</string>\n    <string name=\"script_library_no_output\">没有显现</string>\n    <string name=\"script_library_save_log\">记录施展历史</string>\n    <string name=\"script_library_log_saved\">施展历史已记录到 %s</string>\n    <string name=\"script_library_log_save_failed\">记录施展历史失败：%s</string>\n\n    <string name=\"settings_vibration\">魔力共鸣</string>\n    <string name=\"settings_vibration_summary\">触碰时产生魔力反馈</string>\n    <string name=\"settings_vibration_enabled\">开启魔力共鸣</string>\n    <string name=\"settings_vibration_intensity\">共鸣强度</string>\n    <string name=\"settings_vibration_scope\">共鸣结界范围</string>\n    <string name=\"settings_vibration_scope_global\">全域 (魔法世界)</string>\n    <string name=\"settings_vibration_scope_bottom_bar\">仅基座法阵</string>\n    <string name=\"superuser\">魔法使</string>\n    <string name=\"module\">星之魔法阵</string>\n    <string name=\"search_modules\">搜索魔法阵...</string>\n    <string name=\"search_scripts\">搜索魔法卷轴...</string>\n\n    <!-- Umount Service -->\n    <string name=\"settings_umount_service\">Umount Service</string>\n    <string name=\"settings_umount_service_summary\">配置仪式开始时自动解除的魔法连接点</string>\n    <string name=\"umount_config_title\">Umount 魔法阵设定</string>\n    <string name=\"umount_config_enabled\">启用 Umount</string>\n    <string name=\"umount_config_enabled_summary\">仪式开始时自动解除指定的魔法连接点</string>\n    <string name=\"umount_config_paths_label\">魔法连接点坐标（每行一个）</string>\n    <string name=\"umount_config_paths_placeholder\">/system/vendor/app\\n/system/vendor/priv-app</string>\n    <string name=\"umount_config_paths_helper\">每行输入一个需要解除的魔法连接点坐标</string>\n    <string name=\"umount_config_save\">施展</string>\n    <string name=\"umount_config_save_confirm\">确认施展并保存魔法阵？</string>\n    <string name=\"umount_config_save_success\">魔法施展成功，配置已保存</string>\n    <string name=\"umount_config_save_failed\">魔法施展失败，配置保存失败</string>\n\n    <!-- 我的魔法领域页面 -->\n    <string name=\"my_themes_title\">我的魔法领域</string>\n    <string name=\"my_themes_empty\">魔法领域空荡荡的</string>\n    <string name=\"my_themes_empty_action\">去魔法商店逛逛</string>\n    <string name=\"my_themes_apply\">施展主题</string>\n    <string name=\"my_themes_delete\">遗忘主题</string>\n    <string name=\"my_themes_delete_confirm\">确定要遗忘这个主题魔法吗？</string>\n    <string name=\"my_themes_deleted\">主题已遗忘</string>\n    <string name=\"my_themes_applied\">主题已施展</string>\n    <string name=\"my_themes_apply_failed\">施展主题失败</string>\n    <string name=\"my_themes_details\">主题魔法详情</string>\n\n    <!-- 下载对话框 -->\n    <string name=\"theme_download_title\">正在召唤主题</string>\n    <string name=\"theme_download_completed\">召唤完成</string>\n    <string name=\"theme_download_failed\">召唤失败</string>\n    <string name=\"theme_download_progress\">召唤进度</string>\n    <string name=\"theme_download_file\">主题魔法书</string>\n    <string name=\"theme_download_image\">魔法预览</string>\n    <string name=\"theme_download_cancel\">终止召唤</string>\n    <string name=\"theme_download_pause\">暂停召唤</string>\n    <string name=\"theme_download_resume\">继续召唤</string>\n    <string name=\"theme_download_retry\">重新召唤</string>\n    <string name=\"theme_download_apply\">施展主题</string>\n    <string name=\"theme_download_go_to_my_themes\">我的魔法领域</string>\n    <string name=\"theme_download_retrying\">重新召唤中 (%d/3)...</string>\n    <string name=\"theme_download_preparing\">准备召唤...</string>\n    <string name=\"theme_download_downloading_file\">正在下载主题魔法书...</string>\n    <string name=\"theme_download_downloading_image\">正在下载魔法预览...</string>\n    <string name=\"theme_download_finalizing\">魔法凝聚中...</string>\n    <string name=\"settings_predictive_back\">预知回溯魔法手势</string>\n    <string name=\"settings_predictive_back_summary\">启用 Android 14+ 的预知回溯魔法动画</string>\n\n    <string name=\"home_device_status_battery_charging\">魔力充盈中</string>\n    <string name=\"home_device_status_cpu_temp\">魔法温度</string>\n    <string name=\"home_device_status_memory_trend\">魔法趋势</string>\n    <string name=\"home_storage_partitions\">魔法分区</string>\n    <string name=\"home_device_status_cpu_freq\">魔法频率</string>\n    <string name=\"home_network_rx\">魔法汲取</string>\n    <string name=\"home_network_tx\">魔法传送</string>\n    <string name=\"settings_home_layout_stats\">StatsUI</string>\n    <string name=\"home_stats_more_options\">更多魔法</string>\n    <string name=\"home_stats_kp_label\">KP</string>\n    <string name=\"home_stats_manager_label\">魔法管</string>\n    <string name=\"home_device_status_gpu_load\">魔法负载</string>\n    <string name=\"home_stats_kernel_modules\">内核魔法书</string>\n    <string name=\"home_stats_apm_modules\">APM 魔法书</string>\n    <string name=\"home_stats_superusers\">大魔法师</string>\n    <string name=\"settings_kernel_spoof\">内核伪装魔法</string>\n    <string name=\"settings_kernel_spoof_summary\">伪装内核版本和施法时间</string>\n    <string name=\"settings_kernel_spoof_version\">内核魔法版本</string>\n    <string name=\"settings_kernel_spoof_build_time\">内核施法时间</string>\n    <string name=\"settings_kernel_spoof_restore\">恢复魔法</string>\n\n    <string name=\"kernel_spoof_enabled\">内核伪装魔法已施放</string>\n    <string name=\"kernel_spoof_disabled_restored\">内核伪装魔法已解除并恢复</string>\n    <string name=\"kernel_spoof_failed\">内核伪装魔法施放失败: %d</string>\n    <string name=\"kernel_spoof_applied\">内核伪装魔法已生效</string>\n\n    <string name=\"settings_path_hide\">魔法路径隐藏</string>\n    <string name=\"settings_path_hide_summary\">在魔法核心层面向应用隐藏文件和目录</string>\n    <string name=\"path_hide_paths_label\">隐藏魔法路径（每行一个）</string>\n\n    <string name=\"path_hide_paths_helper\">每行输入一个魔法路径。匹配的路径将返回 ENOENT。</string>\n    <string name=\"path_hide_save\">保存魔法</string>\n    <string name=\"path_hide_enabled\">魔法路径隐藏已启用</string>\n    <string name=\"path_hide_disabled\">魔法路径隐藏已禁用</string>\n    <string name=\"path_hide_applied\">魔法路径隐藏配置已应用</string>\n    <string name=\"path_hide_failed\">魔法路径隐藏操作失败：%d</string>\n    <string name=\"path_hide_uid_mode\">UID 魔法执行模式</string>\n    <string name=\"path_hide_uid_mode_summary\">仅对指定魔法应用 UID 隐藏路径</string>\n    <string name=\"path_hide_uids_label\">目标 UID（每行一个）</string>\n    <string name=\"path_hide_uids_helper\">输入魔法应用 UID，仅这些应用的魔法路径会被隐藏。</string>\n    <string name=\"path_hide_uid_save\">保存魔法 UID</string>\n    <string name=\"path_hide_uid_mode_enabled\">UID 魔法执行模式已启用</string>\n    <string name=\"path_hide_uid_mode_disabled\">UID 魔法执行模式已禁用</string>\n    <string name=\"path_hide_filter_system\">过滤系统 UID</string>\n    <string name=\"path_hide_filter_system_summary\">同时对 root 和系统进程（UID &lt; 10000）隐藏魔法路径</string>\n    <string name=\"path_hide_filter_system_enabled\">系统 UID 过滤已启用</string>\n    <string name=\"path_hide_filter_system_disabled\">系统 UID 过滤已禁用</string>\n    <string name=\"path_hide_filter_system_warning_title\">魔法警告</string>\n    <string name=\"path_hide_filter_system_warning_message\">启用后将同时对 root 和系统进程（UID &lt; 10000）隐藏魔法路径。这可能导致部分魔法功能异常，请谨慎施法。</string>\n    <string name=\"path_hide_uid_applied\">UID 魔法白名单已应用</string>\n    <string name=\"path_hide_select_apps\">选择魔法应用</string>\n    <string name=\"path_hide_no_apps_selected\">未选择魔法应用</string>\n    <string name=\"path_hide_app_removed\">已移除 %d 个已卸载魔法应用的配置</string>\n    <string name=\"path_hide_search_apps\">搜索魔法应用…</string>\n    <string name=\"path_hide_show_system\">显示系统魔法应用</string>\n    <string name=\"netisolate_title\">魔法网络隔离</string>\n    <string name=\"netisolate_enable\">魔法网络隔离已启用</string>\n    <string name=\"netisolate_disable\">魔法网络隔离已禁用</string>\n    <string name=\"netisolate_enable_summary\">在魔法核心层面阻止所选应用的网络访问</string>\n    <string name=\"netisolate_uids_label\">已隔离魔法应用</string>\n    <string name=\"netisolate_uids_hint\">输入要隔离的魔法 UID（每行一个）</string>\n    <string name=\"netisolate_no_uids\">暂无隔离魔法应用</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-night/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"Theme.APatch.WebUI\" parent=\"Theme.APatch\" />\n\n</resources>"
  },
  {
    "path": "app/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name_fpatch\">FPatch</string>\n    <string name=\"desktop_app_name\">Nazwa aplikacji desktopowej</string>\n    <string name=\"home\">Pulpit</string>\n    <string name=\"settings_apm_stay_on_page\">Pozostań na stronie operacji</string>\n    <string name=\"settings_apm_stay_on_page_summary\">Nie wracaj automatycznie po wykonaniu akcji modułu</string>\n    <string name=\"success\">Zakończono sukcesem</string>\n    <string name=\"failure\">Niepowodzenie</string>\n    <string name=\"patch_warnning\">Instalacja wiąże się z ryzykiem. Upewnij się, że utworzono kopię zapasową danych.</string>\n    <string name=\"patch\">Załataj</string>\n    <string name=\"kernel_patch\">KernelPatch</string>\n    <string name=\"android_patch\">AndroidPatch</string>\n    <string name=\"settings_nav_layout_title\">Ustawienia układu nawigacji</string>\n    <string name=\"settings_nav_layout_summary\">Ukryj lub pokaż niektóre elementy nawigacji</string>\n    <string name=\"settings_nav_scheme\">Schemat nawigacji</string>\n    <string name=\"settings_nav_mode\">Tryb paska nawigacji</string>\n    <string name=\"settings_nav_mode_summary\">Wybierz sposób wyświetlania paska nawigacji</string>\n    <string name=\"settings_nav_mode_auto\">Automatycznie Tradycyjny</string>\n    <string name=\"settings_nav_mode_bottom\">Zawsze dolny pasek</string>\n    <string name=\"settings_nav_mode_rail\">Zawsze boczny pasek</string>\n    <string name=\"settings_nav_mode_floating\">Pływający dolny pasek</string>\n    <string name=\"settings_show_apm\">Pokaż moduły systemowe</string>\n    <string name=\"settings_show_kpm\">Pokaż kartę KPM</string>\n    <string name=\"settings_show_superuser\">Pokaż kartę Superuser</string>\n    <string name=\"settings_floating_auto_hide\">Automatyczne ukrywanie</string>\n    <string name=\"settings_floating_auto_hide_summary\">Automatycznie ukrywaj pływający pasek po 3 sekundach bezczynności</string>\n    <string name=\"settings_floating_swipe_hide\">Ukrywanie przy przewijaniu</string>\n    <string name=\"settings_floating_swipe_hide_summary\">Ukrywaj pasek przy przewijaniu w dół, pokazuj przy przewijaniu w górę</string>\n    <string name=\"settings_navbar_glass_effect\">Efekt matowego szkła</string>\n    <string name=\"settings_navbar_glass_effect_summary\">Zastosuj efekt matowego szkła do pływającego paska nawigacji</string>\n    <string name=\"settings_navbar_glass_blur_strength\">Siła rozmycia</string>\n    <string name=\"settings_navbar_glass_transparency\">Przezroczystość tła</string>\n    <string name=\"settings_navbar_glass_highlight_strength\">Intensywność poświaty</string>\n    <string name=\"settings_navbar_glass_specular\">Odbicie lustrzane</string>\n    <string name=\"settings_navbar_glass_specular_summary\">Włącz lustrzane odbicie w górnej części paska nawigacji</string>\n    <string name=\"settings_navbar_glass_inner_glow\">Wewnętrzna poświata</string>\n    <string name=\"settings_navbar_glass_inner_glow_summary\">Włącz delikatny efekt poświaty na dole paska nawigacji</string>\n    <string name=\"settings_navbar_glass_border\">Ramka szklana</string>\n    <string name=\"settings_navbar_glass_border_summary\">Włącz delikatną ramkę wokół paska nawigacji</string>\n    <string name=\"settings_stats_top_layout\">Górny układ</string>\n    <string name=\"settings_stats_top_layout_summary\">Wybierz styl górnej karty informacyjnej</string>\n    <string name=\"settings_stats_top_layout_list\">Lista</string>\n    <string name=\"settings_stats_top_layout_grid\">Siatka</string>\n    <string name=\"settings_block_kernelpatch_update\">Zablokuj aktualizację KernelPatch</string>\n    <string name=\"settings_block_kernelpatch_update_summary\">Nie pokazuj powiadomień o aktualizacji KernelPatch</string>\n    <string name=\"settings_block_androidpatch_update\">Zablokuj aktualizację łatki systemowej</string>\n    <string name=\"settings_block_androidpatch_update_summary\">Nie pokazuj powiadomień o aktualizacji łatki systemowej (APD)</string>\n    <string name=\"settings_disable_module_update_check\">Wyłącz sprawdzanie aktualizacji modułów</string>\n    <string name=\"settings_disable_module_update_check_summary\">Wyłącz automatyczne sprawdzanie aktualizacji dla modułów systemowych</string>\n    <string name=\"reboot\">Uruchom ponownie</string>\n    <string name=\"settings\">Ustawienia</string>\n    <string name=\"reboot_recovery\">Restartuj do Recovery</string>\n    <string name=\"reboot_bootloader\">Restartuj do Bootloadera</string>\n    <string name=\"reboot_download\">Restartuj do Download</string>\n    <string name=\"reboot_edl\">Restartuj do EDL</string>\n    <string name=\"reboot_fastbootd\">Uruchom ponownie w trybie FastbootD</string>\n    <string name=\"about\">O aplikacji</string>\n    <string name=\"developer_and_maintainer\">Deweloper | Opiekun</string>\n    <string name=\"settings_app_language\">Język</string>\n    <string name=\"system_default\">Domyślne ustawienie systemowe</string>\n    <string name=\"settings_global_namespace_mode\">Tryb globalnej przestrzeni nazw</string>\n    <string name=\"settings_global_namespace_mode_summary\">Wszystkie sesje root korzystają z globalnej przestrzeni montowania nazw</string>\n    <string name=\"settings_clear_super_key_dialog\">Czy na pewno chcesz kontynuować?</string>\n    <string name=\"su_exclude_all_title\">Wyklucz wszystkie</string>\n    <string name=\"su_exclude_all_confirm\">Czy na pewno chcesz wykluczyć wszystkie aplikacje bez uprawnień root?</string>\n    <string name=\"home_learn_apatch\">Poznaj FolkPatch</string>\n    <string name=\"home_click_to_learn_apatch\">Dowiedz się o funkcjach FolkPatch i jak go używać</string>\n    <string name=\"settings_hide_apatch_card\">Ukryj kartę \"Dowiedz się o FolkPatch\"</string>\n    <string name=\"settings_hide_apatch_card_summary\">Ukryj kartę \"Dowiedz się o FolkPatch\" na stronie głównej</string>\n    <string name=\"about_source_code\"><![CDATA[<p>Zobacz kod źródłowy na %1$s<p/>Dołącz do naszego kanału %2$s <p/>Dołącz do naszej grupy %3$s]]></string>\n    <string name=\"send_log\">Prześlij logi</string>\n    <string name=\"save_log\">Zapisz logi</string>\n    <string name=\"log_saved\">Zapisano logi</string>\n    <string name=\"safe_mode\">Tryb awaryjny</string>\n    <string name=\"github\">GitHub</string>\n    <string name=\"telegram\">Telegram</string>\n    <string name=\"support_or_donate\">Wsparcie / Darowizna</string>\n    <string name=\"super_key\">SuperKey</string>\n    <string name=\"clear_super_key\">Wyczyść SuperKey</string>\n    <string name=\"patch_set_superkey\">Ustaw SuperKey</string>\n    <string name=\"home_patch_set_key_desc\">Tylko dane uwierzytelniające dla KernelPatch</string>\n    <string name=\"home_patch_next_step\">Kolejny krok</string>\n    <string name=\"home_not_installed\">Nie zainstalowany</string>\n    <string name=\"home_install_unknown\">Nie zainstalowano lub nie uwierzytelniono</string>\n    <string name=\"home_install_unknown_summary\">Kliknij, aby zainstalować</string>\n    <string name=\"home_click_to_install\">Kliknij, aby zainstalować</string>\n    <string name=\"home_working\">Działa</string>\n    <string name=\"home_kp_need_update\">Dostępna nowa wersja</string>\n    <string name=\"home_kp_cando_update\">Aktualizuj</string>\n    <string name=\"home_installing\">Instalowanie</string>\n    <string name=\"kpatch_version\">Wersja: %s</string>\n    <string name=\"apatch_version\">Wersja: %s</string>\n    <string name=\"kpatch_version_update\" formatted=\"false\">Wersja: %s -&gt; %s</string>\n    <string name=\"kpatch_shadow_path_title\">kpatch</string>\n    <string name=\"home_auth_key_title\">Wpisz SuperKey</string>\n    <string name=\"home_auth_key_desc\">Uruchom po uwierzytelnieniu</string>\n    <string name=\"home_kpatch_info_title\">Informacje</string>\n    <string name=\"home_ap_cando_install\">Instaluj</string>\n    <string name=\"home_ap_cando_uninstall\">Odinstaluj</string>\n    <string name=\"home_ap_cando_reboot\">Uruchom ponownie</string>\n    <string name=\"patch_title\">Załataj</string>\n    <string name=\"patch_config_title\">Łatki</string>\n    <string name=\"patch_mode_bootimg_patch\">Tryb: Łatka</string>\n    <string name=\"patch_mode_patch_and_install\">Tryb: Łataj i instaluj</string>\n    <string name=\"patch_mode_install_to_next_slot\">Tryb: Instaluj do nieaktywnego slotu (po OTA)</string>\n    <string name=\"patch_mode_uninstall_patch\">Tryb: Odinstaluj KPatch</string>\n    <string name=\"patch_select_bootimg_btn\">Wybierz rozruch</string>\n    <string name=\"patch_embed_kpm_btn\">Osadź KPM</string>\n    <string name=\"patch_start_patch_btn\">Rozpocznij</string>\n    <string name=\"patch_start_unpatch_btn\">Odłatkowanie</string>\n    <string name=\"patch_item_error\">!!BŁĄD!!</string>\n    <string name=\"patch_item_bootimg\">obraz boot</string>\n    <string name=\"patch_item_bootimg_slot\">Slot:</string>\n    <string name=\"patch_item_bootimg_dev\">Urządzenie:</string>\n    <string name=\"patch_item_kernel\">Jądro</string>\n    <string name=\"patch_item_kpimg\">kpimg</string>\n    <string name=\"patch_item_kpimg_version\">Wersja:</string>\n    <string name=\"patch_item_kpimg_comile_time\">Czas:</string>\n    <string name=\"patch_item_kpimg_config\">Konfiguracja:</string>\n    <string name=\"patch_item_new_extra_kpm\">Osadź nowy</string>\n    <string name=\"patch_item_existed_extra_kpm\">Istniejący</string>\n    <string name=\"patch_item_extra_name\">Nazwa:</string>\n    <string name=\"patch_item_extra_version\">Wersja:</string>\n    <string name=\"patch_item_extra_author\">Autor:</string>\n    <string name=\"patch_item_extra_kpm_license\">Licencja:</string>\n    <string name=\"patch_item_extra_kpm_desciption\">Opis:</string>\n    <string name=\"patch_item_extra_args\">Argumenty:</string>\n    <string name=\"patch_item_extra_event\">Wydarzenie:</string>\n    <string name=\"patch_item_skey\">SuperKey</string>\n    <string name=\"patch_custom_superkey\">Niestandardowy SuperKey</string>\n    <string name=\"patch_custom_superkey_summary\">Nadal możesz ustawić SuperKey do zarządzania Root i jądrem, menedżer jest autoryzowany przez wbudowany podpis jądra</string>\n    <string name=\"patch_item_set_skey_label\">SuperKey powinien mieć od 8 do 63 znaków i zawierać zarówno cyfry, jak i litery, ale bez znaków specjalnych.</string>\n    <string name=\"patch_confirm_superkey\">Potwierdź SuperKey</string>\n    <string name=\"patch_skey_mismatch\">SuperKey się nie zgadza</string>\n\n    <string name=\"home_kernel\">Wersja jądra</string>\n    <string name=\"home_manager_version\">Wersja menedżera</string>\n    <string name=\"home_fingerprint\">Odcisk palca</string>\n    <string name=\"home_selinux_status\">Status SELinux</string>\n    <string name=\"home_selinux_status_disabled\">Wyłączony</string>\n    <string name=\"home_selinux_status_enforcing\">Wymuszony</string>\n    <string name=\"home_selinux_status_permissive\">Pobłażliwy</string>\n    <string name=\"home_selinux_status_unknown\">Nieznany</string>\n    <string name=\"settings_selinux_mode\">Tryb SELinux</string>\n    <string name=\"settings_selinux_mode_summary\">Wybierz tryb wymuszania SELinux</string>\n    <string name=\"settings_selinux_mode_enforcing\">Egzekwowany (Ścisły)</string>\n    <string name=\"settings_selinux_mode_permissive\">Pobłażliwy (Łagodny)</string>\n    <string name=\"settings_selinux_current_mode\">Obecny: %s</string>\n    <string name=\"settings_selinux_mode_enforcing_summary\">SELinux całkowicie egzekwuje zasady dostępu</string>\n    <string name=\"settings_selinux_mode_permissive_summary\">SELinux tylko rejestruje naruszenia, nie blokuje</string>\n    <string name=\"home_device_info\">Urządzenie</string>\n    <string name=\"home_system_version\">Wersja systemu</string>\n    <string name=\"home_kpatch_version\">Wersja KernelPatch</string>\n    <string name=\"home_su_path\">Plik wykonywalny su</string>\n    <string name=\"home_apatch_version\">Wersja FolkPatch</string>\n    <string name=\"kpm\">Moduły KP</string>\n    <string name=\"kpm_kp_not_installed\">KernelPatch jest niezainstalowany</string>\n    <string name=\"kpm_add_kpm\">Dodaj KPM</string>\n    <string name=\"kpm_load\">Załaduj</string>\n    <string name=\"kpm_install\">Instaluj</string>\n    <string name=\"kpm_embed\">Osadź</string>\n    <string name=\"kpm_load_toast_succ\">Pomyślnie załadowano</string>\n    <string name=\"kpm_load_toast_failed\">Załadowanie nie powiodło się</string>\n    <string name=\"kpm_unload_confirm\">Odładować moduł %s?</string>\n    <string name=\"kpm_unload\">Odładuj</string>\n    <string name=\"kpm_control\">Sterowanie</string>\n    <string name=\"kpm_apm_empty\">Brak załadowanych modułów</string>\n    <string name=\"kpm_version\">Wersja</string>\n    <string name=\"kpm_license\">Licencja</string>\n    <string name=\"kpm_author\">Autor</string>\n    <string name=\"kpm_desc\">Opis</string>\n    <string name=\"kpm_args\">Argumenty</string>\n    <string name=\"su_title\">Superuser</string>\n    <string name=\"su_selinux_via_hook\">Omiń przez hak</string>\n    <string name=\"su_pkg_excluded_label\">Wyklucz</string>\n    <string name=\"su_pkg_excluded_setting_title\">Wyklucz modyfikacje</string>\n    <string name=\"su_pkg_excluded_setting_summary\">Włączenie tej opcji umożliwi FolkPatch przywrócenie wszystkich plików zmodyfikowanych przez moduły tej aplikacji.</string>\n    <string name=\"su_pkg_root_setting_title\">Superuser</string>\n    <string name=\"su_pkg_root_setting_summary\">Włączenie tej opcji przyzna dostęp do roota Twojej aplikacji, pozwalając na używanie poleceń SU</string>\n    <string name=\"su_pkg_normal_setting_title\">Tryb normalny</string>\n    <string name=\"su_pkg_normal_setting_summary\">Stan domyślny, brak specjalnego traktowania.</string>\n    <string name=\"su_batch_exclude_title\">Masowe wykluczanie</string>\n    <string name=\"su_batch_exclude_content\">Wyklucz wstrzykiwanie dla wszystkich aplikacji bez roota, wybierz działanie</string>\n    <string name=\"su_exclude_btn\">Wyklucz</string>\n    <string name=\"su_exclude_reverse_btn\">Odwróć</string>\n    <!-- SuperUser App Action Dialog -->\n    <string name=\"su_app_action_title\">Działanie aplikacji</string>\n    <string name=\"su_app_action_content\">Wybierz działanie do wykonania na aplikacji</string>\n    <string name=\"su_app_action_launch\">Uruchom aplikację</string>\n    <string name=\"su_app_action_force_stop\">Wymuś zatrzymanie</string>\n    <string name=\"su_app_action_launch_success\">Uruchamianie %s</string>\n    <string name=\"su_app_action_force_stop_success\">Wymuszono zatrzymanie %s</string>\n    <string name=\"su_app_action_failed\">Operacja nie powiodła się: %s</string>\n\n    <!-- SU Audit Log -->\n    <string name=\"su_audit_log_title\">Dziennik autoryzacji</string>\n    <string name=\"su_audit_log_empty\">Brak rekordów autoryzacji</string>\n    <string name=\"su_audit_log_clear\">Wyczyść dziennik</string>\n    <string name=\"su_audit_log_clear_confirm\">Potwierdź wyczyszczenie wszystkich rekordów autoryzacji?</string>\n    <string name=\"su_audit_action_grant\">Udzielono</string>\n    <string name=\"su_audit_action_revoke\">Unieważniono</string>\n    <string name=\"su_audit_action_exclude\">Wykluczono</string>\n    <string name=\"su_audit_tab_usage\">Dziennik użycia</string>\n    <string name=\"su_audit_tab_operations\">Dziennik operacji</string>\n\n    <string name=\"su_show_system_apps\">Pokaż aplikacje systemowe</string>\n    <string name=\"su_hide_system_apps\">Ukryj aplikacje systemowe</string>\n    <string name=\"su_refresh\">Odśwież</string>\n    <string name=\"apm\">Moduły AP</string>\n    <string name=\"apm_not_installed\">AndroidPatch nie jest zainstalowany</string>\n    <string name=\"apm_failed_to_enable\">Nie udało się włączyć modułu: %s</string>\n    <string name=\"apm_failed_to_disable\">Nie udało się wyłączyć modułu %s</string>\n    <string name=\"apm_empty\">Brak zainstalowanych modułów</string>\n    <string name=\"apm_remove\">Usuń</string>\n    <string name=\"apm_undo\">Cofnij</string>\n    <string name=\"apm_install\">Instaluj</string>\n    <string name=\"apm_uninstall_confirm\">Odinstalować moduł %s?</string>\n    <string name=\"apm_uninstall_success\">Moduł %s odinstalowany</string>\n    <string name=\"apm_uninstall_failed\">Nie udało się odinstalować modułu %s</string>\n    <string name=\"apm_undo_uninstall_success\">Przywracono %s</string>\n    <string name=\"apm_undo_uninstall_failed\">Nie udało się przywrócić: %s</string>\n    <string name=\"apm_version\">Wersja</string>\n    <string name=\"apm_author\">Autor</string>\n    <string name=\"apm_desc\">Opis</string>\n    <string name=\"apm_overlay_fs_not_available\">Moduły są niedostępne, ponieważ OverlayFS jest wyłączony przez jądro!</string>\n    <string name=\"apm_magisk_conflict\">Moduły są niedostępne, ponieważ istnieje konflikt z Magiskiem!</string>\n    <string name=\"apm_mount_warning_title\">Ważne powiadomienie</string>\n    <string name=\"apm_mount_warning_message\">Domyślnie moduły nie są montowane. Użyj wbudowanego systemu montowania lub metamodułów.</string>\n    <string name=\"apm_mount_warning_button\">Rozumiem</string>\n    <string name=\"apm_reboot_to_apply\">Uruchom ponownie, aby zastosować</string>\n    <string name=\"apm_changelog\">Lista zmian</string>\n    <string name=\"apm_update\">Aktualizacja</string>\n    <string name=\"apm_downloading\">Pobieranie modułu %s…</string>\n    <string name=\"apm_start_downloading\">Rozpocznij pobieranie: %s</string>\n    <string name=\"apm_new_version_available\">Dostępna jest nowa wersja %s. Kliknij, aby uaktualnić.</string>\n    <string name=\"hide_apatch_manager\">Ukryj menedżer FolkPatch</string>\n    <string name=\"hide_apatch_manager_summary\">Zainstaluj aplikację proxy z losowym ID pakietu i niestandardową etykietą</string>\n    <string name=\"hide_apatch_dialog_new_manager_name\">Nowa nazwa menedżera</string>\n    <string name=\"hide_apatch_dialog_summary\">Będzie używana jako nowa nazwa aplikacji pokazana w Launcherze</string>\n    <string name=\"hide_apatch_manager_failure\">Nie udało się ukryć. Zgłoś błąd!</string>\n    <string name=\"setting_reset_su_path\">Zresetuj ścieżkę su</string>\n    <string name=\"setting_reset_su_new_path\">Nowa pełna ścieżka</string>\n    <string name=\"settings_folkx_engine_title\">Silnik animacji FolkX</string>\n    <string name=\"settings_folkx_engine_summary\">Używaj płynnych fizycznych animacji sprężynowych przy przełączaniu stron najwyższego poziomu</string>\n    <string name=\"settings_folkx_animation_type\">Typ animacji</string>\n    <string name=\"settings_folkx_animation_speed\">Prędkość animacji</string>\n    <string name=\"settings_folkx_animation_linear\">Ruch liniowy</string>\n    <string name=\"settings_folkx_animation_spatial\">Ruch przestrzenny</string>\n    <string name=\"settings_folkx_animation_fade\">Przejście (Fade in/out)</string>\n    <string name=\"settings_folkx_animation_vertical\">Przesuwanie pionowe</string>\n    <string name=\"settings_folkx_animation_diagonal\">Przesuwanie ukośne</string>\n    <string name=\"apm_webui_open\">Otwórz</string>\n    <string name=\"apm_action\">Akcja</string>\n    <string name=\"module_shortcut_add\">Skrót</string>\n    <string name=\"module_shortcut_not_supported\">Launcher nie obsługuje skrótów pulpitu.</string>\n    <string name=\"module_shortcut_created\">Utworzono skrót na pulpicie.</string>\n    <string name=\"module_shortcut_updated\">Zaktualizowano skrót.</string>\n    <string name=\"module_shortcut_permission_tip_xiaomi\">Proszę włączyć uprawnienie \"Tworzenie skrótów pulpitu\" dla tej aplikacji w ustawieniach Xiaomi.</string>\n    <string name=\"module_shortcut_permission_tip_oppo\">Proszę włączyć uprawnienie \"Skrót pulpitu\" dla tej aplikacji w ustawieniach OPPO.</string>\n    <string name=\"module_shortcut_permission_tip_default\">Jeśli utworzenie skrótu nie powiedzie się, proszę włączyć uprawnienie skrótu pulpitu dla tej aplikacji w ustawieniach systemu.</string>\n    <string name=\"module_shortcut_name\">Nazwa skrótu</string>\n    <string name=\"module_shortcut_icon\">Ikona skrótu</string>\n    <string name=\"module_shortcut_type\">Typ skrótu</string>\n    <string name=\"module_shortcut_type_webui\">WebUI</string>\n    <string name=\"module_shortcut_type_action\">Action</string>\n    <string name=\"module_shortcut_icon_default\">Użyj domyślnej ikony</string>\n    <string name=\"module_shortcut_icon_select\">Wybierz ikonę</string>\n    <string name=\"enable_web_debugging\">Włącz debugowanie WebView</string>\n    <string name=\"enable_web_debugging_summary\">Może być używane do debugowania WebUI. Włącz tylko w razie potrzeby.</string>\n    <string name=\"settings_apm_install_confirm\">Potwierdź przed instalacją</string>\n    <string name=\"settings_apm_install_confirm_summary\">Pokaż okno dialogowe potwierdzenia przed instalacją modułów</string>\n    <string name=\"settings_enable_module_shortcut_add\">Włącz przycisk dodawania skrótu</string>\n    <string name=\"settings_enable_module_shortcut_add_summary\">Pokaż przycisk dodawania skrótu WebUI na stronie modułów systemowych</string>\n    <string name=\"settings_module_sort_optimization\">Optymalizacja sortowania modułów</string>\n    <string name=\"settings_module_sort_optimization_summary\">Umieść moduły systemowe z WebUI i Action na początku listy</string>\n    <string name=\"settings_fold_system_module\">Zwiń Moduły Systemowe</string>\n    <string name=\"settings_fold_system_module_summary\">Kliknij kartę modułu, aby rozwinąć/zwinąć akcje</string>\n    <string name=\"settings_simple_list_bottom_bar\">Prosty dolny pasek listy</string>\n    <string name=\"settings_simple_list_bottom_bar_summary\">Użyj stylu przycisków tylko z ikonami dla akcji modułu, zainspirowane APatch</string>\n    <string name=\"settings_spliced_card_group\">Spliced Card Group</string>\n    <string name=\"settings_spliced_card_group_summary\">Use M3E continuous card group style for module list</string>\n    <string name=\"settings_show_more_module_info\">Pokaż szczegóły modułu</string>\n    <string name=\"settings_show_more_module_info_summary\">Pokaż ID i rozmiar modułu na liście modułów</string>\n    <string name=\"apm_install_confirm_title\">Instalacja modułu</string>\n    <string name=\"apm_install_confirm_content\">Czy na pewno chcesz zainstalować %s?</string>\n    <string name=\"apm_disable_all_title\">Wyłącz wszystkie moduły</string>\n    <string name=\"apm_disable_all_confirm\">Czy na pewno chcesz wyłączyć wszystkie moduły? Spowoduje to wyłączenie wszystkich zainstalowanych modułów.</string>\n    <string name=\"apm_enable_module_banner\">Włącz baner modułu</string>\n    <string name=\"apm_enable_module_banner_summary\">Wyświetl obraz banera dla modułów jeśli jest dostępny</string>\n    <string name=\"apm_enable_folk_banner\">Dostosuj baner modułu</string>\n    <string name=\"apm_enable_folk_banner_summary\">Naciśnij długo kartę modułu, aby wybrać obraz banera</string>\n    <string name=\"apm_folk_banner_title\">FolkBanner</string>\n    <string name=\"apm_folk_banner_select\">Wybierz obraz</string>\n    <string name=\"apm_folk_banner_clear\">Wyczyść FolkBanner</string>\n    <string name=\"apm_folk_banner_saved\">Wstrzyknięto FolkBanner dla %s.</string>\n    <string name=\"apm_folk_banner_cleared\">Wyczyszczono FolkBanner dla %s.</string>\n    <string name=\"apm_folk_banner_failed\">Nie udało się zaktualizować FolkBanner dla %s.</string>\n    <string name=\"apm_banner_api_mode\">Tryb API</string>\n    <string name=\"apm_banner_api_mode_summary\">Użyj API losowych obrazów lub katalogu lokalnego dla banerów modułów</string>\n    <string name=\"apm_banner_api_source\">Konfiguruj źródło API</string>\n    <string name=\"apm_banner_api_source_hint\">Wprowadź URL API lub ścieżkę lokalną</string>\n    <string name=\"apm_banner_api_source_saved\">Źródło API zapisane</string>\n    <string name=\"apm_banner_api_source_not_configured\">Nie skonfigurowano</string>\n    <string name=\"apm_banner_api_url_configured\">URL API skonfigurowany</string>\n    <string name=\"apm_banner_local_dir_configured\">Katalog lokalny skonfigurowany</string>\n    <string name=\"apm_banner_clear_cache\">Wyczyść pamięć podręczną</string>\n    <string name=\"apm_banner_cache_cleared\">Pamięć podręczna banerów wyczyszczona</string>\n    <string name=\"apm_banner_api_config_title\">Konfiguruj źródło API</string>\n    <string name=\"apm_banner_api_config_desc\">Wprowadź URL API losowych obrazów lub ścieżkę katalogu lokalnego. Każdy moduł otrzyma unikalny baner.</string>\n    <string name=\"apm_banner_api_examples_title\">Przykłady:</string>\n    <string name=\"apm_banner_api_examples\">API: https://picsum.photos/800/400\\nLokalnie: /sdcard/Pictures/Banners</string>\n    <!-- Marketplace API -->\n    <string name=\"apm_api_marketplace_title\">Marketplace API</string>\n    <string name=\"apm_api_preview\">Podgląd</string>\n    <string name=\"apm_api_apply\">Zastosuj</string>\n    <string name=\"apm_api_preview_title\">Podgląd API</string>\n    <string name=\"apm_api_preview_failed\">Nie udało się załadować podglądu</string>\n    <string name=\"apm_api_url_label\">URL API:</string>\n    <string name=\"apm_api_apply_success\">Pomyślnie zastosowano źródło API</string>\n    <string name=\"apm_api_verifying\">Weryfikacja…</string>\n    <string name=\"apm_api_retry\">Ponów</string>\n    <string name=\"apm_api_empty\">Brak dostępnych źródeł API</string>\n    <string name=\"settings_banner_custom_opacity\">Nieprzezroczystość banera</string>\n    <string name=\"settings_banner_custom_opacity_summary\">Użyj niestandardowej nieprzezroczystości dla banerów zamiast trybu tapety</string>\n    <string name=\"settings_banner_opacity\">Przezroczystość banera</string>\n    <string name=\"settings_donot_store_superkey\">Nie przechowuj SuperKey lokalnie</string>\n    <string name=\"settings_donot_store_superkey_summary\">Uwierzytelniaj SuperKey przy każdym uruchomieniu menadżera</string>\n    <string name=\"mode_select_page_title\">Zainstaluj</string>\n    <string name=\"mode_select_page_patch_and_install\">Załataj i zainstaluj</string>\n    <string name=\"mode_select_page_select_file\">Wybierz obraz boot do załatania</string>\n    <string name=\"mode_select_page_install_inactive_slot\">Zainstaluj do nieaktywnego slotu (po aktualizacji OTA)</string>\n    <string name=\"mode_select_page_install_inactive_slot_warning\">Twoje urządzenie będzie **ZMUSZONE** do uruchomienia się w bieżącym nieaktywnym slocie po ponownym uruchomieniu!\\nUżywaj tej opcji tylko po zakończeniu aktualizacji OTA.\\nKontynuować?</string>\n    <string name=\"mode_select_page_select_kpimg\">Użyj lokalnego pliku łatki (KPimg)</string>\n    <string name=\"patch_custom_kpimg_label\">Niestandardowy KPimg</string>\n    <string name=\"patch_custom_kpimg_file\">Plik: %s</string>\n    <string name=\"patch_select_kpimg_btn\">Wybierz KPimg</string>\n    <string name=\"home_dialog_auth_fail_title\">Uwierzytelnianie nie powiodło się</string>\n    <string name=\"home_dialog_auth_fail_content\">Nie można uwierzytelnić SuperKey, co powoduje niepowodzenie aktywacji FolkPatch. Oto możliwe przyczyny błędu uwierzytelniania — sprawdź, która dotyczy:\\n1. Nie użyto KernelPatch do spatchowania pliku boot.img albo nie jest jasne, co dokładnie zostało wykonane.\\n2. Spatchowany boot.img nadal znajduje się na komputerze i nie został wgrany (flashowany) na urządzenie.\\n3. SuperKey został wpisany nieprawidłowo albo zawiera nietypowe znaki (np. z innych alfabetów).\\n4. Urządzenie może nie być kompatybilne z FolkPatch i KernelPatch — dalsze próby mogą nie przynieść efektu.\\n5. Mogły zostać wykonane niestandardowe operacje — np. użycie modułów wykluczających nazwy pakietów, co zablokowało działanie FolkPatch.</string>\n    <string name=\"home_more_menu_feedback_or_suggestion\">Opinie bądź sugestie</string>\n    <string name=\"home_more_menu_about\">O aplikacji</string>\n    <string name=\"home_more_menu_document\">Dokumentacja</string>\n    <string name=\"home_dialog_uninstall_title\">Odinstaluj</string>\n    <string name=\"home_dialog_uninstall_ap_only\">Odinstaluj tylko łatkę</string>\n    <string name=\"home_dialog_uninstall_ap_only_desc\">Usuń tylko AndroidPatch, zachowując menedżer.</string>\n    <string name=\"home_dialog_uninstall_all\">Odinstaluj całkowicie</string>\n    <string name=\"home_dialog_uninstall_all_desc\">Usuń AndroidPatch i rozpocznij pełny proces odinstalowywania.</string>\n    <!-- Install Mode Select -->\n    <string name=\"kp_install_methods\">KernelPatch Łatanie/Instalowanie</string>\n    <string name=\"restore_boot_methods\">Wybierz rozruch do przywrócenia do partycji rozruchowej</string>\n    <string name=\"restore_select_file\">Wybierz plik rozruchowy partycji do przywrócenia</string>\n    <string name=\"kpm_control_dialog_title\">Sterowanie KPM</string>\n    <string name=\"kpm_control_dialog_content\">Wprowadź parametry sterowania:</string>\n    <string name=\"kpm_control_paramters\">Parametry</string>\n    <string name=\"kpm_control_outMsg\">Wynik</string>\n    <string name=\"kpm_control_ok\">Operacja przebiegła pomyślnie!</string>\n    <string name=\"kpm_control_failed\">Operacja nie powiodła się!</string>\n    <string name=\"settings_night_mode_follow_sys\">Podążaj za ciemnym motywem systemu</string>\n    <string name=\"settings_night_mode_follow_sys_summary\">Automatyczne przełącz na ciemny motyw na podstawie ustawień systemowych</string>\n    <string name=\"settings_night_theme_enabled\">Włącz ciemny motyw</string>\n    <string name=\"settings_use_system_color_theme\">Motyw kolorów systemu</string>\n    <string name=\"settings_use_system_color_theme_summary\">Używaj motywu kolorystycznego wygenerowanego przez system na podstawie bieżącej tapety</string>\n    <string name=\"settings_custom_color_theme\">Motyw kolorystyczny</string>\n    <string name=\"amber_theme\">Bursztynowy</string>\n    <string name=\"blue_theme\">Niebieski</string>\n    <string name=\"blue_grey_theme\">Niebiesko-szary</string>\n    <string name=\"brown_theme\">Brązowy</string>\n    <string name=\"cyan_theme\">Cyjanowy</string>\n    <string name=\"deep_orange_theme\">Ciemny pomarańczowy</string>\n    <string name=\"deep_purple_theme\">Ciemny fiolet</string>\n    <string name=\"green_theme\">Zielony</string>\n    <string name=\"indigo_theme\">Indygo</string>\n    <string name=\"light_blue_theme\">Jasnoniebieski</string>\n    <string name=\"light_green_theme\">Jasnozielony</string>\n    <string name=\"lime_theme\">Limonkowy</string>\n    <string name=\"orange_theme\">Pomarańczowy</string>\n    <string name=\"pink_theme\">Różowy</string>\n    <string name=\"purple_theme\">Fioletowy</string>\n    <string name=\"red_theme\">Czerwony</string>\n    <string name=\"sakura_theme\">Sakura</string>\n    <string name=\"teal_theme\">Morski</string>\n    <string name=\"yellow_theme\">Żółty</string>\n    <string name=\"about_app_desc\">Implementacja root oparta na KernelPatch, umożliwiająca hookowanie funkcji jądra bez konieczności rekompilacji jądra.</string>\n    <string name=\"about_powered_by\">Zasilany przez %1$s</string>\n    <string name=\"about_telegram_group\">Grupa na Telegramie</string>\n    <!-- Online Module Strings -->\n    <string name=\"online_module_title\">Moduł systemu online</string>\n    <string name=\"online_module_download_start\">Rozpocznij pobieranie: %s</string>\n    <string name=\"online_module_download_complete\">Pobieranie zakończone: %s</string>\n    <string name=\"online_module_download_notification\">Pobieranie %s. Sprawdź panel powiadomień o postępach i folder Pobrane dla ukończonego pliku.</string>\n    <!-- Online KPM Strings -->\n    <string name=\"online_kpm_title\">Moduły jądra online</string>\n    <string name=\"online_kpm_download_start\">Rozpocznij pobieranie: %s</string>\n    <string name=\"online_kpm_download_complete\">Pobieranie zakończone: %s</string>\n    <string name=\"online_kpm_download_notification\">Pobieranie %s. Sprawdź panel powiadomień o postępach i folder Pobrane dla ukończonego pliku。</string>\n    <!-- Online Script Strings -->\n    <string name=\"online_script_title\">Skrypty online</string>\n    <string name=\"online_script_download_start\">Rozpocznij pobieranie: %s</string>\n    <string name=\"online_script_download_complete\">Pobieranie zakończone: %s</string>\n    <string name=\"online_script_download_notification\">Pobieranie %s</string>\n    <!-- Crash Handle Strings -->\n    <string name=\"crash_handle_title\">Aplikacja się wysypała…</string>\n    <string name=\"crash_handle_copy\">Kopiuj</string>\n    <!-- About Screen Strings -->\n    <string name=\"about_app_version\">Wersja aplikacji: %s</string>\n    <string name=\"about_github\">GitHub</string>\n    <string name=\"about_telegram_channel\">Kanał na Telegramie</string>\n    <string name=\"settings_app_dpi\">DPI aplikacji</string>\n    <string name=\"dpi_apply_settings\">Zastosuj</string>\n    <string name=\"dpi_confirm_title\">Potwierdź zmianę DPI</string>\n    <string name=\"dpi_confirm_message\">Zmienić DPI aplikacji z %1$s na %2$s?</string>\n    <string name=\"settings_alt_icon\">Alternatywna ikona</string>\n    <string name=\"alt_icon_summary\">Użyj alternatywnej ikony programu uruchamiającego</string>\n    <string name=\"settings_magic_mount\">API montowania Folk</string>\n    <string name=\"settings_magic_mount_summary\">Włącz wbudowany system montowania modułów</string>\n    <string name=\"settings_new_app_profile_mode\">Domyślny tryb dla nowych aplikacji</string>\n    <string name=\"settings_new_app_profile_normal\">BEZ ROOTA</string>\n    <string name=\"settings_new_app_profile_root\">ROOT</string>\n    <string name=\"settings_new_app_profile_exclude\">Wyklucz</string>\n    <string name=\"settings_hide_service\">Ukryj FolkPatch</string>\n    <string name=\"settings_hide_service_summary\">Ukryj status odblokowania bootloadera w pewnym stopniu</string>\n    <string name=\"settings_app_title\">Tytuł aplikacji</string>\n    <string name=\"app_title_custom\">Niestandardowy</string>\n    <string name=\"settings_custom_app_title\">Ustaw nazwę aplikacji</string>\n    <string name=\"custom_app_title_dialog_title\">Niestandardowa nazwa aplikacji</string>\n    <string name=\"custom_app_title_dialog_hint\">Wprowadź nazwę aplikacji</string>\n    <string name=\"custom_app_title_dialog_confirm\">Potwierdź</string>\n    <string name=\"custom_app_title_dialog_empty\">Nazwa nie może być pusta</string>\n    <string name=\"cancel\">Anuluj</string>\n    <string name=\"settings_custom_background\">Niestandardowe tło</string>\n    <string name=\"settings_custom_background_enabled\">Włączono niestandardowe tło</string>\n    <string name=\"settings_custom_background_opacity\">Przezroczystość karty</string>\n    <string name=\"settings_custom_background_blur\">Rozmycie tła</string>\n    <string name=\"settings_custom_background_dim\">Przyciemnienie tła</string>\n    <string name=\"settings_select_background_image\">Wybierz obraz tła</string>\n    <string name=\"settings_custom_background_summary\">Ustaw niestandardowy obraz tła</string>\n    <string name=\"settings_custom_background_saved\">Tło zapisane pomyślnie</string>\n    <string name=\"settings_custom_background_error\">Nie udało się zapisać tła</string>\n    <string name=\"settings_background_selected\">Wybrano tło</string>\n    <string name=\"settings_background_image_cleared\">Usunięto obraz tła</string>\n    <string name=\"settings_clear_background\">Wyczyść tło</string>\n    <string name=\"settings_clear_background_confirm\">Czy na pewno chcesz wyczyścić obraz tła?</string>\n    <string name=\"settings_multi_background_mode\">Wielotłowe tła</string>\n    <string name=\"settings_multi_background_mode_summary\">Ustaw różne obrazy tła dla różnych stron</string>\n    <string name=\"settings_select_home_background\">Tło strony głównej</string>\n    <string name=\"settings_select_kernel_background\">Tło modułu jądra</string>\n    <string name=\"settings_select_superuser_background\">Tło strony Superuser</string>\n    <string name=\"settings_select_system_module_background\">Tło modułu systemowego</string>\n    <string name=\"settings_select_settings_background\">Tło strony ustawień</string>\n    <string name=\"settings_advanced_title_style\">Zaawansowany styl tytułu</string>\n    <string name=\"settings_advanced_title_style_summary\">Zastąp tytuł górnego paska niestandardowym obrazem</string>\n    <string name=\"settings_advanced_title_style_enabled\">Zaawansowany styl tytułu włączony</string>\n    <string name=\"settings_select_title_image\">Wybierz obraz tytułu</string>\n    <string name=\"settings_title_image_selected\">Wybrano obraz tytułu</string>\n    <string name=\"settings_title_image_saved\">Pomyślnie zapisano obraz tytułu</string>\n    <string name=\"settings_title_image_error\">Nie udało się zapisać obrazu tytułu</string>\n    <string name=\"settings_clear_title_image\">Wyczyść obraz tytułu</string>\n    <string name=\"settings_clear_title_image_confirm\">Czy na pewno chcesz wyczyścić obraz tytułu?</string>\n    <string name=\"settings_title_image_cleared\">Wyczyszczono obraz tytułu</string>\n    <string name=\"settings_title_image_day_opacity\">Nieprzezroczystość trybu dziennego</string>\n    <string name=\"settings_title_image_night_opacity\">Nieprzezroczystość trybu nocnego</string>\n    <string name=\"settings_title_image_dim\">Przyciemnienie tła</string>\n    <string name=\"settings_title_image_offset_x\">Pozycja pozioma</string>\n    <!-- KPM AutoLoad Strings -->\n    <string name=\"kpm_autoload_title\">Automatyczne ładowanie KPM</string>\n    <string name=\"kpm_autoload_enabled\">Włącz automatyczne ładowanie</string>\n    <string name=\"kpm_autoload_enabled_summary\">Automatycznie ładuj moduły KPM przy uruchomieniu urządzenia</string>\n    <string name=\"settings_grid_working_card_hide_check\">Ukryj ikonę statusu</string>\n    <string name=\"settings_grid_working_card_hide_check_summary\">Ukryj znacznik wyboru lub ikonę ostrzeżenia na karcie roboczej</string>\n    <string name=\"settings_grid_working_card_hide_text\">Ukryj tekst statusu</string>\n    <string name=\"settings_grid_working_card_hide_text_summary\">Ukryj tekst \"Działa\" lub \"Nie zainstalowano\" na karcie roboczej</string>\n    <string name=\"settings_grid_working_card_hide_mode\">Ukryj tryb pracy</string>\n    <string name=\"settings_grid_working_card_hide_mode_summary\">Ukryj tekst \\\"W pełni\\\" lub \\\"Częściowo\\\" na karcie roboczej</string>\n    <string name=\"settings_list_card_hide_status_badge\">Użyj Klasycznego Emoji</string>\n    <string name=\"settings_list_card_hide_status_badge_summary\">Odznaka stanu na stronie głównej zmienia się w klasyczny emoji</string>\n    <string name=\"settings_custom_badge_text\">Niestandardowy tekst odznaki</string>\n    <string name=\"settings_custom_badge_text_summary\">Zmodyfikuj wyświetlanie tekstu odznaki, tylko dla zabawy</string>\n    <string name=\"settings_custom_background_dual_dim\">Włącz podwójne przyciemnienie dzienne/nocne</string>\n    <string name=\"settings_custom_background_dual_dim_desc\">Automatyczne dostosowanie przyciemnienia tła dla jasnych i ciemnych motywów</string>\n    <string name=\"settings_custom_background_day_dim\">Przyciemnienie tła w trybie dziennym</string>\n    <string name=\"settings_custom_background_night_dim\">Przyciemnienie tła w trybie nocnym</string>\n    <string name=\"settings_grid_working_card_dual_opacity\">Włącz podwójną przezroczystość dzienną/nocną</string>\n    <string name=\"settings_grid_working_card_dual_opacity_desc\">Automatyczne dostosowanie przezroczystości karty dla jasnych i ciemnych motywów</string>\n    <string name=\"settings_grid_working_card_day_opacity\">Przezroczystość karty w trybie dziennym</string>\n    <string name=\"settings_grid_working_card_night_opacity\">Przezroczystość karty w trybie nocnym</string>\n    <string name=\"kpm_autoload_json_config\">Konfiguracja JSON</string>\n    <string name=\"kpm_autoload_json_label\">Konfiguracja JSON</string>\n    <string name=\"kpm_autoload_json_placeholder\">{\n  \"enabled\": true,\n  \"kpmPaths\": [\n    \"/sciezka/do/modulu1.kpm\",\n    \"/sciezka/do/modulu2.kpm\"\n  ]\n}</string>\n    <string name=\"kpm_autoload_json_error\">Nieprawidłowy format JSON</string>\n    <string name=\"kpm_autoload_json_helper\">Wprowadź poprawny format JSON z tablicą ścieżek do plików KPM</string>\n    <string name=\"kpm_autoload_save\">Zapisz konfigurację</string>\n    <string name=\"kpm_autoload_save_confirm\">Zapisać konfigurację automatycznego ładowania?</string>\n    <string name=\"kpm_autoload_save_success\">Konfiguracja zapisana pomyślnie</string>\n    <string name=\"kpm_autoload_save_failed\">Nie udało się zapisać konfiguracji</string>\n    <string name=\"kpm_autoload_visual_mode\">Tryb wizualny</string>\n    <string name=\"kpm_autoload_json_mode\">Tryb JSON</string>\n    <string name=\"kpm_autoload_add_kpm\">Dodaj KPM</string>\n    <string name=\"kpm_autoload_remove_kpm\">Usuń</string>\n    <string name=\"kpm_autoload_edit_kpm\">Editar</string>\n    <string name=\"kpm_autoload_edit_dialog_title\">Editar KPM</string>    <string name=\"kpm_autoload_event_label\">Evento:</string>    <string name=\"kpm_autoload_args_label\">Argumentos:</string>    <string name=\"kpm_autoload_event_service\">service</string>    <string name=\"kpm_autoload_event_post_fs_data\">post-fs-data</string>\n    <string name=\"kpm_autoload_kpm_list_title\">Lista modułów KPM</string>\n    <string name=\"kpm_autoload_no_kpm_added\">Nie dodano żadnych modułów KPM</string>\n    <string name=\"kpm_autoload_kpm_path\">Ścieżka KPM</string>\n    <string name=\"kpm_autoload_file_not_found\">Nie znaleziono pliku</string>\n    <string name=\"kpm_autoload_select_kpm_file\">Wybierz plik KPM</string>\n    <string name=\"kpm_autoload_first_time_title\">O automatycznym ładowaniu KPM</string>\n    <string name=\"kpm_autoload_first_time_message\">Ta funkcja pozwala automatycznie tymczasowo załadować wszystkie skonfigurowane KPM przy starcie. To podejście jest bardziej wygodne niż bezpośrednie osadzanie w jądrze.\n\nNa przykład, KPM, które zapobiegają modyfikacji partycji mogą być używane tylko tymczasowo, ponieważ osadzanie może spowodować awarię rozruchu. Lub jeśli nie chcesz modyfikować partycji BOOT, możesz użyć tej konfiguracji do szybkiego ładowania modułów.\n\nUpewnij się, że aplikacja jest całkowicie zamknięta i ponownie otwarta, aby polecenia zostały wykonane. Zazwyczaj ładowanie następuje również przy starcie. Czasowe czarny ekran menedżera jest normalnym zjawiskiem! Używana jest czysto后台owa metoda ładowania, pamiętaj, aby ręcznie przesunąć w dół, aby odświeżyć i sprawdzić, czy moduły zostały poprawnie załadowane!</string>\n    <string name=\"kpm_autoload_first_time_confirm\">Rozumiem</string>\n    <string name=\"kpm_autoload_do_not_show_again\">Nie pokazuj ponownie</string>\n    <!-- APM Bulk Install Strings -->\n    <string name=\"apm_bulk_install_title\">Instalacja zbiorcza modułów systemowych</string>\n    <string name=\"apm_bulk_install_list_title\">Lista modułów</string>\n    <string name=\"apm_bulk_install_add\">Dodaj moduły</string>\n    <string name=\"apm_bulk_install_empty\">Brak dodanych modułów</string>\n    <string name=\"apm_bulk_install_action\">Instalacja zbiorcza</string>\n    <string name=\"apm_bulk_install_log_title\">Log instalacji zbiorczej</string>\n    <string name=\"apm_bulk_install_log_start\">Rozpoczynanie instalacji zbiorczej...</string>\n    <string name=\"apm_bulk_install_log_installing\">Instalowanie %1$s</string>\n    <string name=\"apm_batch_install_full_process\">Kompletny proces instalacji wsadowej</string>\n    <string name=\"apm_batch_install_full_process_summary\">Instalacja wsadowa modułów systemowych jest wykonywana przy użyciu pełnego procesu</string>\n    <string name=\"next_module\">Następny moduł</string>\n    <string name=\"apm_bulk_install_log_installed\">Moduł %s zainstalowany.</string>\n    <string name=\"apm_bulk_install_log_done\">Wszystkie operacje zakończone.</string>\n    <string name=\"apm_bulk_install_first_use_text\">Ta funkcja pozwala na zainstalowanie wielu modułów naraz. Jest to szybka metoda instalacji, odpowiednia dla modułów, które podczas instalacji nie wymagają operacji na klawiszach. Dla modułów wymagających interakcji z klawiszami głośności, proszę włącz tryb pełnej instalacji w ustawieniach.</string>\n    <string name=\"apm_bulk_install_remove\">Usuń</string>\n    <string name=\"apm_first_use_title\">Witaj w modułach systemowych</string>\n    <string name=\"apm_first_use_text\">Witaj w modułach systemowych, tutaj używane są moduły kompatybilne z ekosystemem Magisk. Kliknij przycisk w prawym dolnym rogu, aby zainstalować moduły. Możesz również użyć instalatora na górze, aby zainstalować moduły hurtowo. Dostępna jest również funkcja tworzenia kopii zapasowej wszystkich modułów jednym kliknięciem, ale pamiętaj, że to rozwiązanie może nie być odpowiednie dla wszystkich modułów, więc warto robić własne kopie zapasowe.</string>\n    <!-- Hide Settings Strings -->\n    <string name=\"home_hide_su_path\">Ukryj ścieżkę pliku wykonywalnego su</string>\n    <string name=\"home_hide_kpatch_version\">Ukryj wersję poprawki jądra</string>\n    <string name=\"home_hide_fingerprint\">Ukryj odcisk palca</string>\n    <string name=\"home_hide_zygisk\">Ukryj implementację Zygisk</string>\n    <string name=\"home_hide_mount\">Ukryj implementację montowania</string>\n    <string name=\"home_hide_su_path_summary\">Ukryj ścieżkę pliku wykonywalnego su w karcie informacyjnej</string>\n    <string name=\"home_hide_kpatch_version_summary\">Ukryj wersję poprawki jądra w karcie informacyjnej</string>\n    <string name=\"home_hide_zygisk_summary\">Ukryj implementację Zygisk w karcie informacyjnej</string>\n    <string name=\"home_hide_mount_summary\">Ukryj implementację montowania w karcie informacyjnej</string>\n    <string name=\"home_hide_fingerprint_summary\">Ukryj odcisk palca w karcie informacyjnej</string>\n    <string name=\"kpm_page_first_time_title\">Ostrzeżenie</string>\n    <string name=\"kpm_page_first_time_message\">Moduły jądra bezpośrednio modyfikują implementację rozruchu (Boot). W przeciwieństwie do modułów systemowych, nie posiadają one dobrego mechanizmu ratunkowego. Jeśli pojawią się problemy, można je naprawić tylko wchodząc w tryb Fastboot. Zaleca się najpierw załadować moduł, aby upewnić się, że nie ma problemów przed osadzeniem go w Boot. Jeśli moduł może być używany tylko przez ładowanie, możesz wypróbować funkcję automatycznego ładowania modułów KPM. Jeśli nie rozumiesz modułów jądra, nie używaj tej funkcji!</string>\n    <!-- Module Backup/Restore Strings -->\n    <string name=\"apm_backup_title\">Kopia zapasowa modułów</string>\n    <string name=\"apm_restore_title\">Przywróć moduły</string>\n    <string name=\"apm_backup_success\">Kopia zapasowa zakończona sukcesem</string>\n    <string name=\"apm_restore_success\">Przywracanie zakończone sukcesem</string>\n    <string name=\"apm_backup_failed\">Kopia zapasowa nieudana</string>\n    <string name=\"apm_restore_failed\">Przywracanie nieudane</string>\n    <string name=\"apm_backup_failed_msg\">Kopia zapasowa nieudana: %s</string>\n    <string name=\"apm_restore_failed_msg\">Przywracanie nieudane: %s</string>\n    <string name=\"apm_copy_list_title\">Kopiuj listę</string>\n    <string name=\"apm_copy_list_success\">Lista modułów skopiowana</string>\n    <!-- Font Settings -->\n    <string name=\"settings_custom_font\">Niestandardowa czcionka</string>\n    <string name=\"settings_select_font_file\">Wybierz plik czcionki</string>\n    <string name=\"settings_custom_font_enabled\">Włączono niestandardową czcionkę</string>\n    <string name=\"settings_custom_font_summary\">Użyj niestandardowej czcionki TTF</string>\n    <string name=\"settings_font_selected\">Wybrano niestandardową czcionkę</string>\n    <string name=\"settings_custom_font_error\">Nie udało się zapisać pliku czcionki</string>\n    <string name=\"settings_custom_font_saved\">Pomyślnie zapisano niestandardową czcionkę</string>\n    <string name=\"settings_clear_font\">Przywróć domyślną czcionkę</string>\n    <string name=\"settings_clear_font_confirm\">Czy na pewno chcesz przywrócić domyślną czcionkę?</string>\n    <string name=\"settings_font_cleared\">Przywrócono domyślną czcionkę</string>\n    <string name=\"settings_font_select_hint\">Wybierz plik czcionki TTF</string>\n    <!-- Auto Backup Strings -->\n    <string name=\"settings_auto_backup_module\">Automatyczna kopia zapasowa modułów systemowych</string>\n    <string name=\"settings_auto_backup_module_summary\">Automatycznie twórz kopię zapasową pliku modułu w katalogu prywatnym podczas instalacji</string>\n    <string name=\"settings_open_backup_dir\">Otwórz katalog kopii zapasowych</string>\n    <string name=\"backup_dir_empty\">Katalog kopii zapasowych jest pusty</string>\n    <string name=\"backup_dir_open_failed\">Nie udało się otworzyć katalogu kopii zapasowych</string>\n    <string name=\"auto_backup_failed\">Automatyczna kopia zapasowa nie powiodła się: %s</string>\n    <string name=\"auto_backup_success\">Automatyczna kopia zapasowa zakończona sukcesem: %s</string>\n    <string name=\"settings_auto_backup_boot\">Automatyczna kopia zapasowa Boot</string>\n    <string name=\"settings_auto_backup_boot_summary\">Automatycznie kopiuj Boot do lokalnego magazynu (Downloads/FolkPatch/BootBackups)</string>\n    <!-- Auto-added missing strings -->\n    <string name=\"app_name\" translatable=\"false\">FolkPatch</string>\n    <!-- Home Layout Strings -->\n    <string name=\"settings_home_layout_style\">Styl strony głównej</string>\n    <string name=\"settings_home_layout_default\">Lista</string>\n    <string name=\"settings_home_layout_grid\">Siatka</string>\n    <string name=\"settings_home_layout_focus\">Skupienie</string>\n    <string name=\"settings_home_layout_sign\">Sign</string>\n    <string name=\"settings_home_layout_circle\">Okrągły</string>\n    <string name=\"settings_home_layout_dashboard_pro\">Pulpit</string>\n    <!-- Unofficial Version Strings -->\n    <string name=\"unofficial_version_title\">Wersja nieoficjalna</string>\n    <string name=\"unofficial_version_message\">Używasz FolkPatch, który nie jest oficjalną aplikacją, pobierz oficjalną aplikację!</string>\n    <string name=\"go_to_github\">Przejdź na Github</string>\n    <string name=\"settings_save_theme\">Zapisz motyw</string>\n    <string name=\"settings_import_theme\">Importuj motyw</string>\n    <string name=\"settings_theme_saved\">Motyw zapisany</string>\n    <string name=\"settings_theme_imported\">Motyw zaimportowany</string>\n    <string name=\"settings_theme_save_failed\">Nie udało się zapisać motywu</string>\n    <string name=\"settings_theme_import_failed\">Nie udało się zaimportować motywu</string>\n    <string name=\"settings_reset_theme\">Zresetuj motyw</string>\n    <string name=\"settings_reset_theme_confirm\">Czy na pewno chcesz zresetować wszystkie ustawienia motywu do domyślnych? Spowoduje to wyczyszczenie wszystkich niestandardowych tłów, czcionek, muzyki i efektów dźwiękowych.</string>\n    <string name=\"settings_theme_reset\">Motyw zresetowany</string>\n    <string name=\"settings_theme_reset_failed\">Nie udało się zresetować motywu</string>\n    <!-- Theme Strings -->\n    <string name=\"theme_export_title\">Eksportuj motyw</string>\n    <string name=\"theme_import_title\">Importuj motyw</string>\n    <string name=\"theme_name\">Nazwa motywu</string>\n    <string name=\"theme_type\">Typ motywu</string>\n    <string name=\"theme_type_phone\">Telefon</string>\n    <string name=\"theme_type_tablet\">Tablet</string>\n    <string name=\"theme_version\">Wersja</string>\n    <string name=\"theme_author\">Autor</string>\n    <string name=\"theme_description\">Opis</string>\n    <string name=\"theme_export_action\">Eksportuj</string>\n    <string name=\"theme_import_action\">Importuj</string>\n    <string name=\"theme_import_confirm\">Czy na pewno chcesz zaimportować ten motyw?</string>\n    <string name=\"theme_info\">Informacje o motywie</string>\n    <!-- Grid Working Card Background -->\n    <string name=\"settings_grid_working_card_background\">Tło karty roboczej</string>\n    <string name=\"settings_grid_working_card_background_enabled\">Niestandardowe tło karty włączone</string>\n    <string name=\"settings_grid_working_card_background_summary\">Niestandardowy obraz tła dla karty roboczej</string>\n    <string name=\"settings_grid_working_card_background_selected\">Wybrano tło</string>\n    <string name=\"settings_clear_grid_working_card_background\">Wyczyść tło karty</string>\n    <string name=\"settings_clear_grid_working_card_background_confirm\">Czy na pewno chcesz wyczyścić obraz tła karty?</string>\n    <string name=\"settings_grid_working_card_background_saved\">Tło karty zapisane pomyślnie</string>\n    <string name=\"settings_grid_working_card_background_error\">Nie udało się zapisać tła karty</string>\n    <string name=\"settings_grid_working_card_background_cleared\">Tło karty wyczyszczone</string>\n    <!-- Biometric Strings -->\n    <string name=\"action_biometric\">Uwierzytelnianie biometryczne</string>\n    <string name=\"msg_biometric\">Proszę zweryfikować swoją tożsamość biometryczną</string>\n    <string name=\"settings_biometric_login\">Uwierzytelnianie biometryczne</string>\n    <string name=\"settings_biometric_login_summary\">Wymagaj uwierzytelniania biometrycznego przy otwieraniu aplikacji</string>\n    <string name=\"settings_strong_biometric\">Silne uwierzytelnianie biometryczne</string>\n    <string name=\"settings_strong_biometric_summary\">Wymagaj uwierzytelniania przy instalowaniu/deinstalowaniu/wyłączaniu modułów</string>\n    <!-- Theme Store Strings -->\n    <string name=\"theme_store_title\">Sklep motywów</string>\n    <string name=\"theme_source_official\">Oficjalne</string>\n    <string name=\"theme_source_third_party\">Trzecie strony</string>\n    <string name=\"theme_source_local\">Lokalny</string>\n    <string name=\"theme_store_author\">Autor: %s</string>\n    <string name=\"theme_store_version\">Wersja: %s</string>\n    <string name=\"theme_source\">Źródło</string>\n    <string name=\"theme_store_download_failed\">Pobieranie nie powiodło się</string>\n    <string name=\"theme_store_download\">Pobierz</string>\n    <string name=\"theme_store_search_hint\">Szukaj motywów...</string>\n    <!-- Update Check Strings -->\n    <string name=\"settings_auto_update_check\">Automatyczne sprawdzanie aktualizacji</string>\n    <string name=\"settings_auto_update_check_summary\">Automatycznie sprawdzaj aktualizacje przy uruchomieniu aplikacji</string>\n    <string name=\"settings_check_update\">Sprawdź dostępność aktualizacji</string>\n    <string name=\"update_available_title\">Dostępna aktualizacja</string>\n    <string name=\"update_available_message\">Wykryto, że Twoja wersja jest zbyt niska. Czy chcesz pobrać nową wersję?</string>\n    <string name=\"update_action\">Aktualizuj</string>\n    <string name=\"update_close\">Zamknij</string>\n    <string name=\"update_latest\">Masz najnowszą wersję</string>\n    <string name=\"update_error\">Błąd sprawdzania aktualizacji</string>\n    <!--Opcje zwijania-->\n    <string name=\"settings_category_general\">Ogólne</string>\n    <string name=\"settings_category_appearance\">Wygląd</string>\n    <string name=\"settings_appearance_font\">Czcionka</string>\n    <string name=\"settings_appearance_font_summary\">Konfiguracja czcionki</string>\n    <string name=\"settings_appearance_theme\">Motyw</string>\n    <string name=\"settings_appearance_theme_summary\">Sklep z motywami, zapisywanie, importowanie i resetowanie</string>\n    <string name=\"settings_appearance_banner\">Baner</string>\n    <string name=\"settings_appearance_banner_summary\">Konfiguracja banera modułu</string>\n    <string name=\"settings_appearance_layout\">Układ</string>\n    <string name=\"settings_appearance_layout_summary\">Układ strony głównej, nawigacja i dostosowanie kart</string>\n    <string name=\"settings_appearance_background\">Tło</string>\n    <string name=\"settings_appearance_background_summary\">Niestandardowe tło, wideo i wiele teł</string>\n    <string name=\"settings_appearance_night_mode\">Tryb nocny</string>\n    <string name=\"settings_appearance_night_mode_summary\">Ciemny motyw i konfiguracja kolorów</string>\n    <string name=\"settings_amoled_theme\">Czarny motyw AMOLED</string>\n    <string name=\"settings_amoled_theme_desc\">Czysto czarne tło dla trybu ciemnego</string>\n    <string name=\"settings_switch_icon\">Wskaźnik przycisku</string>\n    <string name=\"settings_switch_icon_desc\">Pokaż ikony stanu na przełącznikach</string>\n    <string name=\"settings_category_behavior\">Zachowanie</string>\n    <string name=\"settings_category_function\">Funkcje</string>\n    <string name=\"settings_use_legacy_su_page\">Jednostronicowa autoryzacja superużytkownika</string>\n    <string name=\"settings_use_legacy_su_page_summary\">Strona superużytkownika przełączona na projekt autoryzacji jednostronicowej</string>\n    <string name=\"settings_category_module\">Moduły</string>\n    <string name=\"settings_category_security\">Bezpieczeństwo</string>\n    <!-- Background Music Strings -->\n    <string name=\"settings_background_music\">Muzyka w tle</string>\n    <string name=\"settings_background_music_summary\">Odtwarzaj muzykę w tle, gdy aplikacja jest na pierwszym planie</string>\n    <string name=\"settings_background_music_playing\">Odtwarzanie: %s</string>\n    <string name=\"settings_background_music_enabled\">Muzyka w tle włączona</string>\n    <string name=\"settings_select_music_file\">Wybierz plik muzyczny</string>\n    <string name=\"settings_music_selected\">Muzyka wybrana</string>\n    <string name=\"settings_clear_music\">Wyczyść muzykę</string>\n    <string name=\"settings_clear_music_confirm\">Czy na pewno chcesz wyczyścić muzykę w tle?</string>\n    <string name=\"settings_music_auto_play\">Automatyczne odtwarzanie</string>\n    <string name=\"settings_music_auto_play_summary\">Automatycznie odtwarzaj muzykę po otwarciu aplikacji</string>\n    <string name=\"settings_music_volume\">Głośność</string>\n    <string name=\"settings_music_saved\">Plik muzyczny zapisany</string>\n    <string name=\"settings_music_save_error\">Nie udało się zapisać pliku muzycznego</string>\n    <string name=\"settings_music_cleared\">Muzyka wyczyszczona</string>\n    <string name=\"settings_category_multimedia\">Multimedia</string>\n    <string name=\"settings_category_general_summary\">Język, aktualizacje, SELinux, dostosowania systemu</string>\n    <string name=\"settings_category_appearance_summary\">Motyw, kolory, układ, tło, czcionki</string>\n    <string name=\"settings_category_behavior_summary\">Debugowanie WWW, zachowanie instalacji, ekran główny</string>\n    <string name=\"settings_category_security_summary\">Biometria, zarządzanie superkluczami</string>\n    <string name=\"settings_category_backup_summary\">Lokalna kopia zapasowa, kopia w chmurze, WebDAV</string>\n    <string name=\"settings_category_module_summary\">Informacje o modułach, sortowanie, instalacja wsadowa</string>\n    <string name=\"settings_category_function_summary\">Ukryj FolkPatch, usługa Umount</string>\n    <string name=\"settings_category_multimedia_summary\">Muzyka w tle, dźwięki, wibracje</string>\n    <string name=\"settings_music_playback_control\">Sterowanie odtwarzaniem</string>\n    <string name=\"settings_music_looping\">Odtwarzanie w pętli</string>\n    <string name=\"settings_music_looping_summary\">Powtarzaj bieżący utwór</string>\n    <!-- Theme Store Filter -->\n    <string name=\"theme_store_filter_title\">Filtruj Motywy</string>\n    <string name=\"theme_store_filter_author\">Autor</string>\n    <string name=\"theme_store_filter_author_hint\">Wprowadź nazwę autora</string>\n    <string name=\"theme_store_filter_source\">Źródło</string>\n    <string name=\"theme_store_filter_source_all\">Wszystkie</string>\n    <string name=\"theme_store_filter_type\">Typ Urządzenia</string>\n    <string name=\"theme_store_filter_apply\">Zastosuj</string>\n    <string name=\"theme_store_filter_reset\">Resetuj</string>\n    <!-- Video Background Settings -->\n    <string name=\"settings_video_background\">Tło Wideo</string>\n    <string name=\"settings_video_background_summary\">Użyj wideo jako tło</string>\n    <string name=\"settings_select_video\">Wybierz Wideo</string>\n    <string name=\"settings_video_selected\">Wideo Wybrane</string>\n    <string name=\"settings_video_background_enabled\">Tło Wideo Włączone</string>\n    <string name=\"settings_clear_video_background\">Wyczyść tapetę wideo</string>\n    <string name=\"settings_clear_video_background_confirm\">Czy na pewno chcesz wyczyścić tapetę wideo?</string>\n    <string name=\"settings_video_volume\">Głośność Wideo</string>\n    <!-- File Picker -->\n    <string name=\"file_picker_permission_required\">Wymagane uprawnienia</string>\n    <string name=\"file_picker_permission_desc\">Aby przeglądać pliki, przyznaj uprawnienia \\\"Dostęp do wszystkich plików\\\".</string>\n    <string name=\"file_picker_grant_permission\">Przyznaj uprawnienia</string>\n    <string name=\"file_picker_cancel\">Anuluj</string>\n    <string name=\"file_picker_internal_storage\">Pamięć wewnętrzna</string>\n    <string name=\"file_picker_no_files\">Brak plików</string>\n    <!-- HomeV3 Strings -->\n    <string name=\"home_device_status_title\">Status urządzenia</string>\n    <string name=\"home_device_status_battery_temp\">Temperatura baterii</string>\n    <string name=\"home_device_status_cpu_load\">Obciążenie CPU</string>\n    <string name=\"home_device_status_battery_level\">Poziom baterii</string>\n    <string name=\"home_storage_title\">Pamięć</string>\n    <string name=\"home_storage_internal\">Pamięć wewnętrzna</string>\n    <string name=\"home_storage_ram\">RAM</string>\n    <string name=\"home_storage_zram\">ZRAM</string>\n    <string name=\"home_storage_swap\">Plik swap</string>\n    <string name=\"home_info_kernel\">Jądro</string>\n    <string name=\"home_info_superkey\">SuperKlucz</string>\n    <string name=\"home_info_auth_auth\">Aut</string>\n    <string name=\"home_info_auth_na\">N/D</string>\n    <string name=\"home_info_device_slot\">Slot urządzenia</string>\n    <string name=\"home_info_device_model\">Model urządzenia</string>\n    <string name=\"home_info_running_mode\">Tryb działania</string>\n    <string name=\"home_info_mode_full\">Pełny</string>\n    <string name=\"home_info_mode_half\">Częściowo</string>\n    <string name=\"home_version\">Wersja: %s</string>\n    <string name=\"home_zygisk_implement\">Implementacja Zygisk</string>\n    <string name=\"home_mount_implement\">Implementacja montowania</string>\n    <string name=\"settings_app_list_loading_scheme\">Schemat wczytywania listy aplikacji</string>\n    <string name=\"settings_app_list_loading_scheme_summary\">Wybierz sposób wczytywania listy aplikacji</string>\n    <string name=\"app_list_loading_scheme_root_service\">RootService (Domyślnie)</string>\n    <string name=\"app_list_loading_scheme_package_manager\">PackageManager (API systemowe)</string>\n    <string name=\"app_list_loading_scheme_dialog_title\">Schemat wczytywania</string>\n    <string name=\"su_backup_list\">Lista kopii zapasowych</string>\n    <string name=\"su_restore_list\">Lista przywracania zapasowych</string>\n    <string name=\"backup_success\">Kopia zapasowana pomyślnie</string>\n    <!-- Badge Count Settings -->\n    <string name=\"enable_badge_count\">Ustawienia licznika odznak</string>\n    <string name=\"enable_badge_count_summary\">Skonfiguruj wyświetlanie licznika odznak dla elementów nawigacyjnych</string>\n    <string name=\"badge_superuser\">Pokaż odznakę Superuser</string>\n    <string name=\"badge_apm\">Pokaż odznakę modułu systemowego</string>\n    <string name=\"badge_kernel\">Pokaż odznakę modułu jądra</string>\n    <string name=\"settings_sound_effect\">Efekt dźwiękowy</string>\n    <string name=\"settings_sound_effect_summary\">Odtwarzaj dźwięk przy kliknięciu</string>\n    <string name=\"settings_sound_effect_enabled\">Włączone</string>\n    <string name=\"settings_sound_effect_playing\">Wybrano: %s</string>\n    <string name=\"settings_sound_effect_source\">Źródło dźwięku</string>\n    <string name=\"settings_sound_effect_source_local\">Plik lokalny</string>\n    <string name=\"settings_sound_effect_source_preset\">Wstępne ustawienie</string>\n    <string name=\"settings_sound_effect_preset_title\">Dźwięki wstępne</string>\n    <string name=\"settings_select_sound_effect\">Wybierz plik dźwiękowy</string>\n    <string name=\"settings_sound_effect_selected\">Wybrano plik dźwiękowy</string>\n    <string name=\"settings_sound_effect_scope\">Zakres efektu</string>\n    <string name=\"settings_sound_effect_scope_global\">Globalny (Wszędzie)</string>\n    <string name=\"settings_sound_effect_scope_bottom_bar\">Tylko dolny pasek</string>\n    <string name=\"settings_clear_sound_effect\">Wyczyść efekt dźwiękowy</string>\n    <string name=\"settings_clear_sound_effect_confirm\">Czy na pewno chcesz usunąć efekt dźwiękowy?</string>\n    <string name=\"settings_sound_effect_cleared\">Wyczyszczono efekt dźwiękowy</string>\n    <string name=\"settings_startup_sound\">Dźwięk uruchomienia</string>\n    <string name=\"settings_startup_sound_summary\">Odtwórz dźwięk przy uruchomieniu aplikacji</string>\n    <string name=\"settings_startup_sound_enabled\">Dźwięk uruchomienia włączony</string>\n    <string name=\"settings_startup_sound_playing\">Odtwarzanie: %s</string>\n    <string name=\"settings_select_startup_sound\">Wybierz dźwięk uruchomienia</string>\n    <string name=\"settings_startup_sound_selected\">Dźwięk uruchomienia wybrany</string>\n    <string name=\"settings_clear_startup_sound\">Wyczyść dźwięk uruchomienia</string>\n    <string name=\"settings_clear_startup_sound_confirm\">Czy na pewno chcesz usunąć dźwięk uruchomienia?</string>\n    <string name=\"settings_startup_sound_cleared\">Dźwięk uruchomienia usunięty</string>\n    <string name=\"settings_enable_cloud_backup_summary\">Automatyczna kopia zapasowa w chmurze</string>\n    <string name=\"settings_configure_webdav\">Konfiguruj usługę WebDAV</string>\n    <string name=\"webdav_config_title\">Konfiguracja WebDAV</string>\n    <string name=\"webdav_url\">URL WebDAV</string>\n    <string name=\"webdav_username\">Nazwa użytkownika</string>\n    <string name=\"webdav_password\">Hasło</string>\n    <string name=\"webdav_uploading\">Przesyłanie do WebDAV...</string>\n    <string name=\"webdav_backup_success\">Kopia zapasowa WebDAV udana</string>\n    <string name=\"webdav_backup_failed\">Błąd kopii zapasowej WebDAV: %s</string>\n    <string name=\"save\">Zapisz</string>\n    <string name=\"test\">Test</string>\n    <string name=\"webdav_path_label\">Ścieżka (np. /Backup)</string>\n    <string name=\"webdav_view_logs\">Pokaż logi</string>\n    <string name=\"webdav_backup_logs_title\">Logi kopii zapasowych</string>\n    <string name=\"webdav_no_logs\">Nie znaleziono logów.</string>\n    <string name=\"webdav_clear_logs\">Wyczyść</string>\n    <string name=\"settings_enable_local_backup\">Włącz lokalną kopię zapasową</string>\n    <string name=\"settings_enable_local_backup_summary\">Kopia zapasowa modułów do lokalnej pamięci (Downloads/FolkPatch/ModuleBackups)</string>\n    <string name=\"close\">Zamknij</string>\n    <!-- Backup Settings -->\n    <string name=\"settings_category_backup\">Ustawienia kopii zapasowej</string>\n    <string name=\"settings_enable_cloud_backup\">Włącz kopię zapasową w chmurze</string>\n    <string name=\"webdav_test_success\">Test zakończony sukcesem</string>\n    <string name=\"webdav_test_failed\">Test nieudany: %s</string>\n    <string name=\"restore_success\">Przywracanie zakończone sukcesem</string>\n    <string name=\"patch_output_written_to\"> Łatka zastosowana pomyślnie, plik zapisano do </string>\n    <string name=\"patch_write_failed\"> Błąd zapisu łatkowanego boot.img</string>\n    <!-- Script Library Strings -->\n    <string name=\"script_library\">Biblioteka skryptów</string>\n    <string name=\"script_library_title\">Biblioteka skryptów</string>\n    <string name=\"script_library_empty\">Brak skryptów, kliknij przycisk dodawania</string>\n    <string name=\"script_library_add\">Dodaj skrypt</string>\n    <string name=\"script_library_add_title\">Dodaj skrypt</string>\n    <string name=\"script_library_select_file\">Wybierz plik skryptu</string>\n    <string name=\"script_library_alias\">Pseudonim</string>\n    <string name=\"script_library_alias_hint\">Wprowadź pseudonim skryptu (opcjonalne)</string>\n    <string name=\"script_library_run\">Uruchom</string>\n    <string name=\"script_library_delete\">Usuń</string>\n    <string name=\"script_library_path\">Ścieżka: %s</string>\n    <string name=\"script_library_confirm_delete\">Potwierdzić usunięcie skryptu?</string>\n    <string name=\"script_library_delete_success\">Skrypt usunięty pomyślnie</string>\n    <string name=\"script_library_delete_failed\">Błąd usuwania skryptu</string>\n    <string name=\"script_library_add_success\">Skrypt dodany pomyślnie</string>\n    <string name=\"script_library_add_failed\">Błąd dodawania skryptu: %s</string>\n    <string name=\"script_library_load_failed\">Błąd ładowania biblioteki skryptów</string>\n    <string name=\"script_library_output\">Wyjście wykonania</string>\n    <string name=\"script_library_no_output\">Brak wyjścia</string>\n    <string name=\"script_library_save_log\">Zapisz log</string>\n    <string name=\"script_library_log_saved\">Log zapisany w %s</string>\n    <string name=\"script_library_log_save_failed\">Błąd zapisu logu: %s</string>\n    <string name=\"settings_vibration\">Wibracje i haptyka</string>\n    <string name=\"settings_vibration_summary\">Wibracje przy dotyku</string>\n    <string name=\"settings_vibration_enabled\">Włącz wibracje</string>\n    <string name=\"settings_vibration_intensity\">Intensywność wibracji</string>\n    <string name=\"settings_vibration_scope\">Zakres wibracji</string>\n    <string name=\"settings_vibration_scope_global\">Globalny (wszędzie)</string>\n    <string name=\"settings_vibration_scope_bottom_bar\">Tylko dolny pasek</string>\n    <string name=\"superuser\">Superuser</string>\n    <string name=\"module\">Moduły</string>\n    <string name=\"search_modules\">Szukaj modułów...</string>\n    <string name=\"search_scripts\">Szukaj skryptów...</string>\n    <!-- Umount Service -->\n    <string name=\"settings_umount_service\">Odmontowanie Zig</string>\n    <string name=\"settings_umount_service_summary\">Skonfiguruj punkty montowania do automatycznego odmontowania przy uruchamianiu</string>\n    <string name=\"umount_config_title\">Konfiguracja Umount</string>\n    <string name=\"umount_config_enabled\">Włącz Umount</string>\n    <string name=\"umount_config_enabled_summary\">Automatyczne odmontowanie określonych punktów montowania przy uruchamianiu</string>\n    <string name=\"umount_config_paths_label\">Ścieżki punktów montowania (jedna na wiersz)</string>\n    <string name=\"umount_config_paths_placeholder\">/system/vendor/app\\n/system/vendor/priv-app</string>\n    <string name=\"umount_config_paths_helper\">Wprowadź jedną ścieżkę punktu montowania na wiersz do odmontowania</string>\n    <string name=\"umount_config_save\">Zapisz</string>\n    <string name=\"umount_config_save_confirm\">Potwierdzić zapisanie konfiguracji?</string>\n    <string name=\"umount_config_save_success\">Konfiguracja zapisana pomyślnie</string>\n    <string name=\"umount_config_save_failed\">Nie udało się zapisać konfiguracji</string>\n    <!-- Strona Moje Motywy -->\n    <string name=\"my_themes_title\">Moje motywy</string>\n    <string name=\"my_themes_empty\">Brak motywów</string>\n    <string name=\"my_themes_empty_action\">Przeglądaj Sklep z Motywami</string>\n    <string name=\"my_themes_apply\">Zastosuj motyw</string>\n    <string name=\"my_themes_delete\">Usuń motyw</string>\n    <string name=\"my_themes_delete_confirm\">Czy na pewno chcesz usunąć ten motyw?</string>\n    <string name=\"my_themes_deleted\">Usunięto motyw</string>\n    <string name=\"my_themes_applied\">Zastosowano motyw</string>\n    <string name=\"my_themes_apply_failed\">Nie udało się zastosować motywu</string>\n    <string name=\"my_themes_details\">Szczegóły motywu</string>\n    <!-- Dialog Pobierania -->\n    <string name=\"theme_download_title\">Pobieranie motywu</string>\n    <string name=\"theme_download_completed\">Pobieranie zakończone</string>\n    <string name=\"theme_download_failed\">Pobieranie nieudane</string>\n    <string name=\"theme_download_progress\">Postęp</string>\n    <string name=\"theme_download_file\">Plik motywu</string>\n    <string name=\"theme_download_image\">Obraz podglądu</string>\n    <string name=\"theme_download_cancel\">Anuluj</string>\n    <string name=\"theme_download_pause\">Wstrzymaj</string>\n    <string name=\"theme_download_resume\">Wznów</string>\n    <string name=\"theme_download_retry\">Ponów</string>\n    <string name=\"theme_download_apply\">Zastosuj motyw</string>\n    <string name=\"theme_download_go_to_my_themes\">Moje motywy</string>\n    <string name=\"theme_download_retrying\">Ponawianie (%d/3)...</string>\n    <string name=\"theme_download_preparing\">Przygotowywanie pobierania...</string>\n    <string name=\"theme_download_downloading_file\">Pobieranie pliku motywu...</string>\n    <string name=\"theme_download_downloading_image\">Pobieranie obrazu podglądu...</string>\n    <string name=\"theme_download_finalizing\">Finalizowanie...</string>\n    <string name=\"settings_predictive_back\">Predykcyjny gest wstecz</string>\n    <string name=\"settings_predictive_back_summary\">Włącz animację predykcyjnego gestu wstecz w Androidzie 14+</string>\n    <string name=\"app_title_apatch_folk\">Folk APatch</string>\n    <string name=\"app_title_apatchx\">APatch X</string>\n    <string name=\"app_title_apatch\">APatch</string>\n    <string name=\"patch_mode_restore\">Tryb: Przywracanie</string>\n    <string name=\"ink_wash_theme\">Malarstwo tuszowe</string>\n    <string name=\"theme_color\">Kolor motywu</string>\n    <string name=\"theme_light\">Jasny</string>\n    <string name=\"theme_dark\">Ciemny</string>\n    <string name=\"theme_system\">System</string>\n    <string name=\"loading_modules\">Ładowanie modułów…</string>\n    <string name=\"loading_scripts\">Wyszukiwanie skryptów…</string>\n    <string name=\"loading_themes\">Ładowanie motywów…</string>\n    <string name=\"loading_apis\">Ładowanie źródeł API…</string>\n    <string name=\"app_title_fpatch\">FPatch</string>\n    <string name=\"app_title_folkpatch\">FolkPatch</string>\n    <string name=\"app_title_kernelpatch\">KernelPatch</string>\n    <string name=\"app_title_kernelsu\">KernelSU</string>\n    <string name=\"app_title_supersu\">SuperSU</string>\n    <string name=\"app_title_superuser\">Superuser</string>\n    <string name=\"app_title_superpatch\">SuperPatch</string>\n    <string name=\"app_title_magicpatch\">MagicPatch</string>\n    <string name=\"settings_custom_badge_text_full_half\">W pełni/Częściowo (domyślnie)</string>\n    <string name=\"settings_custom_badge_text_lkm\">LKM</string>\n    <string name=\"settings_custom_badge_text_gki\">GKI</string>\n    <string name=\"settings_custom_badge_text_n_gki\">N-GKI</string>\n    <string name=\"settings_custom_badge_text_oki\">OKI</string>\n    <string name=\"settings_custom_badge_text_built_in\">Wbudowany</string>\n\n    <string name=\"home_device_status_battery_charging\">Ładowanie</string>\n    <string name=\"home_device_status_cpu_temp\">Temperatura CPU</string>\n    <string name=\"home_device_status_memory_trend\">Trend pamięci</string>\n    <string name=\"home_storage_partitions\">Partycje magazynu</string>\n    <string name=\"home_device_status_cpu_freq\">Częstotliwość CPU</string>\n    <string name=\"home_network_rx\">Pobieranie</string>\n    <string name=\"home_network_tx\">Wysyłanie</string>\n    <string name=\"settings_home_layout_stats\">Statystyki</string>\n    <string name=\"home_stats_more_options\">Więcej opcji</string>\n    <string name=\"home_stats_kp_label\">KP</string>\n    <string name=\"home_stats_manager_label\">Menedżer</string>\n    <string name=\"home_device_status_gpu_load\">GPU</string>\n    <string name=\"home_stats_kernel_modules\">Moduły jądra</string>\n    <string name=\"home_stats_apm_modules\">Moduły systemowe</string>\n    <string name=\"home_stats_superusers\">Superużytkownicy</string>\n    <string name=\"settings_kernel_spoof\">Konfiguracja fałszowania jądra</string>\n    <string name=\"settings_kernel_spoof_summary\">Fałszuj wersję jądra i czas budowy</string>\n    <string name=\"settings_kernel_spoof_version\">Wersja jądra</string>\n    <string name=\"settings_kernel_spoof_build_time\">Czas budowy jądra</string>\n    <string name=\"settings_kernel_spoof_restore\">Przywróć</string>\n\n    <string name=\"kernel_spoof_enabled\">Fałszowanie jądra włączone</string>\n    <string name=\"kernel_spoof_disabled_restored\">Fałszowanie jądra wyłączone i przywrócone</string>\n    <string name=\"kernel_spoof_failed\">Błąd fałszowania jądra: %d</string>\n    <string name=\"kernel_spoof_applied\">Fałszowanie jądra zastosowane</string>\n\n    <string name=\"settings_path_hide\">Ukrywanie ścieżek</string>\n    <string name=\"settings_path_hide_summary\">Ukrywaj pliki i katalogi przed aplikacjami na poziomie jądra</string>\n    <string name=\"path_hide_paths_label\">Ukryte ścieżki (po jednej w linii)</string>\n\n    <string name=\"path_hide_paths_helper\">Wprowadź jedną ścieżkę w każdej linii. Pasujące ścieżki zwrócą ENOENT.</string>\n    <string name=\"path_hide_save\">Zapisz</string>\n    <string name=\"path_hide_enabled\">Ukrywanie ścieżek włączone</string>\n    <string name=\"path_hide_disabled\">Ukrywanie ścieżek wyłączone</string>\n    <string name=\"path_hide_applied\">Konfiguracja ukrywania ścieżek zastosowana</string>\n    <string name=\"path_hide_failed\">Błąd operacji ukrywania ścieżek: %d</string>\n    <string name=\"path_hide_uid_mode\">Tryb wykonywania wg UID</string>\n    <string name=\"path_hide_uid_mode_summary\">Ukrywaj ścieżki tylko dla określonych UID aplikacji</string>\n    <string name=\"path_hide_uids_label\">Docelowe UID (po jednym w wierszu)</string>\n    <string name=\"path_hide_uids_helper\">Wprowadź UID aplikacji. Tylko te aplikacje będą miały ukryte ścieżki.</string>\n    <string name=\"path_hide_uid_save\">Zapisz UID</string>\n    <string name=\"path_hide_uid_mode_enabled\">Tryb wykonywania wg UID włączony</string>\n    <string name=\"path_hide_uid_mode_disabled\">Tryb wykonywania wg UID wyłączony</string>\n    <string name=\"path_hide_filter_system\">Filtruj UID systemowe</string>\n    <string name=\"path_hide_filter_system_summary\">Ukrywaj ścieżki również przed procesami root i systemowymi (UID &lt; 10000)</string>\n    <string name=\"path_hide_filter_system_enabled\">Filtrowanie UID systemowych włączone</string>\n    <string name=\"path_hide_filter_system_disabled\">Filtrowanie UID systemowych wyłączone</string>\n    <string name=\"path_hide_filter_system_warning_title\">Ostrzeżenie</string>\n    <string name=\"path_hide_filter_system_warning_message\">Włączenie tej opcji spowoduje ukrywanie ścieżek również przed procesami root i systemowymi (UID &lt; 10000). Może to spowodować nieprawidłowe działanie niektórych funkcji systemu. Postępuj ostrożnie.</string>\n    <string name=\"path_hide_uid_applied\">Biała lista UID zastosowana</string>\n    <string name=\"path_hide_select_apps\">Wybierz aplikacje</string>\n    <string name=\"path_hide_no_apps_selected\">Brak wybranych aplikacji</string>\n    <string name=\"path_hide_app_removed\">Usunięto %d wpisów odinstalowanych aplikacji</string>\n    <string name=\"path_hide_search_apps\">Szukaj aplikacji…</string>\n    <string name=\"path_hide_show_system\">Pokaż aplikacje systemowe</string>\n    <string name=\"netisolate_title\">Izolacja sieciowa</string>\n    <string name=\"netisolate_enable\">Izolacja sieciowa włączona</string>\n    <string name=\"netisolate_disable\">Izolacja sieciowa wyłączona</string>\n    <string name=\"netisolate_enable_summary\">Blokuj dostęp do sieci dla wybranych aplikacji na poziomie jądra</string>\n    <string name=\"netisolate_uids_label\">Zablokowane aplikacje</string>\n    <string name=\"netisolate_uids_hint\">Wprowadź UID do zablokowania (jedno na linię)</string>\n    <string name=\"netisolate_no_uids\">Brak zablokowanych aplikacji</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-pt-rBR/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\" translatable=\"false\">FolkPatch</string>\n    <string name=\"app_name_fpatch\">FPatch</string>\n\n    <string name=\"desktop_app_name\">Nome do Aplicativo da Área de Trabalho</string>\n\n    <string name=\"home\">Início</string>\n\n    <string name=\"success\">Sucesso</string>\n    <string name=\"failure\">Falha</string>\n\n    <string name=\"patch_warnning\">A instalação envolve riscos. Por favor, certifique-se de que seus dados possuem backup.</string>\n    <string name=\"patch\">Patch</string>\n\n    <string name=\"kernel_patch\">KernelPatch</string>\n    <string name=\"android_patch\">AndroidPatch</string>\n\n    <string name=\"settings_nav_layout_title\">Configuração do Layout de Navegação</string>\n    <string name=\"settings_nav_layout_summary\">Ocultar ou mostrar alguns componentes de navegação</string>\n    <string name=\"settings_nav_scheme\">Esquema de navegação</string>\n    <string name=\"settings_nav_mode\">Modo da barra de navegação</string>\n    <string name=\"settings_nav_mode_summary\">Escolha como a barra de navegação é exibida</string>\n    <string name=\"settings_nav_mode_auto\">Automático Tradicional</string>\n    <string name=\"settings_nav_mode_bottom\">Sempre barra inferior</string>\n    <string name=\"settings_nav_mode_rail\">Sempre barra lateral</string>\n    <string name=\"settings_nav_mode_floating\">Barra inferior flutuante</string>\n    <string name=\"settings_show_apm\">Mostrar módulos do sistema</string>\n    <string name=\"settings_show_kpm\">Mostrar KPM</string>\n    <string name=\"settings_show_superuser\">Mostrar SuperUser</string>\n    <string name=\"settings_floating_auto_hide\">Ocultar Automaticamente</string>\n    <string name=\"settings_floating_auto_hide_summary\">Ocultar automaticamente a barra flutuante após 3 segundos de inatividade</string>\n    <string name=\"settings_floating_swipe_hide\">Ocultar ao Deslizar</string>\n    <string name=\"settings_floating_swipe_hide_summary\">Ocultar a barra ao deslizar para baixo, mostrar ao deslizar para cima</string>\n    <string name=\"settings_navbar_glass_effect\">Efeito de vidro fosco</string>\n    <string name=\"settings_navbar_glass_effect_summary\">Aplicar efeito de vidro fosco na barra de navegação flutuante</string>\n    <string name=\"settings_navbar_glass_blur_strength\">Intensidade do desfoque</string>\n    <string name=\"settings_navbar_glass_transparency\">Transparência do fundo</string>\n    <string name=\"settings_navbar_glass_highlight_strength\">Intensidade do destaque</string>\n    <string name=\"settings_navbar_glass_specular\">Reflexo especular</string>\n    <string name=\"settings_navbar_glass_specular_summary\">Ativar reflexo espelhado na parte superior da barra de navegação</string>\n    <string name=\"settings_navbar_glass_inner_glow\">Brilho interno</string>\n    <string name=\"settings_navbar_glass_inner_glow_summary\">Ativar sutil efeito de brilho na parte inferior da barra de navegação</string>\n    <string name=\"settings_navbar_glass_border\">Borda de vidro</string>\n    <string name=\"settings_navbar_glass_border_summary\">Ativar sutil borda ao redor da barra de navegação</string>\n    <string name=\"settings_stats_top_layout\">Layout Superior</string>\n    <string name=\"settings_stats_top_layout_summary\">Escolha o estilo do cartão de informações superior</string>\n    <string name=\"settings_stats_top_layout_list\">Lista</string>\n    <string name=\"settings_stats_top_layout_grid\">Grade</string>\n    <string name=\"settings_block_kernelpatch_update\">Bloquear atualização do KernelPatch</string>\n    <string name=\"settings_block_kernelpatch_update_summary\">Não mostrar notificações de atualização do KernelPatch</string>\n    <string name=\"settings_block_androidpatch_update\">Bloquear atualização do patch do sistema</string>\n    <string name=\"settings_block_androidpatch_update_summary\">Não mostrar notificações de atualização do patch do sistema (APD)</string>\n    <string name=\"settings_disable_module_update_check\">Desativar verificação de atualização de módulos</string>\n    <string name=\"settings_disable_module_update_check_summary\">Desativar verificação automática de atualizações para módulos do sistema</string>\n\n    <string name=\"reboot\">Reiniciar</string>\n    <string name=\"settings\">Configurações</string>\n    <string name=\"reboot_recovery\">Reiniciar no Recovery</string>\n    <string name=\"reboot_bootloader\">Reiniciar no Bootloader</string>\n    <string name=\"reboot_download\">Reiniciar em modo Download</string>\n    <string name=\"reboot_edl\">Reiniciar em modo EDL</string>\n    <string name=\"reboot_fastbootd\">Reiniciar em modo FastbootD</string>\n    <string name=\"about\">Sobre</string>\n    <string name=\"developer_and_maintainer\">Desenvolvedor | Mantenedor</string>\n    <string name=\"settings_app_language\">Idioma</string>\n    <string name=\"system_default\">Padrão do sistema</string>\n    <string name=\"settings_global_namespace_mode\">Modo Namespace Global</string>\n    <string name=\"settings_global_namespace_mode_summary\">Todas as sessões root usam o namespace de montagem global</string>\n    <string name=\"settings_clear_super_key_dialog\">Você realmente deseja prosseguir?</string>\n    <string name=\"settings_grid_working_card_hide_check\">Ocultar ícone de status</string>\n    <string name=\"settings_grid_working_card_hide_check_summary\">Ocultar o ícone de confirmação ou aviso no cartão de status</string>\n    <string name=\"settings_grid_working_card_hide_text\">Ocultar texto de status</string>\n    <string name=\"settings_grid_working_card_hide_text_summary\">Ocultar o texto \\'Funcionando\\' ou \\'Não instalado\\' no cartão de status</string>\n    <string name=\"settings_grid_working_card_hide_mode\">Ocultar modo de operação</string>\n    <string name=\"settings_grid_working_card_hide_mode_summary\">Ocultar o texto \\'Completo\\' ou \\'Metade\\' no cartão de status</string>\n\n    <string name=\"settings_folkx_engine_title\">FolkX Animation Engine</string>\n    <string name=\"settings_folkx_engine_summary\">Usar animação de mola baseada em física suave ao alternar entre páginas de nível superior</string>\n    <string name=\"settings_folkx_animation_type\">Tipo de Animação</string>\n    <string name=\"settings_folkx_animation_speed\">Velocidade da Animação</string>\n    <string name=\"settings_folkx_animation_linear\">Movimento Linear</string>\n    <string name=\"settings_folkx_animation_spatial\">Movimento Espacial</string>\n    <string name=\"settings_folkx_animation_fade\">Fade In/Out</string>\n    <string name=\"settings_folkx_animation_vertical\">Deslize Vertical</string>\n    <string name=\"settings_folkx_animation_diagonal\">Deslize Diagonal</string>\n\n    <string name=\"su_exclude_all_title\">Excluir Tudo</string>\n    <string name=\"su_exclude_all_confirm\">Tem certeza de que deseja excluir todos os apps não-root?</string>\n    <string name=\"su_batch_exclude_title\">Exclusão em Lote</string>\n    <string name=\"su_batch_exclude_content\">Excluir injeção para todos os apps não-ROOT; por favor, selecione uma ação</string>\n    <string name=\"su_exclude_btn\">Excluir</string>\n    <string name=\"su_exclude_reverse_btn\">Reverter</string>\n\n    <!-- SuperUser App Action Dialog -->\n    <string name=\"su_app_action_title\">Ação do Aplicativo</string>\n    <string name=\"su_app_action_content\">Por favor selecione uma ação para o aplicativo</string>\n    <string name=\"su_app_action_launch\">Iniciar Aplicativo</string>\n    <string name=\"su_app_action_force_stop\">Forçar Parada</string>\n    <string name=\"su_app_action_launch_success\">Iniciando %s</string>\n    <string name=\"su_app_action_force_stop_success\">%s forçado a parar</string>\n    <string name=\"su_app_action_failed\">Operação falhou: %s</string>\n\n    <!-- SU Audit Log -->\n    <string name=\"su_audit_log_title\">Registro de autorização</string>\n    <string name=\"su_audit_log_empty\">Nenhum registro de autorização</string>\n    <string name=\"su_audit_log_clear\">Limpar registro</string>\n    <string name=\"su_audit_log_clear_confirm\">Limpar todos os registros de autorização?</string>\n    <string name=\"su_audit_action_grant\">Concedido</string>\n    <string name=\"su_audit_action_revoke\">Revogado</string>\n    <string name=\"su_audit_action_exclude\">Excluído</string>\n    <string name=\"su_audit_tab_usage\">Registro de Uso</string>\n    <string name=\"su_audit_tab_operations\">Registro de Operações</string>\n\n    <string name=\"home_learn_apatch\">Aprenda FolkPatch</string>\n    <string name=\"home_click_to_learn_apatch\">Saiba mais sobre os recursos do FolkPatch e como usar</string>\n    <string name=\"settings_hide_apatch_card\">Ocultar cartão Aprenda FolkPatch</string>\n    <string name=\"settings_hide_apatch_card_summary\">Ocultar o cartão informativo do FolkPatch na tela inicial</string>\n\n    <string name=\"about_source_code\"><![CDATA[<p>Veja o código fonte em %1$s<p/>Entre no nosso canal %2$s<p/>Entre no nosso grupo %3$s]]></string>\n    <string name=\"send_log\">Enviar logs</string>\n    <string name=\"save_log\">Salvar logs</string>\n    <string name=\"log_saved\">Logs salvos</string>\n    <string name=\"safe_mode\">Modo de Segurança</string>\n    <string name=\"github\">GitHub</string>\n    <string name=\"telegram\">Telegram</string>\n    <string name=\"support_or_donate\">Apoiar / Doar</string>\n\n    <string name=\"super_key\">SuperKey</string>\n    <string name=\"clear_super_key\">Limpar SuperKey</string>\n    <string name=\"patch_set_superkey\">Definir SuperKey</string>\n    <string name=\"home_patch_set_key_desc\">Única credencial para o KernelPatch</string>\n    <string name=\"home_patch_next_step\">Próximo passo</string>\n\n    <string name=\"home_not_installed\">Não instalado</string>\n    <string name=\"home_install_unknown\">Não instalado ou autenticado</string>\n    <string name=\"home_install_unknown_summary\">Clique para instalar</string>\n    <string name=\"home_click_to_install\">Clique para instalar</string>\n    <string name=\"home_working\">Funcionando</string>\n    <string name=\"home_kp_need_update\">Nova versão disponível</string>\n    <string name=\"home_kp_cando_update\">Atualizar</string>\n\n    <string name=\"home_installing\">Instalando</string>\n\n    <string name=\"kpatch_version\">Versão: %s</string>\n    <string name=\"apatch_version\">Versão: %s</string>\n\n    <string name=\"kpatch_version_update\" formatted=\"false\">Versão: %s -&gt; %s</string>\n    <string name=\"kpatch_shadow_path_title\">kpatch</string>\n    <string name=\"home_auth_key_title\">Inserir SuperKey</string>\n    <string name=\"home_auth_key_desc\">Iniciar após certificação</string>\n    <string name=\"home_kpatch_info_title\">Info</string>\n\n    <string name=\"home_ap_cando_install\">Instalar</string>\n\n    <string name=\"home_ap_cando_uninstall\">Desinstalar</string>\n    <string name=\"home_ap_cando_reboot\">Reiniciar</string>\n\n    <string name=\"patch_title\">Patch</string>\n\n    <string name=\"patch_config_title\">Patches</string>\n    <string name=\"patch_mode_bootimg_patch\">Modo: Patch (Arquivo)</string>\n    <string name=\"patch_mode_patch_and_install\">Modo: Patch e Instalar (Direto)</string>\n    <string name=\"patch_mode_install_to_next_slot\">Modo: Instalar no slot inativo (Após OTA)</string>\n    <string name=\"patch_mode_restore\">Modo: Restaurar</string>\n    <string name=\"patch_mode_uninstall_patch\">Modo: Desinstalar KPatch</string>\n    <string name=\"patch_select_bootimg_btn\">Selecionar boot</string>\n    <string name=\"patch_embed_kpm_btn\">Embutir KPM</string>\n    <string name=\"patch_start_patch_btn\">Iniciar</string>\n    <string name=\"patch_start_unpatch_btn\">Remover Patch</string>\n    <string name=\"patch_item_error\">!!ERRO!!</string>\n    <string name=\"patch_item_bootimg\">bootimg</string>\n    <string name=\"patch_item_bootimg_slot\">Slot:</string>\n    <string name=\"patch_item_bootimg_dev\">Dispositivo:</string>\n    <string name=\"patch_item_kernel\">Kernel</string>\n    <string name=\"patch_item_kpimg\">kpimg</string>\n    <string name=\"patch_item_kpimg_version\">Versão:</string>\n    <string name=\"patch_item_kpimg_comile_time\">Data:</string>\n    <string name=\"patch_item_kpimg_config\">Config:</string>\n    <string name=\"patch_item_new_extra_kpm\">Embutir novo</string>\n    <string name=\"patch_item_existed_extra_kpm\">Existente</string>\n    <string name=\"patch_item_extra_name\">Nome:</string>\n    <string name=\"patch_item_extra_version\">Versão:</string>\n    <string name=\"patch_item_extra_author\">Autor:</string>\n    <string name=\"patch_item_extra_kpm_license\">Licença:</string>\n    <string name=\"patch_item_extra_kpm_desciption\">Descrição:</string>\n    <string name=\"patch_item_extra_args\">Args:</string>\n    <string name=\"patch_item_extra_event\">Evento:</string>\n    <string name=\"patch_item_skey\">SuperKey</string>\n    <string name=\"patch_custom_superkey\">SuperKey personalizado</string>\n    <string name=\"patch_custom_superkey_summary\">Você ainda pode definir um SuperKey para gerenciar o Root e o kernel, o gerenciador já está autorizado pela assinatura integrada do kernel</string>\n    <string name=\"patch_item_set_skey_label\">A SuperKey deve ter de 8 a 63 caracteres e incluir números e letras, mas sem caracteres especiais.</string>\n    <string name=\"patch_confirm_superkey\">Confirmar SuperKey</string>\n    <string name=\"patch_skey_mismatch\">SuperKey não confere</string>\n\n    <string name=\"home_kernel\">Versão do Kernel</string>\n    <string name=\"home_manager_version\">Versão do Manager</string>\n    <string name=\"home_fingerprint\">Fingerprint</string>\n\n    <string name=\"home_selinux_status\">Status do SELinux</string>\n    <string name=\"home_selinux_status_disabled\">Desativado</string>\n    <string name=\"home_selinux_status_enforcing\">Enforcing (Restrito)</string>\n    <string name=\"home_selinux_status_permissive\">Permissive (Permissivo)</string>\n    <string name=\"home_selinux_status_unknown\">Desconhecido</string>\n\n    <string name=\"settings_selinux_mode\">Modo SELinux</string>\n    <string name=\"settings_selinux_mode_summary\">Selecionar modo de imposição do SELinux</string>\n    <string name=\"settings_selinux_mode_enforcing\">Enforcing (Restrito)</string>\n    <string name=\"settings_selinux_mode_permissive\">Permissive (Permissivo)</string>\n    <string name=\"settings_selinux_current_mode\">Atual: %s</string>\n    <string name=\"settings_selinux_mode_enforcing_summary\">SELinux aplica totalmente as regras de acesso</string>\n    <string name=\"settings_selinux_mode_permissive_summary\">SELinux apenas registra violações, não bloqueia</string>\n\n    <string name=\"home_device_info\">Dispositivo</string>\n    <string name=\"home_system_version\">Versão do sistema</string>\n    <string name=\"home_kpatch_version\">Versão KernelPatch</string>\n    <string name=\"home_su_path\">Executável su</string>\n    <string name=\"home_apatch_version\">Versão FolkPatch</string>\n\n    <string name=\"kpm\">KPModule (Kernel)</string>\n    <string name=\"kpm_kp_not_installed\">KernelPatch não instalado</string>\n    <string name=\"kpm_add_kpm\">Adic. KPM</string>\n    <string name=\"kpm_load\">Carregar</string>\n    <string name=\"kpm_install\">Instalar</string>\n    <string name=\"kpm_embed\">Embutir</string>\n    <string name=\"kpm_load_toast_succ\">Carregado com sucesso</string>\n    <string name=\"kpm_load_toast_failed\">Falha ao carregar</string>\n    <string name=\"kpm_unload_confirm\">Descarregar módulo %s?</string>\n    <string name=\"kpm_unload\">Descarregar</string>\n    <string name=\"kpm_control\">Controle</string>\n    <string name=\"kpm_apm_empty\">Nenhum módulo carregado</string>\n    <string name=\"kpm_version\">Versão</string>\n    <string name=\"kpm_license\">Licença</string>\n    <string name=\"kpm_author\">Autor</string>\n    <string name=\"kpm_desc\">Desc</string>\n    <string name=\"kpm_args\">Args</string>\n\n    <string name=\"su_title\">Superusuário</string>\n    <string name=\"su_selinux_via_hook\">Ignorar via hook (Bypass)</string>\n    <string name=\"su_pkg_excluded_label\">Excluir</string>\n    <string name=\"su_pkg_excluded_setting_title\">Excluir modificações</string>\n    <string name=\"su_pkg_excluded_setting_summary\">Ativar esta opção permitirá ao FolkPatch restaurar quaisquer arquivos modificados pelos módulos para este app.</string>\n    <string name=\"su_pkg_root_setting_title\">Superusuário</string>\n    <string name=\"su_pkg_root_setting_summary\">Ativar esta opção para conceder acesso de superusuário ao seu app, permitindo usar comandos SU</string>\n    <string name=\"su_pkg_normal_setting_title\">Modo Normal</string>\n    <string name=\"su_pkg_normal_setting_summary\">Nenhum acesso root concedido, o aplicativo é executado sem privilégios de superusuário.</string>\n    <string name=\"su_show_system_apps\">Mostrar apps do sistema</string>\n    <string name=\"su_hide_system_apps\">Ocultar apps do sistema</string>\n    <string name=\"su_refresh\">Atualizar</string>\n\n    <string name=\"apm\">Módulo do Sistema</string>\n    <string name=\"apm_not_installed\">AndroidPatch não instalado</string>\n    <string name=\"apm_failed_to_enable\">Falha ao ativar módulo: %s</string>\n    <string name=\"apm_failed_to_disable\">Falha ao desativar módulo: %s</string>\n    <string name=\"apm_empty\">Nenhum módulo instalado</string>\n    <string name=\"apm_remove\">Remover</string>\n    <string name=\"apm_undo\">Desfazer</string>\n    <string name=\"apm_install\">Instalar</string>\n    <string name=\"apm_uninstall_confirm\">Desinstalar o módulo %s?</string>\n    <string name=\"apm_uninstall_success\">%s desinstalado</string>\n    <string name=\"apm_uninstall_failed\">Falha ao desinstalar: %s</string>\n    <string name=\"apm_undo_uninstall_success\">%s restaurado com sucesso</string>\n    <string name=\"apm_undo_uninstall_failed\">Falha ao restaurar: %s</string>\n    <string name=\"apm_version\">Versão</string>\n    <string name=\"apm_author\">Autor</string>\n    <string name=\"apm_desc\">Desc</string>\n    <string name=\"apm_overlay_fs_not_available\">Módulos indisponíveis pois o OverlayFS está desativado pelo kernel!</string>\n    <string name=\"apm_magisk_conflict\">Módulos indisponíveis devido a conflito com Magisk!</string>\n    <string name=\"apm_mount_warning_title\">Aviso importante</string>\n    <string name=\"apm_mount_warning_message\">Por padrão, os módulos não são montados. Use o sistema de montagem integrado ou metamódulos.</string>\n    <string name=\"apm_mount_warning_button\">Entendido</string>\n    <string name=\"apm_reboot_to_apply\">Reinicie para aplicar</string>\n    <string name=\"apm_changelog\">Changelog</string>\n    <string name=\"apm_update\">Atualizar</string>\n    <string name=\"apm_downloading\">Baixando módulo: %s</string>\n    <string name=\"apm_start_downloading\">Iniciando download: %s</string>\n    <string name=\"apm_new_version_available\">Nova versão %s disponível, clique para atualizar.</string>\n\n    <string name=\"hide_apatch_manager\">Ocultar APatch Manager</string>\n    <string name=\"hide_apatch_manager_summary\">Instalar um app proxy com ID de pacote aleatório e nome personalizado</string>\n    <string name=\"hide_apatch_dialog_new_manager_name\">Novo nome do manager</string>\n    <string name=\"hide_apatch_dialog_summary\">Será usado como o novo nome do app exibido no launcher</string>\n    <string name=\"hide_apatch_manager_failure\">Falha ao ocultar. Por favor, reporte o bug!</string>\n\n    <string name=\"setting_reset_su_path\">Redefinir caminho do su</string>\n    <string name=\"setting_reset_su_new_path\">Novo caminho completo</string>\n\n    <string name=\"apm_webui_open\">Abrir</string>\n    <string name=\"apm_action\">Ação</string>\n    <string name=\"module_shortcut_add\">Atalho</string>\n    <string name=\"module_shortcut_not_supported\">O launcher não suporta atalhos da área de trabalho.</string>\n    <string name=\"module_shortcut_created\">Atalho criado na área de trabalho.</string>\n    <string name=\"module_shortcut_updated\">Atalho atualizado.</string>\n    <string name=\"module_shortcut_permission_tip_xiaomi\">Por favor, habilite a permissão \"Criar atalhos da área de trabalho\" para este aplicativo nas configurações do Xiaomi.</string>\n    <string name=\"module_shortcut_permission_tip_oppo\">Por favor, habilite a permissão \"Atalho da área de trabalho\" para este aplicativo nas configurações do OPPO.</string>\n    <string name=\"module_shortcut_permission_tip_default\">Se a criação do atalho falhar, por favor, habilite a permissão de atalho da área de trabalho para este aplicativo nas configurações do sistema.</string>\n    <string name=\"module_shortcut_name\">Nome do atalho</string>\n    <string name=\"module_shortcut_icon\">Ícone do atalho</string>\n    <string name=\"module_shortcut_type\">Tipo de atalho</string>\n    <string name=\"module_shortcut_type_webui\">WebUI</string>\n    <string name=\"module_shortcut_type_action\">Action</string>\n    <string name=\"module_shortcut_icon_default\">Usar ícone padrão</string>\n    <string name=\"module_shortcut_icon_select\">Selecionar ícone</string>\n    <string name=\"enable_web_debugging\">Ativar depuração WebView</string>\n    <string name=\"enable_web_debugging_summary\">Pode ser usado para depurar WebUI. Ative apenas se necessário.</string>\n    <string name=\"settings_apm_install_confirm\">Confirmar antes de instalar</string>\n    <string name=\"settings_apm_install_confirm_summary\">Mostrar uma caixa de diálogo antes de instalar módulos</string>\n    <string name=\"settings_show_more_module_info\">Mostrar detalhes do módulo</string>\n    <string name=\"settings_show_more_module_info_summary\">Mostrar ID e tamanho do módulo na lista</string>\n    <string name=\"settings_apm_stay_on_page\">Permanecer na página</string>\n    <string name=\"settings_apm_stay_on_page_summary\">Não retornar automaticamente após execução de ação no módulo</string>\n    <string name=\"settings_enable_module_shortcut_add\">Ativar botão de adicionar atalho</string>\n    <string name=\"settings_enable_module_shortcut_add_summary\">Mostrar botão de adicionar atalho WebUI na página de módulos do sistema</string>\n    <string name=\"settings_module_sort_optimization\">Otimização de Ordem dos Módulos</string>\n    <string name=\"settings_module_sort_optimization_summary\">Fazer módulos de sistema com WebUI e Ação aparecerem no topo</string>\n    <string name=\"settings_fold_system_module\">Dobrar Módulos do Sistema</string>\n    <string name=\"settings_fold_system_module_summary\">Clique no cartão do módulo para expandir/colapsar ações</string>\n        <string name=\"settings_simple_list_bottom_bar\">Barra Inferior Simples</string>\n        <string name=\"settings_simple_list_bottom_bar_summary\">Usar estilo de botões somente com ícones para ações de módulo, inspirado no APatch</string>\n    <string name=\"settings_spliced_card_group\">Spliced Card Group</string>\n    <string name=\"settings_spliced_card_group_summary\">Use M3E continuous card group style for module list</string>\n    <string name=\"apm_install_confirm_title\">Instalar Módulo</string>\n    <string name=\"apm_install_confirm_content\">Tem certeza de que deseja instalar %s?</string>\n    <string name=\"apm_disable_all_title\">Desativar Todos os Módulos</string>\n    <string name=\"apm_disable_all_confirm\">Tem certeza de que deseja desativar todos os módulos? Isso desativará todos os módulos instalados.</string>\n    <string name=\"apm_enable_module_banner\">Ativar banner de módulo</string>\n    <string name=\"apm_enable_module_banner_summary\">Exibir imagem de banner para módulos se disponível</string>\n    <string name=\"apm_enable_folk_banner\">Personalizar banner de módulo</string>\n    <string name=\"apm_enable_folk_banner_summary\">Pressione longo no cartão do módulo para selecionar a imagem do banner</string>\n    <string name=\"apm_folk_banner_title\">FolkBanner</string>\n    <string name=\"apm_folk_banner_select\">Selecionar imagem</string>\n    <string name=\"apm_folk_banner_clear\">Limpar FolkBanner</string>\n    <string name=\"apm_folk_banner_saved\">FolkBanner injetado para %s.</string>\n    <string name=\"apm_folk_banner_cleared\">FolkBanner limpo para %s.</string>\n    <string name=\"apm_folk_banner_failed\">Falha ao atualizar FolkBanner para %s.</string>\n    <string name=\"apm_banner_api_mode\">Modo API</string>\n    <string name=\"apm_banner_api_mode_summary\">Usar API de imagens aleatórias ou diretório local para banners de módulos</string>\n    <string name=\"apm_banner_api_source\">Configurar fonte API</string>\n    <string name=\"apm_banner_api_source_hint\">Insira URL da API ou caminho local</string>\n    <string name=\"apm_banner_api_source_saved\">Fonte API salva</string>\n    <string name=\"apm_banner_api_source_not_configured\">Não configurado</string>\n    <string name=\"apm_banner_api_url_configured\">URL da API configurada</string>\n    <string name=\"apm_banner_local_dir_configured\">Diretório local configurado</string>\n    <string name=\"apm_banner_clear_cache\">Limpar cache</string>\n    <string name=\"apm_banner_cache_cleared\">Cache de banners limpo</string>\n    <string name=\"apm_banner_api_config_title\">Configurar fonte API</string>\n    <string name=\"apm_banner_api_config_desc\">Insira uma URL de API de imagens aleatórias ou caminho de diretório local. Cada módulo receberá um banner único.</string>\n    <string name=\"apm_banner_api_examples_title\">Exemplos:</string>\n    <string name=\"apm_banner_api_examples\">API: https://picsum.photos/800/400\\nLocal: /sdcard/Pictures/Banners</string>\n    <!-- Marketplace de API -->\n    <string name=\"apm_api_marketplace_title\">Marketplace de API</string>\n    <string name=\"apm_api_preview\">Visualizar</string>\n    <string name=\"apm_api_apply\">Aplicar</string>\n    <string name=\"apm_api_preview_title\">Visualização de API</string>\n    <string name=\"apm_api_preview_failed\">Falha ao carregar visualização</string>\n    <string name=\"apm_api_url_label\">URL da API:</string>\n    <string name=\"apm_api_apply_success\">Fonte de API aplicada com sucesso</string>\n    <string name=\"apm_api_verifying\">Verificando…</string>\n    <string name=\"apm_api_retry\">Tentar novamente</string>\n    <string name=\"apm_api_empty\">Nenhuma fonte de API disponível</string>\n    <string name=\"settings_banner_custom_opacity\">Opacidade do banner</string>\n    <string name=\"settings_banner_custom_opacity_summary\">Usar opacidade personalizada para banners em vez de seguir o modo de papel de parede</string>\n    <string name=\"settings_banner_opacity\">Transparência do banner</string>\n\n    <string name=\"settings_donot_store_superkey\">Não armazenar SuperKey localmente</string>\n    <string name=\"settings_donot_store_superkey_summary\">Autenticar a SuperKey toda vez que o gerenciador iniciar</string>\n\n    <string name=\"mode_select_page_title\">Instalar</string>\n    <string name=\"mode_select_page_patch_and_install\">Aplicar Patch e Instalar</string>\n    <string name=\"mode_select_page_select_file\">Selecione uma imagem de boot para aplicar o patch</string>\n    <string name=\"restore_select_file\">Selecione um arquivo de boot para restaurar na partição</string>\n    <string name=\"mode_select_page_install_inactive_slot\">Instalar no slot inativo (Após OTA)</string>\n    <string name=\"mode_select_page_install_inactive_slot_warning\">Seu dispositivo será **FORÇADO** a inicializar no slot inativo atual após reiniciar!\\nUse esta opção apenas após o término da OTA.\\nContinuar?</string>\n    <string name=\"mode_select_page_select_kpimg\">Usar arquivo de patch local (KPimg)</string>\n    <string name=\"patch_custom_kpimg_label\">KPimg personalizado</string>\n    <string name=\"patch_custom_kpimg_file\">Arquivo: %s</string>\n    <string name=\"patch_select_kpimg_btn\">Selecionar KPimg</string>\n\n    <string name=\"home_dialog_auth_fail_title\">Falha na autenticação</string>\n    <string name=\"home_dialog_auth_fail_content\">Não foi possível autenticar a SuperKey, causando falha na ativação do FolkPatch.\nAqui estão algumas possíveis razões para a falha de autenticação—verifique qual se aplica a você:\n\\n1. Você não usou KernelPatch para aplicar patch no boot.img, ou esqueceu o que realmente fez.\n\\n2. O boot.img com patch ainda está dormindo no seu computador, nunca foi flashado no dispositivo.\n\\n3. A SuperKey foi inserida incorretamente, ou contém símbolos misteriosos, como caracteres de línguas alienígenas.\n\\n4. Seu dispositivo pode não ser compatível com FolkPatch e KernelPatch, tentativas forçadas são em vão.\n\\n5. Você pode ter feito algumas operações misteriosas—como usar certos módulos que excluem nomes de pacotes que bloquearam o FolkPatch, resultando em se eliminar do jogo.\n\n</string>\n\n    <string name=\"home_more_menu_feedback_or_suggestion\">Feedback ou sugestão</string>\n    <string name=\"home_more_menu_about\">Sobre</string>\n    <string name=\"home_more_menu_document\">Documentação</string>\n\n    <string name=\"home_dialog_uninstall_title\">Desinstalar</string>\n    \n    <string name=\"home_dialog_uninstall_ap_only\">Desinstalar apenas o patch</string>\n    <string name=\"home_dialog_uninstall_all\">Desinstalar completamente</string>\n    <string name=\"home_dialog_uninstall_ap_only_desc\">Remove apenas o AndroidPatch e mantém o gerenciador.</string>\n    <string name=\"home_dialog_uninstall_all_desc\">Remove o AndroidPatch e inicia o fluxo de desinstalação completa.</string>\n\n    <string name=\"kpm_control_dialog_title\">Controlar KPM</string>\n    <string name=\"kpm_control_dialog_content\">Por favor, insira parâmetros de controle:</string>\n    <string name=\"kpm_control_paramters\">Parâmetros</string>\n    <string name=\"kpm_control_outMsg\">Saída</string>\n    <string name=\"kpm_control_ok\">Sucesso!</string>\n    <string name=\"kpm_control_failed\">Falhou!</string>\n    \n    <string name=\"settings_night_mode_follow_sys\">Tema escuro segue o sistema</string>\n    <string name=\"settings_night_mode_follow_sys_summary\">Alternar tema escuro automaticamente baseado nas configurações do sistema</string>\n    <string name=\"settings_night_theme_enabled\">Tema escuro</string>\n    \n    <string name=\"settings_use_system_color_theme\">Tema de cor do sistema</string>\n    <string name=\"settings_use_system_color_theme_summary\">Usar tema de cores gerado a partir do papel de parede pelo sistema</string>\n    <string name=\"settings_custom_color_theme\">Tema de cor</string>\n\n    <string name=\"amber_theme\">Âmbar</string>\n    <string name=\"blue_theme\">Azul</string>\n    <string name=\"blue_grey_theme\">Azul Acinzentado</string>\n    <string name=\"brown_theme\">Marrom</string>\n    <string name=\"cyan_theme\">Ciano</string>\n    <string name=\"deep_orange_theme\">Laranja Escuro</string>\n    <string name=\"deep_purple_theme\">Roxo Escuro</string>\n    <string name=\"green_theme\">Verde</string>\n    <string name=\"indigo_theme\">Índigo</string>\n    <string name=\"light_blue_theme\">Azul Claro</string>\n    <string name=\"light_green_theme\">Verde Claro</string>\n    <string name=\"lime_theme\">Lima</string>\n    <string name=\"orange_theme\">Laranja</string>\n    <string name=\"pink_theme\">Rosa</string>\n    <string name=\"purple_theme\">Roxo</string>\n    <string name=\"red_theme\">Vermelho</string>\n    <string name=\"sakura_theme\">Sakura</string>\n    <string name=\"teal_theme\">Verde-azulado (Teal)</string>\n    <string name=\"yellow_theme\">Amarelo</string>\n    <string name=\"theme_color\">Cor do tema</string>\n    <string name=\"theme_light\">Claro</string>\n    <string name=\"theme_dark\">Escuro</string>\n    <string name=\"theme_system\">Sistema</string>\n    <string name=\"loading_modules\">Carregando módulos…</string>\n    <string name=\"loading_scripts\">Procurando scripts…</string>\n    <string name=\"loading_themes\">Carregando temas…</string>\n    <string name=\"loading_apis\">Carregando fontes de API…</string>\n\n    <string name=\"about_app_desc\">Uma implementação Root baseada em KernelPatch, permitindo hooks de função do kernel sem recompilar o kernel.</string>\n    <string name=\"about_powered_by\">Desenvolvido por %1$s</string>\n    <string name=\"about_telegram_group\">Grupo no Telegram</string>\n    \n    <!-- Online Module Strings -->\n    <string name=\"online_module_title\">Módulos de Sistema Online</string>\n    <string name=\"online_module_download_start\">Iniciando download: %s</string>\n    <string name=\"online_module_download_complete\">Download completo: %s</string>\n    <string name=\"online_module_download_notification\">Baixando %s. Verifique o painel de notificações para progresso e a pasta Download para o arquivo.</string>\n\n    <!-- Online KPM Strings -->\n    <string name=\"online_kpm_title\">Módulos de Kernel Online</string>\n    <string name=\"online_kpm_download_start\">Iniciando download: %s</string>\n    <string name=\"online_kpm_download_complete\">Download completo: %s</string>\n    <string name=\"online_kpm_download_notification\">Baixando %s. Verifique o painel de notificações para progresso e a pasta Download para o arquivo.</string>\n\n    <!-- Online Script Strings -->\n    <string name=\"online_script_title\">Scripts Online</string>\n    <string name=\"online_script_download_start\">Iniciando download: %s</string>\n    <string name=\"online_script_download_complete\">Download completo: %s</string>\n    <string name=\"online_script_download_notification\">Baixando %s</string>\n\n    <!-- Crash Handle Strings -->\n    <string name=\"crash_handle_title\">Relatório de Erro</string>\n    <string name=\"crash_handle_copy\">Copiar</string>\n\n    <!-- About Screen Strings -->\n    <string name=\"about_app_version\">Versão do App: %s</string>\n    <string name=\"about_github\">GitHub</string>\n    <string name=\"about_telegram_channel\">Canal do Telegram</string>\n\n    <string name=\"settings_app_dpi\">DPI do App</string>\n    <string name=\"dpi_apply_settings\">Aplicar</string>\n    <string name=\"dpi_confirm_title\">Confirmar alteração de DPI</string>\n    <string name=\"dpi_confirm_message\">Alterar DPI do app de %1$s para %2$s?</string>\n    <string name=\"settings_alt_icon\">Ícone alternativo</string>\n    <string name=\"alt_icon_summary\">Usar ícone alternativo do lançador</string>\n    <string name=\"settings_magic_mount\">Folk Mount API</string>\n    <string name=\"settings_magic_mount_summary\">Ativar o sistema de montagem de módulos integrado</string>\n    <string name=\"settings_new_app_profile_mode\">Modo padrão para novos apps</string>\n    <string name=\"settings_new_app_profile_normal\">SEM ROOT</string>\n    <string name=\"settings_new_app_profile_root\">ROOT</string>\n    <string name=\"settings_new_app_profile_exclude\">Excluir</string>\n    <string name=\"settings_hide_service\">FolkPatch Hide</string>\n    <string name=\"settings_hide_service_summary\">Ocultar o status de desbloqueio do bootloader em certa medida</string>\n    <string name=\"settings_app_title\">Título do App</string>\n    <string name=\"app_title_custom\">Personalizado</string>\n    <string name=\"settings_custom_app_title\">Definir nome do app</string>\n    <string name=\"custom_app_title_dialog_title\">Nome personalizado do app</string>\n    <string name=\"custom_app_title_dialog_hint\">Digite o nome do app</string>\n    <string name=\"custom_app_title_dialog_confirm\">Confirmar</string>\n    <string name=\"custom_app_title_dialog_empty\">O nome não pode ficar vazio</string>\n    <string name=\"cancel\">Cancelar</string>\n    <string name=\"settings_custom_background\">Fundo Personalizado</string>\n    <string name=\"settings_custom_background_enabled\">Fundo Personalizado Ativo</string>\n    <string name=\"settings_custom_background_opacity\">Opacidade do cartão</string>\n    <string name=\"settings_custom_background_blur\">Desfoque do fundo</string>\n    <string name=\"settings_custom_background_dim\">Escurecimento do fundo</string>\n    <string name=\"settings_custom_background_dual_dim\">Escurecimento duplo (dia/noite)</string>\n    <string name=\"settings_custom_background_dual_dim_desc\">Ajustar automaticamente o escurecimento do fundo para temas claros e escuros</string>\n    <string name=\"settings_custom_background_day_dim\">Escurecimento modo dia</string>\n    <string name=\"settings_custom_background_night_dim\">Escurecimento modo noite</string>\n    <string name=\"settings_multi_background_mode\">Modo Multi-Fundo</string>\n    <string name=\"settings_multi_background_mode_summary\">Definir imagens de fundo diferentes para páginas diferentes</string>\n    <string name=\"settings_select_home_background\">Fundo da Página Inicial</string>\n    <string name=\"settings_select_kernel_background\">Fundo Módulos de Kernel</string>\n    <string name=\"settings_select_superuser_background\">Fundo Superusuário</string>\n    <string name=\"settings_select_system_module_background\">Fundo Módulos de Sistema</string>\n    <string name=\"settings_select_settings_background\">Fundo Configurações</string>\n\n    <string name=\"settings_advanced_title_style\">Estilo de Título Avançado</string>\n    <string name=\"settings_advanced_title_style_summary\">Substituir o título da barra superior por uma imagem personalizada</string>\n    <string name=\"settings_advanced_title_style_enabled\">Estilo de título avançado ativado</string>\n    <string name=\"settings_select_title_image\">Selecionar Imagem de Título</string>\n    <string name=\"settings_title_image_selected\">Imagem de título selecionada</string>\n    <string name=\"settings_title_image_saved\">Imagem de título salva com sucesso</string>\n    <string name=\"settings_title_image_error\">Falha ao salvar imagem de título</string>\n    <string name=\"settings_clear_title_image\">Limpar Imagem de Título</string>\n    <string name=\"settings_clear_title_image_confirm\">Tem certeza de que deseja limpar a imagem de título?</string>\n    <string name=\"settings_title_image_cleared\">Imagem de título limpa</string>\n    <string name=\"settings_title_image_day_opacity\">Opacidade do Modo Diurno</string>\n    <string name=\"settings_title_image_night_opacity\">Opacidade do Modo Noturno</string>\n    <string name=\"settings_title_image_dim\">Escurecimento do Fundo</string>\n    <string name=\"settings_title_image_offset_x\">Posição Horizontal</string>\n\n    <!-- Font Settings -->\n    <string name=\"settings_custom_font\">Fonte Personalizada</string>\n    <string name=\"settings_select_font_file\">Selecionar Arquivo de Fonte</string>\n    <string name=\"settings_custom_font_enabled\">Fonte Personalizada Ativa</string>\n    <string name=\"settings_custom_font_summary\">Usar uma fonte TTF personalizada para o app</string>\n    <string name=\"settings_font_selected\">Fonte Personalizada Selecionada</string>\n    <string name=\"settings_custom_font_error\">Falha ao salvar arquivo de fonte</string>\n    <string name=\"settings_custom_font_saved\">Fonte personalizada salva com sucesso</string>\n    <string name=\"settings_clear_font\">Restaurar Fonte Padrão</string>\n    <string name=\"settings_clear_font_confirm\">Tem certeza de que deseja restaurar a fonte padrão?</string>\n    <string name=\"settings_font_cleared\">Fonte padrão restaurada</string>\n    <string name=\"settings_font_select_hint\">Selecione um arquivo de fonte TTF</string>\n    <string name=\"settings_select_background_image\">Selecionar Imagem de Fundo</string>\n    <string name=\"settings_custom_background_summary\">Definir uma imagem de fundo personalizada</string>\n    <string name=\"settings_custom_background_saved\">Fundo salvo com sucesso</string>\n    <string name=\"settings_custom_background_error\">Falha ao salvar fundo</string>\n    <string name=\"settings_background_selected\">Fundo selecionado</string>\n    <string name=\"settings_background_image_cleared\">Imagem de fundo limpa</string>\n    <string name=\"settings_clear_background\">Limpar Fundo</string>\n    <string name=\"settings_clear_background_confirm\">Tem certeza de que deseja limpar a imagem de fundo?</string>\n\n    <string name=\"settings_launcher_icon\">Ícone do Launcher</string>\n\n    <!-- Video Background Settings -->\n    <string name=\"settings_video_background\">Fundo de Vídeo</string>\n    <string name=\"settings_video_background_summary\">Usar um vídeo como fundo</string>\n    <string name=\"settings_select_video\">Selecionar Vídeo</string>\n    <string name=\"settings_video_selected\">Vídeo Selecionado</string>\n    <string name=\"settings_clear_video_background\">Limpar Fundo de Vídeo</string>\n    <string name=\"settings_clear_video_background_confirm\">Tem certeza de que deseja limpar o fundo de vídeo?</string>\n    <string name=\"settings_video_background_enabled\">Fundo de Vídeo Ativado</string>\n    <string name=\"settings_video_volume\">Volume do Vídeo</string>\n    \n    <!-- KPM AutoLoad Strings -->\n    <string name=\"kpm_autoload_title\">Auto-Carregamento KPM</string>\n    <string name=\"kpm_autoload_enabled\">Ativar auto-carregamento</string>\n    <string name=\"kpm_autoload_enabled_summary\">Carregar módulos KPM automaticamente ao iniciar o dispositivo</string>\n    <string name=\"kpm_autoload_json_config\">Configuração JSON</string>\n    <string name=\"kpm_autoload_json_label\">JSON de Configuração</string>\n    <string name=\"kpm_autoload_json_placeholder\">{\n  \"enabled\": true,\n  \"kpmPaths\": [\n    \"/path/to/module1.kpm\",\n    \"/path/to/module2.kpm\"\n  ]\n}</string>\n    <string name=\"kpm_autoload_json_error\">Formato JSON inválido</string>\n    <string name=\"kpm_autoload_json_helper\">Insira um JSON válido com array de caminhos de arquivos KPM</string>\n    <string name=\"kpm_autoload_save\">Salvar Configuração</string>\n    <string name=\"kpm_autoload_save_confirm\">Salvar configuração de auto-carregamento?</string>\n    <string name=\"kpm_autoload_save_success\">Configuração salva com sucesso</string>\n    <string name=\"kpm_autoload_save_failed\">Falha ao salvar configuração</string>\n    <string name=\"kpm_autoload_visual_mode\">Modo Visual</string>\n    <string name=\"kpm_autoload_json_mode\">Modo JSON</string>\n    <string name=\"kpm_autoload_add_kpm\">Adicionar KPM</string>\n    <string name=\"kpm_autoload_remove_kpm\">Remover</string>\n    <string name=\"kpm_autoload_edit_kpm\">Editar</string>\n    <string name=\"kpm_autoload_edit_dialog_title\">Editar KPM</string>    <string name=\"kpm_autoload_event_label\">Evento:</string>    <string name=\"kpm_autoload_args_label\">Argumentos:</string>    <string name=\"kpm_autoload_event_service\">service</string>    <string name=\"kpm_autoload_event_post_fs_data\">post-fs-data</string>\n    \n    <!-- Module Backup/Restore Strings -->\n    <string name=\"apm_backup_title\">Backup de Módulos</string>\n    <string name=\"apm_restore_title\">Restaurar Módulos</string>\n    <string name=\"apm_backup_success\">Backup realizado com sucesso</string>\n    <string name=\"apm_restore_success\">Restauração realizada com sucesso</string>\n    <string name=\"apm_backup_failed\">Falha no backup</string>\n    <string name=\"apm_restore_failed\">Falha na restauração</string>\n    <string name=\"apm_backup_failed_msg\">Falha no backup: %s</string>\n    <string name=\"apm_restore_failed_msg\">Falha na restauração: %s</string>\n    <string name=\"apm_copy_list_title\">Copiar lista</string>\n    <string name=\"apm_copy_list_success\">Lista de módulos copiada</string>\n\n    <!-- APM Bulk Install Strings -->\n    <string name=\"apm_bulk_install_title\">Instalação em Lote de Módulos do Sistema</string>\n    <string name=\"apm_bulk_install_list_title\">Lista de Módulos</string>\n    <string name=\"apm_bulk_install_add\">Adicionar Módulos</string>\n    <string name=\"apm_bulk_install_empty\">Nenhum módulo adicionado</string>\n    <string name=\"apm_bulk_install_action\">Instalar em Lote</string>\n    <string name=\"apm_bulk_install_log_title\">Log da Instalação em Lote</string>\n    <string name=\"apm_bulk_install_log_start\">Iniciando instalação em lote...</string>\n    <string name=\"apm_bulk_install_log_installing\">Instalando %1$s</string>\n    <string name=\"apm_batch_install_full_process\">Processo de instalação em lote completo</string>\n    <string name=\"apm_batch_install_full_process_summary\">A instalação em lote de módulos do sistema é realizada usando o processo completo</string>\n    <string name=\"next_module\">Próximo módulo</string>\n    <string name=\"apm_bulk_install_log_installed\">Módulo %s instalado.</string>\n    <string name=\"apm_bulk_install_log_done\">Todas as operações concluídas.</string>\n    <string name=\"apm_bulk_install_first_use_text\">Este recurso permite instalar vários módulos de uma vez. É um método rápido adequado para módulos que não envolvem operações de tecla durante a instalação. Para módulos que exigem interações com as teclas de volume, ative o modo de instalação completa nas configurações.</string>\n    <string name=\"apm_bulk_install_remove\">Remover</string>\n    <string name=\"apm_first_use_title\">Bem-vindo aos Módulos de Sistema</string>\n    <string name=\"apm_first_use_text\">Bem-vindo aos Módulos de Sistema. Aqui são usados módulos compatíveis com o ecossistema Magisk. Clique no botão no canto inferior direito para instalar módulos. Você também pode usar o instalador no topo para instalação em lote. Um recurso para backup de todos os módulos com um clique também é fornecido, mas note que esta solução pode não ser adequada para todos os módulos, então faça seus próprios backups.</string>\n\n    <string name=\"kpm_autoload_kpm_list_title\">Módulos KPM</string>\n    <string name=\"kpm_autoload_no_kpm_added\">Nenhum módulo KPM adicionado</string>\n    <string name=\"kpm_autoload_kpm_path\">Caminho KPM</string>\n    <string name=\"kpm_autoload_file_not_found\">Arquivo não encontrado</string>\n    <string name=\"kpm_autoload_select_kpm_file\">Selecionar Arquivo KPM</string>\n    <string name=\"kpm_autoload_first_time_title\">Sobre Auto-Carregamento KPM</string>\n    <string name=\"kpm_autoload_first_time_message\">Este recurso permite carregar automaticamente todos os KPMs configurados de forma temporária na inicialização. Esta abordagem é mais conveniente do que embutir diretamente no kernel.\n\nPor exemplo, KPMs que impedem modificação de partição só podem ser usados temporariamente, pois embutir pode causar falhas no boot. Ou se você não quer modificar a partição BOOT, pode usar essa configuração para carregar módulos rapidamente.\n\nPor favor, certifique-se de fechar completamente o app e reabri-lo para que os comandos sejam executados. Geralmente, ele também carregará na inicialização do sistema. É normal que o gerenciador fique com tela preta por um período! Isso usa um método de carregamento em segundo plano, lembre-se de atualizar manualmente (arrastar para baixo) para verificar se os módulos foram carregados corretamente!</string>\n    <string name=\"kpm_autoload_first_time_confirm\">Entendi</string>\n    <string name=\"kpm_autoload_do_not_show_again\">Não mostrar novamente</string>\n\n    <!-- Hide Settings Strings -->\n    <string name=\"home_hide_su_path\">Ocultar caminho do executável su</string>\n    <string name=\"home_hide_kpatch_version\">Ocultar versão do kernel patch</string>\n    <string name=\"home_hide_fingerprint\">Ocultar fingerprint</string>\n    <string name=\"home_hide_zygisk\">Ocultar implementação Zygisk</string>\n    <string name=\"home_hide_mount\">Ocultar implementação de montagem</string>\n    <string name=\"home_hide_su_path_summary\">Ocultar caminho do su no cartão de info</string>\n    <string name=\"home_hide_kpatch_version_summary\">Ocultar versão do kernel patch no cartão de info</string>\n    <string name=\"home_hide_zygisk_summary\">Ocultar implementação Zygisk no cartão de info</string>\n    <string name=\"home_hide_mount_summary\">Ocultar implementação de montagem no cartão de info</string>\n    <string name=\"home_hide_fingerprint_summary\">Ocultar fingerprint no cartão de info</string>\n\n    <string name=\"kpm_page_first_time_title\">Aviso</string>\n    <string name=\"kpm_page_first_time_message\">Módulos de kernel modificam diretamente a implementação de Boot. Diferente dos módulos de sistema, eles carecem de um bom mecanismo de recuperação. Se surgirem problemas, você só poderá corrigi-los entrando no Fastboot. Recomenda-se carregar o módulo primeiro para garantir que não há problemas antes de embuti-lo no Boot. Se um módulo só pode ser usado via carregamento, tente o recurso de auto-carregamento KPM. Se você não entende de módulos de kernel, por favor, não use este recurso!</string>\n\n    <!-- Auto Backup Strings -->\n    <string name=\"settings_auto_backup_module\">Backup Automático de Módulos</string>\n    <string name=\"settings_auto_backup_module_summary\">Fazer backup automático do arquivo do módulo para diretório privado ao instalar</string>\n    <string name=\"settings_open_backup_dir\">Abrir Diretório de Backup</string>\n    <string name=\"backup_dir_empty\">O diretório de backup está vazio</string>\n    <string name=\"backup_dir_open_failed\">Falha ao abrir o diretório de backup</string>\n    <string name=\"auto_backup_failed\">Falha no backup automático: %s</string>\n    <string name=\"auto_backup_success\">Sucesso no backup automático: %s</string>\n    <string name=\"settings_auto_backup_boot\">Backup Automático do Boot</string>\n    <string name=\"settings_auto_backup_boot_summary\">Fazer backup automático do Boot para armazenamento local (Downloads/FolkPatch/BootBackups)</string>\n\n    <!-- Home Layout Strings -->\n    <string name=\"settings_home_layout_style\">Estilo de Layout Inicial</string>\n    <string name=\"settings_home_layout_default\">Lista (ListUI)</string>\n    <string name=\"settings_home_layout_grid\">Grade (GridUI)</string>\n    <string name=\"settings_home_layout_focus\">Foco (FocusUI)</string>\n    <string name=\"settings_home_layout_sign\">SignUI</string>\n    <string name=\"settings_home_layout_circle\">CircleUI</string>\n    <string name=\"settings_home_layout_dashboard_pro\">DashboardUI</string>\n    <string name=\"unofficial_version_title\">Versão Não Oficial</string>\n    <string name=\"unofficial_version_message\">Você está usando o FolkPatch que não é o app oficial, por favor baixe o app oficial!</string>\n    <string name=\"go_to_github\">Ir para Github</string>\n    <string name=\"settings_save_theme\">Salvar tema</string>\n    <string name=\"settings_import_theme\">Importar tema</string>\n    <string name=\"settings_theme_saved\">Tema salvo</string>\n    <string name=\"settings_theme_imported\">Tema importado</string>\n    <string name=\"settings_theme_save_failed\">Falha ao salvar tema</string>\n    <string name=\"settings_theme_import_failed\">Falha ao importar tema</string>\n    <string name=\"settings_reset_theme\">Redefinir tema</string>\n    <string name=\"settings_reset_theme_confirm\">Tem certeza de que deseja redefinir todas as configurações do tema para os padrões? Isso limpará todos os planos de fundo, fontes, músicas e efeitos sonoros personalizados.</string>\n    <string name=\"settings_theme_reset\">Tema redefinido</string>\n    <string name=\"settings_theme_reset_failed\">Falha ao redefinir tema</string>\n\n    <!-- Theme Strings -->\n    <string name=\"theme_export_title\">Exportar Tema</string>\n    <string name=\"theme_import_title\">Importar Tema</string>\n    <string name=\"theme_name\">Nome do Tema</string>\n    <string name=\"theme_type\">Tipo de Tema</string>\n    <string name=\"theme_type_phone\">Celular</string>\n    <string name=\"theme_type_tablet\">Tablet</string>\n    <string name=\"theme_version\">Versão</string>\n    <string name=\"theme_author\">Autor</string>\n    <string name=\"theme_description\">Descrição</string>\n    <string name=\"theme_export_action\">Exportar</string>\n    <string name=\"theme_import_action\">Importar</string>\n    <string name=\"theme_import_confirm\">Tem certeza de que deseja importar este tema?</string>\n    <string name=\"theme_info\">Info do Tema</string>\n\n    <!-- Grid Working Card Background -->\n    <string name=\"settings_grid_working_card_background\">Fundo do Cartão de Status</string>\n    <string name=\"settings_grid_working_card_background_enabled\">Fundo personalizado do cartão ativo</string>\n    <string name=\"settings_grid_working_card_background_summary\">Imagem de fundo personalizada para o cartão de status</string>\n    <string name=\"settings_grid_working_card_background_selected\">Fundo selecionado</string>\n    <string name=\"settings_grid_working_card_dual_opacity\">Opacidade dupla dia/noite</string>\n    <string name=\"settings_grid_working_card_dual_opacity_desc\">Ajustar automaticamente a opacidade do cartão para temas claros e escuros</string>\n    <string name=\"settings_grid_working_card_day_opacity\">Opacidade do cartão (dia)</string>\n    <string name=\"settings_grid_working_card_night_opacity\">Opacidade do cartão (noite)</string>\n    <string name=\"settings_clear_grid_working_card_background\">Limpar Fundo do Cartão</string>\n    <string name=\"settings_clear_grid_working_card_background_confirm\">Tem certeza de que deseja limpar a imagem de fundo do cartão?</string>\n    <string name=\"settings_grid_working_card_background_saved\">Fundo do cartão salvo com sucesso</string>\n    <string name=\"settings_grid_working_card_background_error\">Falha ao salvar fundo do cartão</string>\n    <string name=\"settings_grid_working_card_background_cleared\">Fundo do cartão limpo</string>\n\n    <!-- Biometric Strings -->\n    <string name=\"action_biometric\">Autenticação Biométrica</string>\n    <string name=\"msg_biometric\">Por favor, verifique sua identidade biométrica</string>\n    <string name=\"settings_biometric_login\">Login Biométrico</string>\n    <string name=\"settings_biometric_login_summary\">Exigir autenticação biométrica ao abrir o app</string>\n    <string name=\"settings_strong_biometric\">Autenticação Biométrica Forte</string>\n    <string name=\"settings_strong_biometric_summary\">Exigir autenticação para instalar/desinstalar/desativar módulos</string>\n\n    <!-- Theme Store Strings -->\n    <string name=\"theme_store_title\">Loja de Temas</string>\n    <string name=\"theme_source_official\">Oficial</string>\n    <string name=\"theme_source_third_party\">Terceiros</string>\n    <string name=\"theme_store_author\">Autor: %s</string>\n    <string name=\"theme_store_version\">Versão: %s</string>\n    <string name=\"theme_source\">Fonte</string>\n    <string name=\"theme_store_download_failed\">Download falhou</string>\n    <string name=\"theme_store_download\">Baixar</string>\n    <string name=\"theme_store_search_hint\">Buscar temas...</string>\n\n    <!-- Update Check Strings -->\n    <string name=\"settings_auto_update_check\">Verificação Automática de Atualização</string>\n    <string name=\"settings_auto_update_check_summary\">Verificar atualizações automaticamente ao iniciar o app</string>\n    <string name=\"settings_check_update\">Verificar Atualizações</string>\n    <string name=\"update_available_title\">Atualização Disponível</string>\n    <string name=\"update_available_message\">Detectado que sua versão é antiga, deseja baixar a nova versão?</string>\n    <string name=\"update_action\">Atualizar</string>\n    <string name=\"update_close\">Fechar</string>\n    <string name=\"update_latest\">Você está na versão mais recente</string>\n    <string name=\"update_error\">Erro ao verificar atualizações</string>\n    <string name=\"settings_category_general\">Geral</string>\n    <string name=\"settings_category_appearance\">Aparência</string>\n    <string name=\"settings_appearance_font\">Configuração de fonte</string>\n    <string name=\"settings_appearance_font_summary\">Configuração de fonte personalizada</string>\n    <string name=\"settings_appearance_theme\">Configuração de tema</string>\n    <string name=\"settings_appearance_theme_summary\">Loja de temas, salvar, importar e redefinir</string>\n    <string name=\"settings_appearance_banner\">Configuração de banner</string>\n    <string name=\"settings_appearance_banner_summary\">Configuração de banner do módulo</string>\n    <string name=\"settings_appearance_layout\">Configuração de layout</string>\n    <string name=\"settings_appearance_layout_summary\">Layout da tela inicial, navegação e personalização de cartões</string>\n    <string name=\"settings_appearance_background\">Configuração de fundo</string>\n    <string name=\"settings_appearance_background_summary\">Fundo personalizado, vídeo e multi-fundo</string>\n    <string name=\"settings_appearance_night_mode\">Configuração de modo noturno</string>\n    <string name=\"settings_appearance_night_mode_summary\">Tema escuro e configuração de cor</string>\n    <string name=\"settings_amoled_theme\">Tema Preto AMOLED</string>\n    <string name=\"settings_amoled_theme_desc\">Fundo preto puro para o modo escuro</string>\n    <string name=\"settings_switch_icon\">Indicador de botão</string>\n    <string name=\"settings_switch_icon_desc\">Mostrar ícones de status nos interruptores</string>\n    <string name=\"settings_category_behavior\">Comportamento</string>\n    <string name=\"settings_category_function\">Função</string>\n    <string name=\"settings_use_legacy_su_page\">Autorização de superusuário de página única</string>\n    <string name=\"settings_use_legacy_su_page_summary\">Página de superusuário alterada para design de autorização de página única</string>\n    <string name=\"settings_category_module\">Módulos</string>\n    <string name=\"settings_category_security\">Segurança</string>\n    \n    <!-- Background Music Strings -->\n    <string name=\"settings_background_music\">Música de Fundo</string>\n    <string name=\"settings_background_music_summary\">Tocar música de fundo quando o app estiver em primeiro plano</string>\n    <string name=\"settings_background_music_playing\">Tocando: %s</string>\n    <string name=\"settings_background_music_enabled\">Música de Fundo Ativada</string>\n    <string name=\"settings_select_music_file\">Selecionar arquivo de música</string>\n    <string name=\"settings_music_selected\">Música selecionada</string>\n    <string name=\"settings_clear_music\">Limpar música</string>\n    <string name=\"settings_clear_music_confirm\">Tem certeza de que deseja limpar a música de fundo?</string>\n    <string name=\"settings_music_auto_play\">Reprodução automática</string>\n    <string name=\"settings_music_auto_play_summary\">Tocar música automaticamente ao abrir o app</string>\n    <string name=\"settings_music_looping\">Reprodução em loop</string>\n    <string name=\"settings_music_looping_summary\">Repetir a música atual</string>\n    <string name=\"settings_music_volume\">Volume</string>\n    <string name=\"settings_music_saved\">Arquivo de música salvo</string>\n    <string name=\"settings_music_save_error\">Falha ao salvar arquivo de música</string>\n    <string name=\"settings_music_cleared\">Música limpa</string>\n    <string name=\"settings_category_multimedia\">Multimídia</string>\n    <string name=\"settings_category_general_summary\">Idioma, atualizações, SELinux, ajustes do sistema</string>\n    <string name=\"settings_category_appearance_summary\">Tema, cores, layout, fundo, fontes</string>\n    <string name=\"settings_category_behavior_summary\">Depuração Web, comportamento de instalação, exibição inicial</string>\n    <string name=\"settings_category_security_summary\">Biometria, gerenciamento de superchave</string>\n    <string name=\"settings_category_backup_summary\">Backup local, backup na nuvem, WebDAV</string>\n    <string name=\"settings_category_module_summary\">Informações do módulo, classificação, instalação em lote</string>\n    <string name=\"settings_category_function_summary\">Ocultar FolkPatch, serviço Umount</string>\n    <string name=\"settings_category_multimedia_summary\">Música de fundo, sons, vibração</string>\n    <string name=\"settings_music_playback_control\">Controle de Reprodução</string>\n\n    <!-- Theme Store Filter -->\n    <string name=\"theme_store_filter_title\">Filtrar Temas</string>\n    <string name=\"theme_store_filter_author\">Autor</string>\n    <string name=\"theme_store_filter_author_hint\">Digite o nome do autor</string>\n    <string name=\"theme_store_filter_source\">Fonte</string>\n    <string name=\"theme_store_filter_source_all\">Todos</string>\n    <string name=\"theme_store_filter_type\">Tipo de Dispositivo</string>\n    <string name=\"theme_store_filter_apply\">Aplicar</string>\n    <string name=\"theme_store_filter_reset\">Redefinir</string>\n\n    <!-- File Picker -->\n    <string name=\"file_picker_permission_required\">Permissão Necessária</string>\n    <string name=\"file_picker_permission_desc\">Para navegar nos arquivos, conceda permissão de \\'Acesso a todos os arquivos\\'.</string>\n    <string name=\"file_picker_grant_permission\">Conceder Permissão</string>\n    <string name=\"file_picker_cancel\">Cancelar</string>\n    <string name=\"file_picker_internal_storage\">Armazenamento Interno</string>\n    <string name=\"file_picker_no_files\">Sem arquivos</string>\n\n    <!-- HomeV3 Strings -->\n    <string name=\"home_device_status_title\">Status do Dispositivo</string>\n    <string name=\"home_device_status_battery_temp\">Temp. Bateria</string>\n    <string name=\"home_device_status_cpu_load\">Carga CPU</string>\n    <string name=\"home_device_status_battery_level\">Nível Bateria</string>\n    <string name=\"home_storage_title\">Armazenamento</string>\n    <string name=\"home_storage_internal\">Armaz. Interno</string>\n    <string name=\"home_storage_ram\">RAM</string>\n    <string name=\"home_storage_zram\">ZRAM</string>\n    <string name=\"home_storage_swap\">Arquivo Swap</string>\n    <string name=\"home_info_kernel\">Kernel</string>\n    <string name=\"home_info_superkey\">SuperKey</string>\n    <string name=\"home_info_auth_auth\">Autenticado</string>\n    <string name=\"home_info_auth_na\">N/A</string>\n    <string name=\"home_info_device_slot\">Slot do Dispositivo</string>\n    <string name=\"home_info_device_model\">Modelo</string>\n    <string name=\"home_info_running_mode\">Modo de Execução</string>\n    <string name=\"home_info_mode_full\">Completo</string>\n    <string name=\"home_info_mode_half\">Metade (Half)</string>\n    <string name=\"home_version\">Versão: %s</string>\n    <string name=\"home_zygisk_implement\">Implementação Zygisk</string>\n    <string name=\"home_mount_implement\">Implementação de Montagem</string>\n\n    <string name=\"settings_list_card_hide_status_badge\">Usar Emoji Clássico</string>\n    <string name=\"settings_list_card_hide_status_badge_summary\">O emblema de status na página inicial se transforma em emoji clássico</string>\n    <string name=\"settings_custom_badge_text\">Personalizar Texto do Emblema</string>\n    <string name=\"settings_custom_badge_text_summary\">Modificar a exibição de texto do emblema, apenas por diversão</string>\n\n    <!-- Install Mode Select -->\n    <string name=\"kp_install_methods\">Aplicar Patch/Instalar KernelPatch</string>\n    <string name=\"restore_boot_methods\">Selecione um boot para restaurar na partição boot</string>\n\n    <string name=\"settings_app_list_loading_scheme\">Esquema de Carregamento da Lista de Apps</string>\n    <string name=\"settings_app_list_loading_scheme_summary\">Escolha como carregar a lista de aplicativos</string>\n    <string name=\"app_list_loading_scheme_root_service\">RootService (Padrão)</string>\n    <string name=\"app_list_loading_scheme_package_manager\">PackageManager (API do Sistema)</string>\n    <string name=\"app_list_loading_scheme_dialog_title\">Esquema de Carregamento</string>\n    <string name=\"su_backup_list\">Lista de backup</string>\n    <string name=\"su_restore_list\">Lista de restauração</string>\n    <string name=\"backup_success\">Backup bem-sucedido</string>\n    <!-- Badge Count Settings -->\n    <string name=\"enable_badge_count\">Configurações do contador de selos</string>\n    <string name=\"enable_badge_count_summary\">Configurar a exibição do contador de selos para itens de navegação</string>\n    <string name=\"badge_superuser\">Mostrar selo de superusuário</string>\n    <string name=\"badge_apm\">Mostrar selo de módulo do sistema</string>\n    <string name=\"badge_kernel\">Mostrar selo de módulo do kernel</string>\n    <string name=\"settings_sound_effect\">Efeito sonoro</string>\n    <string name=\"settings_sound_effect_summary\">Tocar efeito sonoro ao clicar</string>\n    <string name=\"settings_sound_effect_enabled\">Ativado</string>\n    <string name=\"settings_sound_effect_playing\">Selecionado: %s</string>\n    <string name=\"settings_sound_effect_source\">Fonte do som</string>\n    <string name=\"settings_sound_effect_source_local\">Arquivo local</string>\n    <string name=\"settings_sound_effect_source_preset\">Predefinição</string>\n    <string name=\"settings_sound_effect_preset_title\">Sons predefinidos</string>\n    <string name=\"settings_select_sound_effect\">Selecionar arquivo de som</string>\n    <string name=\"settings_sound_effect_selected\">Arquivo de som selecionado</string>\n    <string name=\"settings_sound_effect_scope\">Escopo do efeito</string>\n    <string name=\"settings_sound_effect_scope_global\">Global (Em qualquer lugar)</string>\n    <string name=\"settings_sound_effect_scope_bottom_bar\">Apenas barra inferior</string>\n    <string name=\"settings_clear_sound_effect\">Limpar efeito sonoro</string>\n    <string name=\"settings_clear_sound_effect_confirm\">Tem certeza que deseja limpar o efeito sonoro?</string>\n    <string name=\"settings_sound_effect_cleared\">Efeito sonoro limpo</string>\n\n    <string name=\"settings_startup_sound\">Som de inicialização</string>\n    <string name=\"settings_startup_sound_summary\">Tocar som ao iniciar o aplicativo</string>\n    <string name=\"settings_startup_sound_enabled\">Som de inicialização ativado</string>\n    <string name=\"settings_startup_sound_playing\">Tocando: %s</string>\n    <string name=\"settings_select_startup_sound\">Selecionar som de inicialização</string>\n    <string name=\"settings_startup_sound_selected\">Som de inicialização selecionado</string>\n    <string name=\"settings_clear_startup_sound\">Limpar som de inicialização</string>\n    <string name=\"settings_clear_startup_sound_confirm\">Tem certeza que deseja limpar o som de inicialização?</string>\n    <string name=\"settings_startup_sound_cleared\">Som de inicialização limpo</string>\n\n    <string name=\"settings_enable_cloud_backup_summary\">Backup automático na nuvem</string>\n    <string name=\"settings_configure_webdav\">Configurar serviço WebDAV</string>\n    <string name=\"webdav_config_title\">Configuração WebDAV</string>\n    <string name=\"webdav_url\">URL WebDAV</string>\n    <string name=\"webdav_username\">Nome de usuário</string>\n    <string name=\"webdav_password\">Senha</string>\n    <string name=\"webdav_uploading\">Enviando para WebDAV...</string>\n    <string name=\"webdav_backup_success\">Backup WebDAV bem-sucedido</string>\n    <string name=\"webdav_backup_failed\">Falha no backup WebDAV: %s</string>\n    <string name=\"save\">Salvar</string>\n    <string name=\"test\">Testar</string>\n    <string name=\"webdav_path_label\">Caminho (ex. /Backup)</string>\n    <string name=\"webdav_view_logs\">Ver logs</string>\n    <string name=\"webdav_backup_logs_title\">Logs de Backup</string>\n    <string name=\"webdav_no_logs\">Sem logs</string>\n    <string name=\"webdav_clear_logs\">Limpar</string>\n    <string name=\"settings_enable_local_backup\">Ativar backup local</string>\n    <string name=\"settings_enable_local_backup_summary\">Backup de módulos para armazenamento local (Downloads/FolkPatch/ModuleBackups)</string>\n    <string name=\"close\">Fechar</string>\n\n    <!-- Backup Settings -->\n    <string name=\"settings_category_backup\">Configurações de Backup</string>\n    <string name=\"settings_enable_cloud_backup\">Ativar backup na nuvem</string>\n    <string name=\"settings_webdav_url\">URL do WebDAV</string>\n    <string name=\"settings_webdav_username\">Usuário do WebDAV</string>\n    <string name=\"settings_webdav_password\">Senha do WebDAV</string>\n    <string name=\"settings_test_webdav\">Testar conexão WebDAV</string>\n    <string name=\"webdav_test_success\">Teste bem-sucedido</string>\n    <string name=\"webdav_test_failed\">Teste falhou: %s</string>\n    <string name=\"settings_backup_now\">Fazer backup agora</string>\n    <string name=\"backup_failed\">Falha no backup: %s</string>\n    <string name=\"settings_restore_backup\">Restaurar backup</string>\n    <string name=\"restore_success\">Restauração bem-sucedida</string>\n    <string name=\"restore_failed\">Falha na restauração: %s</string>\n    <string name=\"settings_auto_backup\">Backup automático</string>\n    <string name=\"settings_auto_backup_summary\">Backup automático uma vez por dia</string>\n    <string name=\"settings_delete_remote_backup\">Excluir backup na nuvem</string>\n    <string name=\"delete_remote_backup_confirm\">Tem certeza de que deseja excluir o backup na nuvem?</string>\n    <string name=\"delete_success\">Exclusão bem-sucedida</string>\n    <string name=\"delete_failed\">Falha na exclusão: %s</string>\n    <string name=\"settings_encrypt_backup\">Criptografar backup</string>\n    <string name=\"settings_encrypt_backup_summary\">Criptografar arquivo de backup com senha</string>\n    <string name=\"settings_backup_password\">Senha de backup</string>\n    <string name=\"settings_backup_password_hint\">Digite a senha de backup</string>\n\n    <string name=\"patch_output_written_to\"> Patch aplicado com sucesso, arquivo escrito em </string>\n    <string name=\"patch_write_failed\"> Falha ao escrever boot.img com patch</string>\n\n    <!-- Script Library Strings -->\n    <string name=\"script_library\">Biblioteca de Scripts</string>\n    <string name=\"script_library_title\">Biblioteca de Scripts</string>\n    <string name=\"script_library_empty\">Sem scripts, clique no botão adicionar para adicionar</string>\n    <string name=\"script_library_add\">Adicionar Script</string>\n    <string name=\"script_library_add_title\">Adicionar Script</string>\n    <string name=\"script_library_select_file\">Selecionar arquivo de script</string>\n    <string name=\"script_library_alias\">Apelido</string>\n    <string name=\"script_library_alias_hint\">Digite o apelido do script (opcional)</string>\n    <string name=\"script_library_run\">Executar</string>\n    <string name=\"script_library_delete\">Excluir</string>\n    <string name=\"script_library_path\">Caminho: %s</string>\n    <string name=\"script_library_confirm_delete\">Confirmar exclusão do script?</string>\n    <string name=\"script_library_delete_success\">Script excluído com sucesso</string>\n    <string name=\"script_library_delete_failed\">Falha ao excluir script</string>\n    <string name=\"script_library_add_success\">Script adicionado com sucesso</string>\n    <string name=\"script_library_add_failed\">Falha ao adicionar script: %s</string>\n    <string name=\"script_library_load_failed\">Falha ao carregar biblioteca de scripts</string>\n    <string name=\"script_library_output\">Saída de execução</string>\n    <string name=\"script_library_no_output\">Sem saída</string>\n    <string name=\"script_library_save_log\">Salvar log</string>\n    <string name=\"script_library_log_saved\">Log salvo em %s</string>\n    <string name=\"script_library_log_save_failed\">Falha ao salvar log: %s</string>\n    <string name=\"settings_vibration\">Vibração e tato</string>\n    <string name=\"settings_vibration_summary\">Vibrar ao tocar</string>\n    <string name=\"settings_vibration_enabled\">Ativar vibração</string>\n    <string name=\"settings_vibration_intensity\">Intensidade da vibração</string>\n    <string name=\"settings_vibration_scope\">Escopo da vibração</string>\n    <string name=\"settings_vibration_scope_global\">Global (Em todo lugar)</string>\n    <string name=\"settings_vibration_scope_bottom_bar\">Apenas barra inferior</string>\n    <string name=\"superuser\">Superusuário</string>\n    <string name=\"module\">Módulo</string>\n    <string name=\"search_modules\">Buscar módulos...</string>\n    <string name=\"search_scripts\">Buscar scripts...</string>\n\n    <!-- Umount Service -->\n    <string name=\"settings_umount_service\">Umount Service</string>\n    <string name=\"settings_umount_service_summary\">Configurar pontos de montagem para desmontar automaticamente na inicialização</string>\n    <string name=\"umount_config_title\">Configuração do Umount</string>\n    <string name=\"umount_config_enabled\">Ativar Umount</string>\n    <string name=\"umount_config_enabled_summary\">Desmontar automaticamente pontos de montagem especificados na inicialização</string>\n    <string name=\"umount_config_paths_label\">Caminhos dos pontos de montagem (um por linha)</string>\n    <string name=\"umount_config_paths_placeholder\">/system/vendor/app\\n/system/vendor/priv-app</string>\n    <string name=\"umount_config_paths_helper\">Digite um caminho de ponto de montagem por linha para desmontar</string>\n    <string name=\"umount_config_save\">Salvar</string>\n    <string name=\"umount_config_save_confirm\">Confirmar salvar configuração?</string>\n    <string name=\"umount_config_save_success\">Configuração salva com sucesso</string>\n    <string name=\"umount_config_save_failed\">Falha ao salvar configuração</string>\n\n    <!-- Página Meus Temas -->\n    <string name=\"my_themes_title\">Meus Temas</string>\n    <string name=\"my_themes_empty\">Ainda não há temas</string>\n    <string name=\"my_themes_empty_action\">Navegar pela Loja de Temas</string>\n    <string name=\"my_themes_apply\">Aplicar Tema</string>\n    <string name=\"my_themes_delete\">Excluir Tema</string>\n    <string name=\"my_themes_delete_confirm\">Tem certeza de que deseja excluir este tema?</string>\n    <string name=\"my_themes_deleted\">Tema excluído</string>\n    <string name=\"my_themes_applied\">Tema aplicado</string>\n    <string name=\"my_themes_apply_failed\">Falha ao aplicar tema</string>\n    <string name=\"my_themes_details\">Detalhes do Tema</string>\n\n    <!-- Diálogo de Download -->\n    <string name=\"theme_download_title\">Baixando Tema</string>\n    <string name=\"theme_download_completed\">Download concluído</string>\n    <string name=\"theme_download_failed\">Download falhou</string>\n    <string name=\"theme_download_progress\">Progresso</string>\n    <string name=\"theme_download_file\">Arquivo do Tema</string>\n    <string name=\"theme_download_image\">Imagem de Visualização</string>\n    <string name=\"theme_download_cancel\">Cancelar</string>\n    <string name=\"theme_download_pause\">Pausar</string>\n    <string name=\"theme_download_resume\">Retomar</string>\n    <string name=\"theme_download_retry\">Tentar Novamente</string>\n    <string name=\"theme_download_apply\">Aplicar Tema</string>\n    <string name=\"theme_download_go_to_my_themes\">Meus Temas</string>\n    <string name=\"theme_download_retrying\">Tentando Novamente (%d/3)...</string>\n    <string name=\"theme_download_preparing\">Preparando download...</string>\n    <string name=\"theme_download_downloading_file\">Baixando arquivo do tema...</string>\n    <string name=\"theme_download_downloading_image\">Baixando imagem de visualização...</string>\n    <string name=\"theme_download_finalizing\">Finalizando...</string>\n    <string name=\"settings_predictive_back\">Gesto de voltar preditivo</string>\n    <string name=\"settings_predictive_back_summary\">Ativar animação do gesto de voltar preditivo do Android 14+</string>\n\n    <string name=\"home_device_status_battery_charging\">Carregando</string>\n    <string name=\"home_device_status_cpu_temp\">Temp. CPU</string>\n    <string name=\"home_device_status_memory_trend\">Tendência de Memória</string>\n    <string name=\"home_storage_partitions\">Partições de Armazenamento</string>\n    <string name=\"home_device_status_cpu_freq\">Frequência CPU</string>\n    <string name=\"home_network_rx\">Download</string>\n    <string name=\"home_network_tx\">Upload</string>\n    <string name=\"settings_home_layout_stats\">StatsUI</string>\n    <string name=\"home_stats_more_options\">Mais opções</string>\n    <string name=\"home_stats_kp_label\">KP</string>\n    <string name=\"home_stats_manager_label\">Gerenciador</string>\n    <string name=\"home_device_status_gpu_load\">GPU</string>\n    <string name=\"home_stats_kernel_modules\">Módulos do Kernel</string>\n    <string name=\"home_stats_apm_modules\">Módulos do Sistema</string>\n    <string name=\"home_stats_superusers\">Superusuários</string>\n    <string name=\"settings_kernel_spoof\">Configuração de Falsificação do Kernel</string>\n    <string name=\"settings_kernel_spoof_summary\">Falsificar versão do kernel e tempo de compilação</string>\n    <string name=\"settings_kernel_spoof_version\">Versão do Kernel</string>\n    <string name=\"settings_kernel_spoof_build_time\">Tempo de Compilação do Kernel</string>\n    <string name=\"settings_kernel_spoof_restore\">Restaurar</string>\n\n    <string name=\"kernel_spoof_enabled\">Falsificação do kernel ativada</string>\n    <string name=\"kernel_spoof_disabled_restored\">Falsificação do kernel desativada e restaurada</string>\n    <string name=\"kernel_spoof_failed\">Falha na falsificação do kernel: %d</string>\n    <string name=\"kernel_spoof_applied\">Falsificação do kernel aplicada</string>\n\n    <string name=\"settings_path_hide\">Ocultar caminho</string>\n    <string name=\"settings_path_hide_summary\">Ocultar arquivos e diretórios de aplicativos em nível de kernel</string>\n    <string name=\"path_hide_paths_label\">Caminhos ocultos (um por linha)</string>\n\n    <string name=\"path_hide_paths_helper\">Insira um caminho por linha. Caminhos correspondentes retornarão ENOENT.</string>\n    <string name=\"path_hide_save\">Salvar</string>\n    <string name=\"path_hide_enabled\">Ocultar caminho ativado</string>\n    <string name=\"path_hide_disabled\">Ocultar caminho desativado</string>\n    <string name=\"path_hide_applied\">Configuração de ocultar caminho aplicada</string>\n    <string name=\"path_hide_failed\">Falha na operação de ocultar caminho: %d</string>\n    <string name=\"path_hide_uid_mode\">Modo de execução por UID</string>\n    <string name=\"path_hide_uid_mode_summary\">Ocultar caminhos apenas para UIDs de aplicativos específicos</string>\n    <string name=\"path_hide_uids_label\">UIDs de destino (um por linha)</string>\n    <string name=\"path_hide_uids_helper\">Insira os UIDs dos aplicativos. Apenas estes aplicativos terão caminhos ocultos.</string>\n    <string name=\"path_hide_uid_save\">Salvar UIDs</string>\n    <string name=\"path_hide_uid_mode_enabled\">Modo de execução por UID ativado</string>\n    <string name=\"path_hide_uid_mode_disabled\">Modo de execução por UID desativado</string>\n    <string name=\"path_hide_filter_system\">Filtrar UIDs do sistema</string>\n    <string name=\"path_hide_filter_system_summary\">Também ocultar caminhos de processos root e do sistema (UID &lt; 10000)</string>\n    <string name=\"path_hide_filter_system_enabled\">Filtragem de UIDs do sistema ativada</string>\n    <string name=\"path_hide_filter_system_disabled\">Filtragem de UIDs do sistema desativada</string>\n    <string name=\"path_hide_filter_system_warning_title\">Aviso</string>\n    <string name=\"path_hide_filter_system_warning_message\">Ativar isso também ocultará caminhos de processos root e do sistema (UID &lt; 10000). Isso pode causar mau funcionamento de alguns recursos do sistema. Prossiga com cautela.</string>\n    <string name=\"path_hide_uid_applied\">Lista de permissão de UIDs aplicada</string>\n    <string name=\"path_hide_select_apps\">Selecionar apps</string>\n    <string name=\"path_hide_no_apps_selected\">Nenhum app selecionado</string>\n    <string name=\"path_hide_app_removed\">Removidas %d entradas de apps desinstalados</string>\n    <string name=\"path_hide_search_apps\">Pesquisar apps…</string>\n    <string name=\"path_hide_show_system\">Mostrar apps do sistema</string>\n    <string name=\"netisolate_title\">Isolamento de rede</string>\n    <string name=\"netisolate_enable\">Isolamento de rede ativado</string>\n    <string name=\"netisolate_disable\">Isolamento de rede desativado</string>\n    <string name=\"netisolate_enable_summary\">Bloquear acesso à rede dos apps selecionados no nível do kernel</string>\n    <string name=\"netisolate_uids_label\">Apps bloqueados</string>\n    <string name=\"netisolate_uids_hint\">Insira os UIDs para bloquear (um por linha)</string>\n    <string name=\"netisolate_no_uids\">Nenhum app bloqueado</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\" translatable=\"false\">FolkPatch</string>\n    <string name=\"app_name_fpatch\">FPatch</string>\n\n    <string name=\"desktop_app_name\">Имя настольного приложения</string>\n\n    <string name=\"home\">Главная</string>\n\n    <string name=\"settings_apm_stay_on_page\">Оставаться на странице операции</string>\n    <string name=\"settings_apm_stay_on_page_summary\">Не возвращаться автоматически после выполнения действия модуля</string>\n\n    <string name=\"success\">Успешно</string>\n    <string name=\"failure\">Ошибка</string>\n\n    <string name=\"patch_warnning\">Установка сопряжена с рисками. Убедитесь, что у вас есть резервная копия данных.</string>\n    <string name=\"patch\">Патч</string>\n\n    <string name=\"kernel_patch\">KernelPatch</string>\n    <string name=\"android_patch\">AndroidPatch</string>\n\n    <string name=\"settings_nav_layout_title\">Настройки макета навигации</string>\n    <string name=\"settings_nav_layout_summary\">Скрыть или показать некоторые компоненты навигации</string>\n    <string name=\"settings_nav_scheme\">Схема навигации</string>\n    <string name=\"settings_nav_mode\">Режим навигационной панели</string>\n    <string name=\"settings_nav_mode_summary\">Выберите способ отображения навигационной панели</string>\n    <string name=\"settings_nav_mode_auto\">Автоматически Традиционный</string>\n    <string name=\"settings_nav_mode_bottom\">Всегда нижняя панель</string>\n    <string name=\"settings_nav_mode_rail\">Всегда боковая панель</string>\n    <string name=\"settings_nav_mode_floating\">Плавающая нижняя панель</string>\n    <string name=\"settings_show_apm\">Показать системные модули</string>\n    <string name=\"settings_show_kpm\">Показать KPM</string>\n    <string name=\"settings_show_superuser\">Показать Суперпользователя</string>\n    <string name=\"settings_floating_auto_hide\">Автоскрытие панели</string>\n    <string name=\"settings_floating_auto_hide_summary\">Автоматически скрывать плавающую панель через 3 секунды</string>\n    <string name=\"settings_floating_swipe_hide\">Скрытие при прокрутке</string>\n    <string name=\"settings_floating_swipe_hide_summary\">Скрывать панель при прокрутке вниз, показывать при прокрутке вверх</string>\n    <string name=\"settings_navbar_glass_effect\">Эффект матового стекла</string>\n    <string name=\"settings_navbar_glass_effect_summary\">Применить эффект матового стекла к плавающей панели навигации</string>\n    <string name=\"settings_navbar_glass_blur_strength\">Сила размытия</string>\n    <string name=\"settings_navbar_glass_transparency\">Прозрачность фона</string>\n    <string name=\"settings_navbar_glass_highlight_strength\">Интенсивность блика</string>\n    <string name=\"settings_navbar_glass_specular\">Зеркальное отражение</string>\n    <string name=\"settings_navbar_glass_specular_summary\">Включить зеркальный блик в верхней части панели навигации</string>\n    <string name=\"settings_navbar_glass_inner_glow\">Внутреннее свечение</string>\n    <string name=\"settings_navbar_glass_inner_glow_summary\">Включить мягкое свечение в нижней части панели навигации</string>\n    <string name=\"settings_navbar_glass_border\">Стеклянная рамка</string>\n    <string name=\"settings_navbar_glass_border_summary\">Включить тонкую рамку вокруг панели навигации</string>\n    <string name=\"settings_stats_top_layout\">Верхняя карточка</string>\n    <string name=\"settings_stats_top_layout_summary\">Выберите стиль верхней информационной карточки</string>\n    <string name=\"settings_stats_top_layout_list\">Список</string>\n    <string name=\"settings_stats_top_layout_grid\">Сетка</string>\n    <string name=\"settings_block_kernelpatch_update\">Блокировать обновление KernelPatch</string>\n    <string name=\"settings_block_kernelpatch_update_summary\">Не показывать уведомления об обновлении KernelPatch</string>\n    <string name=\"settings_block_androidpatch_update\">Блокировать обновление системного патча</string>\n    <string name=\"settings_block_androidpatch_update_summary\">Не показывать уведомления об обновлении системного патча (APD)</string>\n    <string name=\"settings_disable_module_update_check\">Отключить проверку обновления модулей</string>\n    <string name=\"settings_disable_module_update_check_summary\">Отключить автоматическую проверку обновлений для системных модулей</string>\n\n    <string name=\"reboot\">Перезагрузка</string>\n    <string name=\"settings\">Настройки</string>\n    <string name=\"reboot_recovery\">Перезагрузить в Recovery</string>\n    <string name=\"reboot_bootloader\">Перезагрузить в Bootloader</string>\n    <string name=\"reboot_download\">Перезагрузить в Download</string>\n    <string name=\"reboot_edl\">Перезагрузить в EDL</string>\n    <string name=\"reboot_fastbootd\">Перезагрузить в FastbootD</string>\n    <string name=\"about\">О приложении</string>\n    <string name=\"developer_and_maintainer\">Разработчик | Сопровождающий</string>\n    <string name=\"settings_app_language\">Язык</string>\n    <string name=\"system_default\">По умолчанию системы</string>\n    <string name=\"settings_global_namespace_mode\">Глобальный режим пространства имён</string>\n    <string name=\"settings_global_namespace_mode_summary\">Все root-сессии используют глобальное пространство монтирования</string>\n    <string name=\"settings_clear_super_key_dialog\">Вы действительно хотите продолжить?</string>\n\n    <string name=\"su_exclude_all_title\">Исключить все</string>\n    <string name=\"su_exclude_all_confirm\">Вы уверены, что хотите исключить все приложения без root-прав?</string>\n\n    <string name=\"home_learn_apatch\">Изучить FolkPatch</string>\n    <string name=\"home_click_to_learn_apatch\">Узнайте о функциях FolkPatch и как их использовать</string>\n    <string name=\"settings_hide_apatch_card\">Скрыть карту \"Изучить FolkPatch\"</string>\n    <string name=\"settings_hide_apatch_card_summary\">Скрыть карту \"Изучить FolkPatch\" на главном экране</string>\n\n    <string name=\"about_source_code\"><![CDATA[<p>Просмотреть исходный код на %1$s<p/>Присоединяйтесь к нашему %2$s каналу<p/>Присоединяйтесь к нашей %3$s группе]]></string>\n    <string name=\"send_log\">Отправить логи</string>\n    <string name=\"save_log\">Сохранить логи</string>\n    <string name=\"log_saved\">Логи сохранены</string>\n    <string name=\"safe_mode\">Безопасный режим</string>\n    <string name=\"github\">GitHub</string>\n    <string name=\"telegram\">Telegram</string>\n    <string name=\"support_or_donate\">Поддержка / Пожертвование</string>\n\n    <string name=\"super_key\">SuperKey</string>\n    <string name=\"clear_super_key\">Очистить SuperKey</string>\n    <string name=\"patch_set_superkey\">Установить SuperKey</string>\n    <string name=\"home_patch_set_key_desc\">Единственные учётные данные для KernelPatch</string>\n    <string name=\"home_patch_next_step\">Следующий шаг</string>\n\n    <string name=\"home_not_installed\">Не установлено</string>\n    <string name=\"home_install_unknown\">Не установлено или не проверено</string>\n    <string name=\"home_install_unknown_summary\">Нажмите для установки</string>\n    <string name=\"home_click_to_install\">Нажмите для установки</string>\n    <string name=\"home_working\">Работает</string>\n    <string name=\"home_kp_need_update\">Доступна новая версия</string>\n    <string name=\"home_kp_cando_update\">Обновить</string>\n\n    <string name=\"home_installing\">Установка</string>\n\n    <string name=\"kpatch_version\">Версия: %s</string>\n    <string name=\"apatch_version\">Версия: %s</string>\n\n    <string name=\"kpatch_version_update\" formatted=\"false\">Версия: %s -&gt; %s</string>\n    <string name=\"kpatch_shadow_path_title\">kpatch</string>\n    <string name=\"home_auth_key_title\">Введите SuperKey</string>\n    <string name=\"home_auth_key_desc\">Запустить после аутентификации</string>\n    <string name=\"home_kpatch_info_title\">Информация</string>\n\n    <string name=\"home_ap_cando_install\">Установить</string>\n\n    <string name=\"home_ap_cando_uninstall\">Удалить</string>\n    <string name=\"home_ap_cando_reboot\">Перезагрузить</string>\n\n    <string name=\"patch_title\">Патч</string>\n\n    <string name=\"patch_config_title\">Патчи</string>\n    <string name=\"patch_mode_bootimg_patch\">Режим: Патч</string>\n    <string name=\"patch_mode_patch_and_install\">Режим: Патч и установка</string>\n    <string name=\"patch_mode_install_to_next_slot\">Режим: Установка в неактивный слот (После OTA)</string>\n    <string name=\"patch_mode_uninstall_patch\">Режим: Удаление KPatch</string>\n    <string name=\"patch_select_bootimg_btn\">Выбрать boot</string>\n    <string name=\"patch_embed_kpm_btn\">Встроить KPM</string>\n    <string name=\"patch_start_patch_btn\">Начать</string>\n    <string name=\"patch_start_unpatch_btn\">Удалить патч</string>\n    <string name=\"patch_item_error\">!!ОШИБКА!!</string>\n    <string name=\"patch_item_bootimg\">bootimg</string>\n    <string name=\"patch_item_bootimg_slot\">Слот:</string>\n    <string name=\"patch_item_bootimg_dev\">Устройство:</string>\n    <string name=\"patch_item_kernel\">Ядро</string>\n    <string name=\"patch_item_kpimg\">kpimg</string>\n    <string name=\"patch_item_kpimg_version\">Версия:</string>\n    <string name=\"patch_item_kpimg_comile_time\">Время:</string>\n    <string name=\"patch_item_kpimg_config\">Конфигурация:</string>\n    <string name=\"patch_item_new_extra_kpm\">Встроить новый</string>\n    <string name=\"patch_item_existed_extra_kpm\">Существует</string>\n    <string name=\"patch_item_extra_name\">Имя:</string>\n    <string name=\"patch_item_extra_version\">Версия:</string>\n    <string name=\"patch_item_extra_author\">Автор:</string>\n    <string name=\"patch_item_extra_kpm_license\">Лицензия:</string>\n    <string name=\"patch_item_extra_kpm_desciption\">Описание:</string>\n    <string name=\"patch_item_extra_args\">Аргументы:</string>\n    <string name=\"patch_item_extra_event\">Событие:</string>\n    <string name=\"patch_item_skey\">SuperKey</string>\n    <string name=\"patch_custom_superkey\">Пользовательский SuperKey</string>\n    <string name=\"patch_custom_superkey_summary\">Вы по-прежнему можете использовать SuperKey для управления Root и ядром, менеджер авторизован встроенной подписью ядра</string>\n    <string name=\"patch_item_set_skey_label\">SuperKey должен содержать 8-63 символа и включать цифры и буквы, но без специальных символов.</string>\n    <string name=\"patch_confirm_superkey\">Подтвердите SuperKey</string>\n    <string name=\"patch_skey_mismatch\">SuperKey не совпадает</string>\n\n    <string name=\"home_kernel\">Версия ядра</string>\n    <string name=\"home_manager_version\">Версия менеджера</string>\n    <string name=\"home_fingerprint\">Отпечаток</string>\n\n    <string name=\"home_selinux_status\">Статус SELinux</string>\n    <string name=\"home_selinux_status_disabled\">Отключен</string>\n    <string name=\"home_selinux_status_enforcing\">Принудительный</string>\n    <string name=\"home_selinux_status_permissive\">Разрешающий</string>\n    <string name=\"home_selinux_status_unknown\">Неизвестно</string>\n\n    <string name=\"settings_selinux_mode\">Режим SELinux</string>\n    <string name=\"settings_selinux_mode_summary\">Выбрать режим принудительного применения SELinux</string>\n    <string name=\"settings_selinux_mode_enforcing\">Принудительный (Строгий)</string>\n    <string name=\"settings_selinux_mode_permissive\">Разрешающий (Мягкий)</string>\n    <string name=\"settings_selinux_current_mode\">Текущий: %s</string>\n    <string name=\"settings_selinux_mode_enforcing_summary\">SELinux полностью применяет правила доступа</string>\n    <string name=\"settings_selinux_mode_permissive_summary\">SELinux только регистрирует нарушения, не блокирует</string>\n\n    <string name=\"home_device_info\">Устройство</string>\n    <string name=\"home_system_version\">Версия системы</string>\n    <string name=\"home_kpatch_version\">Версия KernelPatch</string>\n    <string name=\"home_su_path\">Исполняемый файл su</string>\n    <string name=\"home_apatch_version\">Версия FolkPatch</string>\n\n    <string name=\"kpm\">KPModule</string>\n    <string name=\"kpm_kp_not_installed\">KernelPatch не установлен</string>\n    <string name=\"kpm_add_kpm\">Добавить KPM</string>\n    <string name=\"kpm_load\">Загрузить</string>\n    <string name=\"kpm_install\">Установить</string>\n    <string name=\"kpm_embed\">Встроить</string>\n    <string name=\"kpm_load_toast_succ\">Загрузка успешна</string>\n    <string name=\"kpm_load_toast_failed\">Загрузка не удалась</string>\n    <string name=\"kpm_unload_confirm\">Выгрузить модуль %s?</string>\n    <string name=\"kpm_unload\">Выгрузить</string>\n    <string name=\"kpm_control\">Управление</string>\n    <string name=\"kpm_apm_empty\">Нет загруженных модулей</string>\n    <string name=\"kpm_version\">Версия</string>\n    <string name=\"kpm_license\">Лицензия</string>\n    <string name=\"kpm_author\">Автор</string>\n    <string name=\"kpm_desc\">Описание</string>\n    <string name=\"kpm_args\">Аргументы</string>\n\n    <string name=\"su_title\">Суперпользователь</string>\n    <string name=\"su_selinux_via_hook\">Обойти через хук</string>\n    <string name=\"su_pkg_excluded_label\">Исключить</string>\n    <string name=\"su_pkg_excluded_setting_title\">Исключить изменения</string>\n    <string name=\"su_pkg_excluded_setting_summary\">Включение этой опции позволит FolkPatch восстановить любые файлы, измененные модулями этого приложения.</string>\n    <string name=\"su_pkg_root_setting_title\">Суперпользователь</string>\n    <string name=\"su_pkg_root_setting_summary\">Включение этой опции предоставит доступ суперпользователя вашему приложению, позволяя использовать команды SU.</string>\n    <string name=\"su_pkg_normal_setting_title\">Обычный режим</string>\n    <string name=\"su_pkg_normal_setting_summary\">Доступ root не предоставлен, приложение работает без привилегий суперпользователя.</string>\n    <string name=\"su_batch_exclude_title\">Пакетное исключение</string>\n    <string name=\"su_batch_exclude_content\">Исключить инъекцию для всех не-ROOT приложений, выберите действие</string>\n    <string name=\"su_exclude_btn\">Исключить</string>\n    <string name=\"su_exclude_reverse_btn\">Обратить</string>\n\n    <!-- SuperUser App Action Dialog -->\n    <string name=\"su_app_action_title\">Действие приложения</string>\n    <string name=\"su_app_action_content\">Выберите действие для приложения</string>\n    <string name=\"su_app_action_launch\">Запустить приложение</string>\n    <string name=\"su_app_action_force_stop\">Принудительная остановка</string>\n    <string name=\"su_app_action_launch_success\">Запуск %s</string>\n    <string name=\"su_app_action_force_stop_success\">%s принудительно остановлено</string>\n    <string name=\"su_app_action_failed\">Операция не удалась: %s</string>\n\n    <!-- SU Audit Log -->\n    <string name=\"su_audit_log_title\">Журнал авторизации</string>\n    <string name=\"su_audit_log_empty\">Нет записей авторизации</string>\n    <string name=\"su_audit_log_clear\">Очистить журнал</string>\n    <string name=\"su_audit_log_clear_confirm\">Очистить все записи авторизации?</string>\n    <string name=\"su_audit_action_grant\">Разрешено</string>\n    <string name=\"su_audit_action_revoke\">Отозвано</string>\n    <string name=\"su_audit_action_exclude\">Исключено</string>\n    <string name=\"su_audit_tab_usage\">Журнал использования</string>\n    <string name=\"su_audit_tab_operations\">Журнал операций</string>\n\n    <string name=\"su_show_system_apps\">Показать системные приложения</string>\n    <string name=\"su_hide_system_apps\">Скрыть системные приложения</string>\n    <string name=\"su_refresh\">Обновить</string>\n\n    <string name=\"apm\">Системный модуль</string>\n    <string name=\"apm_not_installed\">AndroidPatch не установлен</string>\n    <string name=\"apm_failed_to_enable\">Не удалось включить модуль: %s</string>\n    <string name=\"apm_failed_to_disable\">Не удалось отключить модуль: %s</string>\n    <string name=\"apm_empty\">Нет установленных модулей</string>\n    <string name=\"apm_remove\">Удалить</string>\n    <string name=\"apm_undo\">Отменить</string>\n    <string name=\"apm_install\">Установить</string>\n    <string name=\"apm_uninstall_confirm\">Удалить модуль %s?</string>\n    <string name=\"apm_uninstall_success\">%s удалён</string>\n    <string name=\"apm_uninstall_failed\">Не удалось удалить: %s</string>\n    <string name=\"apm_undo_uninstall_success\">%s восстановлен</string>\n    <string name=\"apm_undo_uninstall_failed\">Не удалось восстановить: %s</string>\n    <string name=\"apm_version\">Версия</string>\n    <string name=\"apm_author\">Автор</string>\n    <string name=\"apm_desc\">Описание</string>\n    <string name=\"apm_overlay_fs_not_available\">Модули недоступны, так как OverlayFS отключён ядром!</string>\n    <string name=\"apm_magisk_conflict\">Модули недоступны из-за конфликта с Magisk!</string>\n    <string name=\"apm_mount_warning_title\">Важное уведомление</string>\n    <string name=\"apm_mount_warning_message\">По умолчанию модули не монтируются. Используйте встроенную систему монтирования или метамодули.</string>\n    <string name=\"apm_mount_warning_button\">Понятно</string>\n    <string name=\"apm_reboot_to_apply\">Перезагрузите, чтобы применить изменения</string>\n    <string name=\"apm_changelog\">Список изменений</string>\n    <string name=\"apm_update\">Обновить</string>\n    <string name=\"apm_downloading\">Загрузка модуля: %s</string>\n    <string name=\"apm_start_downloading\">Начать загрузку: %s</string>\n    <string name=\"apm_new_version_available\">Доступна новая версия %s, нажмите для обновления.</string>\n\n    <string name=\"hide_apatch_manager\">Скрыть менеджер APatch</string>\n    <string name=\"hide_apatch_manager_summary\">Установить прокси-приложение со случайным ID пакета и пользовательской меткой приложения</string>\n    <string name=\"hide_apatch_dialog_new_manager_name\">Новое имя менеджера</string>\n    <string name=\"hide_apatch_dialog_summary\">Будет использоваться как новая метка приложения, отображаемая в лаунчере</string>\n    <string name=\"hide_apatch_manager_failure\">Не удалось скрыть. Пожалуйста, сообщите об ошибке!</string>\n\n    <string name=\"setting_reset_su_path\">Сбросить путь su</string>\n    <string name=\"setting_reset_su_new_path\">Новый полный путь</string>\\\n\n    <string name=\"settings_folkx_engine_title\">Движок анимации FolkX</string>\n    <string name=\"settings_folkx_engine_summary\">Использовать плавные физические пружинные анимации при переключении страниц верхнего уровня</string>\n    <string name=\"settings_folkx_animation_type\">Тип анимации</string>\n    <string name=\"settings_folkx_animation_speed\">Скорость анимации</string>\n    <string name=\"settings_folkx_animation_linear\">Линейное движение</string>\n    <string name=\"settings_folkx_animation_spatial\">Пространственное движение</string>\n    <string name=\"settings_folkx_animation_fade\">Появление/Исчезновение</string>\n    <string name=\"settings_folkx_animation_vertical\">Вертикальное скольжение</string>\n    <string name=\"settings_folkx_animation_diagonal\">Диагональное скольжение</string>\n\n    <string name=\"apm_webui_open\">Открыть</string>\n    <string name=\"apm_action\">Действие</string>\n    <string name=\"module_shortcut_add\">Ярлык</string>\n    <string name=\"module_shortcut_not_supported\">Лаунчер не поддерживает ярлыки на рабочем столе.</string>\n    <string name=\"module_shortcut_created\">Ярлык создан на рабочем столе.</string>\n    <string name=\"module_shortcut_updated\">Ярлык обновлен.</string>\n    <string name=\"module_shortcut_permission_tip_xiaomi\">Пожалуйста, включите разрешение \"Создание ярлыков на рабочем столе\" для этого приложения в настройках Xiaomi.</string>\n    <string name=\"module_shortcut_permission_tip_oppo\">Пожалуйста, включите разрешение \"Ярлык на рабочем столе\" для этого приложения в настройках OPPO.</string>\n    <string name=\"module_shortcut_permission_tip_default\">Если создание ярлыка не удается, пожалуйста, включите разрешение ярлыка на рабочем столе для этого приложения в настройках системы.</string>\n    <string name=\"module_shortcut_name\">Название ярлыка</string>\n    <string name=\"module_shortcut_icon\">Иконка ярлыка</string>\n    <string name=\"module_shortcut_type\">Тип ярлыка</string>\n    <string name=\"module_shortcut_type_webui\">WebUI</string>\n    <string name=\"module_shortcut_type_action\">Action</string>\n    <string name=\"module_shortcut_icon_default\">Использовать иконку по умолчанию</string>\n    <string name=\"module_shortcut_icon_select\">Выбрать иконку</string>\n    <string name=\"enable_web_debugging\">Включить отладку WebView</string>\n    <string name=\"enable_web_debugging_summary\">Можно использовать для отладки WebUI. Включайте только при необходимости.</string>\n    <string name=\"settings_apm_install_confirm\">Подтверждать перед установкой</string>\n    <string name=\"settings_apm_install_confirm_summary\">Показывать диалог подтверждения перед установкой модулей</string>\n    <string name=\"settings_enable_module_shortcut_add\">Включить кнопку добавления ярлыка</string>\n    <string name=\"settings_enable_module_shortcut_add_summary\">Показать кнопку добавления ярлыка WebUI на странице системных модулей</string>\n    <string name=\"settings_module_sort_optimization\">Оптимизация сортировки модулей</string>\n    <string name=\"settings_module_sort_optimization_summary\">Разместить системные модули с WebUI и Action в начале списка</string>\n    <string name=\"settings_fold_system_module\">Свернуть системные модули</string>\n    <string name=\"settings_fold_system_module_summary\">Нажмите на карточку модуля, чтобы развернуть/свернуть действия</string>\n        <string name=\"settings_simple_list_bottom_bar\">Простая нижняя панель списка</string>\n        <string name=\"settings_simple_list_bottom_bar_summary\">Использовать стиль кнопок только с иконками для действий модуля, вдохновлено APatch</string>\n    <string name=\"settings_spliced_card_group\">Spliced Card Group</string>\n    <string name=\"settings_spliced_card_group_summary\">Use M3E continuous card group style for module list</string>\n    <string name=\"settings_show_more_module_info\">Показать подробности о модуле</string>\n    <string name=\"settings_show_more_module_info_summary\">Показать ID и размер модуля в списке модулей</string>\n    <string name=\"apm_install_confirm_title\">Установить модуль</string>\n    <string name=\"apm_install_confirm_content\">Вы уверены, что хотите установить %s?</string>\n    <string name=\"apm_disable_all_title\">Отключить все модули</string>\n    <string name=\"apm_disable_all_confirm\">Вы уверены, что хотите отключить все модули? Это приведёт к отключению всех установленных модулей.</string>\n    <string name=\"apm_enable_module_banner\">Включить баннер модуля</string>\n    <string name=\"apm_enable_module_banner_summary\">Отображать изображение баннера для модулей если доступно</string>\n    <string name=\"apm_enable_folk_banner\">Настроить баннер модуля</string>\n    <string name=\"apm_enable_folk_banner_summary\">Длительное нажатие на карточку модуля для выбора изображения баннера</string>\n    <string name=\"apm_folk_banner_title\">FolkBanner</string>\n    <string name=\"apm_folk_banner_select\">Выбрать изображение</string>\n    <string name=\"apm_folk_banner_clear\">Очистить FolkBanner</string>\n    <string name=\"apm_folk_banner_saved\">FolkBanner внедрен для %s.</string>\n    <string name=\"apm_folk_banner_cleared\">FolkBanner очищен для %s.</string>\n    <string name=\"apm_folk_banner_failed\">Не удалось обновить FolkBanner для %s.</string>\n    <string name=\"apm_banner_api_mode\">Режим API</string>\n    <string name=\"apm_banner_api_mode_summary\">Использовать API случайных изображений или локальный каталог для баннеров модулей</string>\n    <string name=\"apm_banner_api_source\">Настроить источник API</string>\n    <string name=\"apm_banner_api_source_hint\">Введите URL API или локальный путь</string>\n    <string name=\"apm_banner_api_source_saved\">Источник API сохранен</string>\n    <string name=\"apm_banner_api_source_not_configured\">Не настроено</string>\n    <string name=\"apm_banner_api_url_configured\">URL API настроен</string>\n    <string name=\"apm_banner_local_dir_configured\">Локальный каталог настроен</string>\n    <string name=\"apm_banner_clear_cache\">Очистить кэш</string>\n    <string name=\"apm_banner_cache_cleared\">Кэш баннеров очищен</string>\n    <string name=\"apm_banner_api_config_title\">Настроить источник API</string>\n    <string name=\"apm_banner_api_config_desc\">Введите URL API случайных изображений или путь к локальному каталогу. Каждый модуль получит уникальный баннер.</string>\n    <string name=\"apm_banner_api_examples_title\">Примеры:</string>\n    <string name=\"apm_banner_api_examples\">API: https://picsum.photos/800/400\\nЛокально: /sdcard/Pictures/Banners</string>\n    <!-- Рынок API -->\n    <string name=\"apm_api_marketplace_title\">Маркетплейс API</string>\n    <string name=\"apm_api_preview\">Предпросмотр</string>\n    <string name=\"apm_api_apply\">Применить</string>\n    <string name=\"apm_api_preview_title\">Предпросмотр API</string>\n    <string name=\"apm_api_preview_failed\">Не удалось загрузить предпросмотр</string>\n    <string name=\"apm_api_url_label\">URL API:</string>\n    <string name=\"apm_api_apply_success\">Источник API успешно применён</string>\n    <string name=\"apm_api_verifying\">Проверка…</string>\n    <string name=\"apm_api_retry\">Повторить</string>\n    <string name=\"apm_api_empty\">Нет доступных источников API</string>\n    <string name=\"settings_banner_custom_opacity\">Непрозрачность баннера</string>\n    <string name=\"settings_banner_custom_opacity_summary\">Использовать пользовательскую непрозрачность для баннеров вместо режима обоев</string>\n    <string name=\"settings_banner_opacity\">Прозрачность баннера</string>\n\n    <string name=\"settings_donot_store_superkey\">Не сохранять SuperKey локально</string>\n    <string name=\"settings_donot_store_superkey_summary\">Аутентифицировать SuperKey каждый раз при запуске менеджера</string>\n\n    <string name=\"mode_select_page_title\">Установка</string>\n    <string name=\"mode_select_page_patch_and_install\">Патч и установка</string>\n    <string name=\"mode_select_page_select_file\">Выберите образ boot для патча</string>\n    <string name=\"mode_select_page_install_inactive_slot\">Установить в неактивный слот (После OTA)</string>\n    <string name=\"mode_select_page_install_inactive_slot_warning\">Ваше устройство будет **ПРИНУДИТЕЛЬНО** загружено в текущий неактивный слот после перезагрузки!\\nИспользуйте эту опцию только после завершения OTA.\\nПродолжить?</string>\n    <string name=\"mode_select_page_select_kpimg\">Использовать локальный файл патча (KPimg)</string>\n    <string name=\"patch_custom_kpimg_label\">Пользовательский KPimg</string>\n    <string name=\"patch_custom_kpimg_file\">Файл: %s</string>\n    <string name=\"patch_select_kpimg_btn\">Выбрать KPimg</string>\n\n    <string name=\"home_dialog_auth_fail_title\">Ошибка аутентификации</string>\n    <string name=\"home_dialog_auth_fail_content\">Не удалось аутентифицировать SuperKey, что привело к сбою активации FolkPatch.\nВот несколько возможных причин сбоя аутентификации—пожалуйста, проверьте, какая из них относится к вам:\n\n\\n1. Вы вообще не использовали KernelPatch для патчинга boot.img или забыли, что на самом деле делали.\n\\n2. Пропатченный boot.img всё ещё спит на вашем компьютере, ни разу не был прошит в устройство.\n\\n3. SuperKey был введён неправильно или содержит загадочные символы, например символы из инопланетных языков.\n\\n4. Ваше устройство может быть несовместимо с FolkPatch и KernelPatch, принудительные попытки бесполезны.\n\\n5. Вы могли выполнить какие-то загадочные операции—например, использовать определённые модули, исключающие имена пакетов, которые заблокировали FolkPatch, в результате чего вы исключили себя из игры.\n\n</string>\n\n    <string name=\"home_more_menu_feedback_or_suggestion\">Обратная связь или предложение</string>\n    <string name=\"home_more_menu_about\">О приложении</string>\n    <string name=\"home_more_menu_document\">Документация</string>\n\n    <string name=\"home_dialog_uninstall_title\">Удаление</string>\n    \n    <string name=\"home_dialog_uninstall_ap_only\">Удалить только патч</string>\n    <string name=\"home_dialog_uninstall_ap_only_desc\">Удалить только AndroidPatch, сохранив менеджер.</string>\n    <string name=\"home_dialog_uninstall_all\">Полностью удалить</string>\n    <string name=\"home_dialog_uninstall_all_desc\">Удалить AndroidPatch и запустить процесс полного удаления.</string>\n    \n    <!-- Install Mode Select -->\n    <string name=\"kp_install_methods\">KernelPatch Патчинг/Установка</string>\n    <string name=\"restore_boot_methods\">Выберите загрузочный образ для восстановления в загрузочный раздел</string>\n    <string name=\"restore_select_file\">Выберите файл загрузочного раздела для восстановления</string>\n\n    <string name=\"kpm_control_dialog_title\">Управление KPM</string>\n    <string name=\"kpm_control_dialog_content\">Пожалуйста, введите параметры управления:</string>\n    <string name=\"kpm_control_paramters\">Параметры</string>\n    <string name=\"kpm_control_outMsg\">Вывод</string>\n    <string name=\"kpm_control_ok\">Успешно!</string>\n    <string name=\"kpm_control_failed\">Ошибка!</string>\n    \n    <string name=\"settings_night_mode_follow_sys\">Тёмная тема следует системе</string>\n    <string name=\"settings_night_mode_follow_sys_summary\">Автоматически переключать тёмную тему на основе системных настроек</string>\n    <string name=\"settings_night_theme_enabled\">Тёмная тема</string>\n    \n    <string name=\"settings_use_system_color_theme\">Системная цветовая тема</string>\n    <string name=\"settings_use_system_color_theme_summary\">Использовать цветовую тему, сгенерированную системой из обоев</string>\n    <string name=\"settings_custom_color_theme\">Цветовая тема</string>\n\n    <string name=\"amber_theme\">Янтарная</string>\n    <string name=\"blue_theme\">Синяя</string>\n    <string name=\"blue_grey_theme\">Серо-синяя</string>\n    <string name=\"brown_theme\">Коричневая</string>\n    <string name=\"cyan_theme\">Бирюзовая</string>\n    <string name=\"deep_orange_theme\">Тёмно-оранжевая</string>\n    <string name=\"deep_purple_theme\">Тёмно-фиолетовая</string>\n    <string name=\"green_theme\">Зелёная</string>\n    <string name=\"indigo_theme\">Индиго</string>\n    <string name=\"light_blue_theme\">Светло-синяя</string>\n    <string name=\"light_green_theme\">Светло-зелёная</string>\n    <string name=\"lime_theme\">Лаймовая</string>\n    <string name=\"orange_theme\">Оранжевая</string>\n    <string name=\"pink_theme\">Розовая</string>\n    <string name=\"purple_theme\">Фиолетовая</string>\n    <string name=\"red_theme\">Красная</string>\n    <string name=\"sakura_theme\">Сакура</string>\n    <string name=\"teal_theme\">Бирюзовая</string>\n    <string name=\"yellow_theme\">Жёлтая</string>\n    <string name=\"theme_color\">Цвет темы</string>\n    <string name=\"theme_light\">Светлая</string>\n    <string name=\"theme_dark\">Тёмная</string>\n    <string name=\"theme_system\">Система</string>\n    <string name=\"loading_modules\">Загрузка модулей…</string>\n    <string name=\"loading_scripts\">Поиск скриптов…</string>\n    <string name=\"loading_themes\">Загрузка тем…</string>\n    <string name=\"loading_apis\">Загрузка источников API…</string>\n\n    <string name=\"about_app_desc\">Реализация Root на основе KernelPatch, позволяющая перехватывать функции ядра без перекомпиляции ядра.</string>\n    <string name=\"about_powered_by\">Работает на %1$s</string>\n    <string name=\"about_telegram_group\">Группа в Telegram</string>\n    \n    <!-- Online Module Strings -->\n    <string name=\"online_module_title\">Онлайн системные модули</string>\n    <string name=\"online_module_download_start\">Начать загрузку: %s</string>\n    <string name=\"online_module_download_complete\">Загрузка завершена: %s</string>\n    <string name=\"online_module_download_notification\">Загрузка %s. Проверьте панель уведомлений для отслеживания прогресса и папку Загрузки для завершённого файла.</string>\n\n    <!-- Online KPM Strings -->\n    <string name=\"online_kpm_title\">Онлайн модули ядра</string>\n    <string name=\"online_kpm_download_start\">Начать загрузку: %s</string>\n    <string name=\"online_kpm_download_complete\">Загрузка завершена: %s</string>\n    <string name=\"online_kpm_download_notification\">Загрузка %s. Проверьте панель уведомлений для отслеживания прогресса и папку Загрузки для завершённого файла.</string>\n\n    <!-- Online Script Strings -->\n    <string name=\"online_script_title\">Онлайн скрипты</string>\n    <string name=\"online_script_download_start\">Начать загрузку: %s</string>\n    <string name=\"online_script_download_complete\">Загрузка завершена: %s</string>\n    <string name=\"online_script_download_notification\">Загрузка %s</string>\n\n    <!-- Crash Handle Strings -->\n    <string name=\"crash_handle_title\">Отчёт о сбое</string>\n    <string name=\"crash_handle_copy\">Копировать</string>\n\n    <!-- About Screen Strings -->\n    <string name=\"about_app_version\">Версия приложения: %s</string>\n    <string name=\"about_github\">GitHub</string>\n    <string name=\"about_telegram_channel\">Канал в Telegram</string>\n\n    <string name=\"settings_app_dpi\">DPI приложения</string>\n    <string name=\"dpi_apply_settings\">Применить</string>\n    <string name=\"dpi_confirm_title\">Подтверждение изменения DPI</string>\n    <string name=\"dpi_confirm_message\">Изменить DPI приложения с %1$s на %2$s?</string>\n    <string name=\"settings_alt_icon\">Альтернативная иконка</string>\n    <string name=\"alt_icon_summary\">Использовать альтернативный значок запуска</string>\n    <string name=\"settings_magic_mount\">Folk Mount API</string>\n    <string name=\"settings_magic_mount_summary\">Включить встроенную систему монтирования модулей</string>\n    <string name=\"settings_new_app_profile_mode\">Режим по умолчанию для новых приложений</string>\n    <string name=\"settings_new_app_profile_normal\">БЕЗ ROOT</string>\n    <string name=\"settings_new_app_profile_root\">ROOT</string>\n    <string name=\"settings_new_app_profile_exclude\">Исключить</string>\n    <string name=\"settings_hide_service\">FolkPatch Hide</string>\n    <string name=\"settings_hide_service_summary\">Скрывать статус разблокировки загрузчика в некоторой степени</string>\n    <string name=\"settings_app_title\">Заголовок приложения</string>\n    <string name=\"app_title_custom\">Пользовательский</string>\n    <string name=\"settings_custom_app_title\">Задать имя приложения</string>\n    <string name=\"custom_app_title_dialog_title\">Пользовательское имя приложения</string>\n    <string name=\"custom_app_title_dialog_hint\">Введите имя приложения</string>\n    <string name=\"custom_app_title_dialog_confirm\">Подтвердить</string>\n    <string name=\"custom_app_title_dialog_empty\">Имя не может быть пустым</string>\n    <string name=\"cancel\">Отмена</string>\n    <string name=\"settings_custom_background\">Пользовательский фон</string>\n    <string name=\"settings_custom_background_enabled\">Пользовательский фон включен</string>\n    <string name=\"settings_custom_background_opacity\">Прозрачность карточки</string>\n    <string name=\"settings_custom_background_blur\">Размытие фона</string>\n    <string name=\"settings_custom_background_dim\">Затемнение фона</string>\n    <string name=\"settings_select_background_image\">Выбрать фоновое изображение</string>\n    <string name=\"settings_custom_background_summary\">Установить пользовательское фоновое изображение</string>\n    <string name=\"settings_custom_background_saved\">Фон успешно сохранён</string>\n    <string name=\"settings_custom_background_error\">Не удалось сохранить фон</string>\n    <string name=\"settings_background_selected\">Фон выбран</string>\n    <string name=\"settings_background_image_cleared\">Фоновое изображение очищено</string>\n    <string name=\"settings_clear_background\">Очистить фон</string>\n    <string name=\"settings_clear_background_confirm\">Вы уверены, что хотите очистить фоновое изображение?</string>\n    <string name=\"settings_multi_background_mode\">Режим множественных фонов</string>\n    <string name=\"settings_multi_background_mode_summary\">Установить разные фоновые изображения для разных страниц</string>\n    <string name=\"settings_select_home_background\">Фон главной страницы</string>\n    <string name=\"settings_select_kernel_background\">Фон модуля ядра</string>\n    <string name=\"settings_select_superuser_background\">Фон суперпользователя</string>\n    <string name=\"settings_select_system_module_background\">Фон системного модуля</string>\n    <string name=\"settings_select_settings_background\">Фон страницы настроек</string>\n\n    <string name=\"settings_advanced_title_style\">Расширенный стиль заголовка</string>\n    <string name=\"settings_advanced_title_style_summary\">Заменить заголовок верхней панели на自定义 изображение</string>\n    <string name=\"settings_advanced_title_style_enabled\">Расширенный стиль заголовка включен</string>\n    <string name=\"settings_select_title_image\">Выбрать изображение заголовка</string>\n    <string name=\"settings_title_image_selected\">Изображение заголовка выбрано</string>\n    <string name=\"settings_title_image_saved\">Изображение заголовка успешно сохранено</string>\n    <string name=\"settings_title_image_error\">Не удалось сохранить изображение заголовка</string>\n    <string name=\"settings_clear_title_image\">Очистить изображение заголовка</string>\n    <string name=\"settings_clear_title_image_confirm\">Вы уверены, что хотите очистить изображение заголовка?</string>\n    <string name=\"settings_title_image_cleared\">Изображение заголовка очищено</string>\n    <string name=\"settings_title_image_day_opacity\">Прозрачность дневного режима</string>\n    <string name=\"settings_title_image_night_opacity\">Прозрачность ночного режима</string>\n    <string name=\"settings_title_image_dim\">Затемнение фона</string>\n    <string name=\"settings_title_image_offset_x\">Горизонтальная позиция</string>\n\n    <string name=\"settings_launcher_icon\">Иконка лаунчера</string>\n    <string name=\"settings_grid_working_card_hide_check\">Скрыть значок статуса</string>\n    <string name=\"settings_grid_working_card_hide_check_summary\">Скрыть галочку или значок предупреждения на рабочей карточке</string>\n    <string name=\"settings_grid_working_card_hide_text\">Скрыть текст статуса</string>\n    <string name=\"settings_grid_working_card_hide_text_summary\">Скрыть текст «Работает» или «Не установлено» на рабочей карточке</string>\n    <string name=\"settings_grid_working_card_hide_mode\">Скрыть режим работы</string>\n    <string name=\"settings_grid_working_card_hide_mode_summary\">Скрыть текст «Full» или «Half» на рабочей карточке</string>\n    <string name=\"settings_list_card_hide_status_badge\">Использовать классический эмодзи</string>\n    <string name=\"settings_list_card_hide_status_badge_summary\">Статусный бейдж на главной странице превращается в классический эмодзи</string>\n    <string name=\"settings_custom_badge_text\">Пользовательский текст бейджа</string>\n    <string name=\"settings_custom_badge_text_summary\">Изменить текстовое отображение бейджа, только для развлечения</string>\n    <string name=\"settings_custom_background_dual_dim\">Включить двойную дневную/ночную затемненность</string>\n    <string name=\"settings_custom_background_dual_dim_desc\">Автоматическая регулировка затемнения фона для светлой и тёмной темы</string>\n    <string name=\"settings_custom_background_day_dim\">Затемненность фона в дневном режиме</string>\n    <string name=\"settings_custom_background_night_dim\">Затемненность фона в ночном режиме</string>\n    <string name=\"settings_grid_working_card_dual_opacity\">Включить двойную дневную/ночную прозрачность</string>\n    <string name=\"settings_grid_working_card_dual_opacity_desc\">Автоматическая регулировка прозрачности карточки для светлой и тёмной темы</string>\n    <string name=\"settings_grid_working_card_day_opacity\">Прозрачность карточки в дневном режиме</string>\n    <string name=\"settings_grid_working_card_night_opacity\">Прозрачность карточки в ночном режиме</string>\n    \n    <!-- KPM AutoLoad Strings -->\n    <string name=\"kpm_autoload_title\">Автозагрузка KPM</string>\n    <string name=\"kpm_autoload_enabled\">Включить автозагрузку</string>\n    <string name=\"kpm_autoload_enabled_summary\">Автоматически загружать модули KPM при загрузке устройства</string>\n    <string name=\"kpm_autoload_json_config\">Конфигурация JSON</string>\n    <string name=\"kpm_autoload_json_label\">Конфигурация JSON</string>\n    <string name=\"kpm_autoload_json_placeholder\">{\n  \"enabled\": true,\n  \"kpmPaths\": [\n    \"/path/to/module1.kpm\",\n    \"/path/to/module2.kpm\"\n  ]\n}</string>\n    <string name=\"kpm_autoload_json_error\">Неверный формат JSON</string>\n    <string name=\"kpm_autoload_json_helper\">Введите валидный JSON с массивом путей к KPM файлам</string>\n    <string name=\"kpm_autoload_save\">Сохранить конфигурацию</string>\n    <string name=\"kpm_autoload_save_confirm\">Сохранить конфигурацию автозагрузки?</string>\n    <string name=\"kpm_autoload_save_success\">Конфигурация успешно сохранена</string>\n    <string name=\"kpm_autoload_save_failed\">Не удалось сохранить конфигурацию</string>\n    <string name=\"kpm_autoload_visual_mode\">Визуальный режим</string>\n    <string name=\"kpm_autoload_json_mode\">Режим JSON</string>\n    <string name=\"kpm_autoload_add_kpm\">Добавить KPM</string>\n    <string name=\"kpm_autoload_remove_kpm\">Удалить</string>\n    <string name=\"kpm_autoload_edit_kpm\">Editar</string>\n    <string name=\"kpm_autoload_edit_dialog_title\">Editar KPM</string>    <string name=\"kpm_autoload_event_label\">Evento:</string>    <string name=\"kpm_autoload_args_label\">Argumentos:</string>    <string name=\"kpm_autoload_event_service\">service</string>    <string name=\"kpm_autoload_event_post_fs_data\">post-fs-data</string>\n    <string name=\"kpm_autoload_kpm_list_title\">Модули KPM</string>\n    <string name=\"kpm_autoload_no_kpm_added\">Нет добавленных модулей KPM</string>\n    <string name=\"kpm_autoload_kpm_path\">Путь к KPM</string>\n    <string name=\"kpm_autoload_file_not_found\">Файл не найден</string>\n    <string name=\"kpm_autoload_select_kpm_file\">Выбрать файл KPM</string>\n    <string name=\"kpm_autoload_first_time_title\">О автозагрузке KPM</string>\n    <string name=\"kpm_autoload_first_time_message\">Эта функция позволяет автоматически загружать все настроенные KPM временно при загрузке. Этот подход более удобен, чем встраивание напрямую в ядро.\n\nНапример, KPM, которые предотвращают модификацию разделов, могут использоваться только временно, так как встраивание может вызвать отказы загрузки. Или если вы не хотите изменять раздел BOOT, вы можете использовать эту конфигурацию для быстрой загрузки модулей.\n\nПожалуйста, убедитесь, что приложение полностью закрыто и перезапущено для выполнения команд. Обычно это также загружается при загрузке. Нормально, если менеджер имеет чёрный экран в течение некоторого времени! Это использует чистый фоновый метод загрузки, не забудьте вручную потянуть вниз для обновления и проверки, правильно ли загружены модули!</string>\n    <string name=\"kpm_autoload_first_time_confirm\">Понятно</string>\n    <string name=\"kpm_autoload_do_not_show_again\">Не показывать снова</string>\n\n    <!-- APM Bulk Install Strings -->\n    <string name=\"apm_bulk_install_title\">Массовая установка системных модулей</string>\n    <string name=\"apm_bulk_install_list_title\">Список модулей</string>\n    <string name=\"apm_bulk_install_add\">Добавить модули</string>\n    <string name=\"apm_bulk_install_empty\">Нет добавленных модулей</string>\n    <string name=\"apm_bulk_install_action\">Массовая установка</string>\n    <string name=\"apm_bulk_install_log_title\">Журнал массовой установки</string>\n    <string name=\"apm_bulk_install_log_start\">Начало массовой установки...</string>\n    <string name=\"apm_bulk_install_log_installing\">Установка %1$s</string>\n    <string name=\"apm_batch_install_full_process\">Полный процесс пакетной установки</string>\n    <string name=\"apm_batch_install_full_process_summary\">Пакетная установка модулей системы выполняется с использованием полного процесса</string>\n    <string name=\"next_module\">Следующий модуль</string>\n    <string name=\"apm_bulk_install_log_installed\">Модуль %s установлен.</string>\n    <string name=\"apm_bulk_install_log_done\">Все операции завершены.</string>\n    <string name=\"apm_bulk_install_first_use_text\">Эта функция позволяет устанавливать несколько модулей одновременно. Это быстрый метод установки, подходящий для модулей, которые не требуют операций с кнопками во время установки. Для модулей, требующих взаимодействия с кнопками громкости, пожалуйста, включите режим полной установки в настройках.</string>\n    <string name=\"apm_bulk_install_remove\">Удалить</string>\n    <string name=\"apm_first_use_title\">Добро пожаловать в системные модули</string>\n    <string name=\"apm_first_use_text\">Добро пожаловать в системные модули, здесь используются модули, совместимые с экосистемой Magisk. Нажмите кнопку в правом нижнем углу, чтобы установить модули. Вы также можете использовать установщик вверху для массовой установки модулей. Также предоставляется функция резервного копирования всех модулей одним нажатием, но обратите внимание, что это решение может не подходить для всех модулей, поэтому делайте свои резервные копии.</string>\n\n    <!-- Hide Settings Strings -->\n    <string name=\"home_hide_su_path\">Скрыть путь исполняемого файла su</string>\n    <string name=\"home_hide_kpatch_version\">Скрыть версию патча ядра</string>\n    <string name=\"home_hide_fingerprint\">Скрыть отпечаток</string>\n    <string name=\"home_hide_zygisk\">Скрыть реализацию Zygisk</string>\n    <string name=\"home_hide_mount\">Скрыть реализацию монтирования</string>\n    <string name=\"home_hide_su_path_summary\">Скрыть путь исполняемого файла su в информационной карточке</string>\n    <string name=\"home_hide_kpatch_version_summary\">Скрыть версию патча ядра в информационной карточке</string>\n    <string name=\"home_hide_zygisk_summary\">Скрыть реализацию Zygisk в информационной карточке</string>\n    <string name=\"home_hide_mount_summary\">Скрыть реализацию монтирования в информационной карточке</string>\n    <string name=\"home_hide_fingerprint_summary\">Скрыть отпечаток в информационной карточке</string>\n\n    <string name=\"kpm_page_first_time_title\">Предупреждение</string>\n    <string name=\"kpm_page_first_time_message\">Модули ядра напрямую изменяют реализацию BOOT. В отличие от системных модулей, они не имеют хорошего механизма восстановления. Если возникнут проблемы, вы можете исправить их только войдя в Fastboot. Рекомендуется сначала загрузить модуль, чтобы убедиться, что нет проблем, перед встраиванием его в BOOT. Если модуль можно использовать только загрузкой, вы можете попробовать функцию автоматической загрузки модулей KPM. Если вы не понимаете модули ядра, пожалуйста, не используйте эту функцию!</string>\n    <!-- Module Backup/Restore Strings -->\n    <string name=\"apm_backup_title\">Резервное копирование модулей</string>\n    <string name=\"apm_restore_title\">Восстановление модулей</string>\n    <string name=\"apm_backup_success\">Резервное копирование выполнено</string>\n    <string name=\"apm_restore_success\">Восстановление выполнено</string>\n    <string name=\"apm_backup_failed\">Ошибка резервного копирования</string>\n    <string name=\"apm_restore_failed\">Ошибка восстановления</string>\n    <string name=\"apm_backup_failed_msg\">Ошибка резервного копирования: %s</string>\n    <string name=\"apm_restore_failed_msg\">Ошибка восстановления: %s</string>\n    <string name=\"apm_copy_list_title\">Копировать список</string>\n    <string name=\"apm_copy_list_success\">Список модулей скопирован</string>\n\n    <!-- Font Settings -->\n    <string name=\"settings_custom_font\">Пользовательский шрифт</string>\n    <string name=\"settings_select_font_file\">Выбрать файл шрифта</string>\n    <string name=\"settings_custom_font_enabled\">Пользовательский шрифт включен</string>\n    <string name=\"settings_custom_font_summary\">Использовать пользовательский шрифт TTF</string>\n    <string name=\"settings_font_selected\">Пользовательский шрифт выбран</string>\n    <string name=\"settings_custom_font_error\">Не удалось сохранить файл шрифта</string>\n    <string name=\"settings_custom_font_saved\">Пользовательский шрифт успешно сохранен</string>\n    <string name=\"settings_clear_font\">Восстановить шрифт по умолчанию</string>\n    <string name=\"settings_clear_font_confirm\">Вы уверены, что хотите восстановить шрифт по умолчанию?</string>\n    <string name=\"settings_font_cleared\">Шрифт по умолчанию восстановлен</string>\n    <string name=\"settings_font_select_hint\">Выберите файл шрифта TTF</string>\n\n    <!-- Auto Backup Strings -->\n    <string name=\"settings_auto_backup_module\">Автоматическое резервное копирование модулей</string>\n    <string name=\"settings_auto_backup_module_summary\">Автоматически сохранять копию модуля в личный каталог при установке</string>\n    <string name=\"settings_open_backup_dir\">Открыть каталог резервных копий</string>\n    <string name=\"backup_dir_empty\">Каталог резервных копий пуст</string>\n    <string name=\"backup_dir_open_failed\">Не удалось открыть каталог резервных копий</string>\n    <string name=\"auto_backup_failed\">Ошибка резервного копирования: %s</string>\n    <string name=\"auto_backup_success\">Резервная копия создана: %s</string>\n    <string name=\"settings_auto_backup_boot\">Автоматическое резервное копирование Boot</string>\n    <string name=\"settings_auto_backup_boot_summary\">Автоматически копировать Boot в локальное хранилище (Downloads/FolkPatch/BootBackups)</string>\n\n    <!-- Auto-added missing strings -->\n\n    <!-- Home Layout Strings -->\n    <string name=\"settings_home_layout_style\">Стиль главной страницы</string>\n    <string name=\"settings_home_layout_default\">ListUI</string>\n    <string name=\"settings_home_layout_grid\">GridUI</string>\n    <string name=\"settings_home_layout_focus\">FocusUI</string>\n    <string name=\"settings_home_layout_sign\">SignUI</string>\n    <string name=\"settings_home_layout_circle\">CircleUI</string>\n    <string name=\"settings_home_layout_dashboard_pro\">DashboardUI</string>\n    <string name=\"unofficial_version_title\">Неофициальная версия</string>\n    <string name=\"unofficial_version_message\">Вы используете FolkPatch, который не является официальным приложением, пожалуйста, скачайте официальное приложение!</string>\n    <string name=\"go_to_github\">Перейти на Github</string>\n    <string name=\"settings_save_theme\">Сохранить тему</string>\n    <string name=\"settings_import_theme\">Импортировать тему</string>\n    <string name=\"settings_theme_saved\">Тема сохранена</string>\n    <string name=\"settings_theme_imported\">Тема импортирована</string>\n    <string name=\"settings_theme_save_failed\">Не удалось сохранить тему</string>\n    <string name=\"settings_theme_import_failed\">Не удалось импортировать тему</string>\n    <string name=\"settings_reset_theme\">Сбросить тему</string>\n    <string name=\"settings_reset_theme_confirm\">Вы уверены, что хотите сбросить все настройки темы до значений по умолчанию? Это очистит все пользовательские фоны, шрифты, музыку и звуковые эффекты.</string>\n    <string name=\"settings_theme_reset\">Тема сброшена</string>\n    <string name=\"settings_theme_reset_failed\">Не удалось сбросить тему</string>\n\n    <!-- Theme Strings -->\n    <string name=\"theme_export_title\">Экспорт темы</string>\n    <string name=\"theme_import_title\">Импорт темы</string>\n    <string name=\"theme_name\">Название темы</string>\n    <string name=\"theme_type\">Тип темы</string>\n    <string name=\"theme_type_phone\">Телефон</string>\n    <string name=\"theme_type_tablet\">Планшет</string>\n    <string name=\"theme_version\">Версия</string>\n    <string name=\"theme_author\">Автор</string>\n    <string name=\"theme_description\">Описание</string>\n    <string name=\"theme_export_action\">Экспорт</string>\n    <string name=\"theme_import_action\">Импорт</string>\n    <string name=\"theme_import_confirm\">Вы уверены, что хотите импортировать эту тему?</string>\n    <string name=\"theme_info\">Информация о теме</string>\n    <!-- Grid Working Card Background -->\n    <string name=\"settings_grid_working_card_background\">Фон рабочей карточки</string>\n    <string name=\"settings_grid_working_card_background_enabled\">Пользовательский фон карточки включен</string>\n    <string name=\"settings_grid_working_card_background_summary\">Пользовательское фоновое изображение для рабочей карточки</string>\n    <string name=\"settings_grid_working_card_background_selected\">Фон выбран</string>\n    <string name=\"settings_clear_grid_working_card_background\">Очистить фон карточки</string>\n    <string name=\"settings_clear_grid_working_card_background_confirm\">Вы уверены, что хотите очистить фоновое изображение карточки?</string>\n    <string name=\"settings_grid_working_card_background_saved\">Фон карточки успешно сохранен</string>\n    <string name=\"settings_grid_working_card_background_error\">Не удалось сохранить фон карточки</string>\n    <string name=\"settings_grid_working_card_background_cleared\">Фон карточки очищен</string>\n\n    <!-- Biometric Strings -->\n    <string name=\"action_biometric\">Биометрическая аутентификация</string>\n    <string name=\"msg_biometric\">Пожалуйста, подтвердите свою биометрическую личность</string>\n    <string name=\"settings_biometric_login\">Биометрическая аутентификация</string>\n    <string name=\"settings_biometric_login_summary\">Требовать биометрическую аутентификацию при открытии приложения</string>\n    <string name=\"settings_strong_biometric\">Сильная биометрическая аутентификация</string>\n    <string name=\"settings_strong_biometric_summary\">Требовать аутентификацию при установке/удалении/отключении модулей</string>\n\n    <!-- Theme Store Strings -->\n    <string name=\"theme_store_title\">Магазин тем</string>\n    <string name=\"theme_source_official\">Официальные</string>\n    <string name=\"theme_source_third_party\">Третьи лица</string>\n    <string name=\"theme_source_local\">Локальный</string>\n    <string name=\"theme_store_author\">Автор: %s</string>\n    <string name=\"theme_store_version\">Версия: %s</string>\n    <string name=\"theme_source\">Источник</string>\n    <string name=\"theme_store_download_failed\">Загрузка не удалась</string>\n    <string name=\"theme_store_download\">Загрузить</string>\n    <string name=\"theme_store_search_hint\">Поиск тем...</string>\n\n    <!-- Update Check Strings -->\n    <string name=\"settings_auto_update_check\">Автоматическая проверка обновлений</string>\n    <string name=\"settings_auto_update_check_summary\">Автоматически проверять обновления при запуске приложения</string>\n    <string name=\"settings_check_update\">Проверить обновления</string>\n    <string name=\"update_available_title\">Доступно обновление</string>\n    <string name=\"update_available_message\">Обнаружено, что ваша версия устарела. Хотите скачать новую версию?</string>\n    <string name=\"update_action\">Обновить</string>\n    <string name=\"update_close\">Закрыть</string>\n    <string name=\"update_latest\">У вас последняя версия</string>\n    <string name=\"update_error\">Ошибка при проверке обновлений</string>\n    <!--Варианты сворачивания-->\n    <string name=\"settings_category_general\">Общие</string>\n    <string name=\"superuser\">Суперпользователь</string>\n    <string name=\"module\">Модули</string>\n    <string name=\"settings_category_appearance\">Внешний вид</string>\n    <string name=\"settings_appearance_font\">Настройки шрифта</string>\n    <string name=\"settings_appearance_font_summary\">Настройка пользовательского шрифта</string>\n    <string name=\"settings_appearance_theme\">Настройки темы</string>\n    <string name=\"settings_appearance_theme_summary\">Магазин тем, сохранение, импорт и сброс</string>\n    <string name=\"settings_appearance_banner\">Настройки баннера</string>\n    <string name=\"settings_appearance_banner_summary\">Настройка баннера модуля</string>\n    <string name=\"settings_appearance_layout\">Настройки макета</string>\n    <string name=\"settings_appearance_layout_summary\">Макет главной страницы, навигация и настройка карточек</string>\n    <string name=\"settings_appearance_background\">Настройки фона</string>\n    <string name=\"settings_appearance_background_summary\">Пользовательский фон, видео и многофоновый режим</string>\n    <string name=\"settings_appearance_night_mode\">Настройки ночного режима</string>\n    <string name=\"settings_appearance_night_mode_summary\">Темная тема и настройка цветов</string>\n    <string name=\"settings_amoled_theme\">Чёрная тема AMOLED</string>\n    <string name=\"settings_amoled_theme_desc\">Чисто чёрный фон для тёмного режима</string>\n    <string name=\"settings_switch_icon\">Индикатор кнопки</string>\n    <string name=\"settings_switch_icon_desc\">Показать значки состояния на переключателях</string>\n    <string name=\"settings_category_behavior\">Поведение</string>\n    <string name=\"settings_category_function\">Функция</string>\n    <string name=\"settings_use_legacy_su_page\">Авторизация суперпользователя на одной странице</string>\n    <string name=\"settings_use_legacy_su_page_summary\">Страница суперпользователя переключена на одностраничный дизайн авторизации</string>\n    <string name=\"settings_category_module\">Модули</string>\n    <string name=\"settings_category_security\">Безопасность</string>\n    \n    <!-- Background Music Strings -->\n    <string name=\"settings_background_music\">Фоновая музыка</string>\n    <string name=\"settings_background_music_summary\">Воспроизводить фоновую музыку, когда приложение на переднем плане</string>\n    <string name=\"settings_background_music_playing\">Воспроизведение: %s</string>\n    <string name=\"settings_background_music_enabled\">Фоновая музыка включена</string>\n    <string name=\"settings_select_music_file\">Выбрать музыкальный файл</string>\n    <string name=\"settings_music_selected\">Музыка выбрана</string>\n    <string name=\"settings_clear_music\">Очистить музыку</string>\n    <string name=\"settings_clear_music_confirm\">Вы уверены, что хотите очистить фоновую музыку?</string>\n    <string name=\"settings_music_auto_play\">Автовоспроизведение</string>\n    <string name=\"settings_music_auto_play_summary\">Автоматически воспроизводить музыку при открытии приложения</string>\n    <string name=\"settings_music_volume\">Громкость</string>\n    <string name=\"settings_music_saved\">Музыкальный файл сохранён</string>\n    <string name=\"settings_music_save_error\">Не удалось сохранить музыкальный файл</string>\n    <string name=\"settings_music_cleared\">Музыка очищена</string>\n    <string name=\"settings_category_multimedia\">Мультимедиа</string>\n    <string name=\"settings_category_general_summary\">Язык, обновления, SELinux, системные настройки</string>\n    <string name=\"settings_category_appearance_summary\">Тема, цвета, макет, фон, шрифты</string>\n    <string name=\"settings_category_behavior_summary\">Отладка Web, поведение установки, отображение на главном</string>\n    <string name=\"settings_category_security_summary\">Биометрия, управление суперключами</string>\n    <string name=\"settings_category_backup_summary\">Локальное резервное копирование, облачное, WebDAV</string>\n    <string name=\"settings_category_module_summary\">Информация о модулях, сортировка, пакетная установка</string>\n    <string name=\"settings_category_function_summary\">Скрытие FolkPatch, служба Umount</string>\n    <string name=\"settings_category_multimedia_summary\">Фоновая музыка, звуки, вибрация</string>\n    <string name=\"settings_music_playback_control\">Управление воспроизведением</string>\n    <string name=\"settings_music_looping\">Зациклить</string>\n    <string name=\"settings_music_looping_summary\">Повторять текущую композицию</string>\n    <!-- Theme Store Filter -->\n    <string name=\"theme_store_filter_title\">Фильтровать Темы</string>\n    <string name=\"theme_store_filter_author\">Автор</string>\n    <string name=\"theme_store_filter_author_hint\">Введите имя автора</string>\n    <string name=\"theme_store_filter_source\">Источник</string>\n    <string name=\"theme_store_filter_source_all\">Все</string>\n    <string name=\"theme_store_filter_type\">Тип Устройства</string>\n    <string name=\"theme_store_filter_apply\">Применить</string>\n    <string name=\"theme_store_filter_reset\">Сброс</string>\n\n    <!-- Video Background Settings -->\n    <string name=\"settings_video_background\">Видеофон</string>\n    <string name=\"settings_video_background_summary\">Использовать видео в качестве фона</string>\n    <string name=\"settings_select_video\">Выбрать Видео</string>\n    <string name=\"settings_video_selected\">Видео Выбрано</string>\n    <string name=\"settings_video_background_enabled\">Видеофон Включен</string>\n    <string name=\"settings_clear_video_background\">Очистить видеообои</string>\n    <string name=\"settings_clear_video_background_confirm\">Вы уверены, что хотите очистить видеообои?</string>\n    <string name=\"settings_video_volume\">Громкость Видео</string>\n    <!-- File Picker -->\n    <string name=\"file_picker_permission_required\">Требуется разрешение</string>\n    <string name=\"file_picker_permission_desc\">Для просмотра файлов предоставьте разрешение \\'Доступ ко всем файлам\\'.</string>\n    <string name=\"file_picker_grant_permission\">Предоставить разрешение</string>\n    <string name=\"file_picker_cancel\">Отмена</string>\n    <string name=\"file_picker_internal_storage\">Внутреннее хранилище</string>\n    <string name=\"file_picker_no_files\">Нет файлов</string>\n    <!-- HomeV3 Strings -->\n    <string name=\"home_device_status_title\">Состояние устройства</string>\n    <string name=\"home_device_status_battery_temp\">Температура батареи</string>\n    <string name=\"home_device_status_cpu_load\">Нагрузка ЦПУ</string>\n    <string name=\"home_device_status_battery_level\">Уровень батареи</string>\n    <string name=\"home_storage_title\">Хранилище</string>\n    <string name=\"home_storage_internal\">Внутреннее хранилище</string>\n    <string name=\"home_storage_ram\">ОЗУ</string>\n    <string name=\"home_storage_zram\">ZRAM</string>\n    <string name=\"home_storage_swap\">Файл подкачки</string>\n    <string name=\"home_info_kernel\">Ядро</string>\n    <string name=\"home_info_superkey\">SuperKey</string>\n    <string name=\"home_info_auth_auth\">Аут</string>\n    <string name=\"home_info_auth_na\">Н/Д</string>\n    <string name=\"home_info_device_slot\">Слот устройства</string>\n    <string name=\"home_info_device_model\">Модель устройства</string>\n    <string name=\"home_info_running_mode\">Режим работы</string>\n    <string name=\"home_info_mode_full\">Полный</string>\n    <string name=\"home_info_mode_half\">Частичный</string>\n    <string name=\"home_version\">Версия: %s</string>\n    <string name=\"home_zygisk_implement\">Реализация Zygisk</string>\n    <string name=\"home_mount_implement\">Реализация монтирования</string>\n\n    <string name=\"settings_app_list_loading_scheme\">Схема загрузки списка приложений</string>\n    <string name=\"settings_app_list_loading_scheme_summary\">Выберите способ загрузки списка приложений</string>\n    <string name=\"app_list_loading_scheme_root_service\">RootService (По умолчанию)</string>\n    <string name=\"app_list_loading_scheme_package_manager\">PackageManager (Системное API)</string>\n    <string name=\"app_list_loading_scheme_dialog_title\">Схема загрузки</string>\n    <string name=\"su_backup_list\">Список резервных копий</string>\n    <string name=\"su_restore_list\">Список восстановлений</string>\n    <string name=\"backup_success\">Резервное копирование успешно</string>\n    <!-- Badge Count Settings -->\n    <string name=\"enable_badge_count\">Настройки счётчика значков</string>\n    <string name=\"enable_badge_count_summary\">Настройка отображения счётчика значков для элементов навигации</string>\n    <string name=\"badge_superuser\">Показывать значок суперпользователя</string>\n    <string name=\"badge_apm\">Показывать значок системного модуля</string>\n    <string name=\"badge_kernel\">Показывать значок модуля ядра</string>\n    <string name=\"settings_sound_effect\">Звуковой эффект</string>\n    <string name=\"settings_sound_effect_summary\">Воспроизводить звук при нажатии</string>\n    <string name=\"settings_sound_effect_enabled\">Включено</string>\n    <string name=\"settings_sound_effect_playing\">Выбрано: %s</string>\n    <string name=\"settings_sound_effect_source\">Источник звука</string>\n    <string name=\"settings_sound_effect_source_local\">Локальный файл</string>\n    <string name=\"settings_sound_effect_source_preset\">Предустановка</string>\n    <string name=\"settings_sound_effect_preset_title\">Предустановленные звуки</string>\n    <string name=\"settings_select_sound_effect\">Выбрать звуковой файл</string>\n    <string name=\"settings_sound_effect_selected\">Звуковой файл выбран</string>\n    <string name=\"settings_sound_effect_scope\">Область действия</string>\n    <string name=\"settings_sound_effect_scope_global\">Везде</string>\n    <string name=\"settings_sound_effect_scope_bottom_bar\">Только нижняя панель</string>\n    <string name=\"settings_clear_sound_effect\">Очистить звуковой эффект</string>\n    <string name=\"settings_clear_sound_effect_confirm\">Вы уверены, что хотите удалить звуковой эффект?</string>\n    <string name=\"settings_sound_effect_cleared\">Звуковой эффект удален</string>\n\n    <string name=\"settings_startup_sound\">Звук запуска</string>\n    <string name=\"settings_startup_sound_summary\">Воспроизводить звук при запуске приложения</string>\n    <string name=\"settings_startup_sound_enabled\">Звук запуска включен</string>\n    <string name=\"settings_startup_sound_playing\">Воспроизведение: %s</string>\n    <string name=\"settings_select_startup_sound\">Выбрать звук запуска</string>\n    <string name=\"settings_startup_sound_selected\">Звук запуска выбран</string>\n    <string name=\"settings_clear_startup_sound\">Очистить звук запуска</string>\n    <string name=\"settings_clear_startup_sound_confirm\">Вы уверены, что хотите удалить звук запуска?</string>\n    <string name=\"settings_startup_sound_cleared\">Звук запуска удален</string>\n\n    <string name=\"settings_enable_cloud_backup_summary\">Автоматическое резервное копирование в облако</string>\n    <string name=\"settings_configure_webdav\">Настроить службу WebDAV</string>\n    <string name=\"webdav_config_title\">Конфигурация WebDAV</string>\n    <string name=\"webdav_url\">URL WebDAV</string>\n    <string name=\"webdav_username\">Имя пользователя</string>\n    <string name=\"webdav_password\">Пароль</string>\n    <string name=\"webdav_uploading\">Загрузка в WebDAV...</string>\n    <string name=\"webdav_backup_success\">Резервная копия WebDAV успешно создана</string>\n    <string name=\"webdav_backup_failed\">Ошибка резервного копирования WebDAV: %s</string>\n    <string name=\"save\">Сохранить</string>\n    <string name=\"test\">Тест</string>\n    <string name=\"webdav_path_label\">Путь (напр. /Backup)</string>\n    <string name=\"webdav_view_logs\">Просмотр логов</string>\n    <string name=\"webdav_backup_logs_title\">Логи резервного копирования</string>\n    <string name=\"webdav_no_logs\">Нет логов</string>\n    <string name=\"webdav_clear_logs\">Очистить</string>\n    <string name=\"settings_enable_local_backup\">Включить локальное резервное копирование</string>\n    <string name=\"settings_enable_local_backup_summary\">Резервное копирование модулей в локальное хранилище (Downloads/FolkPatch/ModuleBackups)</string>\n    <string name=\"close\">Закрыть</string>\n\n    <!-- Backup Settings -->\n    <string name=\"settings_category_backup\">Настройки резервного копирования</string>\n    <string name=\"settings_enable_cloud_backup\">Включить облачное резервное копирование</string>\n    <string name=\"settings_webdav_url\">URL WebDAV</string>\n    <string name=\"settings_webdav_username\">Имя пользователя WebDAV</string>\n    <string name=\"settings_webdav_password\">Пароль WebDAV</string>\n    <string name=\"settings_test_webdav\">Проверить соединение WebDAV</string>\n    <string name=\"webdav_test_success\">Тест успешен</string>\n    <string name=\"webdav_test_failed\">Тест не удался: %s</string>\n    <string name=\"settings_backup_now\">Создать резервную копию сейчас</string>\n    <string name=\"backup_failed\">Ошибка резервного копирования: %s</string>\n    <string name=\"settings_restore_backup\">Восстановить из резервной копии</string>\n    <string name=\"restore_success\">Восстановление успешно</string>\n    <string name=\"restore_failed\">Ошибка восстановления: %s</string>\n    <string name=\"settings_auto_backup\">Автоматическое резервное копирование</string>\n    <string name=\"settings_auto_backup_summary\">Автоматическое резервное копирование раз в день</string>\n    <string name=\"settings_delete_remote_backup\">Удалить облачную резервную копию</string>\n    <string name=\"delete_remote_backup_confirm\">Вы уверены, что хотите удалить облачную резервную копию?</string>\n    <string name=\"delete_success\">Удаление успешно</string>\n    <string name=\"delete_failed\">Ошибка удаления: %s</string>\n    <string name=\"settings_encrypt_backup\">Шифровать резервную копию</string>\n    <string name=\"settings_encrypt_backup_summary\">Шифровать файл резервной копии паролем</string>\n    <string name=\"settings_backup_password\">Пароль резервной копии</string>\n    <string name=\"settings_backup_password_hint\">Введите пароль резервной копии</string>\n\n    <string name=\"patch_output_written_to\"> Патч применен успешно, файл записан в </string>\n    <string name=\"patch_write_failed\"> Ошибка записи пропатченного boot.img</string>\n\n    <!-- Script Library Strings -->\n    <string name=\"script_library\">Библиотека скриптов</string>\n    <string name=\"script_library_title\">Библиотека скриптов</string>\n    <string name=\"script_library_empty\">Нет скриптов, нажмите кнопку добавления</string>\n    <string name=\"script_library_add\">Добавить скрипт</string>\n    <string name=\"script_library_add_title\">Добавить скрипт</string>\n    <string name=\"script_library_select_file\">Выбрать файл скрипта</string>\n    <string name=\"script_library_alias\">Псевдоним</string>\n    <string name=\"script_library_alias_hint\">Введите псевдоним скрипта (опционально)</string>\n    <string name=\"script_library_run\">Запустить</string>\n    <string name=\"script_library_delete\">Удалить</string>\n    <string name=\"script_library_path\">Путь: %s</string>\n    <string name=\"script_library_confirm_delete\">Подтвердить удаление скрипта?</string>\n    <string name=\"script_library_delete_success\">Скрипт удален успешно</string>\n    <string name=\"script_library_delete_failed\">Ошибка удаления скрипта</string>\n    <string name=\"script_library_add_success\">Скрипт добавлен успешно</string>\n    <string name=\"script_library_add_failed\">Ошибка добавления скрипта: %s</string>\n    <string name=\"script_library_load_failed\">Ошибка загрузки библиотеки скриптов</string>\n    <string name=\"script_library_output\">Вывод выполнения</string>\n    <string name=\"script_library_no_output\">Нет вывода</string>\n    <string name=\"script_library_save_log\">Сохранить лог</string>\n    <string name=\"script_library_log_saved\">Лог сохранен в %s</string>\n    <string name=\"script_library_log_save_failed\">Ошибка сохранения лога: %s</string>\n    <string name=\"settings_vibration\">Вибрация и тактильная отдача</string>\n    <string name=\"settings_vibration_summary\">Вибрация при касании</string>\n    <string name=\"settings_vibration_enabled\">Включить вибрацию</string>\n    <string name=\"settings_vibration_intensity\">Интенсивность вибрации</string>\n    <string name=\"settings_vibration_scope\">Область вибрации</string>\n    <string name=\"settings_vibration_scope_global\">Везде (Глобально)</string>\n    <string name=\"settings_vibration_scope_bottom_bar\">Только нижняя панель</string>\n    <string name=\"search_modules\">Поиск модулей...</string>\n    <string name=\"search_scripts\">Поиск скриптов...</string>\n\n    <!-- Umount Service -->\n    <string name=\"settings_umount_service\">Umount Service</string>\n    <string name=\"settings_umount_service_summary\">Настройка точек монтирования для автоматического отключения при загрузке</string>\n    <string name=\"umount_config_title\">Настройка Umount</string>\n    <string name=\"umount_config_enabled\">Включить Umount</string>\n    <string name=\"umount_config_enabled_summary\">Автоматическое отключение указанных точек монтирования при загрузке</string>\n    <string name=\"umount_config_paths_label\">Пути точек монтирования (по одному на строку)</string>\n    <string name=\"umount_config_paths_placeholder\">/system/vendor/app\\n/system/vendor/priv-app</string>\n    <string name=\"umount_config_paths_helper\">Введите один путь точки монтирования для отключения на строку</string>\n    <string name=\"umount_config_save\">Сохранить</string>\n    <string name=\"umount_config_save_confirm\">Подтвердить сохранение конфигурации?</string>\n    <string name=\"umount_config_save_success\">Конфигурация успешно сохранена</string>\n    <string name=\"umount_config_save_failed\">Не удалось сохранить конфигурацию</string>\n\n    <!-- Страница Мои Темы -->\n    <string name=\"my_themes_title\">Мои темы</string>\n    <string name=\"my_themes_empty\">Тем пока нет</string>\n    <string name=\"my_themes_empty_action\">Посетить магазин тем</string>\n    <string name=\"my_themes_apply\">Применить тему</string>\n    <string name=\"my_themes_delete\">Удалить тему</string>\n    <string name=\"my_themes_delete_confirm\">Вы уверены, что хотите удалить эту тему?</string>\n    <string name=\"my_themes_deleted\">Тема удалена</string>\n    <string name=\"my_themes_applied\">Тема применена</string>\n    <string name=\"my_themes_apply_failed\">Не удалось применить тему</string>\n    <string name=\"my_themes_details\">Детали темы</string>\n\n    <!-- Диалог Загрузки -->\n    <string name=\"theme_download_title\">Загрузка темы</string>\n    <string name=\"theme_download_completed\">Загрузка завершена</string>\n    <string name=\"theme_download_failed\">Ошибка загрузки</string>\n    <string name=\"theme_download_progress\">Прогресс</string>\n    <string name=\"theme_download_file\">Файл темы</string>\n    <string name=\"theme_download_image\">Изображение предпросмотра</string>\n    <string name=\"theme_download_cancel\">Отмена</string>\n    <string name=\"theme_download_pause\">Пауза</string>\n    <string name=\"theme_download_resume\">Продолжить</string>\n    <string name=\"theme_download_retry\">Повторить</string>\n    <string name=\"theme_download_apply\">Применить тему</string>\n    <string name=\"theme_download_go_to_my_themes\">Мои темы</string>\n    <string name=\"theme_download_retrying\">Повтор (%d/3)...</string>\n    <string name=\"theme_download_preparing\">Подготовка загрузки...</string>\n    <string name=\"theme_download_downloading_file\">Загрузка файла темы...</string>\n    <string name=\"theme_download_downloading_image\">Загрузка изображения предпросмотра...</string>\n    <string name=\"theme_download_finalizing\">Завершение...</string>\n    <string name=\"settings_predictive_back\">Предиктивный жест назад</string>\n    <string name=\"settings_predictive_back_summary\">Включить анимацию предиктивного жеста назад (Android 14+)</string>\n\n    <string name=\"home_device_status_battery_charging\">Зарядка</string>\n    <string name=\"home_device_status_cpu_temp\">Температура ЦПУ</string>\n    <string name=\"home_device_status_memory_trend\">Тренд памяти</string>\n    <string name=\"home_storage_partitions\">Разделы хранилища</string>\n    <string name=\"home_device_status_cpu_freq\">Частота ЦПУ</string>\n    <string name=\"home_network_rx\">Загрузка</string>\n    <string name=\"home_network_tx\">Отдача</string>\n    <string name=\"settings_home_layout_stats\">StatsUI</string>\n    <string name=\"home_stats_more_options\">Ещё</string>\n    <string name=\"home_stats_kp_label\">KP</string>\n    <string name=\"home_stats_manager_label\">Менеджер</string>\n    <string name=\"home_device_status_gpu_load\">GPU</string>\n    <string name=\"home_stats_kernel_modules\">Модули ядра</string>\n    <string name=\"home_stats_apm_modules\">Системные модули</string>\n    <string name=\"home_stats_superusers\">Суперпользователи</string>\n    <string name=\"settings_kernel_spoof\">Настройка подмены ядра</string>\n    <string name=\"settings_kernel_spoof_summary\">Подмена версии ядра и времени сборки</string>\n    <string name=\"settings_kernel_spoof_version\">Версия ядра</string>\n    <string name=\"settings_kernel_spoof_build_time\">Время сборки ядра</string>\n    <string name=\"settings_kernel_spoof_restore\">Восстановить</string>\n\n    <string name=\"kernel_spoof_enabled\">Подмена ядра включена</string>\n    <string name=\"kernel_spoof_disabled_restored\">Подмена ядра отключена и восстановлена</string>\n    <string name=\"kernel_spoof_failed\">Ошибка подмены ядра: %d</string>\n    <string name=\"kernel_spoof_applied\">Подмена ядра применена</string>\n\n    <string name=\"settings_path_hide\">Скрытие путей</string>\n    <string name=\"settings_path_hide_summary\">Скрывать файлы и каталоги от приложений на уровне ядра</string>\n    <string name=\"path_hide_paths_label\">Скрытые пути (по одному на строку)</string>\n\n    <string name=\"path_hide_paths_helper\">Введите по одному пути на строку. Совпадающие пути будут возвращать ENOENT.</string>\n    <string name=\"path_hide_save\">Сохранить</string>\n    <string name=\"path_hide_enabled\">Скрытие путей включено</string>\n    <string name=\"path_hide_disabled\">Скрытие путей отключено</string>\n    <string name=\"path_hide_applied\">Конфигурация скрытия путей применена</string>\n    <string name=\"path_hide_failed\">Ошибка операции скрытия путей: %d</string>\n    <string name=\"path_hide_uid_mode\">Режим выполнения по UID</string>\n    <string name=\"path_hide_uid_mode_summary\">Скрывать пути только для указанных UID приложений</string>\n    <string name=\"path_hide_uids_label\">Целевые UID (по одному на строку)</string>\n    <string name=\"path_hide_uids_helper\">Введите UID приложений. Пути будут скрыты только для этих приложений.</string>\n    <string name=\"path_hide_uid_save\">Сохранить UID</string>\n    <string name=\"path_hide_uid_mode_enabled\">Режим выполнения по UID включён</string>\n    <string name=\"path_hide_uid_mode_disabled\">Режим выполнения по UID отключён</string>\n    <string name=\"path_hide_filter_system\">Фильтровать системные UID</string>\n    <string name=\"path_hide_filter_system_summary\">Также скрывать пути от процессов root и системы (UID &lt; 10000)</string>\n    <string name=\"path_hide_filter_system_enabled\">Фильтрация системных UID включена</string>\n    <string name=\"path_hide_filter_system_disabled\">Фильтрация системных UID отключена</string>\n    <string name=\"path_hide_filter_system_warning_title\">Предупреждение</string>\n    <string name=\"path_hide_filter_system_warning_message\">Включение скроет пути также от процессов root и системы (UID &lt; 10000). Это может привести к нарушению работы некоторых системных функций. Действуйте с осторожностью.</string>\n    <string name=\"path_hide_uid_applied\">Белый список UID применён</string>\n    <string name=\"path_hide_select_apps\">Выбрать приложения</string>\n    <string name=\"path_hide_no_apps_selected\">Приложения не выбраны</string>\n    <string name=\"path_hide_app_removed\">Удалено %d устаревших записей удалённых приложений</string>\n    <string name=\"path_hide_search_apps\">Поиск приложений…</string>\n    <string name=\"path_hide_show_system\">Показать системные приложения</string>\n    <string name=\"netisolate_title\">Сетевая изоляция</string>\n    <string name=\"netisolate_enable\">Сетевая изоляция включена</string>\n    <string name=\"netisolate_disable\">Сетевая изоляция отключена</string>\n    <string name=\"netisolate_enable_summary\">Блокировать сетевой доступ для выбранных приложений на уровне ядра</string>\n    <string name=\"netisolate_uids_label\">Заблокированные приложения</string>\n    <string name=\"netisolate_uids_hint\">Введите UID для блокировки (по одному на строку)</string>\n    <string name=\"netisolate_no_uids\">Нет заблокированных приложений</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-tr-rTR/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\" translatable=\"false\">FolkPatch</string>\n    <string name=\"app_name_fpatch\">FPatch</string>\n\n    <string name=\"desktop_app_name\">Masaüstü Uygulama Adı</string>\n\n    <string name=\"home\">Ana Sayfa</string>\n\n    <string name=\"success\">Başarılı</string>\n    <string name=\"failure\">Başarısız</string>\n\n    <string name=\"patch_warnning\">Kurulum riskler içerir. Lütfen verilerinizin yedeklendiğinden emin olun.</string>\n    <string name=\"patch\">Yama</string>\n\n    <string name=\"kernel_patch\">KernelPatch</string>\n    <string name=\"android_patch\">AndroidPatch</string>\n\n    <string name=\"settings_nav_layout_title\">Gezinme Düzeni Ayarları</string>\n    <string name=\"settings_nav_layout_summary\">Bazı gezinme bileşenlerini gizle veya göster</string>\n    <string name=\"settings_nav_scheme\">Gezinme Şeması</string>\n    <string name=\"settings_nav_mode\">Gezinme Çubuğu Modu</string>\n    <string name=\"settings_nav_mode_summary\">Gezinme çubuğunun nasıl görüntüleneceğini seçin</string>\n    <string name=\"settings_nav_mode_auto\">Otomatik Geleneksel</string>\n    <string name=\"settings_nav_mode_bottom\">Her Zaman Alt Çubuk</string>\n    <string name=\"settings_nav_mode_rail\">Her Zaman Yan Çubuk</string>\n    <string name=\"settings_nav_mode_floating\">Kayan Alt Çubuk</string>\n    <string name=\"settings_show_apm\">Sistem Modüllerini Göster</string>\n    <string name=\"settings_show_kpm\">KPM Göster</string>\n    <string name=\"settings_show_superuser\">SuperUser Göster</string>\n    <string name=\"settings_floating_auto_hide\">Otomatik Gizle</string>\n    <string name=\"settings_floating_auto_hide_summary\">3 saniye işlem yapılmadığında kayan çubuğu otomatik gizle</string>\n    <string name=\"settings_floating_swipe_hide\">Kaydırarak Gizle</string>\n    <string name=\"settings_floating_swipe_hide_summary\">Aşağı kaydırmada gizle, yukarı kaydırmada göster</string>\n    <string name=\"settings_navbar_glass_effect\">Buzlanmış cam efekti</string>\n    <string name=\"settings_navbar_glass_effect_summary\">Kayan gezinme çubuğuna buzlanmış cam efekti uygula</string>\n    <string name=\"settings_navbar_glass_blur_strength\">Bulanıklık yoğunluğu</string>\n    <string name=\"settings_navbar_glass_transparency\">Arka plan saydamlığı</string>\n    <string name=\"settings_navbar_glass_highlight_strength\">Vurgu yoğunluğu</string>\n    <string name=\"settings_navbar_glass_specular\">Ayna yansıması</string>\n    <string name=\"settings_navbar_glass_specular_summary\">Gezinme çubuğunun üst kısmında ayna benzeri vurguyu etkinleştir</string>\n    <string name=\"settings_navbar_glass_inner_glow\">İç parıltı</string>\n    <string name=\"settings_navbar_glass_inner_glow_summary\">Gezinme çubuğunun alt kısmında hafif parıltı efektini etkinleştir</string>\n    <string name=\"settings_navbar_glass_border\">Cam kenarlık</string>\n    <string name=\"settings_navbar_glass_border_summary\">Gezinme çubuğu çevresinde hafif kenarlık çizgisini etkinleştir</string>\n    <string name=\"settings_stats_top_layout\">Üst Yerleşim</string>\n    <string name=\"settings_stats_top_layout_summary\">Üst bilgi kartı stilini seçin</string>\n    <string name=\"settings_stats_top_layout_list\">Liste</string>\n    <string name=\"settings_stats_top_layout_grid\">Izgara</string>\n    <string name=\"settings_block_kernelpatch_update\">KernelPatch Güncellemesini Engelle</string>\n    <string name=\"settings_block_kernelpatch_update_summary\">KernelPatch güncelleme bildirimlerini gösterme</string>\n    <string name=\"settings_block_androidpatch_update\">Sistem Yaması Güncellemesini Engelle</string>\n    <string name=\"settings_block_androidpatch_update_summary\">Sistem yaması (APD) güncelleme bildirimlerini gösterme</string>\n\n    <string name=\"reboot\">Yeniden Başlat</string>\n    <string name=\"settings\">Ayarlar</string>\n    <string name=\"reboot_recovery\">Recovery Moduna Yeniden Başlat</string>\n    <string name=\"reboot_bootloader\">Bootloader’a Yeniden Başlat</string>\n    <string name=\"reboot_download\">Download Moduna Yeniden Başlat</string>\n    <string name=\"reboot_edl\">EDL Moduna Yeniden Başlat</string>\n    <string name=\"reboot_fastbootd\">FastbootD Moduna Yeniden Başlat</string>\n    <string name=\"about\">Hakkında</string>\n    <string name=\"developer_and_maintainer\">Geliştirici | Bakımcı</string>\n    <string name=\"settings_app_language\">Dil</string>\n    <string name=\"system_default\">Sistem varsayılanı</string>\n    <string name=\"settings_global_namespace_mode\">Global ad alanı modu</string>\n    <string name=\"settings_global_namespace_mode_summary\">Tüm root oturumları global mount ad alanını kullanır</string>\n    <string name=\"settings_clear_super_key_dialog\">Gerçekten devam etmek istiyor musunuz?</string>\n    <string name=\"settings_grid_working_card_hide_check\">Durum simgesini gizle</string>\n    <string name=\"settings_grid_working_card_hide_check_summary\">Çalışma kartındaki onay veya uyarı simgesini gizle</string>\n    <string name=\"settings_grid_working_card_hide_text\">Durum metnini gizle</string>\n    <string name=\"settings_grid_working_card_hide_text_summary\">Çalışma kartındaki \\'Çalışıyor\\' veya \\'Yüklü değil\\' metnini gizle</string>\n    <string name=\"settings_grid_working_card_hide_mode\">Çalışma modunu gizle</string>\n    <string name=\"settings_grid_working_card_hide_mode_summary\">Çalışma kartındaki \\'Tam\\' veya \\'Yarım\\' metnini gizle</string>\n\n\n\t<string name=\"settings_folkx_engine_title\">FolkX Animasyon Motoru</string>\n    <string name=\"settings_folkx_engine_summary\">Üst seviye sayfalar arasında geçişte yumuşak, fizik tabanlı yay animasyonu kullan</string>\n    <string name=\"settings_folkx_animation_type\">Animasyon Türü</string>\n    <string name=\"settings_folkx_animation_speed\">Animasyon Hızı</string>\n    <string name=\"settings_folkx_animation_linear\">Doğrusal Hareket</string>\n    <string name=\"settings_folkx_animation_spatial\">Mekânsal Hareket</string>\n    <string name=\"settings_folkx_animation_fade\">Solma (Fade In/Out)</string>\n    <string name=\"settings_folkx_animation_vertical\">Dikey Kaydırma</string>\n    <string name=\"settings_folkx_animation_diagonal\">Çapraz Kaydırma</string>\n\n    <string name=\"su_exclude_all_title\">Tümünü Hariç Tut</string>\n    <string name=\"su_exclude_all_confirm\">Root olmayan tüm uygulamaları hariç tutmak istediğinize emin misiniz?</string>\n    <string name=\"su_batch_exclude_title\">Toplu Hariç Tutma</string>\n    <string name=\"su_batch_exclude_content\">ROOT olmayan tüm uygulamalar için enjeksiyonu hariç tut, lütfen bir işlem seçin</string>\n\t<string name=\"su_exclude_btn\">Hariç Tut</string>\n    <string name=\"su_exclude_reverse_btn\">Tersine Çevir</string>\n\n    <string name=\"home_learn_apatch\">FolkPatch’i Öğren</string>\n    <string name=\"home_click_to_learn_apatch\">FolkPatch özelliklerini ve nasıl kullanılacağını öğrenin</string>\n    <string name=\"settings_hide_apatch_card\">FolkPatch’i Öğren kartını gizle</string>\n    <string name=\"settings_hide_apatch_card_summary\">Ana ekrandaki FolkPatch’i Öğren kartını gizle</string>\n\n    <string name=\"about_source_code\"><![CDATA[<p>Kaynak kodunu görüntüle: %1$s<p/>%2$s kanalımıza katıl<p/>%3$s grubumuza katıl]]></string>\n    <string name=\"send_log\">Günlükleri gönder</string>\n    <string name=\"save_log\">Günlükleri kaydet</string>\n    <string name=\"log_saved\">Günlükler kaydedildi</string>\n    <string name=\"safe_mode\">Güvenli mod</string>\n    <string name=\"github\">GitHub</string>\n    <string name=\"telegram\">Telegram</string>\n    <string name=\"support_or_donate\">Destek / Bağış</string>\n\t\n    <string name=\"super_key\">SuperKey</string>\n    <string name=\"clear_super_key\">SuperKey’i Temizle</string>\n    <string name=\"patch_set_superkey\">SuperKey Ayarla</string>\n    <string name=\"home_patch_set_key_desc\">KernelPatch için tek kimlik bilgisi</string>\n    <string name=\"home_patch_next_step\">Sonraki adım</string>\n\n    <string name=\"home_not_installed\">Yüklü değil</string>\n    <string name=\"home_install_unknown\">Yüklü değil veya doğrulanmamış</string>\n    <string name=\"home_install_unknown_summary\">Yüklemek için tıklayın</string>\n    <string name=\"home_click_to_install\">Yüklemek için tıklayın</string>\n    <string name=\"home_working\">Çalışıyor</string>\n    <string name=\"home_version\">Sürüm: %s</string>\n    <string name=\"home_kp_need_update\">Yeni sürüm mevcut</string>\n    <string name=\"home_kp_cando_update\">Güncelle</string>\n\n    <string name=\"home_installing\">Yükleniyor</string>\n    <string name=\"kpatch_version\">Sürüm: %s</string>\n    <string name=\"apatch_version\">Sürüm: %s</string>\n\n    <string name=\"kpatch_version_update\" formatted=\"false\">Sürüm: %s -&gt; %s</string>\n    <string name=\"kpatch_shadow_path_title\">kpatch</string>\n    <string name=\"home_auth_key_title\">SuperKey Girin</string>\n    <string name=\"home_auth_key_desc\">Doğrulama sonrası başlat</string>\n    <string name=\"home_kpatch_info_title\">Bilgi</string>\n\n    <string name=\"home_ap_cando_install\">Yükle</string>\n\n    <string name=\"home_ap_cando_uninstall\">Kaldır</string>\n    <string name=\"home_ap_cando_reboot\">Yeniden Başlat</string>\n\n    <string name=\"patch_title\">Yama</string>\n\n    <string name=\"patch_config_title\">Yamalar</string>\n    <string name=\"patch_mode_bootimg_patch\">Mod: Yama</string>\n    <string name=\"patch_mode_patch_and_install\">Mod: Yama ve yükle</string>\n    <string name=\"patch_mode_install_to_next_slot\">Mod: Etkin olmayan slota yükle (OTA sonrası)</string>\n    <string name=\"patch_mode_restore\">Mod: Geri Yükle</string>\n    <string name=\"patch_mode_uninstall_patch\">Mod: KPatch’i Kaldır</string>\n    <string name=\"patch_select_bootimg_btn\">Boot seç</string>\n    <string name=\"patch_embed_kpm_btn\">KPM Göm</string>\n    <string name=\"patch_start_patch_btn\">Başlat</string>\n    <string name=\"patch_start_unpatch_btn\">Yamayı Kaldır</string>\n    <string name=\"patch_item_error\">!!HATA!!</string>\n    <string name=\"patch_item_bootimg\">bootimg</string>\n    <string name=\"patch_item_bootimg_slot\">Slot:</string>\n    <string name=\"patch_item_bootimg_dev\">Cihaz:</string>\n    <string name=\"patch_item_kernel\">Kernel</string>\n    <string name=\"patch_item_kpimg\">kpimg</string>\n    <string name=\"patch_item_kpimg_version\">Sürüm:</string>\n    <string name=\"patch_item_kpimg_comile_time\">Zaman:</string>\n    <string name=\"patch_item_kpimg_config\">Yapılandırma:</string>\n    <string name=\"patch_item_new_extra_kpm\">Yeni göm</string>\n    <string name=\"patch_item_existed_extra_kpm\">Mevcut</string>\n    <string name=\"patch_item_extra_name\">Ad:</string>\n    <string name=\"patch_item_extra_version\">Sürüm:</string>\n    <string name=\"patch_item_extra_author\">Yazar:</string>\n    <string name=\"patch_item_extra_kpm_license\">Lisans:</string>\n    <string name=\"patch_item_extra_kpm_desciption\">Açıklama:</string>\n    <string name=\"patch_item_extra_args\">Argümanlar:</string>\n    <string name=\"patch_item_extra_event\">Olay:</string>\n    <string name=\"patch_item_skey\">SuperKey</string>\n    <string name=\"patch_custom_superkey\">Özel SuperKey</string>\n    <string name=\"patch_custom_superkey_summary\">Hâlâ root ve kernel yönetimi için SuperKey kullanabilirsiniz, yönetici çekirdek tarafından yerleşik imza ile yetkilendirilmiştir</string>\n    <string name=\"patch_item_set_skey_label\">SuperKey 8-63 karakter uzunluğunda olmalı; rakam ve harf içermeli, özel karakter içermemelidir.</string>\n    <string name=\"patch_confirm_superkey\">SuperKey Onayla</string>\n    <string name=\"patch_skey_mismatch\">SuperKey eşleşmiyor</string>\n\n    <string name=\"home_kernel\">Kernel sürümü</string>\n    <string name=\"home_manager_version\">Yönetici sürümü</string>\n    <string name=\"home_fingerprint\">Parmak izi</string>\n\n    <string name=\"home_selinux_status\">SELinux durumu</string>\n    <string name=\"home_selinux_status_disabled\">Devre dışı</string>\n    <string name=\"home_selinux_status_enforcing\">Zorlayıcı</string>\n    <string name=\"home_selinux_status_permissive\">İzin verici</string>\n    <string name=\"home_selinux_status_unknown\">Bilinmiyor</string>\n \n\t<string name=\"settings_selinux_mode\">SELinux Modu</string>\n    <string name=\"settings_selinux_mode_summary\">SELinux zorunluluk modunu seçin</string>\n    <string name=\"settings_selinux_mode_enforcing\">Zorlayıcı (Sıkı)</string>\n    <string name=\"settings_selinux_mode_permissive\">İzin verici (Esnek)</string>\n    <string name=\"settings_selinux_current_mode\">Geçerli: %s</string>\n    <string name=\"settings_selinux_mode_enforcing_summary\">SELinux erişim kurallarını tamamen uygular</string>\n    <string name=\"settings_selinux_mode_permissive_summary\">SELinux yalnızca ihlalleri kaydeder, engellemez</string>\n\n    <string name=\"home_device_info\">Cihaz</string>\n    <string name=\"home_system_version\">Sistem sürümü</string>\n    <string name=\"home_kpatch_version\">KernelPatch sürümü</string>\n    <string name=\"home_su_path\">Çalıştırılabilir su</string>\n    <string name=\"home_apatch_version\">FolkPatch sürümü</string>\n\n    <string name=\"kpm\">KPModule</string>\n    <string name=\"kpm_kp_not_installed\">KernelPatch yüklü değil</string>\n    <string name=\"kpm_add_kpm\">KPM Ekle</string>\n    <string name=\"kpm_load\">Yükle</string>\n    <string name=\"kpm_install\">Kur</string>\n    <string name=\"kpm_embed\">Göm</string>\n    <string name=\"kpm_load_toast_succ\">Yükleme başarılı</string>\n    <string name=\"kpm_load_toast_failed\">Yükleme başarısız</string>\n    <string name=\"kpm_unload_confirm\">%s modülü kaldırılsın mı?</string>\n    <string name=\"kpm_unload\">Kaldır</string>\n    <string name=\"kpm_control\">Kontrol</string>\n    <string name=\"kpm_apm_empty\">Yüklü modül yok</string>\n    <string name=\"kpm_version\">Sürüm</string>\n    <string name=\"kpm_license\">Lisans</string>\n    <string name=\"kpm_author\">Yazar</string>\n    <string name=\"kpm_desc\">Açıklama</string>\n    <string name=\"kpm_args\">Argümanlar</string>\n\n    <string name=\"su_title\">Superuser</string>\n    <string name=\"su_selinux_via_hook\">Hook ile baypas</string>\n    <string name=\"su_pkg_excluded_label\">Hariç tut</string>\n    <string name=\"su_pkg_excluded_setting_title\">Değişiklikleri hariç tut</string>\n    <string name=\"su_pkg_excluded_setting_summary\">Bu seçenek etkinleştirildiğinde FolkPatch, bu uygulamanın modülleri tarafından değiştirilen dosyaları geri yükleyebilir.</string>\n    <string name=\"su_pkg_root_setting_title\">Superuser</string>\n    <string name=\"su_pkg_root_setting_summary\">Bu seçenek etkinleştirildiğinde uygulamaya superuser erişimi verilir ve SU komutlarını kullanmasına izin verilir.</string>\n    <string name=\"su_pkg_normal_setting_title\">Normal Mod</string>\n    <string name=\"su_pkg_normal_setting_summary\">Root erişimi verilmedi, uygulama superuser yetkileri olmadan çalışır.</string>\n    <string name=\"su_show_system_apps\">Sistem uygulamalarını göster</string>\n    <string name=\"su_hide_system_apps\">Sistem uygulamalarını gizle</string>\n    <string name=\"su_refresh\">Yenile</string>\n    <string name=\"apm\">Sistem Modülü</string>\n\n\n    <string name=\"apm_not_installed\">AndroidPatch yüklü değil</string>\n    <string name=\"apm_failed_to_enable\">Modül etkinleştirilemedi: %s</string>\n    <string name=\"apm_failed_to_disable\">Modül devre dışı bırakılamadı: %s</string>\n    <string name=\"apm_empty\">Yüklü modül yok</string>\n    <string name=\"apm_remove\">Kaldır</string>\n    <string name=\"apm_undo\">Geri al</string>\n    <string name=\"apm_install\">Kur</string>\n    <string name=\"apm_uninstall_confirm\">%s modülü kaldırılsın mı?</string>\n    <string name=\"apm_uninstall_success\">%s kaldırıldı</string>\n    <string name=\"apm_uninstall_failed\">Kaldırılamadı: %s</string>\n    <string name=\"apm_undo_uninstall_success\">%s geri yüklendi</string>\n    <string name=\"apm_undo_uninstall_failed\">Geri yükleme başarısız: %s</string>\n    <string name=\"apm_version\">Sürüm</string>\n    <string name=\"apm_author\">Yazar</string>\n    <string name=\"apm_desc\">Açıklama</string>\n    <string name=\"apm_overlay_fs_not_available\">Kernel tarafından OverlayFS devre dışı bırakıldığı için modüller kullanılamıyor!</string>\n    <string name=\"apm_magisk_conflict\">Magisk ile çakışma nedeniyle modüller kullanılamıyor!</string>\n    <string name=\"apm_mount_warning_title\">Önemli Uyarı</string>\n    <string name=\"apm_mount_warning_message\">Modüller varsayılan olarak bağlanmaz. Lütfen dahili bağlama sistemini veya metamodule kullanın.</string>\n    <string name=\"apm_mount_warning_button\">Anladım</string>\n    <string name=\"apm_reboot_to_apply\">Uygulamak için yeniden başlat</string>\n    <string name=\"apm_changelog\">Değişiklik günlüğü</string>\n    <string name=\"apm_update\">Güncelle</string>\n    <string name=\"apm_downloading\">Modül indiriliyor: %s</string>\n    <string name=\"apm_start_downloading\">İndirme başlatılıyor: %s</string>\n    <string name=\"apm_new_version_available\">Yeni sürüm %s mevcut, yükseltmek için tıklayın.</string>\n\n    <string name=\"hide_apatch_manager\">APatch Manager’ı Gizle</string>\n    <string name=\"hide_apatch_manager_summary\">Rastgele paket kimliği ve özel uygulama etiketiyle bir proxy uygulama yükle</string>\n    <string name=\"hide_apatch_dialog_new_manager_name\">Yeni yönetici adı</string>\n    <string name=\"hide_apatch_dialog_summary\">Launcher’da gösterilecek yeni uygulama etiketi olarak kullanılacaktır</string>\n    <string name=\"hide_apatch_manager_failure\">Gizleme başarısız. Lütfen hatayı bildirin!</string>\n\n    <string name=\"setting_reset_su_path\">su yolunu sıfırla</string>\n    <string name=\"setting_reset_su_new_path\">Yeni tam yol</string>\n \n \n\t<string name=\"apm_webui_open\">Aç</string>\n    <string name=\"apm_action\">Eylem</string>\n    <string name=\"module_shortcut_add\">Kısayol</string>\n    <string name=\"module_shortcut_not_supported\">Launcher masaüstü kısayollarını desteklemiyor.</string>\n    <string name=\"module_shortcut_created\">Masaüstünde kısayol oluşturuldu.</string>\n    <string name=\"module_shortcut_updated\">Kısayol güncellendi.</string>\n    <string name=\"module_shortcut_permission_tip_xiaomi\">Lütfen Xiaomi ayarlarında bu uygulama için \\\"Masaüstü kısayolu oluştur\\\" iznini etkinleştirin.</string>\n    <string name=\"module_shortcut_permission_tip_oppo\">Lütfen OPPO ayarlarında bu uygulama için \\\"Masaüstü kısayolu\\\" iznini etkinleştirin.</string>\n    <string name=\"module_shortcut_permission_tip_default\">Kısayol oluşturma başarısız olursa, sistem ayarlarında bu uygulama için masaüstü kısayolu iznini etkinleştirin.</string>\n    <string name=\"enable_web_debugging\">WebView hata ayıklamayı etkinleştir</string>\n    <string name=\"enable_web_debugging_summary\">WebUI hata ayıklamak için kullanılabilir. Yalnızca gerektiğinde etkinleştirin.</string>\n    <string name=\"settings_apm_install_confirm\">Kurulumdan önce onay iste</string>\n    <string name=\"settings_apm_install_confirm_summary\">Modüller kurulmadan önce onay iletişim kutusu göster</string>\n    <string name=\"settings_show_more_module_info\">Modül ayrıntılarını göster</string>\n    <string name=\"settings_show_more_module_info_summary\">Modül listesinde modül kimliğini ve boyutunu göster</string>\n    <string name=\"settings_apm_stay_on_page\">İşlem sayfasında kal</string>\n    <string name=\"settings_apm_stay_on_page_summary\">Modül işlemi tamamlandıktan sonra otomatik geri dönme</string>\n    <string name=\"settings_enable_module_shortcut_add\">Kısayol ekleme düğmesini etkinleştir</string>\n    <string name=\"settings_enable_module_shortcut_add_summary\">WebUI kısayol ekleme düğmesini göster</string>\n    <string name=\"settings_disable_module_update_check\">Modül güncelleme kontrolünü devre dışı bırak</string>\n    <string name=\"settings_disable_module_update_check_summary\">Sistem modülleri için otomatik güncelleme kontrolünü kapat</string>\n    <string name=\"settings_module_sort_optimization\">Modül Sıralama Optimizasyonu</string>\n    <string name=\"settings_module_sort_optimization_summary\">WebUI ve Eylem içeren sistem modüllerini en üste taşı</string>\n    <string name=\"settings_fold_system_module\">Sistem Modüllerini Katla</string>\n    <string name=\"settings_fold_system_module_summary\">Eylemleri genişletmek/daraltmak için modül kartına tıkla</string>\n    <string name=\"settings_simple_list_bottom_bar\">Basit Liste Alt Çubuğu</string>\n    <string name=\"settings_simple_list_bottom_bar_summary\">Modül eylemleri için yalnızca simge düğmeleri kullan (APatch’ten esinlenmiştir)</string>\n    <string name=\"settings_spliced_card_group\">Spliced Card Group</string>\n    <string name=\"settings_spliced_card_group_summary\">Use M3E continuous card group style for module list</string>\n    <string name=\"apm_install_confirm_title\">Modül Kur</string>\n    <string name=\"apm_install_confirm_content\">%s kurulmak istiyor musunuz?</string>\n    <string name=\"apm_disable_all_title\">Tüm Modülleri Devre Dışı Bırak</string>\n    <string name=\"module_shortcut_name\">Kısayol adı</string>\n    <string name=\"module_shortcut_icon\">Kısayol simgesi</string>\n    <string name=\"module_shortcut_type\">Kısayol türü</string>\n    <string name=\"module_shortcut_type_webui\">WebUI</string>\n    <string name=\"module_shortcut_type_action\">Eylem</string>\n    <string name=\"module_shortcut_icon_default\">Varsayılan simgeyi kullan</string>\n    <string name=\"module_shortcut_icon_select\">Simge seç</string>\n    <string name=\"apm_disable_all_confirm\">Tüm modülleri devre dışı bırakmak istediğinize emin misiniz? Bu, yüklü tüm modülleri devre dışı bırakacaktır.</string>\n    <string name=\"apm_enable_module_banner\">Modül Banner’ını Etkinleştir</string>\n    <string name=\"apm_enable_module_banner_summary\">Mevcutsa modüller için banner görseli göster</string>\n    <string name=\"apm_enable_folk_banner\">Modül Banner’ını Özelleştir</string>\n    <string name=\"apm_enable_folk_banner_summary\">Banner görselini seçmek için modül kartına uzun basın</string>\n    <string name=\"apm_folk_banner_title\">FolkBanner</string>\n    <string name=\"apm_folk_banner_select\">Görsel seç</string>\n    <string name=\"apm_folk_banner_clear\">FolkBanner’ı Temizle</string>\n    <string name=\"apm_folk_banner_saved\">%s için FolkBanner eklendi.</string>\n    <string name=\"apm_folk_banner_cleared\">%s için FolkBanner temizlendi.</string>\n    <string name=\"apm_folk_banner_failed\">%s için FolkBanner güncellenemedi.</string>\n    <string name=\"apm_banner_api_mode\">API Modu</string>\n    <string name=\"apm_banner_api_mode_summary\">Modül bannerları için rastgele görsel API veya yerel dizin kullan</string>\n    <string name=\"apm_banner_api_source\">API Kaynağını Yapılandır</string>\n    <string name=\"apm_banner_api_source_hint\">API URL veya yerel yol girin</string>\n    <string name=\"apm_banner_api_source_saved\">API kaynağı kaydedildi</string>\n    <string name=\"apm_banner_api_source_not_configured\">Yapılandırılmadı</string>\n    <string name=\"apm_banner_api_url_configured\">API URL yapılandırıldı</string>\n    <string name=\"apm_banner_local_dir_configured\">Yerel dizin yapılandırıldı</string>\n    <string name=\"apm_banner_clear_cache\">Önbelleği Temizle</string>\n    <string name=\"apm_banner_cache_cleared\">Banner önbelleği temizlendi</string>\n    <string name=\"apm_banner_api_config_title\">API Kaynağını Yapılandır</string>\n    <string name=\"apm_banner_api_config_desc\">Rastgele görsel API URL veya yerel dizin yolu girin. Her modüle özel bir banner görseli verilecektir.</string>\n    <string name=\"apm_banner_api_examples_title\">Örnekler:</string>\n    <string name=\"apm_banner_api_examples\">API: https://picsum.photos/800/400\\nYerel: /sdcard/Pictures/Banners</string>\n    <string name=\"apm_api_marketplace_title\">API Pazarı</string>\n    <string name=\"apm_api_preview\">Önizleme</string>\n    <string name=\"apm_api_apply\">Uygula</string>\n    <string name=\"apm_api_preview_title\">API Önizleme</string>\n    <string name=\"apm_api_preview_failed\">Önizleme yüklenemedi</string>\n    <string name=\"apm_api_url_label\">API URL:</string>\n    <string name=\"apm_api_apply_success\">API kaynağı başarıyla uygulandı</string>\n    <string name=\"apm_api_verifying\">Doğrulanıyor…</string>\n    <string name=\"apm_api_retry\">Yeniden Dene</string>\n    <string name=\"apm_api_empty\">Kullanılabilir API kaynağı yok</string>\n    <string name=\"settings_banner_custom_opacity\">Banner Opaklığı</string>\n    <string name=\"settings_banner_custom_opacity_summary\">Duvar kağıdı modunu takip etmek yerine banner’lar için özel opaklık kullan</string>\n    <string name=\"settings_banner_opacity\">Banner şeffaflığı</string>\n\n    <string name=\"settings_donot_store_superkey\">SuperKey’i yerelde saklama</string>\n    <string name=\"settings_donot_store_superkey_summary\">Yönetici her başlatıldığında SuperKey doğrulaması iste</string>\n\n\n\n    <string name=\"mode_select_page_title\">Kur</string>\n    <string name=\"mode_select_page_patch_and_install\">Yamala ve kur</string>\n    <string name=\"mode_select_page_select_file\">Yamalanacak boot imajını seçin</string>\n    <string name=\"restore_select_file\">Geri yüklenecek boot bölüm dosyasını seçin</string>\n    <string name=\"mode_select_page_install_inactive_slot\">Etkin olmayan slota kur (OTA sonrası)</string>\n    <string name=\"mode_select_page_install_inactive_slot_warning\">Cihazınız yeniden başlatma sonrası mevcut etkin olmayan slottan **ZORLA** açılacaktır!\\nBu seçeneği yalnızca OTA tamamlandıktan sonra kullanın.\\nDevam edilsin mi?</string>\n    <string name=\"mode_select_page_select_kpimg\">Yerel yama dosyası kullan (KPimg)</string>\n    <string name=\"patch_custom_kpimg_label\">Özel KPimg</string>\n    <string name=\"patch_custom_kpimg_file\">Dosya: %s</string>\n    <string name=\"patch_select_kpimg_btn\">KPimg Seç</string>\n\n    <string name=\"home_dialog_auth_fail_title\">Kimlik doğrulama başarısız</string>\n    <string name=\"home_dialog_auth_fail_content\">SuperKey doğrulanamadı, bu da FolkPatch aktivasyonunun başarısız olmasına neden oldu.\nKimlik doğrulama başarısızlığının bazı olası nedenleri aşağıdadır—lütfen hangisinin sizin için geçerli olduğunu kontrol edin:\n\n\\n1. KernelPatch ile boot.img dosyasını düzlemediniz veya gerçekten ne yaptığınızı unuttunuz.\n\\n2. Düzeltme yapılmış boot.img hala bilgisayarınızda uyuyor, cihaza asla flashlanmadı.\n\\n3. SuperKey yanlış girildi veya gizemli semboller içeriyor, örneğin uzaylı dillerinden karakterler.\n\\n4. Cihazınız FolkPatch ve KernelPatch ile uyumlu olmayabilir, zorlama girişleri boşunadır.\n\\n5. Bazı gizemli işlemler yapmış olabilirsiniz—örneğin, FolkPatch\\'i engelleyen ve sonunda sizi oyunun dışına iten paket adı dışlama modülleri kullanmak gibi.\n\\nLütfen önce dikkatlice kontrol edin, sonra tekrar deneyin. Sorun devam ederse, sorular sormak için resmi deponun issues sayfasına davetlisiniz.\nSonuçta, bazı sorunlar tamamen sizin kendi yapmanızdan kaynaklanıyor olabilir!\nBol şans! Ayrıca, aşağıdaki belge butonuna tıklamak kesinlikle doğru!</string>\n\n    <string name=\"home_more_menu_feedback_or_suggestion\">Geri bildirim veya öneri</string>\n    <string name=\"home_more_menu_about\">Hakkında</string>\n    <string name=\"home_more_menu_document\">Belge</string>\n\n    <string name=\"home_dialog_uninstall_title\">Kaldır</string>\n    \n    <string name=\"home_dialog_uninstall_ap_only\">Yalnızca yamayı kaldır</string>\n    <string name=\"home_dialog_uninstall_all\">Tamamen kaldır</string>\n    <string name=\"home_dialog_uninstall_ap_only_desc\">Yalnızca AndroidPatch’i kaldır ve yöneticiyi koru.</string>\n    <string name=\"home_dialog_uninstall_all_desc\">AndroidPatch’i kaldır ve tam kaldırma sürecini başlat.</string>\n\n    <string name=\"kpm_control_dialog_title\">KPM Kontrolü</string>\n    <string name=\"kpm_control_dialog_content\">Lütfen kontrol parametrelerini girin:</string>\n    <string name=\"kpm_control_paramters\">Parametreler</string>\n    <string name=\"kpm_control_outMsg\">Çıktı</string>\n    <string name=\"kpm_control_ok\">Başarılı!</string>\n    <string name=\"kpm_control_failed\">Başarısız!</string>\n    \n    <string name=\"settings_night_mode_follow_sys\">Koyu tema sistemle uyumlu</string>\n    <string name=\"settings_night_mode_follow_sys_summary\">Koyu temayı sistem ayarlarına göre otomatik değiştir</string>\n    <string name=\"settings_night_theme_enabled\">Koyu tema</string>\n    \n    <string name=\"settings_use_system_color_theme\">Sistem renk teması</string>\n    <string name=\"settings_use_system_color_theme_summary\">Sistem tarafından duvar kağıdından oluşturulan renk temasını kullan</string>\n    <string name=\"settings_custom_color_theme\">Renk teması</string>\n\n    <string name=\"amber_theme\">Amber</string>\n    <string name=\"blue_theme\">Mavi</string>\n    <string name=\"blue_grey_theme\">Mavi Gri</string>\n    <string name=\"brown_theme\">Kahverengi</string>\n    <string name=\"cyan_theme\">Camgöbeği</string>\n    <string name=\"deep_orange_theme\">Koyu Turuncu</string>\n    <string name=\"deep_purple_theme\">Koyu Mor</string>\n    <string name=\"green_theme\">Yeşil</string>\n    <string name=\"indigo_theme\">İndigo</string>\n    <string name=\"light_blue_theme\">Açık Mavi</string>\n    <string name=\"light_green_theme\">Açık Yeşil</string>\n    <string name=\"lime_theme\">Lime</string>\n    <string name=\"orange_theme\">Turuncu</string>\n    <string name=\"pink_theme\">Pembe</string>\n    <string name=\"purple_theme\">Mor</string>\n    <string name=\"red_theme\">Kırmızı</string>\n    <string name=\"sakura_theme\">Sakura</string>\n    <string name=\"teal_theme\">Teal</string>\n    <string name=\"yellow_theme\">Sarı</string>\n    <string name=\"theme_color\">Tema rengi</string>\n    <string name=\"theme_light\">Açık</string>\n    <string name=\"theme_dark\">Koyu</string>\n    <string name=\"theme_system\">Sistem</string>\n    <string name=\"loading_modules\">Modüller yükleniyor…</string>\n    <string name=\"loading_scripts\">Betikler aranıyor…</string>\n    <string name=\"loading_themes\">Temalar yükleniyor…</string>\n    <string name=\"loading_apis\">API kaynakları yükleniyor…</string>\n\n    <string name=\"about_app_desc\">Çekirdeği yeniden derlemeden kernel modüllerini ve kernel fonksiyon hook’larını destekleyen, kernel tabanlı bir root çözümü.</string>\n    <string name=\"about_powered_by\">%1$s tarafından desteklenmektedir</string>\n    <string name=\"about_telegram_group\">Telegram Grubu</string>\n    \n    <!-- Online Module Strings -->\n    <string name=\"online_module_title\">Çevrim İçi Sistem Modülü</string>\n    <string name=\"online_module_download_start\">İndirme başlatılıyor: %s</string>\n    <string name=\"online_module_download_complete\">İndirme tamamlandı: %s</string>\n    <string name=\"online_module_download_notification\">%s indiriliyor. İlerleme için bildirim panelini, tamamlanan dosya için İndirilenler klasörünü kontrol edin.</string>\n\n    <!-- Online KPM Strings -->\n    <string name=\"online_kpm_title\">Çevrim İçi Kernel Modülleri</string>\n    <string name=\"online_kpm_download_start\">İndirme başlatılıyor: %s</string>\n    <string name=\"online_kpm_download_complete\">İndirme tamamlandı: %s</string>\n    <string name=\"online_kpm_download_notification\">%s indiriliyor. İlerleme için bildirim panelini, tamamlanan dosya için İndirilenler klasörünü kontrol edin.</string>\n\n    <!-- Online Script Strings -->\n    <string name=\"online_script_title\">Çevrim İçi Betikler</string>\n    <string name=\"online_script_download_start\">İndirme başlatılıyor: %s</string>\n    <string name=\"online_script_download_complete\">İndirme tamamlandı: %s</string>\n    <string name=\"online_script_download_notification\">%s indiriliyor</string>\n\n    <!-- Crash Handle Strings -->\n    <string name=\"crash_handle_title\">Çökme Raporu</string>\n    <string name=\"crash_handle_copy\">Kopyala</string>\n\n    <!-- About Screen Strings -->\n    <string name=\"about_app_version\">Uygulama Sürümü: %s</string>\n    <string name=\"about_github\">GitHub</string>\n    <string name=\"about_telegram_channel\">Telegram Kanalı</string>\n\n    <!-- App Title Strings -->\n    <string name=\"app_title_fpatch\">FPatch</string>\n    <string name=\"app_title_apatch_folk\">APatch Folk</string>\n    <string name=\"app_title_apatchx\">APatch X</string>\n    <string name=\"app_title_apatch\">APatch</string>\n    <string name=\"app_title_folkpatch\">FolkPatch</string>\n    <string name=\"app_title_kernelpatch\">KernelPatch</string>\n    <string name=\"app_title_kernelsu\">KernelSU</string>\n    <string name=\"app_title_supersu\">SuperSU</string>\n    <string name=\"app_title_superuser\">Superuser</string>\n    <string name=\"app_title_superpatch\">SuperPatch</string>\n    <string name=\"app_title_magicpatch\">MagicPatch</string>\n\n    <string name=\"settings_app_dpi\">Uygulama DPI</string>\n    <string name=\"dpi_apply_settings\">Uygula</string>\n    <string name=\"dpi_confirm_title\">DPI değişikliğini onayla</string>\n    <string name=\"dpi_confirm_message\">Uygulama DPI değerini %1$s ile %2$s arasında değiştirmek ister misiniz?</string>\n    <string name=\"settings_magic_mount\">Folk Mount API</string>\n    <string name=\"settings_magic_mount_summary\">Dahili modül bağlama sistemini etkinleştir</string>\n    <string name=\"settings_new_app_profile_mode\">Yeni uygulamalar için varsayılan mod</string>\n    <string name=\"settings_new_app_profile_normal\">ROOT YOK</string>\n    <string name=\"settings_new_app_profile_root\">ROOT</string>\n    <string name=\"settings_new_app_profile_exclude\">Hariç tut</string>\n    <string name=\"settings_hide_service\">FolkPatch Hide</string>\n    <string name=\"settings_hide_service_summary\">Bootloader kilidi açık durumunu bir ölçüde gizle</string>\n    <string name=\"settings_app_title\">Uygulama Başlığı</string>\n    <string name=\"app_title_custom\">Özel</string>\n    <string name=\"settings_custom_app_title\">Uygulama adını ayarla</string>\n    <string name=\"custom_app_title_dialog_title\">Özel uygulama adı</string>\n    <string name=\"custom_app_title_dialog_hint\">Uygulama adını girin</string>\n    <string name=\"custom_app_title_dialog_confirm\">Onayla</string>\n    <string name=\"custom_app_title_dialog_empty\">Ad boş olamaz</string>\n    <string name=\"cancel\">İptal</string>\n    <string name=\"settings_custom_background\">Özel Arka Plan</string>\n    <string name=\"settings_custom_background_enabled\">Özel Arka Plan Etkin</string>\n    <string name=\"settings_custom_background_opacity\">Kart opaklığı</string>\n    <string name=\"settings_custom_background_blur\">Arka plan bulanıklığı</string>\n    <string name=\"settings_custom_background_dim\">Arka plan karartma</string>\n    <string name=\"settings_custom_background_dual_dim\">Çift gündüz/gece karartmayı etkinleştir</string>\n    <string name=\"settings_custom_background_dual_dim_desc\">Açık ve koyu temalar için arka plan karartmasını otomatik olarak ayarla</string>\n    <string name=\"settings_custom_background_day_dim\">Gündüz modu arka plan karartması</string>\n    <string name=\"settings_custom_background_night_dim\">Gece modu arka plan karartması</string>\n    <string name=\"settings_multi_background_mode\">Çoklu Arka Plan Modu</string>\n    <string name=\"settings_multi_background_mode_summary\">Farklı sayfalar için farklı arka plan görselleri ayarla</string>\n    <string name=\"settings_select_home_background\">Ana Sayfa Arka Planı</string>\n    <string name=\"settings_select_kernel_background\">Kernel Modülü Arka Planı</string>\n    <string name=\"settings_select_superuser_background\">Superuser Arka Planı</string>\n    <string name=\"settings_select_system_module_background\">Sistem Modülü Arka Planı</string>\n    <string name=\"settings_select_settings_background\">Ayarlar Arka Planı</string>\n\n\n    <string name=\"settings_advanced_title_style\">Gelişmiş Başlık Stili</string>\n    <string name=\"settings_advanced_title_style_summary\">Üst bar başlığını özel bir resimle değiştir</string>\n    <string name=\"settings_advanced_title_style_enabled\">Gelişmiş başlık stili etkinleştirildi</string>\n    <string name=\"settings_select_title_image\">Başlık Resmi Seç</string>\n    <string name=\"settings_title_image_selected\">Başlık resmi seçildi</string>\n    <string name=\"settings_title_image_saved\">Başlık resmi başarıyla kaydedildi</string>\n    <string name=\"settings_title_image_error\">Başlık resmi kaydedilemedi</string>\n    <string name=\"settings_clear_title_image\">Başlık Resmini Temizle</string>\n    <string name=\"settings_clear_title_image_confirm\">Başlık resmini temizlemek istediğinizden emin misiniz?</string>\n    <string name=\"settings_title_image_cleared\">Başlık resmi temizlendi</string>\n    <string name=\"settings_title_image_day_opacity\">Gündüz Modu Opaklığı</string>\n    <string name=\"settings_title_image_night_opacity\">Gece Modu Opaklığı</string>\n    <string name=\"settings_title_image_dim\">Arka Plan Karartma</string>\n    <string name=\"settings_title_image_offset_x\">Yatay Konum</string>\n\n    <!-- Font Settings -->\n    <string name=\"settings_custom_font\">Özel Yazı Tipi</string>\n    <string name=\"settings_select_font_file\">Yazı Tipi Dosyası Seç</string>\n    <string name=\"settings_custom_font_enabled\">Özel Yazı Tipi Etkin</string>\n    <string name=\"settings_custom_font_summary\">Uygulama için özel bir TTF yazı tipi kullan</string>\n    <string name=\"settings_font_selected\">Özel Yazı Tipi Seçildi</string>\n    <string name=\"settings_custom_font_error\">Yazı tipi dosyası kaydedilemedi</string>\n    <string name=\"settings_custom_font_saved\">Özel yazı tipi başarıyla kaydedildi</string>\n    <string name=\"settings_clear_font\">Varsayılan Yazı Tipine Dön</string>\n    <string name=\"settings_clear_font_confirm\">Varsayılan yazı tipini geri yüklemek istediğinize emin misiniz?</string>\n    <string name=\"settings_font_cleared\">Varsayılan yazı tipi geri yüklendi</string>\n    <string name=\"settings_font_select_hint\">Bir TTF yazı tipi dosyası seçin</string>\n    <string name=\"settings_select_background_image\">Arka Plan Görseli Seç</string>\n    <string name=\"settings_custom_background_summary\">Özel bir arka plan görseli ayarla</string>\n    <string name=\"settings_custom_background_saved\">Arka plan başarıyla kaydedildi</string>\n    <string name=\"settings_custom_background_error\">Arka plan kaydedilemedi</string>\n    <string name=\"settings_background_selected\">Arka plan seçildi</string>\n    <string name=\"settings_background_image_cleared\">Arka plan görseli temizlendi</string>\n    <string name=\"settings_clear_background\">Arka Planı Temizle</string>\n    <string name=\"settings_clear_background_confirm\">Arka plan görselini temizlemek istediğinize emin misiniz?</string>\n\n    <string name=\"settings_alt_icon\">Alternatif Simge</string>\n    <string name=\"alt_icon_summary\">Alternatif launcher simgesini kullan</string>\n\n    <!-- Video Background Settings -->\n    <string name=\"settings_video_background\">Video Arka Plan</string>\n    <string name=\"settings_video_background_summary\">Arka plan olarak video kullan</string>\n    <string name=\"settings_select_video\">Video Seç</string>\n    <string name=\"settings_video_selected\">Video Seçildi</string>\n    <string name=\"settings_clear_video_background\">Video Arka Planını Temizle</string>\n    <string name=\"settings_clear_video_background_confirm\">Video arka planını temizlemek istediğinize emin misiniz?</string>\n    <string name=\"settings_video_background_enabled\">Video Arka Plan Etkin</string>\n    <string name=\"settings_video_volume\">Video Sesi</string>\n    \n    <!-- KPM AutoLoad Strings -->\n    <string name=\"kpm_autoload_title\">KPM Otomatik Yükleme</string>\n    <string name=\"kpm_autoload_enabled\">Otomatik yüklemeyi etkinleştir</string>\n    <string name=\"kpm_autoload_enabled_summary\">Cihaz açıldığında KPM modüllerini otomatik olarak yükle</string>\n    <string name=\"kpm_autoload_json_config\">JSON Yapılandırması</string>\n    <string name=\"kpm_autoload_json_label\">Yapılandırma JSON’u</string>\n    <string name=\"kpm_autoload_json_placeholder\">{\n  \"enabled\": true,\n  \"kpmPaths\": [\n    \"/path/to/module1.kpm\",\n    \"/path/to/module2.kpm\"\n  ]\n}</string>\n    <string name=\"kpm_autoload_json_error\">Geçersiz JSON formatı</string>\n    <string name=\"kpm_autoload_json_helper\">KPM dosya yollarını içeren geçerli bir JSON girin</string>\n    <string name=\"kpm_autoload_save\">Yapılandırmayı Kaydet</string>\n    <string name=\"kpm_autoload_save_confirm\">Otomatik yükleme yapılandırması kaydedilsin mi?</string>\n    <string name=\"kpm_autoload_save_success\">Yapılandırma başarıyla kaydedildi</string>\n    <string name=\"kpm_autoload_save_failed\">Yapılandırma kaydedilemedi</string>\n    <string name=\"kpm_autoload_visual_mode\">Görsel Mod</string>\n    <string name=\"kpm_autoload_json_mode\">JSON Modu</string>\n    <string name=\"kpm_autoload_add_kpm\">KPM Ekle</string>\n    <string name=\"kpm_autoload_remove_kpm\">Kaldır</string>\n    <string name=\"kpm_autoload_edit_kpm\">Editar</string>\n    <string name=\"kpm_autoload_edit_dialog_title\">Editar KPM</string>    <string name=\"kpm_autoload_event_label\">Evento:</string>    <string name=\"kpm_autoload_args_label\">Argumentos:</string>    <string name=\"kpm_autoload_event_service\">service</string>    <string name=\"kpm_autoload_event_post_fs_data\">post-fs-data</string>\n    \n    <!-- Module Backup/Restore Strings -->\n    <string name=\"apm_backup_title\">Modülleri Yedekle</string>\n    <string name=\"apm_restore_title\">Modülleri Geri Yükle</string>\n    <string name=\"apm_backup_success\">Yedekleme başarılı</string>\n    <string name=\"apm_restore_success\">Geri yükleme başarılı</string>\n    <string name=\"apm_backup_failed\">Yedekleme başarısız</string>\n    <string name=\"apm_restore_failed\">Geri yükleme başarısız</string>\n    <string name=\"apm_backup_failed_msg\">Yedekleme başarısız: %s</string>\n    <string name=\"apm_restore_failed_msg\">Geri yükleme başarısız: %s</string>\n    <string name=\"apm_copy_list_title\">Listeyi Kopyala</string>\n    <string name=\"apm_copy_list_success\">Modül listesi kopyalandı</string>\n\n    <!-- APM Bulk Install Strings -->\n    <string name=\"apm_bulk_install_title\">Sistem Modülü Toplu Kurulum</string>\n    <string name=\"apm_bulk_install_list_title\">Modül Listesi</string>\n    <string name=\"apm_bulk_install_add\">Modül Ekle</string>\n    <string name=\"apm_bulk_install_empty\">Eklenmiş modül yok</string>\n    <string name=\"apm_bulk_install_action\">Toplu Kurulum</string>\n    <string name=\"apm_bulk_install_log_title\">Toplu Kurulum Günlüğü</string>\n    <string name=\"apm_bulk_install_log_start\">Toplu kurulum başlatılıyor...</string>\n    <string name=\"apm_bulk_install_log_installing\">%1$s kuruluyor</string>\n    <string name=\"apm_batch_install_full_process\">Tam toplu kurulum süreci</string>\n    <string name=\"apm_batch_install_full_process_summary\">Sistem modüllerinin toplu kurulumu tam süreç kullanılarak yapılır</string>\n    <string name=\"next_module\">Sonraki modül</string>\n    <string name=\"apm_bulk_install_log_installed\">%s modülü kuruldu.</string>\n    <string name=\"apm_bulk_install_log_done\">Tüm işlemler tamamlandı.</string>\n    <string name=\"apm_bulk_install_first_use_text\">Bu özellik, birden fazla modülü aynı anda kurmanıza olanak tanır. Kurulum sırasında tuş etkileşimi gerektirmeyen modüller için uygun, hızlı bir kurulum yöntemidir. Ses tuşu etkileşimi gerektiren modüller için ayarlardan tam süreç kurulum modunu etkinleştirin.</string>\n    <string name=\"apm_bulk_install_remove\">Kaldır</string>\n    <string name=\"apm_first_use_title\">Sistem Modüllerine Hoş Geldiniz</string>\n    <string name=\"apm_first_use_text\">Sistem Modüllerine hoş geldiniz. Bu bölüm, Magisk ekosistemiyle uyumlu modülleri kullanır. Modül yüklemek için sağ alt köşedeki düğmeye tıklayın. Üst kısımdaki yükleyiciyi kullanarak modülleri toplu olarak da kurabilirsiniz. Tek tıkla tüm modülleri yedekleme özelliği de sunulmaktadır; ancak bu yöntemin tüm modüller için uygun olmayabileceğini unutmayın, bu yüzden kendi yedeklerinizi almanız önerilir.</string>\n\n    <string name=\"kpm_autoload_kpm_list_title\">KPM Modülleri</string>\n    <string name=\"kpm_autoload_no_kpm_added\">Eklenmiş KPM modülü yok</string>\n    <string name=\"kpm_autoload_kpm_path\">KPM Yolu</string>\n    <string name=\"kpm_autoload_file_not_found\">Dosya bulunamadı</string>\n    <string name=\"kpm_autoload_select_kpm_file\">KPM Dosyası Seç</string>\n    <string name=\"kpm_autoload_first_time_title\">KPM Otomatik Yükleme Hakkında</string>\n    <string name=\"kpm_autoload_first_time_message\">Bu özellik, yapılandırılmış tüm KPM’leri geçici olarak önyükleme sırasında otomatik yüklemenizi sağlar. Bu yaklaşım, doğrudan çekirdeğe gömmeye kıyasla daha pratiktir.\n\nÖrneğin, bölüm değişikliklerini engelleyen KPM’ler yalnızca geçici olarak kullanılabilir; çünkü gömme işlemi açılış hatalarına neden olabilir. Ya da BOOT bölümünü değiştirmek istemiyorsanız, bu yapılandırmayı kullanarak modülleri hızlıca yükleyebilirsiniz.\n\nKomutların çalışması için uygulamanın tamamen kapatılıp yeniden açıldığından emin olun. Genellikle önyükleme sırasında da yükleme yapılır. Yöneticinin bir süre siyah ekran göstermesi normaldir! Bu yöntem tamamen arka planda yükleme kullanır; modüllerin doğru şekilde yüklenip yüklenmediğini kontrol etmek için manuel olarak aşağı kaydırıp yenilemeyi unutmayın!</string>\n    <string name=\"kpm_autoload_first_time_confirm\">Anladım</string>\n    <string name=\"kpm_autoload_do_not_show_again\">Bir daha gösterme</string>\n\n    <!-- Hide Settings Strings -->\n    <string name=\"home_hide_su_path\">su çalıştırılabilir yolunu gizle</string>\n    <string name=\"home_hide_kpatch_version\">Kernel patch sürümünü gizle</string>\n    <string name=\"home_hide_fingerprint\">Parmak izini gizle</string>\n    <string name=\"home_hide_zygisk\">Zygisk uygulamasını gizle</string>\n    <string name=\"home_hide_mount\">Mount uygulamasını gizle</string>\n    <string name=\"home_hide_su_path_summary\">Bilgi kartında su çalıştırılabilir yolunu gizle</string>\n    <string name=\"home_hide_kpatch_version_summary\">Bilgi kartında kernel patch sürümünü gizle</string>\n    <string name=\"home_hide_fingerprint_summary\">Bilgi kartında parmak izini gizle</string>\n    <string name=\"home_hide_zygisk_summary\">Bilgi kartında Zygisk uygulamasını gizle</string>\n    <string name=\"home_hide_mount_summary\">Bilgi kartında mount uygulamasını gizle</string>\n\n    <string name=\"kpm_page_first_time_title\">Uyarı</string>\n    <string name=\"kpm_page_first_time_message\">Kernel modülleri Boot uygulamasını doğrudan değiştirir. Sistem modüllerinin aksine, iyi bir kurtarma mekanizmasına sahip değildirler. Sorun oluşursa yalnızca Fastboot’a girerek düzeltme yapabilirsiniz. Boot’a gömmeden önce sorun olmadığından emin olmak için önce modülü yüklemeniz önerilir. Bir modül yalnızca yüklenerek kullanılabiliyorsa, otomatik KPM modülü yükleme özelliğini deneyebilirsiniz. Kernel modüllerini anlamıyorsanız lütfen bu özelliği kullanmayın!</string>\n\n\n    <!-- Auto Backup Strings -->\n    <string name=\"settings_auto_backup_module\">Sistem Modüllerini Otomatik Yedekle</string>\n    <string name=\"settings_auto_backup_module_summary\">Kurulum sırasında modül dosyasını özel dizine otomatik olarak yedekle</string>\n    <string name=\"settings_open_backup_dir\">Yedekleme Dizinini Aç</string>\n    <string name=\"backup_dir_empty\">Yedekleme dizini boş</string>\n    <string name=\"backup_dir_open_failed\">Yedekleme dizini açılamadı</string>\n    <string name=\"auto_backup_failed\">Otomatik yedekleme başarısız: %s</string>\n    <string name=\"auto_backup_success\">Otomatik yedekleme başarılı: %s</string>\n    <string name=\"settings_auto_backup_boot\">Boot’u Otomatik Yedekle</string>\n    <string name=\"settings_auto_backup_boot_summary\">Boot’u yerel depolamaya otomatik yedekle (Downloads/FolkPatch/BootBackups)</string>\n\n    <!-- Home Layout Strings -->\n    <string name=\"settings_home_layout_style\">Ana Sayfa Düzen Stili</string>\n    <string name=\"settings_home_layout_default\">ListUI</string>\n    <string name=\"settings_home_layout_grid\">GridUI</string>\n    <string name=\"settings_home_layout_focus\">FocusUI</string>\n    <string name=\"settings_home_layout_sign\">SignUI</string>\n    <string name=\"settings_home_layout_circle\">CircleUI</string>\n    <string name=\"settings_home_layout_dashboard_pro\">DashboardUI</string>\n    <string name=\"unofficial_version_title\">Resmi Olmayan Sürüm</string>\n    <string name=\"unofficial_version_message\">Resmi olmayan bir FolkPatch sürümü kullanıyorsunuz, lütfen resmi uygulamayı indirin!</string>\n    <string name=\"go_to_github\">GitHub’a Git</string>\n    <string name=\"settings_save_theme\">Temayı Kaydet</string>\n    <string name=\"settings_import_theme\">Temayı İçe Aktar</string>\n    <string name=\"settings_theme_saved\">Tema kaydedildi</string>\n    <string name=\"settings_theme_imported\">Tema içe aktarıldı</string>\n    <string name=\"settings_theme_save_failed\">Tema kaydedilemedi</string>\n    <string name=\"settings_theme_import_failed\">Tema içe aktarılamadı</string>\n    <string name=\"settings_reset_theme\">Temayı Sıfırla</string>\n    <string name=\"settings_reset_theme_confirm\">Tüm tema ayarlarını varsayılan değerlere sıfırlamak istediğinizden emin misiniz? Bu işlem tüm özel arka planları, yazı tiplerini, müzikleri ve ses efektlerini siler.</string>\n    <string name=\"settings_theme_reset\">Tema sıfırlandı</string>\n    <string name=\"settings_theme_reset_failed\">Tema sıfırlanamadı</string>\n\n    <!-- Theme Strings -->\n    <string name=\"theme_export_title\">Temayı Dışa Aktar</string>\n    <string name=\"theme_import_title\">Temayı İçe Aktar</string>\n    <string name=\"theme_name\">Tema Adı</string>\n    <string name=\"theme_type\">Tema Türü</string>\n    <string name=\"theme_type_phone\">Telefon</string>\n    <string name=\"theme_type_tablet\">Tablet</string>\n    <string name=\"theme_version\">Sürüm</string>\n    <string name=\"theme_author\">Yazar</string>\n    <string name=\"theme_description\">Açıklama</string>\n    <string name=\"theme_export_action\">Dışa Aktar</string>\n    <string name=\"theme_import_action\">İçe Aktar</string>\n    <string name=\"theme_import_confirm\">Bu temayı içe aktarmak istediğinize emin misiniz?</string>\n    <string name=\"theme_info\">Tema Bilgisi</string>\n\n    <!-- Grid Working Card Background -->\n    <string name=\"settings_grid_working_card_background\">Çalışma Kartı Arka Planı</string>\n    <string name=\"settings_grid_working_card_background_enabled\">Özel kart arka planı etkin</string>\n    <string name=\"settings_grid_working_card_background_summary\">Çalışma kartı için özel arka plan görseli</string>\n    <string name=\"settings_grid_working_card_background_selected\">Arka plan seçildi</string>\n    <string name=\"settings_grid_working_card_dual_opacity\">Çift gündüz/gece opaklığını etkinleştir</string>\n    <string name=\"settings_grid_working_card_dual_opacity_desc\">Açık ve koyu temalar için kart opaklığını otomatik olarak ayarla</string>\n    <string name=\"settings_grid_working_card_day_opacity\">Gündüz modu kart opaklığı</string>\n    <string name=\"settings_grid_working_card_night_opacity\">Gece modu kart opaklığı</string>\n    <string name=\"settings_clear_grid_working_card_background\">Kart Arka Planını Temizle</string>\n    <string name=\"settings_clear_grid_working_card_background_confirm\">Kart arka plan görselini temizlemek istediğinize emin misiniz?</string>\n    <string name=\"settings_grid_working_card_background_saved\">Kart arka planı başarıyla kaydedildi</string>\n    <string name=\"settings_grid_working_card_background_error\">Kart arka planı kaydedilemedi</string>\n    <string name=\"settings_grid_working_card_background_cleared\">Kart arka planı temizlendi</string>\n\n    <!-- Biometric Strings -->\n    <string name=\"action_biometric\">Biyometrik Kimlik Doğrulama</string>\n    <string name=\"msg_biometric\">Lütfen biyometrik kimliğinizi doğrulayın</string>\n    <string name=\"settings_biometric_login\">Biyometrik Kimlik Doğrulama</string>\n    <string name=\"settings_biometric_login_summary\">Uygulama açılırken biyometrik kimlik doğrulaması iste</string>\n    <string name=\"settings_strong_biometric\">Güçlü Biyometrik Kimlik Doğrulama</string>\n    <string name=\"settings_strong_biometric_summary\">Modülleri kurma/kaldırma/devre dışı bırakma için doğrulama iste</string>\n\n    <!-- Theme Store Strings -->\n    <string name=\"theme_store_title\">Tema Mağazası</string>\n    <string name=\"theme_source_official\">Resmi</string>\n    <string name=\"theme_source_third_party\">Üçüncü Taraf</string>\n    <string name=\"theme_source_local\">Yerel</string>\n    <string name=\"theme_store_author\">Yazar: %s</string>\n    <string name=\"theme_store_version\">Sürüm: %s</string>\n    <string name=\"theme_source\">Kaynak</string>\n    <string name=\"theme_store_download_failed\">İndirme başarısız</string>\n    <string name=\"theme_store_download\">İndir</string>\n    <string name=\"theme_store_search_hint\">Tema ara...</string>\n\n    <!-- Update Check Strings -->\n    <string name=\"settings_auto_update_check\">Otomatik Güncelleme Kontrolü</string>\n    <string name=\"settings_auto_update_check_summary\">Uygulama başlatıldığında güncellemeleri otomatik kontrol et</string>\n    <string name=\"settings_check_update\">Güncellemeleri Kontrol Et</string>\n    <string name=\"update_available_title\">Güncelleme Mevcut</string>\n    <string name=\"update_available_message\">Sürümünüzün eski olduğu tespit edildi, yeni sürümü indirmek ister misiniz?</string>\n    <string name=\"update_action\">Güncelle</string>\n    <string name=\"update_close\">Kapat</string>\n    <string name=\"update_latest\">En güncel sürümü kullanıyorsunuz</string>\n    <string name=\"update_error\">Güncelleme kontrol edilirken hata oluştu</string>\n    <string name=\"settings_category_general\">Genel</string>\n    <string name=\"settings_app_list_loading_scheme\">Uygulama Listesi Yükleme Şeması</string>\n    <string name=\"settings_app_list_loading_scheme_summary\">Uygulama listesinin nasıl yükleneceğini seçin</string>\n    <string name=\"app_list_loading_scheme_root_service\">RootService (Varsayılan)</string>\n    <string name=\"app_list_loading_scheme_package_manager\">PackageManager (Sistem API)</string>\n    <string name=\"app_list_loading_scheme_dialog_title\">Yükleme Şeması</string>\n    <string name=\"superuser\">Superuser</string>\n    <string name=\"module\">Modüller</string>\n    <string name=\"settings_category_appearance\">Görünüm</string>\n    <string name=\"settings_appearance_font\">Yazı Tipi Ayarları</string>\n    <string name=\"settings_appearance_font_summary\">Özel yazı tipi yapılandırması</string>\n    <string name=\"settings_appearance_theme\">Tema Ayarları</string>\n    <string name=\"settings_appearance_theme_summary\">Tema mağazası, kaydetme, içe aktarma ve sıfırlama</string>\n    <string name=\"settings_appearance_banner\">Banner Ayarları</string>\n    <string name=\"settings_appearance_banner_summary\">Modül banner yapılandırması</string>\n    <string name=\"settings_appearance_layout\">Düzen Ayarları</string>\n    <string name=\"settings_appearance_layout_summary\">Ana sayfa düzeni, gezinme ve kart özelleştirme</string>\n    <string name=\"settings_appearance_background\">Arka Plan Ayarları</string>\n    <string name=\"settings_appearance_background_summary\">Özel arka plan, video ve çoklu arka plan</string>\n    <string name=\"settings_appearance_night_mode\">Gece Modu Ayarları</string>\n    <string name=\"settings_appearance_night_mode_summary\">Koyu tema ve renk yapılandırması</string>\n    <string name=\"settings_amoled_theme\">AMOLED Siyah Tema</string>\n    <string name=\"settings_amoled_theme_desc\">Karanlık mod için tam siyah arka plan</string>\n    <string name=\"settings_switch_icon\">Düğme Göstergesi</string>\n    <string name=\"settings_switch_icon_desc\">Anahtar düğmelerinde durum simgelerini göster</string>\n    <string name=\"settings_category_behavior\">Davranış</string>\n    <string name=\"settings_category_function\">Fonksiyon</string>\n    <string name=\"settings_category_module\">Modüller</string>\n    <string name=\"settings_category_security\">Güvenlik</string>\n    \n    <!-- Background Music Strings -->\n    <string name=\"settings_background_music\">Arka Plan Müziği</string>\n    <string name=\"settings_background_music_summary\">Uygulama ön plandayken arka plan müziği çal</string>\n    <string name=\"settings_background_music_playing\">Çalıyor: %s</string>\n    <string name=\"settings_background_music_enabled\">Arka Plan Müziği Etkin</string>\n    <string name=\"settings_select_music_file\">Müzik dosyası seç</string>\n    <string name=\"settings_music_selected\">Müzik seçildi</string>\n    <string name=\"settings_clear_music\">Müziği temizle</string>\n    <string name=\"settings_clear_music_confirm\">Arka plan müziğini temizlemek istediğinize emin misiniz?</string>\n    <string name=\"settings_music_auto_play\">Otomatik oynat</string>\n    <string name=\"settings_music_auto_play_summary\">Uygulama açıldığında müziği otomatik çal</string>\n    <string name=\"settings_music_looping\">Tekrarla</string>\n    <string name=\"settings_music_looping_summary\">Geçerli şarkıyı tekrar et</string>\n    <string name=\"settings_music_volume\">Ses seviyesi</string>\n    <string name=\"settings_music_saved\">Müzik dosyası kaydedildi</string>\n    <string name=\"settings_music_save_error\">Müzik dosyası kaydedilemedi</string>\n    <string name=\"settings_music_cleared\">Müzik temizlendi</string>\n    <string name=\"settings_category_multimedia\">Multimedya</string>\n    <string name=\"settings_category_general_summary\">Dil, güncellemeler, SELinux, sistem ayarları</string>\n    <string name=\"settings_category_appearance_summary\">Tema, renkler, düzen, arka plan, yazı tipleri</string>\n    <string name=\"settings_category_behavior_summary\">Web hata ayıklama, kurulum davranışı, ana sayfa görüntüsü</string>\n    <string name=\"settings_category_security_summary\">Biyometrik, süper anahtar yönetimi</string>\n    <string name=\"settings_category_backup_summary\">Yerel yedekleme, bulut yedekleme, WebDAV</string>\n    <string name=\"settings_category_module_summary\">Modül bilgisi, sıralama, toplu kurulum</string>\n    <string name=\"settings_category_function_summary\">FolkPatch Gizle, Umount hizmeti</string>\n    <string name=\"settings_category_multimedia_summary\">Arka plan müziği, sesler, titreşim</string>\n    <string name=\"settings_use_legacy_su_page\">Tek sayfalı Superuser yetkilendirmesi</string>\n    <string name=\"settings_use_legacy_su_page_summary\">Superuser sayfasını tek sayfalı yetkilendirme tasarımına geçir</string>\n    <string name=\"settings_music_playback_control\">Oynatma Kontrolü</string>\n\n    <!-- Theme Store Filter -->\n    <string name=\"theme_store_filter_title\">Temaları Filtrele</string>\n    <string name=\"theme_store_filter_author\">Yazar</string>\n    <string name=\"theme_store_filter_author_hint\">Yazar adını girin</string>\n    <string name=\"theme_store_filter_source\">Kaynak</string>\n    <string name=\"theme_store_filter_source_all\">Tümü</string>\n    <string name=\"theme_store_filter_type\">Cihaz Türü</string>\n    <string name=\"theme_store_filter_apply\">Uygula</string>\n    <string name=\"theme_store_filter_reset\">Sıfırla</string>\n\n    <!-- File Picker -->\n    <string name=\"file_picker_permission_required\">İzin Gerekli</string>\n    <string name=\"file_picker_permission_desc\">Dosyalara göz atmak için lütfen \\'Tüm dosyalara erişim\\' iznini verin.</string>\n    <string name=\"file_picker_grant_permission\">İzin Ver</string>\n    <string name=\"file_picker_cancel\">İptal</string>\n    <string name=\"file_picker_internal_storage\">Dahili Depolama</string>\n    <string name=\"file_picker_no_files\">Dosya yok</string>\n\n    <!-- HomeV3 Strings -->\n    <string name=\"home_device_status_title\">Cihaz Durumu</string>\n    <string name=\"home_device_status_battery_temp\">Batarya Sıcaklığı</string>\n    <string name=\"home_device_status_cpu_load\">CPU Yükü</string>\n    <string name=\"home_device_status_battery_level\">Batarya Seviyesi</string>\n    <string name=\"home_storage_title\">Depolama</string>\n    <string name=\"home_storage_internal\">Dahili Depolama</string>\n    <string name=\"home_storage_ram\">RAM</string>\n    <string name=\"home_storage_zram\">ZRAM</string>\n    <string name=\"home_storage_swap\">Swap Dosyası</string>\n    <string name=\"home_info_kernel\">Kernel</string>\n    <string name=\"home_info_superkey\">SuperKey</string>\n    <string name=\"home_info_auth_auth\">Yetkili</string>\n    <string name=\"home_info_auth_na\">Yok</string>\n    <string name=\"home_info_device_slot\">Cihaz Slotu</string>\n    <string name=\"home_info_device_model\">Cihaz Modeli</string>\n    <string name=\"home_info_running_mode\">Çalışma Modu</string>\n    <string name=\"home_info_mode_full\">Tam</string>\n    <string name=\"home_info_mode_half\">Yarım</string>\n    <string name=\"home_zygisk_implement\">Zygisk Uygulaması</string>\n    <string name=\"home_mount_implement\">Mount Uygulaması</string>\n\n    <string name=\"settings_list_card_hide_status_badge\">Klasik Emoji Kullan</string>\n    <string name=\"settings_list_card_hide_status_badge_summary\">Ana sayfa durum rozetini klasik emojiye dönüştür</string>\n    <string name=\"settings_custom_badge_text\">Özel Rozet Metni</string>\n    <string name=\"settings_custom_badge_text_summary\">Rozetin metin gösterimini değiştir, sadece eğlence amaçlı</string>\n\n    <!-- Install Mode Select -->\n    <string name=\"kp_install_methods\">KernelPatch Yamala/Kur</string>\n    <string name=\"restore_boot_methods\">Geri yüklenecek boot bölümünü seçin</string>\n\n\n    <string name=\"su_backup_list\">Yedekleme Listesi</string>\n    <string name=\"su_restore_list\">Geri Yükleme Listesi</string>\n    <string name=\"backup_success\">Yedekleme başarılı</string>\n    <string name=\"restore_success\">Geri yükleme başarılı</string>\n\n    <!-- SuperUser App Action Dialog -->\n    <string name=\"su_app_action_title\">Uygulama Eylemi</string>\n    <string name=\"su_app_action_content\">Uygulama üzerinde yapılacak işlemi seçin</string>\n    <string name=\"su_app_action_launch\">Uygulamayı Aç</string>\n    <string name=\"su_app_action_force_stop\">Zorla Durdur</string>\n    <string name=\"su_app_action_launch_success\">%s başlatılıyor</string>\n    <string name=\"su_app_action_force_stop_success\">%s zorla durduruldu</string>\n    <string name=\"su_app_action_failed\">İşlem başarısız: %s</string>\n\n    <!-- SU Audit Log -->\n    <string name=\"su_audit_log_title\">Yetkilendirme Günlüğü</string>\n    <string name=\"su_audit_log_empty\">Henüz yetkilendirme kaydı yok</string>\n    <string name=\"su_audit_log_clear\">Günlüğü Temizle</string>\n    <string name=\"su_audit_log_clear_confirm\">Tüm yetkilendirme kayıtları temizlensin mi?</string>\n    <string name=\"su_audit_action_grant\">İzin Verildi</string>\n    <string name=\"su_audit_action_revoke\">İptal Edildi</string>\n    <string name=\"su_audit_action_exclude\">Dışlandı</string>\n    <string name=\"su_audit_tab_usage\">Kullanım Günlüğü</string>\n    <string name=\"su_audit_tab_operations\">İşlem Günlüğü</string>\n\n    <string name=\"patch_output_written_to\"> Çıktı dosyası şuraya yazıldı: </string>\n    <string name=\"patch_write_failed\"> Yamalanmış boot.img yazılamadı</string>\n\n    <!-- Badge Count Settings -->\n    <string name=\"enable_badge_count\">Rozet Sayısı Ayarları</string>\n    <string name=\"enable_badge_count_summary\">Gezinme öğeleri için rozet sayısı gösterimini yapılandır</string>\n    <string name=\"badge_superuser\">SuperUser Rozetini Göster</string>\n    <string name=\"badge_apm\">Sistem Modülü Rozetini Göster</string>\n    <string name=\"badge_kernel\">Kernel Modülü Rozetini Göster</string>\n    <string name=\"settings_sound_effect\">Ses Efekti</string>\n    <string name=\"settings_sound_effect_summary\">Tıklamada ses efekti çal</string>\n    <string name=\"settings_sound_effect_enabled\">Etkin</string>\n    <string name=\"settings_sound_effect_playing\">Seçili: %s</string>\n    <string name=\"settings_sound_effect_source\">Ses Kaynağı</string>\n    <string name=\"settings_sound_effect_source_local\">Yerel Dosya</string>\n    <string name=\"settings_sound_effect_source_preset\">Hazır</string>\n    <string name=\"settings_sound_effect_preset_title\">Hazır Sesler</string>\n    <string name=\"settings_select_sound_effect\">Ses Efekti Seç</string>\n    <string name=\"settings_sound_effect_selected\">Ses efekti dosyası seçildi</string>\n    <string name=\"settings_sound_effect_scope\">Efekt Kapsamı</string>\n    <string name=\"settings_sound_effect_scope_global\">Genel (Her Yerde)</string>\n    <string name=\"settings_sound_effect_scope_bottom_bar\">Yalnızca Alt Çubuk</string>\n    <string name=\"settings_clear_sound_effect\">Ses Efektini Temizle</string>\n    <string name=\"settings_clear_sound_effect_confirm\">Ses efektini temizlemek istediğinize emin misiniz?</string>\n    <string name=\"settings_sound_effect_cleared\">Ses efekti temizlendi</string>\n\n    <string name=\"settings_startup_sound\">Başlangıç Sesi</string>\n    <string name=\"settings_startup_sound_summary\">Uygulama başlatıldığında ses çal</string>\n    <string name=\"settings_startup_sound_enabled\">Başlangıç Sesi Etkin</string>\n    <string name=\"settings_startup_sound_playing\">Çalıyor: %s</string>\n    <string name=\"settings_select_startup_sound\">Başlangıç Sesi Seç</string>\n    <string name=\"settings_startup_sound_selected\">Başlangıç sesi seçildi</string>\n    <string name=\"settings_clear_startup_sound\">Başlangıç Sesini Temizle</string>\n    <string name=\"settings_clear_startup_sound_confirm\">Başlangıç sesini temizlemek istediğinize emin misiniz?</string>\n    <string name=\"settings_startup_sound_cleared\">Başlangıç sesi temizlendi</string>\n\n    <!-- Backup Settings -->\n    <string name=\"settings_category_backup\">Yedekleme</string>\n    <string name=\"settings_enable_cloud_backup\">Bulut Yedeklemeyi Etkinleştir</string>\n    <string name=\"settings_enable_cloud_backup_summary\">Flashlanan modülleri otomatik olarak bulut servisine yedekle</string>\n    <string name=\"settings_configure_webdav\">WebDAV Servisini Yapılandır</string>\n    <string name=\"webdav_config_title\">WebDAV Yapılandırması</string>\n    <string name=\"webdav_url\">WebDAV URL</string>\n    <string name=\"webdav_username\">Kullanıcı adı</string>\n    <string name=\"webdav_password\">Parola</string>\n    <string name=\"webdav_test_success\">Test Başarılı</string>\n    <string name=\"webdav_test_failed\">Test Başarısız: %s</string>\n    <string name=\"webdav_uploading\">WebDAV’a yükleniyor...</string>\n    <string name=\"webdav_backup_success\">WebDAV Yedekleme Başarılı</string>\n    <string name=\"webdav_backup_failed\">WebDAV Yedekleme Başarısız: %s</string>\n    <string name=\"save\">Kaydet</string>\n    <string name=\"test\">Test</string>\n    <string name=\"webdav_path_label\">Yol (örn. /Backup)</string>\n    <string name=\"webdav_view_logs\">Günlükleri Görüntüle</string>\n    <string name=\"webdav_backup_logs_title\">Yedekleme Günlükleri</string>\n    <string name=\"webdav_no_logs\">Günlük bulunamadı.</string>\n    <string name=\"webdav_clear_logs\">Temizle</string>\n    <string name=\"settings_enable_local_backup\">Yerel Yedeklemeyi Etkinleştir</string>\n    <string name=\"settings_enable_local_backup_summary\">Modülleri yerel depolamaya yedekle (Downloads/FolkPatch/ModuleBackups)</string>\n    <string name=\"close\">Kapat</string>\n\n\n\n    <string name=\"script_library\">Betik Kütüphanesi</string>\n    <string name=\"script_library_title\">Betik Kütüphanesi</string>\n    <string name=\"script_library_empty\">Betik yok, eklemek için ekle düğmesine tıklayın</string>\n    <string name=\"script_library_add\">Betik Ekle</string>\n    <string name=\"script_library_add_title\">Betik Ekle</string>\n    <string name=\"script_library_select_file\">Betik Dosyası Seç</string>\n    <string name=\"script_library_alias\">Takma Ad</string>\n    <string name=\"script_library_alias_hint\">Betik takma adını girin (isteğe bağlı)</string>\n    <string name=\"script_library_run\">Çalıştır</string>\n    <string name=\"script_library_delete\">Sil</string>\n    <string name=\"script_library_path\">Yol: %s</string>\n    <string name=\"script_library_confirm_delete\">Betiği silmeyi onaylıyor musunuz?</string>\n    <string name=\"script_library_delete_success\">Betik başarıyla silindi</string>\n    <string name=\"script_library_delete_failed\">Betik silinemedi</string>\n    <string name=\"script_library_add_success\">Betik başarıyla eklendi</string>\n    <string name=\"script_library_add_failed\">Betik eklenemedi: %s</string>\n    <string name=\"script_library_load_failed\">Betik kütüphanesi yüklenemedi</string>\n    <string name=\"script_library_output\">Çalıştırma Çıktısı</string>\n    <string name=\"script_library_no_output\">Çıktı yok</string>\n    <string name=\"script_library_save_log\">Günlüğü Kaydet</string>\n    <string name=\"script_library_log_saved\">Günlük şuraya kaydedildi: %s</string>\n    <string name=\"script_library_log_save_failed\">Günlük kaydedilemedi: %s</string>\n    <string name=\"settings_vibration\">Titreşim ve Dokunma</string>\n    <string name=\"settings_vibration_summary\">Dokunma olaylarında titret</string>\n    <string name=\"settings_vibration_enabled\">Titreşimi etkinleştir</string>\n    <string name=\"settings_vibration_intensity\">Titreşim yoğunluğu</string>\n    <string name=\"settings_vibration_scope\">Titreşim kapsamı</string>\n    <string name=\"settings_vibration_scope_global\">Küresel (Her yer)</string>\n    <string name=\"settings_vibration_scope_bottom_bar\">Sadece alt çubuk</string>\n    <string name=\"search_modules\">Modülleri ara...</string>\n    <string name=\"search_scripts\">Script ara...</string>\n\n    <!-- Umount Service -->\n    <string name=\"settings_umount_service\">Umount Service</string>\n    <string name=\"settings_umount_service_summary\">Önyükleme sırasında otomatik olarak bağlantısı kesilecek bağlama noktalarını yapılandırın</string>\n    <string name=\"umount_config_title\">Umount Yapılandırması</string>\n    <string name=\"umount_config_enabled\">Umount\\'u Etkinleştir</string>\n    <string name=\"umount_config_enabled_summary\">Önyükleme sırasında belirtilen bağlama noktalarını otomatik olarak bağlantıyı kes</string>\n    <string name=\"umount_config_paths_label\">Bağlama Noktası Yolları (satır başına bir)</string>\n    <string name=\"umount_config_paths_placeholder\">/system/vendor/app\\n/system/vendor/priv-app</string>\n    <string name=\"umount_config_paths_helper\">Bağlantısı kesilecek her satıra bir bağlama noktası yolu girin</string>\n    <string name=\"umount_config_save\">Kaydet</string>\n    <string name=\"umount_config_save_confirm\">Yapılandırmayı kaydetmeyi onaylıyor musunuz?</string>\n    <string name=\"umount_config_save_success\">Yapılandırma başarıyla kaydedildi</string>\n    <string name=\"umount_config_save_failed\">Yapılandırma kaydedilemedi</string>\n\n    <!-- My Themes Page -->\n    <string name=\"my_themes_title\">Temalarım</string>\n    <string name=\"my_themes_empty\">Henüz tema yok</string>\n    <string name=\"my_themes_empty_action\">Tema Mağazasına Göz At</string>\n    <string name=\"my_themes_apply\">Temayı Uygula</string>\n    <string name=\"my_themes_delete\">Temayı Sil</string>\n    <string name=\"my_themes_delete_confirm\">Bu temayı silmek istediğinizden emin misiniz?</string>\n    <string name=\"my_themes_deleted\">Tema silindi</string>\n    <string name=\"my_themes_applied\">Tema uygulandı</string>\n    <string name=\"my_themes_apply_failed\">Tema uygulanamadı</string>\n    <string name=\"my_themes_details\">Tema Detayları</string>\n\n    <!-- İndirme Diyaloğu -->\n    <string name=\"theme_download_title\">Tema İndiriliyor</string>\n    <string name=\"theme_download_completed\">İndirme tamamlandı</string>\n    <string name=\"theme_download_failed\">İndirme başarısız</string>\n    <string name=\"theme_download_progress\">İlerleme</string>\n    <string name=\"theme_download_file\">Tema Dosyası</string>\n    <string name=\"theme_download_image\">Önizleme Resmi</string>\n    <string name=\"theme_download_cancel\">İptal</string>\n    <string name=\"theme_download_pause\">Duraklat</string>\n    <string name=\"theme_download_resume\">Devam Et</string>\n    <string name=\"theme_download_retry\">Yeniden Dene</string>\n    <string name=\"theme_download_apply\">Temayı Uygula</string>\n    <string name=\"theme_download_go_to_my_themes\">Temalarım</string>\n    <string name=\"theme_download_retrying\">Yeniden deneniyor (%d/3)...</string>\n    <string name=\"theme_download_preparing\">İndirme hazırlanıyor...</string>\n    <string name=\"theme_download_downloading_file\">Tema dosyası indiriliyor...</string>\n    <string name=\"theme_download_downloading_image\">Önizleme resmi indiriliyor...</string>\n    <string name=\"theme_download_finalizing\">Tamamlanıyor...</string>\n    <string name=\"settings_predictive_back\">Tahmin Edici Geri Hareketi</string>\n    <string name=\"settings_predictive_back_summary\">Android 14+ tahmin edici geri hareketi animasyonunu etkinleştir</string>\n\n    <string name=\"home_device_status_battery_charging\">Şarj Oluyor</string>\n    <string name=\"home_device_status_cpu_temp\">CPU Sıcaklığı</string>\n    <string name=\"home_device_status_memory_trend\">Bellek Trendi</string>\n    <string name=\"home_storage_partitions\">Depolama Bölümleri</string>\n    <string name=\"home_device_status_cpu_freq\">CPU Frekansı</string>\n    <string name=\"home_network_rx\">İndirme</string>\n    <string name=\"home_network_tx\">Yükleme</string>\n    <string name=\"settings_home_layout_stats\">StatsUI</string>\n    <string name=\"home_stats_more_options\">Diğer seçenekler</string>\n    <string name=\"home_stats_kp_label\">KP</string>\n    <string name=\"home_stats_manager_label\">Yönetici</string>\n    <string name=\"home_device_status_gpu_load\">GPU</string>\n    <string name=\"home_stats_kernel_modules\">Çekirdek Modüller</string>\n    <string name=\"home_stats_apm_modules\">Sistem Modülleri</string>\n    <string name=\"home_stats_superusers\">Süper Kullanıcılar</string>\n    <string name=\"settings_kernel_spoof\">Çekirdek Sahtekarlık Yapılandırması</string>\n    <string name=\"settings_kernel_spoof_summary\">Çekirdek sürümünü ve yapım zamanını sahtele</string>\n    <string name=\"settings_kernel_spoof_version\">Çekirdek Sürümü</string>\n    <string name=\"settings_kernel_spoof_build_time\">Çekirdek Yapım Zamanı</string>\n    <string name=\"settings_kernel_spoof_restore\">Geri Yükle</string>\n\n    <string name=\"kernel_spoof_enabled\">Çekirdek sahtekarlığı etkinleştirildi</string>\n    <string name=\"kernel_spoof_disabled_restored\">Çekirdek sahtekarlığı devre dışı bırakıldı ve geri yüklendi</string>\n    <string name=\"kernel_spoof_failed\">Çekirdek sahtekarlığı başarısız: %d</string>\n    <string name=\"kernel_spoof_applied\">Çekirdek sahtekarlığı uygulandı</string>\n\n    <string name=\"settings_path_hide\">Yolu Gizle</string>\n    <string name=\"settings_path_hide_summary\">Dosya ve dizinleri uygulamalardan çekirdek seviyesinde gizle</string>\n    <string name=\"path_hide_paths_label\">Gizli Yollar (her satıra bir tane)</string>\n\n    <string name=\"path_hide_paths_helper\">Her satıra bir yol girin. Eşleşen yollar ENOENT döndürecektir.</string>\n    <string name=\"path_hide_save\">Kaydet</string>\n    <string name=\"path_hide_enabled\">Yol gizleme etkinleştirildi</string>\n    <string name=\"path_hide_disabled\">Yol gizleme devre dışı bırakıldı</string>\n    <string name=\"path_hide_applied\">Yol gizleme yapılandırması uygulandı</string>\n    <string name=\"path_hide_failed\">Yol gizleme işlemi başarısız: %d</string>\n    <string name=\"path_hide_uid_mode\">UID Yürütme Modu</string>\n    <string name=\"path_hide_uid_mode_summary\">Yolları yalnızca belirli uygulama UID\\'leri için gizle</string>\n    <string name=\"path_hide_uids_label\">Hedef UID\\'ler (her satıra bir tane)</string>\n    <string name=\"path_hide_uids_helper\">Uygulama UID\\'lerini girin. Yalnızca bu uygulamaların yolları gizlenir.</string>\n    <string name=\"path_hide_uid_save\">UID\\'leri Kaydet</string>\n    <string name=\"path_hide_uid_mode_enabled\">UID yürütme modu etkinleştirildi</string>\n    <string name=\"path_hide_uid_mode_disabled\">UID yürütme modu devre dışı bırakıldı</string>\n    <string name=\"path_hide_filter_system\">Sistem UID\\'lerini Filtrele</string>\n    <string name=\"path_hide_filter_system_summary\">Yol ayrıca root ve sistem süreçlerinden (UID &lt; 10000) gizlensin</string>\n    <string name=\"path_hide_filter_system_enabled\">Sistem UID filtreleme etkinleştirildi</string>\n    <string name=\"path_hide_filter_system_disabled\">Sistem UID filtreleme devre dışı bırakıldı</string>\n    <string name=\"path_hide_filter_system_warning_title\">Uyarı</string>\n    <string name=\"path_hide_filter_system_warning_message\">Bunu etkinleştirmek, root ve sistem süreçlerinden (UID &lt; 10000) yolları da gizleyecektir. Bazı sistem özelliklerinin düzgün çalışmamasına neden olabilir. Dikkatli olun.</string>\n    <string name=\"path_hide_uid_applied\">UID beyaz liste uygulandı</string>\n    <string name=\"path_hide_select_apps\">Uygulama seç</string>\n    <string name=\"path_hide_no_apps_selected\">Hiçbir uygulama seçilmedi</string>\n    <string name=\"path_hide_app_removed\">%d kaldırılan uygulamanın yapılandırması temizlendi</string>\n    <string name=\"path_hide_search_apps\">Uygulama ara…</string>\n    <string name=\"path_hide_show_system\">Sistem uygulamalarını göster</string>\n    <string name=\"netisolate_title\">Ağ İzolasyonu</string>\n    <string name=\"netisolate_enable\">Ağ İzolasyonu etkinleştirildi</string>\n    <string name=\"netisolate_disable\">Ağ İzolasyonu devre dışı bırakıldı</string>\n    <string name=\"netisolate_enable_summary\">Seçilen uygulamaların ağ erişimini çekirdek düzeyinde engelle</string>\n    <string name=\"netisolate_uids_label\">Engellenen Uygulamalar</string>\n    <string name=\"netisolate_uids_hint\">Engellenecek UID\\'leri girin (satır başına bir)</string>\n    <string name=\"netisolate_no_uids\">Engellenen uygulama yok</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-vi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\" translatable=\"false\">FolkPatch</string>\n    <string name=\"app_name_fpatch\">FPatch</string>\n\n    <string name=\"desktop_app_name\">Tên ứng dụng máy tính để bàn</string>\n\n    <string name=\"home\">Trang chủ</string>\n\n    <string name=\"success\">Thành công</string>\n    <string name=\"failure\">Thất bại</string>\n\n    <string name=\"patch_warnning\">Việc vá có rủi ro, vui lòng đảm bảo bạn đã sao lưu dữ liệu.</string>\n    <string name=\"patch\">Vá</string>\n\n    <string name=\"kernel_patch\">N bản vá nhân</string>\n    <string name=\"android_patch\">N bản vá hệ thống</string>\n\n    <string name=\"settings_nav_layout_title\">Cài đặt bố cục điều hướng</string>\n    <string name=\"settings_nav_layout_summary\">Ẩn hoặc hiển thị một số thành phần điều hướng</string>\n    <string name=\"settings_nav_scheme\">Phương thức điều hướng</string>\n    <string name=\"settings_nav_mode\">Chế độ thanh điều hướng</string>\n    <string name=\"settings_nav_mode_summary\">Chọn cách hiển thị thanh điều hướng</string>\n    <string name=\"settings_nav_mode_auto\">Tự động Truyền thống</string>\n    <string name=\"settings_nav_mode_bottom\">Luôn ở thanh dưới</string>\n    <string name=\"settings_nav_mode_rail\">Luôn ở thanh bên</string>\n    <string name=\"settings_nav_mode_floating\">Thanh dưới nổi</string>\n    <string name=\"settings_show_apm\">Hiển thị mô-đun hệ thống</string>\n    <string name=\"settings_show_kpm\">Hiển thị KPM</string>\n    <string name=\"settings_show_superuser\">Hiển thị SuperUser</string>\n    <string name=\"settings_floating_auto_hide\">Tự động ẩn thanh nổi</string>\n    <string name=\"settings_floating_auto_hide_summary\">Tự động ẩn thanh nổi sau 3 giây không hoạt động</string>\n    <string name=\"settings_floating_swipe_hide\">Vuốt để ẩn thanh nổi</string>\n    <string name=\"settings_floating_swipe_hide_summary\">Ẩn thanh khi vuốt xuống, hiện khi vuốt lên</string>\n    <string name=\"settings_navbar_glass_effect\">Hiệu ứng kính mờ</string>\n    <string name=\"settings_navbar_glass_effect_summary\">Áp dụng hiệu ứng kính mờ cho thanh điều hướng nổi</string>\n    <string name=\"settings_navbar_glass_blur_strength\">Độ làm mờ</string>\n    <string name=\"settings_navbar_glass_transparency\">Độ trong suốt của nền</string>\n    <string name=\"settings_navbar_glass_highlight_strength\">Cường độ phản chiếu</string>\n    <string name=\"settings_navbar_glass_specular\">Phản chiếu gương</string>\n    <string name=\"settings_navbar_glass_specular_summary\">Bật hiệu ứng phản chiếu gương ở phần trên thanh điều hướng</string>\n    <string name=\"settings_navbar_glass_inner_glow\">Phát sáng bên trong</string>\n    <string name=\"settings_navbar_glass_inner_glow_summary\">Bật hiệu ứng phát sáng nhẹ ở phần dưới thanh điều hướng</string>\n    <string name=\"settings_navbar_glass_border\">Viền kính</string>\n    <string name=\"settings_navbar_glass_border_summary\">Bật viền tinh tế xung quanh thanh điều hướng</string>\n    <string name=\"settings_stats_top_layout\">Bố cục trên</string>\n    <string name=\"settings_stats_top_layout_summary\">Chọn kiểu thẻ thông tin trên</string>\n    <string name=\"settings_stats_top_layout_list\">Danh sách</string>\n    <string name=\"settings_stats_top_layout_grid\">Lưới</string>\n    <string name=\"settings_block_kernelpatch_update\">Chặn cập nhật KernelPatch</string>\n    <string name=\"settings_block_kernelpatch_update_summary\">Không hiển thị thông báo cập nhật KernelPatch</string>\n    <string name=\"settings_block_androidpatch_update\">Chặn cập nhật bản vá hệ thống</string>\n    <string name=\"settings_block_androidpatch_update_summary\">Không hiển thị thông báo cập nhật bản vá hệ thống (APD)</string>\n    <string name=\"settings_disable_module_update_check\">Vô hiệu hóa kiểm tra cập nhật module</string>\n    <string name=\"settings_disable_module_update_check_summary\">Vô hiệu hóa kiểm tra cập nhật tự động cho module hệ thống</string>\n\n    <string name=\"reboot\">Khởi động lại</string>\n    <string name=\"settings\">Cài đặt</string>\n    <string name=\"reboot_recovery\">Khởi động lại vào Recovery</string>\n    <string name=\"reboot_bootloader\">Khởi động lại vào Bootloader</string>\n    <string name=\"reboot_download\">Khởi động lại vào Chế độ Tải về</string>\n    <string name=\"reboot_edl\">Khởi động lại vào EDL</string>\n    <string name=\"reboot_fastbootd\">Khởi động lại vào FastbootD</string>\n    <string name=\"about\">Về</string>\n    <string name=\"developer_and_maintainer\">Nhà phát triển | Người bảo trì</string>\n    <string name=\"settings_app_language\">Ngôn ngữ</string>\n    <string name=\"system_default\">Mặc định hệ thống</string>\n    <string name=\"settings_global_namespace_mode\">Chế độ không gian tên toàn cục</string>\n    <string name=\"settings_global_namespace_mode_summary\">Tất cả phiên làm việc root sử dụng không gian tên gắn kết toàn cục</string>\n    <string name=\"settings_clear_super_key_dialog\">Bạn có chắc chắn muốn tiếp tục?</string>\n\n    <string name=\"home_learn_apatch\">Tìm hiểu về FolkPatch</string>\n    <string name=\"home_click_to_learn_apatch\">Tìm hiểu về chức năng và cách sử dụng FolkPatch</string>\n    <string name=\"settings_hide_apatch_card\">Ẩn thẻ \"Tìm hiểu về FolkPatch\"</string>\n    <string name=\"settings_hide_apatch_card_summary\">Ẩn thẻ \"Tìm hiểu về FolkPatch\" trên trang chủ</string>\n\n    <string name=\"about_source_code\"><![CDATA[<p>Xem mã nguồn tại %1$s<p/>Tham gia kênh %2$s của chúng tôi<p/>Tham gia nhóm %3$s của chúng tôi]]></string>\n    <string name=\"send_log\">Gửi nhật ký</string>\n    <string name=\"save_log\">Lưu nhật ký</string>\n    <string name=\"log_saved\">Nhật ký đã được lưu</string>\n    <string name=\"safe_mode\">Chế độ an toàn</string>\n    <string name=\"github\">GitHub</string>\n    <string name=\"telegram\">Telegram</string>\n    <string name=\"support_or_donate\">Hỗ trợ / Quyên góp</string>\n\n    <string name=\"super_key\">Khóa siêu cấp</string>\n    <string name=\"clear_super_key\">Xóa khóa siêu cấp</string>\n    <string name=\"patch_set_superkey\">Đặt khóa siêu cấp</string>\n    <string name=\"home_patch_set_key_desc\">Bản ghi chứng thực duy nhất của KernelPatch</string>\n    <string name=\"home_patch_next_step\">Bước tiếp theo</string>\n\n    <string name=\"home_not_installed\">Chưa cài đặt</string>\n    <string name=\"home_install_unknown\">Chưa cài đặt hoặc chưa xác thực</string>\n    <string name=\"home_install_unknown_summary\">Nhấn để cài đặt</string>\n    <string name=\"home_click_to_install\">Nhấn để cài đặt</string>\n    <string name=\"home_working\">Đang hoạt động</string>\n    <string name=\"home_kp_need_update\">Có phiên bản mới có sẵn</string>\n    <string name=\"home_kp_cando_update\">Cập nhật</string>\n\n    <string name=\"home_installing\">Đang cài đặt</string>\n\n    <string name=\"kpatch_version\">Phiên bản: %s</string>\n    <string name=\"apatch_version\">Phiên bản: %s</string>\n\n    <string name=\"kpatch_version_update\" formatted=\"false\">Phiên bản: %s -&gt; %s</string>\n    <string name=\"kpatch_shadow_path_title\">kpatch</string>\n    <string name=\"home_auth_key_title\">Nhập khóa siêu cấp</string>\n    <string name=\"home_auth_key_desc\">Xác thực để bắt đầu sử dụng</string>\n    <string name=\"home_kpatch_info_title\">Thông tin</string>\n\n    <string name=\"home_ap_cando_install\">Cài đặt</string>\n\n    <string name=\"home_ap_cando_uninstall\">Gỡ cài đặt</string>\n    <string name=\"home_ap_cando_reboot\">Khởi động lại</string>\n\n    <string name=\"patch_title\">Vá</string>\n\n    <string name=\"patch_config_title\">N bản vá</string>\n    <string name=\"patch_mode_bootimg_patch\">Chế độ: Vá</string>\n    <string name=\"patch_mode_patch_and_install\">Chế độ: Vá và cài đặt</string>\n    <string name=\"patch_mode_install_to_next_slot\">Chế độ: Cài đặt vào slot chưa kích hoạt (sau OTA)</string>\n    <string name=\"patch_mode_uninstall_patch\">Chế độ: Gỡ cài đặt KPatch</string>\n    <string name=\"patch_select_bootimg_btn\">Chọn ảnh đệm</string>\n    <string name=\"patch_embed_kpm_btn\">Nhúng KPM</string>\n    <string name=\"patch_start_patch_btn\">Bắt đầu</string>\n    <string name=\"patch_start_unpatch_btn\">Khôi phục</string>\n    <string name=\"patch_item_error\">!!Lỗi!!</string>\n    <string name=\"patch_item_bootimg\">bootimg</string>\n    <string name=\"patch_item_bootimg_slot\">Slot:</string>\n    <string name=\"patch_item_bootimg_dev\">Thiết bị:</string>\n    <string name=\"patch_item_kernel\">Nhân</string>\n    <string name=\"patch_item_kpimg\">kpimg</string>\n    <string name=\"patch_item_kpimg_version\">Phiên bản:</string>\n    <string name=\"patch_item_kpimg_comile_time\">Thời gian:</string>\n    <string name=\"patch_item_kpimg_config\">Cấu hình:</string>\n    <string name=\"patch_item_new_extra_kpm\">Nhúng module mới</string>\n    <string name=\"patch_item_existed_extra_kpm\">Đã tồn tại</string>\n    <string name=\"patch_item_extra_name\">Tên:</string>\n    <string name=\"patch_item_extra_version\">Phiên bản:</string>\n    <string name=\"patch_item_extra_author\">Tác giả:</string>\n    <string name=\"patch_item_extra_kpm_license\">Giấy phép:</string>\n    <string name=\"patch_item_extra_kpm_desciption\">Mô tả:</string>\n    <string name=\"patch_item_extra_args\">Tham số:</string>\n    <string name=\"patch_item_extra_event\">Sự kiện:</string>\n    <string name=\"patch_item_skey\">Khóa siêu cấp</string>\n    <string name=\"patch_custom_superkey\">Khóa siêu cấp tùy chỉnh</string>\n    <string name=\"patch_custom_superkey_summary\">Bạn vẫn có thể đặt khóa siêu cấp để quản lý Root và kernel, trình quản lý đã được ủy quyền bằng chữ ký tích hợp</string>\n    <string name=\"patch_item_set_skey_label\">Độ dài khóa siêu cấp phải từ 8-63 ký tự, bao gồm chữ số và chữ cái, không chứa ký tự đặc biệt.</string>\n    <string name=\"patch_confirm_superkey\">Xác nhận khóa siêu cấp</string>\n    <string name=\"patch_skey_mismatch\">Khóa siêu cấp không khớp</string>\n\n    <string name=\"home_kernel\">Phiên bản nhân</string>\n    <string name=\"home_manager_version\">Phiên bản trình quản lý</string>\n    <string name=\"home_fingerprint\">Bản in vân tay</string>\n\n    <string name=\"home_selinux_status\">Trạng thái SELinux</string>\n    <string name=\"home_selinux_status_disabled\">Đã vô hiệu hóa</string>\n    <string name=\"home_selinux_status_enforcing\">Thực thi</string>\n    <string name=\"home_selinux_status_permissive\">Chế độ khoan dung</string>\n    <string name=\"home_selinux_status_unknown\">Không xác định</string>\n\n    <string name=\"settings_selinux_mode\">Chế độ SELinux</string>\n    <string name=\"settings_selinux_mode_summary\">Chọn chế độ thực thi SELinux</string>\n    <string name=\"settings_selinux_mode_enforcing\">Thực thi (Nghiêm ngặt)</string>\n    <string name=\"settings_selinux_mode_permissive\">Khoan dung (Nới lỏng)</string>\n    <string name=\"settings_selinux_current_mode\">Hiện tại: %s</string>\n    <string name=\"settings_selinux_mode_enforcing_summary\">SELinux thực thi đầy đủ các quy tắc truy cập</string>\n    <string name=\"settings_selinux_mode_permissive_summary\">SELinux chỉ ghi lại vi phạm, không từ chối</string>\n\n    <string name=\"home_device_info\">Thiết bị</string>\n    <string name=\"home_system_version\">Phiên bản hệ thống</string>\n    <string name=\"home_kpatch_version\">Phiên bản bản vá nhân</string>\n    <string name=\"home_su_path\">Tệp thực thi su</string>\n    <string name=\"home_apatch_version\">Phiên bản FolkPatch</string>\n\n    <string name=\"kpm\">Module nhân</string>\n    <string name=\"kpm_kp_not_installed\">Chưa cài đặt bản vá nhân</string>\n    <string name=\"kpm_add_kpm\">Thêm KPM</string>\n    <string name=\"kpm_load\">Tải</string>\n    <string name=\"kpm_install\">Cài đặt</string>\n    <string name=\"kpm_embed\">Nhúng</string>\n    <string name=\"kpm_load_toast_succ\">Tải thành công</string>\n    <string name=\"kpm_load_toast_failed\">Tải thất bại</string>\n    <string name=\"kpm_unload_confirm\">Bạn có muốn gỡ module %s không?</string>\n    <string name=\"kpm_unload\">Gỡ tải</string>\n    <string name=\"kpm_control\">Kiểm soát</string>\n    <string name=\"kpm_apm_empty\">Chưa tải module</string>\n    <string name=\"kpm_version\">Phiên bản</string>\n    <string name=\"kpm_license\">Giấy phép</string>\n    <string name=\"kpm_author\">Tác giả</string>\n    <string name=\"kpm_desc\">Mô tả</string>\n    <string name=\"kpm_args\">Tham số</string>\n\n    <string name=\"su_title\">Người dùng siêu cấp</string>\n    <string name=\"su_selinux_via_hook\">Bỏ qua qua hook</string>\n    <string name=\"su_pkg_excluded_label\">Loại trừ</string>\n    <string name=\"su_batch_exclude_title\">Loại trừ hàng loạt</string>\n    <string name=\"su_batch_exclude_content\">Loại trừ chèn cho tất cả phần mềm không có quyền ROOT, vui lòng chọn một hành động</string>\n    <string name=\"su_exclude_btn\">Loại trừ</string>\n    <string name=\"su_exclude_reverse_btn\">Loại trừ ngược</string>\n\n    <!-- SuperUser App Action Dialog -->\n    <string name=\"su_app_action_title\">Thao tác ứng dụng</string>\n    <string name=\"su_app_action_content\">Vui lòng chọn một hành động để thực hiện trên ứng dụng</string>\n    <string name=\"su_app_action_launch\">Khởi chạy ứng dụng</string>\n    <string name=\"su_app_action_force_stop\">Buộc dừng</string>\n    <string name=\"su_app_action_launch_success\">Đang khởi chạy %s</string>\n    <string name=\"su_app_action_force_stop_success\">Đã buộc dừng %s</string>\n    <string name=\"su_app_action_failed\">Thao tác thất bại: %s</string>\n\n    <!-- SU Audit Log -->\n    <string name=\"su_audit_log_title\">Nhật ký ủy quyền</string>\n    <string name=\"su_audit_log_empty\">Chưa có bản ghi ủy quyền</string>\n    <string name=\"su_audit_log_clear\">Xóa nhật ký</string>\n    <string name=\"su_audit_log_clear_confirm\">Xác nhận xóa tất cả bản ghi ủy quyền?</string>\n    <string name=\"su_audit_action_grant\">Đã cấp quyền</string>\n    <string name=\"su_audit_action_revoke\">Đã thu hồi</string>\n    <string name=\"su_audit_action_exclude\">Đã loại trừ</string>\n    <string name=\"su_audit_tab_usage\">Nhật ký sử dụng</string>\n    <string name=\"su_audit_tab_operations\">Nhật ký thao tác</string>\n\n    <string name=\"su_pkg_excluded_setting_title\">Chỉnh sửa loại trừ</string>\n    <string name=\"su_pkg_excluded_setting_summary\">Bật tùy chọn này sẽ cho phép FolkPatch khôi phục mọi tệp đã được module ứng dụng này sửa đổi。</string>\n    <string name=\"su_pkg_root_setting_title\">Superuser</string>\n    <string name=\"su_pkg_root_setting_summary\">Bật tùy chọn này để cấp quyền superuser cho ứng dụng của bạn, ứng dụng có thể sử dụng lệnh SU</string>\n    <string name=\"su_pkg_normal_setting_title\">Chế độ thường</string>\n    <string name=\"su_pkg_normal_setting_summary\">Không cấp quyền root, ứng dụng chạy không có đặc quyền superuser.</string>\n    <string name=\"su_show_system_apps\">Hiển thị ứng dụng hệ thống</string>\n    <string name=\"su_hide_system_apps\">Ẩn ứng dụng hệ thống</string>\n    <string name=\"su_refresh\">Làm mới</string>\n\n    <string name=\"apm\">Module hệ thống</string>\n    <string name=\"apm_not_installed\">Chưa cài đặt bản vá hệ thống</string>\n    <string name=\"apm_failed_to_enable\">Không thể bật module: %s</string>\n    <string name=\"apm_failed_to_disable\">Không thể vô hiệu hóa module: %s</string>\n    <string name=\"apm_empty\">Chưa cài đặt module</string>\n    <string name=\"apm_remove\">Xóa</string>\n    <string name=\"apm_undo\">Hoàn tác</string>\n    <string name=\"apm_install\">Cài đặt</string>\n    <string name=\"apm_uninstall_confirm\">Bạn có muốn gỡ cài đặt module %s không?</string>\n    <string name=\"apm_uninstall_success\">%s đã được gỡ cài đặt</string>\n    <string name=\"apm_uninstall_failed\">Gỡ cài đặt thất bại: %s</string>\n    <string name=\"apm_undo_uninstall_success\">Đã khôi phục %s</string>\n    <string name=\"apm_undo_uninstall_failed\">Khôi phục thất bại: %s</string>\n    <string name=\"apm_version\">Phiên bản</string>\n    <string name=\"apm_author\">Tác giả</string>\n    <string name=\"apm_desc\">Mô tả</string>\n    <string name=\"apm_overlay_fs_not_available\">OverlayFS bị nhân vô hiệu hóa, module không khả dụng!</string>\n    <string name=\"apm_magisk_conflict\">Module không khả dụng do xung đột với Magisk!</string>\n    <string name=\"apm_mount_warning_title\">Thông báo quan trọng</string>\n    <string name=\"apm_mount_warning_message\">Theo mặc định, module không được gắn kết. Vui lòng sử dụng hệ thống gắn kết tích hợp hoặc metamodule.</string>\n    <string name=\"apm_mount_warning_button\">Đã hiểu</string>\n    <string name=\"apm_reboot_to_apply\">Khởi động lại để áp dụng</string>\n    <string name=\"apm_changelog\">Nhật ký cập nhật</string>\n    <string name=\"apm_update\">Cập nhật</string>\n    <string name=\"apm_downloading\">Đang tải module: %s</string>\n    <string name=\"apm_start_downloading\">Bắt đầu tải: %s</string>\n    <string name=\"apm_new_version_available\">Phiên bản mới %s có sẵn, nhấn để nâng cấp.</string>\n\n    <string name=\"hide_apatch_manager\">Ẩn trình quản lý APatch</string>\n    <string name=\"hide_apatch_manager_summary\">Cài đặt một ứng dụng proxy với tên gói ngẫu nhiên và tên ứng dụng tùy chỉnh</string>\n    <string name=\"hide_apatch_dialog_new_manager_name\">Tên trình quản lý mới</string>\n    <string name=\"hide_apatch_dialog_summary\">Đây sẽ là tên ứng dụng mới hiển thị trong trình khởi chạy</string>\n    <string name=\"hide_apatch_manager_failure\">Ẩn thất bại. Vui lòng báo cáo lỗi này!</string>\n\n    <string name=\"setting_reset_su_path\">Đặt lại đường dẫn su</string>\n    <string name=\"setting_reset_su_new_path\">Đường dẫn đầy đủ mới</string>\n\n    <string name=\"settings_folkx_engine_title\">Động cơ hoạt hình FolkX</string>\n    <string name=\"settings_folkx_engine_summary\">Sử dụng hoạt hình lò xo vật lý mượt mà khi chuyển đổi các trang cấp cao nhất</string>\n    <string name=\"settings_folkx_animation_type\">Loại hoạt hình</string>\n    <string name=\"settings_folkx_animation_speed\">Tốc độ hoạt hình</string>\n    <string name=\"settings_folkx_animation_linear\">Chuyển động tuyến tính</string>\n    <string name=\"settings_folkx_animation_spatial\">Chuyển động không gian</string>\n    <string name=\"settings_folkx_animation_fade\">Mờ dần/Hien dần</string>\n    <string name=\"settings_folkx_animation_vertical\">Trượt theo chiều dọc</string>\n    <string name=\"settings_folkx_animation_diagonal\">Trượt theo đường chéo</string>\n\n    <string name=\"apm_webui_open\">Mở</string>\n    <string name=\"apm_action\">Hành động</string>\n    <string name=\"module_shortcut_add\">Lối tắt</string>\n    <string name=\"module_shortcut_not_supported\">Bộ khởi chạy không hỗ trợ lối tắt màn hình chính.</string>\n    <string name=\"module_shortcut_created\">Đã tạo lối tắt trên màn hình chính.</string>\n    <string name=\"module_shortcut_updated\">Lối tắt đã được cập nhật.</string>\n    <string name=\"module_shortcut_permission_tip_xiaomi\">Vui lòng bật quyền \"Tạo lối tắt màn hình chính\" cho ứng dụng này trong cài đặt của Xiaomi.</string>\n    <string name=\"module_shortcut_permission_tip_oppo\">Vui lòng bật quyền \"Lối tắt màn hình chính\" cho ứng dụng này trong cài đặt của OPPO.</string>\n    <string name=\"module_shortcut_permission_tip_default\">Nếu việc tạo lối tắt không thành công, vui lòng bật quyền lối tắt màn hình chính cho ứng dụng này trong cài đặt hệ thống.</string>\n    <string name=\"module_shortcut_name\">Tên lối tắt</string>\n    <string name=\"module_shortcut_icon\">Biểu tượng lối tắt</string>\n    <string name=\"module_shortcut_type\">Loại lối tắt</string>\n    <string name=\"module_shortcut_type_webui\">WebUI</string>\n    <string name=\"module_shortcut_type_action\">Action</string>\n    <string name=\"module_shortcut_icon_default\">Sử dụng biểu tượng mặc định</string>\n    <string name=\"module_shortcut_icon_select\">Chọn biểu tượng</string>\n    <string name=\"enable_web_debugging\">Bật gỡ lỗi WebView</string>\n    <string name=\"enable_web_debugging_summary\">Có thể dùng để gỡ lỗi WebUI. Vui lòng chỉ bật khi cần.</string>\n    <string name=\"settings_apm_install_confirm\">Xác nhận cài đặt module</string>\n    <string name=\"settings_apm_install_confirm_summary\">Hiển thị hộp thoại xác nhận trước khi cài đặt module</string>\n    <string name=\"settings_apm_stay_on_page\">Ở lại trang hành động</string>\n    <string name=\"settings_apm_stay_on_page_summary\">Module hệ thống không tự trả về sau khi thực hiện hành động</string>\n    <string name=\"settings_enable_module_shortcut_add\">Bật nút thêm lối tắt</string>\n    <string name=\"settings_enable_module_shortcut_add_summary\">Hiển thị nút thêm lối tắt WebUI trên trang module hệ thống</string>\n    <string name=\"settings_module_sort_optimization\">Tối ưu hóa sắp xếp module</string>\n    <string name=\"settings_module_sort_optimization_summary\">Đưa các module hệ thống có WebUI và Action lên đầu danh sách</string>\n    <string name=\"settings_fold_system_module\">Gấp gọn module hệ thống</string>\n    <string name=\"settings_fold_system_module_summary\">Nhấp vào thẻ module để mở rộng/gộp lại các hành động</string>\n    <string name=\"settings_simple_list_bottom_bar\">Thanh dưới đơn giản</string>\n    <string name=\"settings_simple_list_bottom_bar_summary\">Sử dụng kiểu nút chỉ có biểu tượng cho các hành động module, lấy cảm hứng từ APatch</string>\n    <string name=\"settings_spliced_card_group\">Spliced Card Group</string>\n    <string name=\"settings_spliced_card_group_summary\">Use M3E continuous card group style for module list</string>\n    <string name=\"apm_install_confirm_title\">Cài đặt module</string>\n    <string name=\"apm_install_confirm_content\">Bạn có chắc chắn muốn cài đặt %s không?</string>\n    <string name=\"settings_show_more_module_info\">Hiển thị chi tiết module</string>\n    <string name=\"settings_show_more_module_info_summary\">Hiển thị ID và kích thước module trong danh sách module</string>\n    <string name=\"apm_disable_all_title\">Vô hiệu hóa tất cả module</string>\n    <string name=\"apm_disable_all_confirm\">Bạn có chắc chắn muốn vô hiệu hóa tất cả module không? Điều này sẽ vô hiệu hóa tất cả các module đã cài đặt.</string>\n    <string name=\"apm_enable_module_banner\">Bật banner module</string>\n    <string name=\"apm_enable_module_banner_summary\">Hiển thị ảnh banner cho module nếu có sẵn</string>\n    <string name=\"apm_enable_folk_banner\">Tùy chỉnh banner module</string>\n    <string name=\"apm_enable_folk_banner_summary\">Nhấn giữ thẻ module để chọn ảnh banner</string>\n    <string name=\"apm_folk_banner_title\">FolkBanner</string>\n    <string name=\"apm_folk_banner_select\">Chọn ảnh</string>\n    <string name=\"apm_folk_banner_clear\">Xóa FolkBanner</string>\n    <string name=\"apm_folk_banner_saved\">Đã thêm FolkBanner cho %s.</string>\n    <string name=\"apm_folk_banner_cleared\">Đã xóa FolkBanner cho %s.</string>\n    <string name=\"apm_folk_banner_failed\">Cập nhật FolkBanner cho %s thất bại.</string>\n    <string name=\"apm_banner_api_mode\">Chế độ API</string>\n    <string name=\"apm_banner_api_mode_summary\">Sử dụng API hình ảnh ngẫu nhiên hoặc thư mục cục bộ cho banner mô-đun</string>\n    <string name=\"apm_banner_api_source\">Cấu hình nguồn API</string>\n    <string name=\"apm_banner_api_source_hint\">Nhập URL API hoặc đường dẫn cục bộ</string>\n    <string name=\"apm_banner_api_source_saved\">Đã lưu nguồn API</string>\n    <string name=\"apm_banner_api_source_not_configured\">Chưa cấu hình</string>\n    <string name=\"apm_banner_api_url_configured\">Đã cấu hình URL API</string>\n    <string name=\"apm_banner_local_dir_configured\">Đã cấu hình thư mục cục bộ</string>\n    <string name=\"apm_banner_clear_cache\">Xóa bộ nhớ đệm</string>\n    <string name=\"apm_banner_cache_cleared\">Đã xóa bộ nhớ đệm banner</string>\n    <string name=\"apm_banner_api_config_title\">Cấu hình nguồn API</string>\n    <string name=\"apm_banner_api_config_desc\">Nhập URL API hình ảnh ngẫu nhiên hoặc đường dẫn thư mục cục bộ. Mỗi mô-đun sẽ có banner riêng.</string>\n    <string name=\"apm_banner_api_examples_title\">Ví dụ:</string>\n    <string name=\"apm_banner_api_examples\">API: https://picsum.photos/800/400\\nCục bộ: /sdcard/Pictures/Banners</string>\n    <!-- Chợ API -->\n    <string name=\"apm_api_marketplace_title\">Chợ API</string>\n    <string name=\"apm_api_preview\">Xem trước</string>\n    <string name=\"apm_api_apply\">Áp dụng</string>\n    <string name=\"apm_api_preview_title\">Xem trước API</string>\n    <string name=\"apm_api_preview_failed\">Không thể tải xem trước</string>\n    <string name=\"apm_api_url_label\">URL API:</string>\n    <string name=\"apm_api_apply_success\">Đã áp dụng nguồn API thành công</string>\n    <string name=\"apm_api_verifying\">Đang xác minh…</string>\n    <string name=\"apm_api_retry\">Thử lại</string>\n    <string name=\"apm_api_empty\">Không có nguồn API nào</string>\n    <string name=\"settings_banner_custom_opacity\">Độ mờ của banner</string>\n    <string name=\"settings_banner_custom_opacity_summary\">Sử dụng độ mờ tùy chỉnh cho banner thay vì theo chế độ hình nền</string>\n    <string name=\"settings_banner_opacity\">Độ đặc của banner</string>\n\n    <string name=\"settings_donot_store_superkey\">Không lưu khóa siêu cấp tại địa phương</string>\n    <string name=\"settings_donot_store_superkey_summary\">Xác thực khóa siêu cấp mỗi khi khởi chạy trình quản lý</string>\n\n    <string name=\"mode_select_page_title\">Cài đặt</string>\n    <string name=\"mode_select_page_patch_and_install\">Vá và cài đặt</string>\n    <string name=\"mode_select_page_select_file\">Chọn ảnh đệm để vá</string>\n    <string name=\"mode_select_page_install_inactive_slot\">Cài đặt vào slot chưa kích hoạt (sau OTA)</string>\n    <string name=\"mode_select_page_install_inactive_slot_warning\">Thiết bị của bạn SẼ BỊ BẮT BUỘC khởi động vào slot chưa kích hoạt hiện tại sau khi khởi động lại!\\nChỉ sử dụng tùy chọn này sau khi OTA hoàn tất.\\nTiếp tục?</string>\n    <string name=\"mode_select_page_select_kpimg\">Sử dụng tệp vá cục bộ (KPimg)</string>\n    <string name=\"patch_custom_kpimg_label\">KPimg tùy chỉnh</string>\n    <string name=\"patch_custom_kpimg_file\">Tệp: %s</string>\n    <string name=\"patch_select_kpimg_btn\">Chọn KPimg</string>\n\n    <string name=\"home_dialog_auth_fail_title\">Xác thực thất bại</string>\n    <string name=\"home_dialog_auth_fail_content\">Không thể xác thực khóa siêu cấp, gây ra việc kích hoạt FolkPatch thất bại.\nDưới đây là một số nguyên nhân có thể gây ra thất bại xác thực—vui lòng kiểm tra xem nguyên nhân nào áp dụng với bạn:\n\n\\n1. Bạn hoàn toàn không sử dụng KernelPatch để vá boot.img, hoặc quên những gì bạn thực sự đã làm.\n\\n2. boot.img đã vá vẫn đang nằm trên máy tính của bạn ngủ, chưa bao giờ được flash vào thiết bị.\n\\n3. Khóa siêu cấp được nhập sai, hoặc chứa các ký tự bí ẩn, như các ký tự từ ngôn ngữ ngoài hành tinh.\n\\n4. Thiết bị của bạn có thể không tương thích với FolkPatch và KernelPatch, cố gắng ép buộc là vô ích.\n\\n5. Bạn có thể đã thực hiện một số thao tác bí ẩn—như sử dụng một số mô-đun loại trừ tên gói đã chặn FolkPatch, dẫn đến việc tự loại bỏ mình khỏi trò chơi.\n\n</string>\n\n    <string name=\"home_more_menu_feedback_or_suggestion\">Phản hồi hoặc đề xuất</string>\n    <string name=\"home_more_menu_about\">Về</string>\n    <string name=\"home_more_menu_document\">Tài liệu</string>\n\n    <string name=\"home_dialog_uninstall_title\">Gỡ cài đặt</string>\n    \n    <string name=\"home_dialog_uninstall_ap_only\">Chỉ gỡ bản vá</string>\n    <string name=\"home_dialog_uninstall_ap_only_desc\">Chỉ gỡ AndroidPatch và giữ lại trình quản lý.</string>\n    <string name=\"home_dialog_uninstall_all\">Gỡ hoàn toàn</string>\n    <string name=\"home_dialog_uninstall_all_desc\">Gỡ AndroidPatch và bắt đầu quy trình gỡ cài đặt hoàn toàn.</string>\n    \n    <!-- Install Mode Select -->\n    <string name=\"kp_install_methods\">KernelPatch Vá/安装</string>\n    <string name=\"restore_boot_methods\">Chọn một boot để khôi phục vào phân vùng boot</string>\n    <string name=\"restore_select_file\">Chọn tệp boot phân vùng để khôi phục</string>\n\n    <string name=\"kpm_control_dialog_title\">Kiểm soát KPM</string>\n    <string name=\"kpm_control_dialog_content\">Vui lòng nhập tham số kiểm soát:</string>\n    <string name=\"kpm_control_paramters\">Tham số</string>\n    <string name=\"kpm_control_outMsg\">Đầu ra</string>\n    <string name=\"kpm_control_ok\">Thành công!</string>\n    <string name=\"kpm_control_failed\">Thất bại!</string>\n    \n    <string name=\"settings_night_mode_follow_sys\">Chế độ tối theo hệ thống</string>\n    <string name=\"settings_night_mode_follow_sys_summary\">Tự động chuyển đổi chế độ tối theo cài đặt hệ thống</string>\n    <string name=\"settings_night_theme_enabled\">Chế độ tối</string>\n    \n    <string name=\"settings_use_system_color_theme\">Chủ đề màu hệ thống</string>\n    <string name=\"settings_use_system_color_theme_summary\">Sử dụng chủ đề màu mà hệ thống tạo từ hình nền</string>\n    <string name=\"settings_custom_color_theme\">Chủ đề màu</string>\n\n    <string name=\"amber_theme\">Màu cam sa</string>\n    <string name=\"blue_theme\">Màu xanh dương</string>\n    <string name=\"blue_grey_theme\">Màu xám xanh dương</string>\n    <string name=\"brown_theme\">Màu nâu</string>\n    <string name=\"cyan_theme\">Màu cyan</string>\n    <string name=\"deep_orange_theme\">Màu cam đậm</string>\n    <string name=\"deep_purple_theme\">Màu tím đậm</string>\n    <string name=\"green_theme\">Màu xanh lá</string>\n    <string name=\"indigo_theme\">Màu chàm</string>\n    <string name=\"light_blue_theme\">Màu xanh dương nhạt</string>\n    <string name=\"light_green_theme\">Màu xanh lá nhạt</string>\n    <string name=\"lime_theme\">Màu chanh xanh</string>\n    <string name=\"orange_theme\">Màu cam</string>\n    <string name=\"pink_theme\">Màu hồng</string>\n    <string name=\"purple_theme\">Màu tím</string>\n    <string name=\"red_theme\">Màu đỏ</string>\n    <string name=\"sakura_theme\">Màu hồng anh đào</string>\n    <string name=\"teal_theme\">Màu xanh ngọc</string>\n    <string name=\"yellow_theme\">Màu vàng</string>\n    <string name=\"theme_color\">Màu chủ đề</string>\n    <string name=\"theme_light\">Sáng</string>\n    <string name=\"theme_dark\">Tối</string>\n    <string name=\"theme_system\">Hệ thống</string>\n    <string name=\"loading_modules\">Đang tải mô-đun…</string>\n    <string name=\"loading_scripts\">Đang tìm kiếm tập lệnh…</string>\n    <string name=\"loading_themes\">Đang tải chủ đề…</string>\n    <string name=\"loading_apis\">Đang tải nguồn API…</string>\n\n    <string name=\"about_app_desc\">Cài đặt Root dựa trên KernelPatch, cho phép hook hàm nhân mà không cần biên dịch lại nhân.</string>\n    <string name=\"about_powered_by\">Được hỗ trợ bởi %1$s</string>\n    <string name=\"about_telegram_group\">Nhóm Telegram</string>\n    \n    <!-- Online Module Strings -->\n    <string name=\"online_module_title\">Module hệ thống trực tuyến</string>\n    <string name=\"online_module_download_start\">Bắt đầu tải: %s</string>\n    <string name=\"online_module_download_complete\">Tải hoàn tất: %s</string>\n    <string name=\"online_module_download_notification\">Đang tải %s. Vui lòng xem thanh thông báo để biết tiến độ, vui lòng xem thư mục tải sau khi tải hoàn tất.</string>\n\n    <!-- Online KPM Strings -->\n    <string name=\"online_kpm_title\">Module nhân trực tuyến</string>\n    <string name=\"online_kpm_download_start\">Bắt đầu tải: %s</string>\n    <string name=\"online_kpm_download_complete\">Tải hoàn tất: %s</string>\n    <string name=\"online_kpm_download_notification\">Đang tải %s. Vui lòng xem thanh thông báo để biết tiến độ, vui lòng xem thư mục tải sau khi tải hoàn tất.</string>\n\n    <!-- Online Script Strings -->\n    <string name=\"online_script_title\">Kịch bản trực tuyến</string>\n    <string name=\"online_script_download_start\">Bắt đầu tải: %s</string>\n    <string name=\"online_script_download_complete\">Tải hoàn tất: %s</string>\n    <string name=\"online_script_download_notification\">Đang tải %s</string>\n\n    <!-- Crash Handle Strings -->\n    <string name=\"crash_handle_title\">Báo cáo lỗi sập</string>\n    <string name=\"crash_handle_copy\">Sao chép</string>\n\n    <!-- About Screen Strings -->\n    <string name=\"about_app_version\">Phiên bản ứng dụng: %s</string>\n    <string name=\"about_github\">GitHub</string>\n    <string name=\"about_telegram_channel\">Kênh Telegram</string>\n\n    <string name=\"settings_app_dpi\">DPI ứng dụng</string>\n    <string name=\"dpi_apply_settings\">Áp dụng</string>\n    <string name=\"dpi_confirm_title\">Xác nhận thay đổi DPI</string>\n    <string name=\"dpi_confirm_message\">Thay đổi DPI ứng dụng từ %1$s sang %2$s?</string>\n    <string name=\"settings_magic_mount\">Folk Mount API</string>\n    <string name=\"settings_magic_mount_summary\">Bật hệ thống gắn kết mô-đun tích hợp</string>\n    <string name=\"settings_new_app_profile_mode\">Chế độ mặc định cho ứng dụng mới</string>\n    <string name=\"settings_new_app_profile_normal\">KHÔNG ROOT</string>\n    <string name=\"settings_new_app_profile_root\">ROOT</string>\n    <string name=\"settings_new_app_profile_exclude\">Loại trừ</string>\n    <string name=\"settings_hide_service\">FolkPatch Hide</string>\n    <string name=\"settings_hide_service_summary\">Ẩn trạng thái mở khóa bootloader ở một mức độ nhất định</string>\n    <string name=\"settings_app_title\">Tên ứng dụng</string>\n    <string name=\"app_title_custom\">Tùy chỉnh</string>\n    <string name=\"settings_custom_app_title\">Đặt tên ứng dụng</string>\n    <string name=\"custom_app_title_dialog_title\">Tên ứng dụng tùy chỉnh</string>\n    <string name=\"custom_app_title_dialog_hint\">Nhập tên ứng dụng</string>\n    <string name=\"custom_app_title_dialog_confirm\">Xác nhận</string>\n    <string name=\"custom_app_title_dialog_empty\">Tên không được để trống</string>\n    <string name=\"cancel\">Hủy</string>\n    <string name=\"settings_custom_background\">Hình nền tùy chỉnh</string>\n    <string name=\"settings_custom_background_enabled\">Đã bật hình nền tùy chỉnh</string>\n    <string name=\"settings_custom_background_opacity\">Độ mờ của thẻ</string>\n    <string name=\"settings_custom_background_blur\">Làm mờ nền</string>\n    <string name=\"settings_custom_background_dim\">Mức tối của nền</string>\n    <string name=\"settings_alt_icon\">Biểu tượng thay thế</string>\n    <string name=\"alt_icon_summary\">Sử dụng biểu tượng trình khởi chạy thay thế</string>\n\n    <!-- Font Settings -->\n    <string name=\"settings_custom_font\">Font chữ tùy chỉnh</string>\n    <string name=\"settings_select_font_file\">Chọn tệp font chữ</string>\n    <string name=\"settings_custom_font_enabled\">Đã bật font chữ tùy chỉnh</string>\n    <string name=\"settings_custom_font_summary\">Sử dụng font chữ TTF tùy chỉnh</string>\n    <string name=\"settings_font_selected\">Đã chọn font chữ tùy chỉnh</string>\n    <string name=\"settings_custom_font_error\">Lưu tệp font chữ thất bại</string>\n    <string name=\"settings_custom_font_saved\">Lưu font chữ tùy chỉnh thành công</string>\n    <string name=\"settings_clear_font\">Khôi phục font chữ mặc định</string>\n    <string name=\"settings_clear_font_confirm\">Bạn có chắc chắn muốn khôi phục font chữ mặc định không?</string>\n    <string name=\"settings_font_cleared\">Đã khôi phục font chữ mặc định</string>\n    <string name=\"settings_font_select_hint\">Vui lòng chọn một tệp font chữ TTF</string>\n    <string name=\"settings_select_background_image\">Chọn hình ảnh nền</string>\n    <string name=\"settings_custom_background_summary\">Đặt hình ảnh nền tùy chỉnh</string>\n    <string name=\"settings_custom_background_saved\">Lưu nền thành công</string>\n    <string name=\"settings_custom_background_error\">Lưu nền thất bại</string>\n    <string name=\"settings_background_selected\">Đã chọn nền</string>\n    <string name=\"settings_background_image_cleared\">Hình ảnh nền đã được xóa</string>\n    <string name=\"settings_clear_background\">Xóa nền</string>\n    <string name=\"settings_clear_background_confirm\">Bạn có chắc chắn muốn xóa hình ảnh nền không?</string>\n    <string name=\"settings_multi_background_mode\">Chế độ đa nền</string>\n    <string name=\"settings_multi_background_mode_summary\">Thiết lập hình ảnh nền khác nhau cho các trang khác nhau</string>\n    <string name=\"settings_select_home_background\">Nền trang chủ</string>\n    <string name=\"settings_select_kernel_background\">Nền mô-đun kernel</string>\n    <string name=\"settings_select_superuser_background\">Nền siêu người dùng</string>\n    <string name=\"settings_select_system_module_background\">Nền mô-đun hệ thống</string>\n    <string name=\"settings_select_settings_background\">Nền trang cài đặt</string>\n\n    <string name=\"settings_advanced_title_style\">Kiểu Tiêu Đề Nâng Cao</string>\n    <string name=\"settings_advanced_title_style_summary\">Thay thế tiêu đề thanh trên bằng hình ảnh tùy chỉnh</string>\n    <string name=\"settings_advanced_title_style_enabled\">Đã bật kiểu tiêu đề nâng cao</string>\n    <string name=\"settings_select_title_image\">Chọn Hình Ảnh Tiêu Đề</string>\n    <string name=\"settings_title_image_selected\">Đã chọn hình ảnh tiêu đề</string>\n    <string name=\"settings_title_image_saved\">Lưu hình ảnh tiêu đề thành công</string>\n    <string name=\"settings_title_image_error\">Không thể lưu hình ảnh tiêu đề</string>\n    <string name=\"settings_clear_title_image\">Xóa Hình Ảnh Tiêu Đề</string>\n    <string name=\"settings_clear_title_image_confirm\">Bạn có chắc chắn muốn xóa hình ảnh tiêu đề không?</string>\n    <string name=\"settings_title_image_cleared\">Đã xóa hình ảnh tiêu đề</string>\n    <string name=\"settings_title_image_day_opacity\">Độ Mờ Chế Độ Ngày</string>\n    <string name=\"settings_title_image_night_opacity\">Độ Mờ Chế Độ Đêm</string>\n    <string name=\"settings_title_image_dim\">Độ Tối Của Nền</string>\n    <string name=\"settings_title_image_offset_x\">Vị Trí Ngang</string>\n\n    <string name=\"settings_launcher_icon\">Biểu tượng màn hình chính</string>\n    <string name=\"settings_grid_working_card_hide_check\">Ẩn biểu tượng trạng thái</string>\n    <string name=\"settings_grid_working_card_hide_check_summary\">Ẩn dấu kiểm hoặc biểu tượng cảnh báo trên thẻ đang hoạt động</string>\n    <string name=\"settings_grid_working_card_hide_text\">Ẩn văn bản trạng thái</string>\n    <string name=\"settings_grid_working_card_hide_text_summary\">Ẩn văn bản \"Đang hoạt động\" hoặc \"Chưa cài đặt\" trên thẻ làm việc</string>\n    <string name=\"settings_grid_working_card_hide_mode\">Ẩn chế độ làm việc</string>\n    <string name=\"settings_grid_working_card_hide_mode_summary\">Ẩn văn bản \"Full\" hoặc \"Half\" trên thẻ làm việc</string>\n    <string name=\"settings_list_card_hide_status_badge\">Sử Dụng Emoji Cổ Điển</string>\n    <string name=\"settings_list_card_hide_status_badge_summary\">Huy hiệu trạng thái trên trang chủ trở thành emoji cổ điển</string>\n    <string name=\"settings_custom_badge_text\">Tùy Chỉnh Văn Bản Huy Hiệu</string>\n    <string name=\"settings_custom_badge_text_summary\">Thay đổi hiển thị văn bản của huy hiệu, chỉ để giải trí</string>\n    <string name=\"settings_custom_background_dual_dim\">Bật chế độ độ tối ngày/đêm kép</string>\n    <string name=\"settings_custom_background_dual_dim_desc\">Tự động điều chỉnh độ tối nền cho chủ đề sáng và tối</string>\n    <string name=\"settings_custom_background_day_dim\">Độ tối nền ban ngày</string>\n    <string name=\"settings_custom_background_night_dim\">Độ tối nền ban đêm</string>\n    <string name=\"settings_grid_working_card_dual_opacity\">Bật chế độ độ trong suốt ngày/đêm kép</string>\n    <string name=\"settings_grid_working_card_dual_opacity_desc\">Tự động điều chỉnh độ trong suốt thẻ cho chủ đề sáng và tối</string>\n    <string name=\"settings_grid_working_card_day_opacity\">Độ trong suốt thẻ ban ngày</string>\n    <string name=\"settings_grid_working_card_night_opacity\">Độ trong suốt thẻ ban đêm</string>\n    \n    <string name=\"su_exclude_all_title\">Loại trừ hàng loạt</string>\n    <string name=\"su_exclude_all_confirm\">Bạn có chắc chắn muốn đặt tất cả ứng dụng chưa được cấp quyền Root thành loại trừ không?</string>\n\n    <!-- 隐藏设定字符串 -->\n    <string name=\"home_hide_su_path\">Ẩn đường dẫn tệp thực thi su</string>\n    <string name=\"home_hide_kpatch_version\">Ẩn bản vá phiên bản nhân</string>\n    <string name=\"home_hide_fingerprint\">Ẩn thông tin vân tay</string>\n    <string name=\"home_hide_zygisk\">Ẩn triển khai Zygisk</string>\n    <string name=\"home_hide_mount\">Ẩn triển khai gắn kết</string>\n    <string name=\"home_hide_su_path_summary\">Ẩn đường dẫn tệp thực thi su trong thẻ thông tin</string>\n    <string name=\"home_hide_kpatch_version_summary\">Ẩn thông tin bản vá phiên bản nhân trong thẻ thông tin</string>\n    <string name=\"home_hide_zygisk_summary\">Ẩn triển khai Zygisk trong thẻ thông tin</string>\n    <string name=\"home_hide_mount_summary\">Ẩn triển khai gắn kết trong thẻ thông tin</string>\n    <string name=\"home_hide_fingerprint_summary\">Ẩn thông tin vân tay trong thẻ thông tin</string>\n    <!-- KPM AutoLoad Strings -->\n    <string name=\"kpm_autoload_title\">Tải tự động KPM</string>\n    <string name=\"kpm_autoload_enabled\">Bật tải tự động</string>\n    <string name=\"kpm_autoload_enabled_summary\">Tự động tải module KPM khi khởi động máy</string>\n    <string name=\"kpm_autoload_json_config\">Cấu hình JSON</string>\n    <string name=\"kpm_autoload_json_label\">Cấu hình JSON</string>\n    <string name=\"kpm_autoload_json_placeholder\">{\n  \"enabled\": true,\n  \"kpmPaths\": [\n    \"/path/to/module1.kpm\",\n    \"/path/to/module2.kpm\"\n  ]\n}</string>\n    <string name=\"kpm_autoload_json_error\">Định dạng JSON không hợp lệ</string>\n    <string name=\"kpm_autoload_json_helper\">Nhập JSON hợp lệ, bao gồm mảng đường dẫn tệp KPM</string>\n    <string name=\"kpm_autoload_save\">Lưu cấu hình</string>\n    <string name=\"kpm_autoload_save_confirm\">Lưu cấu hình tải tự động?</string>\n    <string name=\"kpm_autoload_save_success\">Lưu cấu hình thành công</string>\n    <string name=\"kpm_autoload_save_failed\">Lưu cấu hình thất bại</string>\n    <string name=\"kpm_autoload_visual_mode\">Chế độ trực quan</string>\n    <string name=\"kpm_autoload_json_mode\">Chế độ JSON</string>\n\n    <!-- APM Bulk Install Strings -->\n    <string name=\"apm_bulk_install_title\">Cài đặt hàng loạt module hệ thống</string>\n    <string name=\"apm_bulk_install_list_title\">Danh sách module</string>\n    <string name=\"apm_bulk_install_add\">Thêm module</string>\n    <string name=\"apm_bulk_install_empty\">Chưa thêm module</string>\n    <string name=\"apm_bulk_install_action\">Flash hàng loạt</string>\n    <string name=\"apm_bulk_install_log_title\">Nhật ký flash hàng loạt</string>\n    <string name=\"apm_bulk_install_log_start\">Bắt đầu flash hàng loạt...</string>\n    <string name=\"apm_bulk_install_log_installing\">Đang flash %1$s</string>\n    <string name=\"apm_batch_install_full_process\">Quy trình cài đặt hàng loạt hoàn chỉnh</string>\n    <string name=\"apm_batch_install_full_process_summary\">Cài đặt hàng loạt mô-đun hệ thống được thực hiện sử dụng quy trình hoàn chỉnh</string>\n    <string name=\"next_module\">Mô-đun tiếp theo</string>\n    <string name=\"apm_bulk_install_log_installed\">Module %s flash hoàn tất.</string>\n    <string name=\"apm_bulk_install_log_done\">Tất cả hành động đã hoàn tất.</string>\n    <string name=\"apm_bulk_install_first_use_text\">Tính năng này cho phép bạn flash nhiều module một lần, thuộc loại flash nhanh, phù hợp để flash các module không liên quan đến thao tác nút trong quá trình, vui lòng bật chế độ flash quy trình hoàn chỉnh trong cài đặt cho các thao tác liên quan đến nhấn nút âm lượng v.v.</string>\n    <string name=\"apm_bulk_install_remove\">Xóa</string>\n    <string name=\"apm_first_use_title\">Chào mừng bạn sử dụng module hệ thống</string>\n    <string name=\"apm_first_use_text\">Chào mừng bạn sử dụng module hệ thống, nơi này sử dụng module tương thích với hệ sinh thái Magisk, nhấn nút góc dưới bên phải để cài đặt module, bạn cũng có thể sử dụng trình cài đặt ở đầu trang để cài đặt module hàng loạt, cũng cung cấp một chức năng sao lưu tất cả module một nút, nhưng lưu ý phương án này có thể không áp dụng cho tất cả module, vẫn hãy tự sao lưu thêm</string>\n    <string name=\"kpm_autoload_add_kpm\">Thêm KPM</string>\n    <string name=\"kpm_autoload_remove_kpm\">Xóa</string>\n    <string name=\"kpm_autoload_edit_kpm\">Editar</string>\n    <string name=\"kpm_autoload_edit_dialog_title\">Editar KPM</string>    <string name=\"kpm_autoload_event_label\">Evento:</string>    <string name=\"kpm_autoload_args_label\">Argumentos:</string>    <string name=\"kpm_autoload_event_service\">service</string>    <string name=\"kpm_autoload_event_post_fs_data\">post-fs-data</string>\n    <string name=\"kpm_autoload_kpm_list_title\">Danh sách module KPM</string>\n    <string name=\"kpm_autoload_no_kpm_added\">Chưa thêm module KPM</string>\n    <string name=\"kpm_autoload_kpm_path\">Đường dẫn KPM</string>\n    <string name=\"kpm_autoload_file_not_found\">Không tìm thấy tệp</string>\n    <string name=\"kpm_autoload_select_kpm_file\">Chọn tệp KPM</string>\n    <string name=\"kpm_autoload_first_time_title\">Về Tải tự động KPM</string>\n    <string name=\"kpm_autoload_first_time_message\">Tính năng này cho phép bạn tự động tải tạm thời tất cả KPM trong tệp cấu hình khi khởi động máy. Phương án này thuận tiện hơn so với việc nhúng trực tiếp vào nhân.\n\nVí dụ, KPM có chức năng ngăn phân vùng bị sửa đổi, KPM loại này chỉ có thể tải tạm thời sử dụng, nhưng việc nhúng sẽ gây ra không khởi động được máy. Hoặc bạn không muốn phá hủy phân vùng BOOT thì có thể sử dụng cấu hình này để tải module nhanh chóng.\n\nCần đảm bảo phần mềm thoát hoàn toàn vào mới sẽ thực thi lệnh, thường来说 khi vào máy khi khởi động cũng sẽ tải, trình quản lý màn hình đen một thời gian là hiện tượng bình thường, sử dụng phương án tải ghi ở nền thuần túy, nhớ vuốt lên thủ công để làm mới xem đã tải đúng chưa!</string>\n    <string name=\"kpm_autoload_first_time_confirm\">Đã hiểu</string>\n    <string name=\"kpm_autoload_do_not_show_again\">Không hiển thị lại</string>\n\n    <string name=\"kpm_page_first_time_title\">Cảnh báo</string>\n    <string name=\"kpm_page_first_time_message\">Module nhân là thực hiện sửa đổi trực tiếp Boot, nó không có cơ chế cứu khôi tốt như module hệ thống, nếu có vấn đề chỉ có thể vào Fastboot để sửa chữa. Khuyên bạn nên tải module trước khi không có vấn đề thì mới nhúng vào Boot. Nếu là module chỉ có thể tải sử dụng, có thể thử sử dụng chức năng tải tự động module KPM. Nếu bạn không hiểu về module nhân, vui lòng không sử dụng chức năng này!</string>\n    <!-- Module Backup/Restore Strings -->\n    <string name=\"apm_backup_title\">Sao lưu module</string>\n    <string name=\"apm_restore_title\">Khôi phục module</string>\n    <string name=\"apm_backup_success\">Sao lưu thành công</string>\n    <string name=\"apm_restore_success\">Khôi phục thành công</string>\n    <string name=\"apm_backup_failed\">Sao lưu thất bại</string>\n    <string name=\"apm_restore_failed\">Khôi phục thất bại</string>\n    <string name=\"apm_backup_failed_msg\">Sao lưu thất bại: %s</string>\n    <string name=\"apm_restore_failed_msg\">Khôi phục thất bại: %s</string>\n    <string name=\"apm_copy_list_title\">Sao chép danh sách</string>\n    <string name=\"apm_copy_list_success\">Đã sao chép danh sách mô-đun</string>\n\n    <!-- Auto Backup Strings -->\n    <string name=\"settings_auto_backup_module\">Sao lưu tự động module hệ thống</string>\n    <string name=\"settings_auto_backup_module_summary\">Tự động sao lưu tệp vào thư mục riêng khi cài đặt module</string>\n    <string name=\"settings_open_backup_dir\">Mở thư mục sao lưu</string>\n    <string name=\"backup_dir_empty\">Thư mục sao lưu trống</string>\n    <string name=\"backup_dir_open_failed\">Mở thư mục sao lưu thất bại</string>\n    <string name=\"auto_backup_failed\">Sao lưu tự động thất bại: %s</string>\n    <string name=\"auto_backup_success\">Sao lưu tự động thành công: %s</string>\n    <string name=\"settings_auto_backup_boot\">Sao lưu tự động Boot</string>\n    <string name=\"settings_auto_backup_boot_summary\">Tự động sao lưu Boot vào bộ nhớ cục bộ (Downloads/FolkPatch/BootBackups)</string>\n\n    <!-- Home Layout Strings -->\n    <string name=\"settings_home_layout_style\">Kiểu bố cục trang chủ</string>\n    <string name=\"settings_home_layout_default\">ListUI</string>\n    <string name=\"settings_home_layout_grid\">GridUI</string>\n    <string name=\"settings_home_layout_focus\">FocusUI</string>\n    <string name=\"settings_home_layout_sign\">SignUI</string>\n    <string name=\"settings_home_layout_circle\">CircleUI</string>\n    <string name=\"settings_home_layout_dashboard_pro\">DashboardUI</string>\n    <string name=\"unofficial_version_title\">Phiên bản không chính thức</string>\n    <string name=\"unofficial_version_message\">Bạn đang sử dụng trình quản lý FolkPatch phiên bản thứ ba, có thể gây thiệt hại tài sản, vui lòng tải APP bản quyền sử dụng!</string>\n    <string name=\"go_to_github\">Đi đến Github</string>\n    <string name=\"settings_save_theme\">Lưu chủ đề</string>\n    <string name=\"settings_import_theme\">Nhập chủ đề</string>\n    <string name=\"settings_theme_saved\">Chủ đề đã được lưu</string>\n    <string name=\"settings_theme_imported\">Chủ đề đã được nhập</string>\n    <string name=\"settings_theme_save_failed\">Lưu chủ đề thất bại</string>\n    <string name=\"settings_theme_import_failed\">Nhập chủ đề thất bại</string>\n    <string name=\"settings_reset_theme\">Đặt lại chủ đề</string>\n    <string name=\"settings_reset_theme_confirm\">Bạn có chắc chắn muốn đặt lại tất cả cài đặt chủ đề về mặc định? Điều này sẽ xóa tất cả hình nền, phông chữ, nhạc và hiệu ứng âm thanh tùy chỉnh.</string>\n    <string name=\"settings_theme_reset\">Đã đặt lại chủ đề</string>\n    <string name=\"settings_theme_reset_failed\">Không thể đặt lại chủ đề</string>\n\n    <!-- Theme Strings -->\n    <string name=\"theme_export_title\">Xuất chủ đề</string>\n    <string name=\"theme_import_title\">Nhập chủ đề</string>\n    <string name=\"theme_name\">Tên chủ đề</string>\n    <string name=\"theme_type\">Loại chủ đề</string>\n    <string name=\"theme_type_phone\">Điện thoại</string>\n    <string name=\"theme_type_tablet\">Máy tính bảng</string>\n    <string name=\"theme_version\">Phiên bản</string>\n    <string name=\"theme_author\">Tác giả</string>\n    <string name=\"theme_description\">Mô tả</string>\n    <string name=\"theme_export_action\">Xuất</string>\n    <string name=\"theme_import_action\">Nhập</string>\n    <string name=\"theme_import_confirm\">Bạn có chắc chắn muốn nhập chủ đề này không?</string>\n    <string name=\"theme_info\">Thông tin chủ đề</string>\n\n    <!-- Grid Working Card Background -->\n    <string name=\"settings_grid_working_card_background\">Nền thẻ làm việc</string>\n    <string name=\"settings_grid_working_card_background_enabled\">Đã bật nền thẻ tùy chỉnh</string>\n    <string name=\"settings_grid_working_card_background_summary\">Đặt hình ảnh nền tùy chỉnh cho thẻ làm việc</string>\n    <string name=\"settings_grid_working_card_background_selected\">Nền đã được chọn</string>\n    <string name=\"settings_clear_grid_working_card_background\">Xóa nền thẻ</string>\n    <string name=\"settings_clear_grid_working_card_background_confirm\">Bạn có chắc chắn muốn xóa hình ảnh nền thẻ không?</string>\n    <string name=\"settings_grid_working_card_background_saved\">Lưu nền thẻ thành công</string>\n    <string name=\"settings_grid_working_card_background_error\">Lưu nền thẻ thất bại</string>\n    <string name=\"settings_grid_working_card_background_cleared\">Nền thẻ đã được xóa</string>\n\n    <!-- Biometric Strings -->\n    <string name=\"action_biometric\">Xác thực sinh trắc học</string>\n    <string name=\"msg_biometric\">Vui lòng xác minh danh tính sinh trắc học của bạn</string>\n    <string name=\"settings_biometric_login\">Xác thực sinh trắc học</string>\n    <string name=\"settings_biometric_login_summary\">Yêu cầu xác thực sinh trắc học khi mở ứng dụng</string>\n    <string name=\"settings_strong_biometric\">Xác thực sinh trắc học mạnh</string>\n    <string name=\"settings_strong_biometric_summary\">Yêu cầu xác thực khi cài đặt/gỡ cài/tắt module</string>\n\n    <!-- Theme Store Strings -->\n    <string name=\"theme_store_title\">Cửa hàng chủ đề</string>\n    <string name=\"theme_store_search_hint\">Tìm kiếm chủ đề...</string>\n    <string name=\"theme_source_official\">Chính thức</string>\n    <string name=\"theme_source_third_party\">Bên thứ ba</string>\n    <string name=\"theme_source_local\">Cục bộ</string>\n    <string name=\"theme_source\">Nguồn</string>\n    <string name=\"theme_install\">Cài đặt</string>\n    <string name=\"theme_install_success\">Cài đặt thành công</string>\n    <string name=\"theme_install_failed\">Cài đặt thất bại</string>\n    <string name=\"theme_download\">Tải xuống</string>\n    <string name=\"theme_store_author\">Tác giả</string>\n    <string name=\"theme_store_version\">Phiên bản</string>\n\n    <!-- Update Check Strings -->\n    <string name=\"settings_auto_update_check\">Kiểm tra cập nhật tự động</string>\n    <string name=\"settings_auto_update_check_summary\">Tự động kiểm tra cập nhật khi khởi động ứng dụng</string>\n    <string name=\"settings_check_update\">Kiểm tra cập nhật</string>\n    <string name=\"update_available_title\">Có bản cập nhật mới</string>\n    <string name=\"update_available_message\">Phát hiện phiên bản của bạn quá cũ, bạn có muốn tải xuống phiên bản mới không?</string>\n    <string name=\"update_action\">Cập nhật</string>\n    <string name=\"update_close\">Đóng</string>\n    <string name=\"update_latest\">Bạn đang sử dụng phiên bản mới nhất</string>\n    <string name=\"update_error\">Lỗi kiểm tra cập nhật</string>\n    <!--Tùy chọn thu gọn-->\n    <string name=\"settings_category_general\">Chung</string>\n    <string name=\"superuser\">Người dùng siêu cấp</string>\n    <string name=\"module\">Module hệ thống</string>\n    <string name=\"settings_category_appearance\">Giao diện</string>\n    <string name=\"settings_appearance_font\">Cài đặt phông chữ</string>\n    <string name=\"settings_appearance_font_summary\">Cấu hình phông chữ tùy chỉnh</string>\n    <string name=\"settings_appearance_theme\">Cài đặt主题</string>\n    <string name=\"settings_appearance_theme_summary\">Cửa hàng theme, lưu, nhập và đặt lại</string>\n    <string name=\"settings_appearance_banner\">Cài đặt banner</string>\n    <string name=\"settings_appearance_banner_summary\">Cấu hình banner module</string>\n    <string name=\"settings_appearance_layout\">Cài đặt layout</string>\n    <string name=\"settings_appearance_layout_summary\">Layout trang chủ, navigation và tùy chỉnh card</string>\n    <string name=\"settings_appearance_background\">Cài đặt background</string>\n    <string name=\"settings_appearance_background_summary\">Background tùy chỉnh, video và multi-background</string>\n    <string name=\"settings_appearance_night_mode\">Cài đặt chế độ tối</string>\n    <string name=\"settings_appearance_night_mode_summary\">Theme tối và cấu hình màu</string>\n    <string name=\"settings_amoled_theme\">Chủ đề Đen AMOLED</string>\n    <string name=\"settings_amoled_theme_desc\">Nền đen hoàn toàn cho chế độ tối</string>\n    <string name=\"settings_switch_icon\">Bật chỉ báo nút</string>\n    <string name=\"settings_switch_icon_desc\">Hiển thị biểu tượng trạng thái trên công tắc</string>\n    <string name=\"settings_category_behavior\">Hành vi</string>\n    <string name=\"settings_category_function\">Chức năng</string>\n    <string name=\"settings_use_legacy_su_page\">Ủy quyền siêu người dùng một trang</string>\n    <string name=\"settings_use_legacy_su_page_summary\">Trang siêu người dùng chuyển sang thiết kế ủy quyền một trang</string>\n    <string name=\"settings_category_module\">Mô-đun</string>\n    <string name=\"settings_category_security\">Bảo mật</string>\n    \n    <!-- Background Music Strings -->\n    <string name=\"settings_background_music\">Nhạc nền</string>\n    <string name=\"settings_background_music_summary\">Phát nhạc nền khi ứng dụng ở foreground</string>\n    <string name=\"settings_background_music_playing\">Đang phát: %s</string>\n    <string name=\"settings_background_music_enabled\">Nhạc nền đã được kích hoạt</string>\n    <string name=\"settings_select_music_file\">Chọn tệp nhạc</string>\n    <string name=\"settings_music_selected\">Nhạc đã chọn</string>\n    <string name=\"settings_clear_music\">Xóa nhạc</string>\n    <string name=\"settings_clear_music_confirm\">Bạn có chắc muốn xóa nhạc nền không?</string>\n    <string name=\"settings_music_auto_play\">Tự động phát</string>\n    <string name=\"settings_music_auto_play_summary\">Tự động phát nhạc khi mở ứng dụng</string>\n    <string name=\"settings_music_volume\">Âm lượng</string>\n    <string name=\"settings_music_saved\">Tệp nhạc đã được lưu</string>\n    <string name=\"settings_music_save_error\">Lưu tệp nhạc thất bại</string>\n    <string name=\"settings_music_cleared\">Nhạc đã được xóa</string>\n    <string name=\"settings_category_multimedia\">Đa phương tiện</string>\n    <string name=\"settings_category_general_summary\">Ngôn ngữ, cập nhật, SELinux, tinh chỉnh hệ thống</string>\n    <string name=\"settings_category_appearance_summary\">Chủ đề, màu sắc, bố cục, nền, phông chữ</string>\n    <string name=\"settings_category_behavior_summary\">Gỡ lỗi Web, hành vi cài đặt, hiển thị trang chủ</string>\n    <string name=\"settings_category_security_summary\">Sinh trắc học, quản lý siêu khóa</string>\n    <string name=\"settings_category_backup_summary\">Sao lưu cục bộ, sao lưu đám mây, WebDAV</string>\n    <string name=\"settings_category_module_summary\">Thông tin mô-đun, sắp xếp, cài đặt hàng loạt</string>\n    <string name=\"settings_category_function_summary\">Ẩn FolkPatch, dịch vụ Umount</string>\n    <string name=\"settings_category_multimedia_summary\">Nhạc nền, âm thanh, rung</string>\n    <string name=\"settings_music_playback_control\">Điều khiển phát nhạc</string>\n    <string name=\"settings_music_looping\">Lặp lại</string>\n    <string name=\"settings_music_looping_summary\">Lặp lại bài hát hiện tại</string>\n    <!-- Theme Store Filter -->\n    <string name=\"theme_store_filter_title\">Lọc Chủ Đề</string>\n    <string name=\"theme_store_filter_author\">Tác Giả</string>\n    <string name=\"theme_store_filter_author_hint\">Nhập tên tác giả</string>\n    <string name=\"theme_store_filter_source\">Nguồn</string>\n    <string name=\"theme_store_filter_source_all\">Tất Cả</string>\n    <string name=\"theme_store_filter_type\">Loại Thiết Bị</string>\n    <string name=\"theme_store_filter_apply\">Áp Dụng</string>\n    <string name=\"theme_store_filter_reset\">Đặt Lại</string>\n\n    <!-- Video Background Settings -->\n    <string name=\"settings_video_background\">Nền Video</string>\n    <string name=\"settings_video_background_summary\">Sử dụng video làm nền</string>\n    <string name=\"settings_select_video\">Chọn Video</string>\n    <string name=\"settings_video_selected\">Đã Chọn Video</string>\n    <string name=\"settings_video_background_enabled\">Nền Video Đã Bật</string>\n    <string name=\"settings_clear_video_background\">Xóa hình nền video</string>\n    <string name=\"settings_clear_video_background_confirm\">Bạn có chắc chắn muốn xóa hình nền video không?</string>\n    <string name=\"settings_video_volume\">Âm Lượng Video</string>\n    <!-- File Picker -->\n    <string name=\"file_picker_permission_required\">Cần quyền truy cập</string>\n    <string name=\"file_picker_permission_desc\">Để duyệt tệp, vui lòng cấp quyền \\'Truy cập tất cả các tệp\\'.</string>\n    <string name=\"file_picker_grant_permission\">Cấp quyền</string>\n    <string name=\"file_picker_cancel\">Hủy</string>\n    <string name=\"file_picker_internal_storage\">Bộ nhớ trong</string>\n    <string name=\"file_picker_no_files\">Không có tệp</string>\n    <!-- HomeV3 Strings -->\n    <string name=\"home_device_status_title\">Trạng thái thiết bị</string>\n    <string name=\"home_device_status_battery_temp\">Nhiệt độ pin</string>\n    <string name=\"home_device_status_cpu_load\">Tải CPU</string>\n    <string name=\"home_device_status_battery_level\">Mức pin</string>\n    <string name=\"home_storage_title\">Lưu trữ</string>\n    <string name=\"home_storage_internal\">Bộ nhớ trong</string>\n    <string name=\"home_storage_ram\">RAM</string>\n    <string name=\"home_storage_zram\">ZRAM</string>\n    <string name=\"home_storage_swap\">Tệp trao đổi</string>\n    <string name=\"home_info_kernel\">Nhân</string>\n    <string name=\"home_info_superkey\">Khóa siêu cấp</string>\n    <string name=\"home_info_auth_auth\">Xác thực</string>\n    <string name=\"home_info_auth_na\">N/A</string>\n    <string name=\"home_info_device_slot\">Khe thiết bị</string>\n    <string name=\"home_info_device_model\">Mô hình thiết bị</string>\n    <string name=\"home_info_running_mode\">Chế độ chạy</string>\n    <string name=\"home_info_mode_full\">Đầy đủ</string>\n    <string name=\"home_info_mode_half\">Một phần</string>\n    <string name=\"home_version\">Phiên bản: %s</string>\n    <string name=\"home_zygisk_implement\">Triển khai Zygisk</string>\n    <string name=\"home_mount_implement\">Triển khai gắn kết</string>\n\n    <string name=\"settings_app_list_loading_scheme\">Sơ đồ tải danh sách ứng dụng</string>\n    <string name=\"settings_app_list_loading_scheme_summary\">Chọn cách tải danh sách ứng dụng</string>\n    <string name=\"app_list_loading_scheme_root_service\">RootService (Mặc định)</string>\n    <string name=\"app_list_loading_scheme_package_manager\">PackageManager (API hệ thống)</string>\n    <string name=\"app_list_loading_scheme_dialog_title\">Sơ đồ tải</string>\n    <string name=\"su_backup_list\">Danh sách sao lưu</string>\n    <string name=\"su_restore_list\">Danh sách khôi phục</string>\n    <string name=\"backup_success\">Sao lưu thành công</string>\n    <!-- Badge Count Settings -->\n    <string name=\"enable_badge_count\">Cài đặt đếm huy hiệu</string>\n    <string name=\"enable_badge_count_summary\">Cấu hình hiển thị số đếm huy hiệu cho các mục điều hướng</string>\n    <string name=\"badge_superuser\">Hiển thị huy hiệu siêu người dùng</string>\n    <string name=\"badge_apm\">Hiển thị huy hiệu mô-đun hệ thống</string>\n    <string name=\"badge_kernel\">Hiển thị huy hiệu mô-đun hạt nhân</string>\n    <string name=\"settings_sound_effect\">Hiệu ứng âm thanh</string>\n    <string name=\"settings_sound_effect_summary\">Phát âm thanh khi nhấn</string>\n    <string name=\"settings_sound_effect_enabled\">Đã bật</string>\n    <string name=\"settings_sound_effect_playing\">Đang chọn: %s</string>\n    <string name=\"settings_sound_effect_source\">Nguồn âm thanh</string>\n    <string name=\"settings_sound_effect_source_local\">Tệp cục bộ</string>\n    <string name=\"settings_sound_effect_source_preset\">Đặt trước</string>\n    <string name=\"settings_sound_effect_preset_title\">Âm thanh đặt trước</string>\n    <string name=\"settings_select_sound_effect\">Chọn tập tin âm thanh</string>\n    <string name=\"settings_sound_effect_selected\">Đã chọn tập tin âm thanh</string>\n    <string name=\"settings_sound_effect_scope\">Phạm vi hiệu ứng</string>\n    <string name=\"settings_sound_effect_scope_global\">Toàn cục (Mọi nơi)</string>\n    <string name=\"settings_sound_effect_scope_bottom_bar\">Chỉ thanh dưới</string>\n    <string name=\"settings_clear_sound_effect\">Xóa hiệu ứng âm thanh</string>\n    <string name=\"settings_clear_sound_effect_confirm\">Bạn có chắc chắn muốn xóa hiệu ứng âm thanh không?</string>\n    <string name=\"settings_sound_effect_cleared\">Đã xóa hiệu ứng âm thanh</string>\n\n    <string name=\"settings_startup_sound\">Âm thanh khởi động</string>\n    <string name=\"settings_startup_sound_summary\">Phát âm thanh khi khởi động ứng dụng</string>\n    <string name=\"settings_startup_sound_enabled\">Đã bật âm thanh khởi động</string>\n    <string name=\"settings_startup_sound_playing\">Đang phát: %s</string>\n    <string name=\"settings_select_startup_sound\">Chọn âm thanh khởi động</string>\n    <string name=\"settings_startup_sound_selected\">Đã chọn âm thanh khởi động</string>\n    <string name=\"settings_clear_startup_sound\">Xóa âm thanh khởi động</string>\n    <string name=\"settings_clear_startup_sound_confirm\">Bạn có chắc chắn muốn xóa âm thanh khởi động không?</string>\n    <string name=\"settings_startup_sound_cleared\">Đã xóa âm thanh khởi động</string>\n\n    <string name=\"settings_enable_cloud_backup_summary\">Sao lưu tự động lên đám mây</string>\n    <string name=\"settings_configure_webdav\">Cấu hình dịch vụ WebDAV</string>\n    <string name=\"webdav_config_title\">Cấu hình WebDAV</string>\n    <string name=\"webdav_url\">URL WebDAV</string>\n    <string name=\"webdav_username\">Tên người dùng</string>\n    <string name=\"webdav_password\">Mật khẩu</string>\n    <string name=\"webdav_uploading\">Đang tải lên WebDAV...</string>\n    <string name=\"webdav_backup_success\">Sao lưu WebDAV thành công</string>\n    <string name=\"webdav_backup_failed\">Sao lưu WebDAV thất bại: %s</string>\n    <string name=\"save\">Lưu</string>\n    <string name=\"test\">Kiểm tra</string>\n    <string name=\"webdav_path_label\">Đường dẫn (ví dụ: /Backup)</string>\n    <string name=\"webdav_view_logs\">Xem nhật ký</string>\n    <string name=\"webdav_backup_logs_title\">Nhật ký sao lưu</string>\n    <string name=\"webdav_no_logs\">Không có nhật ký</string>\n    <string name=\"webdav_clear_logs\">Xóa</string>\n    <string name=\"settings_enable_local_backup\">Bật sao lưu cục bộ</string>\n    <string name=\"settings_enable_local_backup_summary\">Sao lưu mô-đun vào bộ nhớ cục bộ (Downloads/FolkPatch/ModuleBackups)</string>\n    <string name=\"close\">Đóng</string>\n\n    <!-- Backup Settings -->\n    <string name=\"settings_category_backup\">Cài đặt sao lưu</string>\n    <string name=\"settings_enable_cloud_backup\">Bật sao lưu đám mây</string>\n    <string name=\"settings_webdav_url\">URL WebDAV</string>\n    <string name=\"settings_webdav_username\">Tên người dùng WebDAV</string>\n    <string name=\"settings_webdav_password\">Mật khẩu WebDAV</string>\n    <string name=\"settings_test_webdav\">Kiểm tra kết nối WebDAV</string>\n    <string name=\"webdav_test_success\">Kiểm tra thành công</string>\n    <string name=\"webdav_test_failed\">Kiểm tra thất bại: %s</string>\n    <string name=\"settings_backup_now\">Sao lưu ngay</string>\n    <string name=\"backup_failed\">Sao lưu thất bại: %s</string>\n    <string name=\"settings_restore_backup\">Khôi phục sao lưu</string>\n    <string name=\"restore_success\">Khôi phục thành công</string>\n    <string name=\"restore_failed\">Khôi phục thất bại: %s</string>\n    <string name=\"settings_auto_backup\">Sao lưu tự động</string>\n    <string name=\"settings_auto_backup_summary\">Tự động sao lưu một lần mỗi ngày</string>\n    <string name=\"settings_delete_remote_backup\">Xóa sao lưu đám mây</string>\n    <string name=\"delete_remote_backup_confirm\">Bạn có chắc chắn muốn xóa sao lưu đám mây không?</string>\n    <string name=\"delete_success\">Xóa thành công</string>\n    <string name=\"delete_failed\">Xóa thất bại: %s</string>\n    <string name=\"settings_encrypt_backup\">Mã hóa sao lưu</string>\n    <string name=\"settings_encrypt_backup_summary\">Mã hóa tệp sao lưu bằng mật khẩu</string>\n    <string name=\"settings_backup_password\">Mật khẩu sao lưu</string>\n    <string name=\"settings_backup_password_hint\">Nhập mật khẩu sao lưu</string>\n\n    <string name=\"patch_output_written_to\"> Vá thành công, tệp được ghi vào </string>\n    <string name=\"patch_write_failed\"> Ghi boot.img đã vá thất bại</string>\n\n    <!-- Script Library Strings -->\n    <string name=\"script_library\">Thư viện Script</string>\n    <string name=\"script_library_title\">Thư viện Script</string>\n    <string name=\"script_library_empty\">Không có script nào, nhấn nút thêm để thêm</string>\n    <string name=\"script_library_add\">Thêm Script</string>\n    <string name=\"script_library_add_title\">Thêm Script</string>\n    <string name=\"script_library_select_file\">Chọn tệp script</string>\n    <string name=\"script_library_alias\">Bí danh</string>\n    <string name=\"script_library_alias_hint\">Nhập bí danh script (tùy chọn)</string>\n    <string name=\"script_library_run\">Chạy</string>\n    <string name=\"script_library_delete\">Xóa</string>\n    <string name=\"script_library_path\">Đường dẫn: %s</string>\n    <string name=\"script_library_confirm_delete\">Xác nhận xóa script?</string>\n    <string name=\"script_library_delete_success\">Xóa script thành công</string>\n    <string name=\"script_library_delete_failed\">Xóa script thất bại</string>\n    <string name=\"script_library_add_success\">Thêm script thành công</string>\n    <string name=\"script_library_add_failed\">Thêm script thất bại: %s</string>\n    <string name=\"script_library_load_failed\">Tải thư viện script thất bại</string>\n    <string name=\"script_library_output\">Kết quả thực thi</string>\n    <string name=\"script_library_no_output\">Không có kết quả</string>\n    <string name=\"script_library_save_log\">Lưu nhật ký</string>\n    <string name=\"script_library_log_saved\">Nhật ký đã lưu vào %s</string>\n    <string name=\"script_library_log_save_failed\">Lưu nhật ký thất bại: %s</string>\n    <string name=\"settings_vibration\">Rung &amp; Phản hồi xúc giác</string>\n    <string name=\"settings_vibration_summary\">Rung khi chạm</string>\n    <string name=\"settings_vibration_enabled\">Bật rung</string>\n    <string name=\"settings_vibration_intensity\">Cường độ rung</string>\n    <string name=\"settings_vibration_scope\">Phạm vi rung</string>\n    <string name=\"settings_vibration_scope_global\">Toàn cầu (Mọi nơi)</string>\n    <string name=\"settings_vibration_scope_bottom_bar\">Chỉ thanh dưới cùng</string>\n    <string name=\"search_modules\">Tìm kiếm module...</string>\n    <string name=\"search_scripts\">Tìm kiếm script...</string>\n\n    <!-- Umount Service -->\n    <string name=\"settings_umount_service\">Umount Service</string>\n    <string name=\"settings_umount_service_summary\">Cấu hình điểm mount để tự động unmount khi khởi động</string>\n    <string name=\"umount_config_title\">Cấu hình Umount</string>\n    <string name=\"umount_config_enabled\">Bật Umount</string>\n    <string name=\"umount_config_enabled_summary\">Tự động unmount các điểm mount đã chỉ định khi khởi động</string>\n    <string name=\"umount_config_paths_label\">Đường dẫn điểm mount (mỗi dòng một cái)</string>\n    <string name=\"umount_config_paths_placeholder\">/system/vendor/app\\n/system/vendor/priv-app</string>\n    <string name=\"umount_config_paths_helper\">Nhập một đường dẫn điểm mount để unmount trên mỗi dòng</string>\n    <string name=\"umount_config_save\">Lưu</string>\n    <string name=\"umount_config_save_confirm\">Xác nhận lưu cấu hình?</string>\n    <string name=\"umount_config_save_success\">Lưu cấu hình thành công</string>\n    <string name=\"umount_config_save_failed\">Lưu cấu hình thất bại</string>\n\n    <!-- Trang Chủ đề của tôi -->\n    <string name=\"my_themes_title\">Chủ đề của tôi</string>\n    <string name=\"my_themes_empty\">Chưa có chủ đề nào</string>\n    <string name=\"my_themes_empty_action\">Duyệt cửa hàng chủ đề</string>\n    <string name=\"my_themes_apply\">Áp dụng chủ đề</string>\n    <string name=\"my_themes_delete\">Xóa chủ đề</string>\n    <string name=\"my_themes_delete_confirm\">Bạn có chắc chắn muốn xóa chủ đề này?</string>\n    <string name=\"my_themes_deleted\">Đã xóa chủ đề</string>\n    <string name=\"my_themes_applied\">Đã áp dụng chủ đề</string>\n    <string name=\"my_themes_apply_failed\">Không thể áp dụng chủ đề</string>\n    <string name=\"my_themes_details\">Chi tiết chủ đề</string>\n\n    <!-- Hộp thoại Tải xuống -->\n    <string name=\"theme_download_title\">Đang tải xuống chủ đề</string>\n    <string name=\"theme_download_completed\">Tải xuống hoàn tất</string>\n    <string name=\"theme_download_failed\">Tải xuống thất bại</string>\n    <string name=\"theme_download_progress\">Tiến độ</string>\n    <string name=\"theme_download_file\">Tệp chủ đề</string>\n    <string name=\"theme_download_image\">Hình ảnh xem trước</string>\n    <string name=\"theme_download_cancel\">Hủy</string>\n    <string name=\"theme_download_pause\">Tạm dừng</string>\n    <string name=\"theme_download_resume\">Tiếp tục</string>\n    <string name=\"theme_download_retry\">Thử lại</string>\n    <string name=\"theme_download_apply\">Áp dụng chủ đề</string>\n    <string name=\"theme_download_go_to_my_themes\">Chủ đề của tôi</string>\n    <string name=\"theme_download_retrying\">Đang thử lại (%d/3)...</string>\n    <string name=\"theme_download_preparing\">Đang chuẩn bị tải xuống...</string>\n    <string name=\"theme_download_downloading_file\">Đang tải xuống tệp chủ đề...</string>\n    <string name=\"theme_download_downloading_image\">Đang tải xuống hình ảnh xem trước...</string>\n    <string name=\"theme_download_finalizing\">Đang hoàn tất...</string>\n    <string name=\"settings_predictive_back\">Cử chỉ quay lại dự đoán</string>\n    <string name=\"settings_predictive_back_summary\">Bật hoạt ảnh cử chỉ quay lại dự đoán Android 14+</string>\n\n    <string name=\"home_device_status_battery_charging\">Đang sạc</string>\n    <string name=\"home_device_status_cpu_temp\">Nhiệt độ CPU</string>\n    <string name=\"home_device_status_memory_trend\">Xu hướng bộ nhớ</string>\n    <string name=\"home_storage_partitions\">Phân vùng lưu trữ</string>\n    <string name=\"home_device_status_cpu_freq\">Tần số CPU</string>\n    <string name=\"home_network_rx\">Tải xuống</string>\n    <string name=\"home_network_tx\">Tải lên</string>\n    <string name=\"settings_home_layout_stats\">StatsUI</string>\n    <string name=\"home_stats_more_options\">Tùy chọn khác</string>\n    <string name=\"home_stats_kp_label\">KP</string>\n    <string name=\"home_stats_manager_label\">Trình quản lý</string>\n    <string name=\"home_device_status_gpu_load\">GPU</string>\n    <string name=\"home_stats_kernel_modules\">Mô-đun nhân</string>\n    <string name=\"home_stats_apm_modules\">Mô-đun hệ thống</string>\n    <string name=\"home_stats_superusers\">Siêu người dùng</string>\n    <string name=\"settings_kernel_spoof\">Cấu hình giả mạo nhân</string>\n    <string name=\"settings_kernel_spoof_summary\">Giả mạo phiên bản nhân và thời gian xây dựng</string>\n    <string name=\"settings_kernel_spoof_version\">Phiên bản nhân</string>\n    <string name=\"settings_kernel_spoof_build_time\">Thời gian xây dựng nhân</string>\n    <string name=\"settings_kernel_spoof_restore\">Khôi phục</string>\n\n    <string name=\"kernel_spoof_enabled\">Giả mạo nhân đã bật</string>\n    <string name=\"kernel_spoof_disabled_restored\">Giả mạo nhân đã tắt và khôi phục</string>\n    <string name=\"kernel_spoof_failed\">Giả mạo nhân thất bại: %d</string>\n    <string name=\"kernel_spoof_applied\">Giả mạo nhân đã áp dụng</string>\n\n    <string name=\"settings_path_hide\">Ẩn đường dẫn</string>\n    <string name=\"settings_path_hide_summary\">Ẩn tệp và thư mục khỏi ứng dụng ở cấp độ nhân</string>\n    <string name=\"path_hide_paths_label\">Đường dẫn ẩn (mỗi dòng một đường dẫn)</string>\n\n    <string name=\"path_hide_paths_helper\">Nhập một đường dẫn mỗi dòng. Các đường dẫn khớp sẽ trả về ENOENT.</string>\n    <string name=\"path_hide_save\">Lưu</string>\n    <string name=\"path_hide_enabled\">Ẩn đường dẫn đã bật</string>\n    <string name=\"path_hide_disabled\">Ẩn đường dẫn đã tắt</string>\n    <string name=\"path_hide_applied\">Cấu hình ẩn đường dẫn đã áp dụng</string>\n    <string name=\"path_hide_failed\">Thao tác ẩn đường dẫn thất bại: %d</string>\n    <string name=\"path_hide_uid_mode\">Chế độ thực thi theo UID</string>\n    <string name=\"path_hide_uid_mode_summary\">Chỉ ẩn đường dẫn cho các UID ứng dụng cụ thể</string>\n    <string name=\"path_hide_uids_label\">UID mục tiêu (mỗi dòng một UID)</string>\n    <string name=\"path_hide_uids_helper\">Nhập UID ứng dụng. Chỉ những ứng dụng này mới có đường dẫn bị ẩn.</string>\n    <string name=\"path_hide_uid_save\">Lưu UID</string>\n    <string name=\"path_hide_uid_mode_enabled\">Đã bật chế độ thực thi theo UID</string>\n    <string name=\"path_hide_uid_mode_disabled\">Đã tắt chế độ thực thi theo UID</string>\n    <string name=\"path_hide_filter_system\">Lọc UID hệ thống</string>\n    <string name=\"path_hide_filter_system_summary\">Đồng thời ẩn đường dẫn khỏi tiến trình root và hệ thống (UID &lt; 10000)</string>\n    <string name=\"path_hide_filter_system_enabled\">Đã bật lọc UID hệ thống</string>\n    <string name=\"path_hide_filter_system_disabled\">Đã tắt lọc UID hệ thống</string>\n    <string name=\"path_hide_filter_system_warning_title\">Cảnh báo</string>\n    <string name=\"path_hide_filter_system_warning_message\">Bật tính năng này sẽ đồng thời ẩn đường dẫn khỏi tiến trình root và hệ thống (UID &lt; 10000). Điều này có thể gây lỗi một số chức năng hệ thống. Vui lòng thận trọng.</string>\n    <string name=\"path_hide_uid_applied\">Đã áp dụng danh sách trắng UID</string>\n    <string name=\"path_hide_select_apps\">Chọn ứng dụng</string>\n    <string name=\"path_hide_no_apps_selected\">Chưa chọn ứng dụng nào</string>\n    <string name=\"path_hide_app_removed\">Đã xóa %d cấu hình của ứng dụng đã gỡ cài đặt</string>\n    <string name=\"path_hide_search_apps\">Tìm ứng dụng…</string>\n    <string name=\"path_hide_show_system\">Hiển thị ứng dụng hệ thống</string>\n    <string name=\"netisolate_title\">Cách ly mạng</string>\n    <string name=\"netisolate_enable\">Cách ly mạng đã bật</string>\n    <string name=\"netisolate_disable\">Cách ly mạng đã tắt</string>\n    <string name=\"netisolate_enable_summary\">Chặn truy cập mạng cho các ứng dụng đã chọn ở cấp kernel</string>\n    <string name=\"netisolate_uids_label\">Ứng dụng bị chặn</string>\n    <string name=\"netisolate_uids_hint\">Nhập UID cần chặn (mỗi dòng một)</string>\n    <string name=\"netisolate_no_uids\">Không có ứng dụng bị chặn</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-zh-rAG/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\" translatable=\"false\">FolkPatch</string>\n    <string name=\"app_name_fpatch\">FPatch</string>\n\n    <string name=\"desktop_app_name\">桌面上的名字喵</string>\n\n    <string name=\"home\">首页</string>\n\n    <string name=\"success\">成功啦喵！</string>\n    <string name=\"failure\">呜喵... 搞砸了啦...</string>\n\n    <string name=\"patch_warnning\">修补可是很危险的喵 数据要自己备份好哦，要是出事了，人家才不管你呢喵！</string>\n    <string name=\"patch\">开始修补喵！</string>\n\n    <string name=\"kernel_patch\">内核补丁哒</string>\n    <string name=\"android_patch\">系统补丁哒</string>\n\n    <string name=\"settings_nav_layout_title\">导航布局设置喵</string>\n    <string name=\"settings_nav_layout_summary\">隐藏东西喵，不要一直问人家怎么弄啦喵</string>\n    <string name=\"settings_nav_scheme\">导航方案设置喵</string>\n    <string name=\"settings_nav_mode\">导航栏模式喵</string>\n    <string name=\"settings_nav_mode_summary\">选择导航栏的显示方式喵</string>\n    <string name=\"settings_nav_mode_auto\">自动传统导航栏 喵</string>\n    <string name=\"settings_nav_mode_bottom\">始终底部导航栏 喵</string>\n    <string name=\"settings_nav_mode_rail\">始终侧边导航栏 喵</string>\n    <string name=\"settings_nav_mode_floating\">悬浮底部导航栏 喵</string>\n    <string name=\"settings_show_apm\">显示系统模块喵</string>\n    <string name=\"settings_show_kpm\">显示内核模块喵</string>\n    <string name=\"settings_show_superuser\">显示超级用户喵</string>\n    <string name=\"settings_floating_auto_hide\">自动隐藏悬浮底栏喵</string>\n    <string name=\"settings_floating_auto_hide_summary\">3秒没操作就自动藏起来喵</string>\n    <string name=\"settings_floating_swipe_hide\">滑动隐藏悬浮底栏喵</string>\n    <string name=\"settings_floating_swipe_hide_summary\">往下滑藏起来，往上滑亮出来喵</string>\n    <string name=\"settings_navbar_glass_effect\">毛玻璃效果喵</string>\n    <string name=\"settings_navbar_glass_effect_summary\">给悬浮导航栏加上毛玻璃效果喵</string>\n    <string name=\"settings_navbar_glass_blur_strength\">模糊强度喵</string>\n    <string name=\"settings_navbar_glass_transparency\">背景透明度喵</string>\n    <string name=\"settings_navbar_glass_highlight_strength\">高光强度喵</string>\n    <string name=\"settings_navbar_glass_specular\">镜面反射喵</string>\n    <string name=\"settings_navbar_glass_specular_summary\">在导航栏顶部启用镜面高光效果喵</string>\n    <string name=\"settings_navbar_glass_inner_glow\">内发光喵</string>\n    <string name=\"settings_navbar_glass_inner_glow_summary\">在导航栏底部启用微光效果喵</string>\n    <string name=\"settings_navbar_glass_border\">玻璃边框喵</string>\n    <string name=\"settings_navbar_glass_border_summary\">在导航栏周围启用微妙的边框描边喵</string>\n    <string name=\"settings_stats_top_layout\">顶部布局喵</string>\n    <string name=\"settings_stats_top_layout_summary\">选择顶部信息卡片样式喵</string>\n    <string name=\"settings_stats_top_layout_list\">列表样式喵</string>\n    <string name=\"settings_stats_top_layout_grid\">网格样式喵</string>\n    <string name=\"settings_block_kernelpatch_update\">不要提示内核补丁更新啦喵</string>\n    <string name=\"settings_block_kernelpatch_update_summary\">烦死啦，人家才不要看更新提示呢喵！</string>\n    <string name=\"settings_block_androidpatch_update\">不要提示系统补丁更新啦喵</string>\n    <string name=\"settings_block_androidpatch_update_summary\">才不要看系统补丁的更新提示呢，烦死了喵！</string>\n\n    <string name=\"reboot\">要重启一下哦喵！</string>\n    <string name=\"settings\">设置</string>\n    <string name=\"reboot_recovery\">重启到Recovery模式哒喵！</string>\n    <string name=\"reboot_bootloader\">重启到Bootloader模式哒喵！</string>\n    <string name=\"reboot_download\">重启到下载模式哒喵！</string>\n    <string name=\"reboot_edl\">重启到EDL模式哒喵！</string>\n    <string name=\"reboot_fastbootd\">重启到FastbootD模式哒喵！</string>\n    <string name=\"about\">关于人家哒</string>\n    <string name=\"developer_and_maintainer\">开发者喵 | 维护者喵</string>\n    <string name=\"settings_app_language\">语言设置喵</string>\n    <string name=\"system_default\">系统默认哒</string>\n    <string name=\"settings_global_namespace_mode\">全局命名空间模式喵</string>\n    <string name=\"settings_global_namespace_mode_summary\">所有root会话都用全局挂载哒，不要乱改哦喵！</string>\n    <string name=\"settings_clear_super_key_dialog\">真的要继续嘛？不要后悔哦喵！</string>\n\n    <string name=\"home_learn_apatch\">了解一下FolkPatch呗喵</string>\n    <string name=\"home_click_to_learn_apatch\">快戳人家喵！看看FolkPatch能干什么喵</string>\n    <string name=\"settings_hide_apatch_card\">藏起这张卡片喵</string>\n    <string name=\"settings_hide_apatch_card_summary\">把首页那个\"了解FolkPatch\"的卡片藏起来啦喵</string>\n    <string name=\"settings_grid_working_card_hide_check\">把状态图标藏起来喵</string>\n    <string name=\"settings_grid_working_card_hide_check_summary\">把工作卡片上的勾勾或者警告标标藏起来哦喵</string>\n    <string name=\"settings_grid_working_card_hide_text\">把状态文字藏起来喵</string>\n    <string name=\"settings_grid_working_card_hide_text_summary\">把工作卡片上“工作中”或者“没安装”的小字字藏起来喵</string>\n    <string name=\"settings_grid_working_card_hide_mode\">把工作模式藏起来喵</string>\n    <string name=\"settings_grid_working_card_hide_mode_summary\">把工作卡片上那个“Full”或者“Half”藏起来嘛喵</string>\n\n    <string name=\"settings_folkx_engine_title\">FolkX动画引擎喵</string>\n    <string name=\"settings_folkx_engine_summary\">切页面的时候用那个弹簧动画哒，不要嫌卡哦喵</string>\n    <string name=\"settings_folkx_animation_type\">动画类型喵</string>\n    <string name=\"settings_folkx_animation_speed\">动画速度喵</string>\n    <string name=\"settings_folkx_animation_linear\">直直地动喵</string>\n    <string name=\"settings_folkx_animation_spatial\">空间感地动喵</string>\n    <string name=\"settings_folkx_animation_fade\">变来变去喵</string>\n    <string name=\"settings_folkx_animation_vertical\">上下滑滑喵</string>\n    <string name=\"settings_folkx_animation_diagonal\">斜着滑滑喵</string>\n\n    <string name=\"about_source_code\"><![CDATA[<p>去 %1$s 看看人家的源码喵<p/>加人家的 %2$s 频道哦喵<p/>还要加人家的 %3$s 群呢喵]]></string>\n    <string name=\"send_log\">发送日志喵！</string>\n    <string name=\"save_log\">保存日志喵！</string>\n    <string name=\"log_saved\">日志已经乖乖存好了喵！</string>\n    <string name=\"safe_mode\">安全模式喵</string>\n    <string name=\"github\">GitHub</string>\n    <string name=\"telegram\">Telegram</string>\n    <string name=\"support_or_donate\">支持喵 / 捐赠喵</string>\n\n    <string name=\"super_key\">超级密钥喵</string>\n    <string name=\"clear_super_key\">清掉这个密钥喵</string>\n    <string name=\"patch_set_superkey\">设置超级密钥喵</string>\n    <string name=\"home_patch_set_key_desc\">KernelPatch只认这个哒喵！</string>\n    <string name=\"home_patch_next_step\">下一步哒喵！不要磨蹭哦</string>\n\n    <string name=\"home_not_installed\">还没有装喵！</string>\n    <string name=\"home_install_unknown\">没装或者没认证喵！</string>\n    <string name=\"home_install_unknown_summary\">戳戳人家安装喵！</string>\n    <string name=\"home_click_to_install\">戳人家一下就安装喵！</string>\n    <string name=\"home_working\">正在干活喵！</string>\n    <string name=\"home_kp_need_update\">有新版本出来啦喵！</string>\n    <string name=\"home_kp_cando_update\">快更新喵！</string>\n\n    <string name=\"home_installing\">正在安装中喵！不要催人家啦</string>\n\n    <string name=\"kpatch_version\">版本号：%s 喵</string>\n    <string name=\"apatch_version\">版本号：%s 喵</string>\n\n    <string name=\"kpatch_version_update\" formatted=\"false\">版本号：%s -> %s 喵</string>\n    <string name=\"kpatch_shadow_path_title\">kpatch 喵</string>\n    <string name=\"home_auth_key_title\">输超级密钥喵！</string>\n    <string name=\"home_auth_key_desc\">认证完才能用哦，不要乱戳人家哒喵</string>\n    <string name=\"home_kpatch_info_title\">信息哒</string>\n\n    <string name=\"home_ap_cando_install\">装一下喵！</string>\n\n    <string name=\"home_ap_cando_uninstall\">卸掉喵！</string>\n    <string name=\"home_ap_cando_reboot\">重启一下喵！</string>\n\n    <string name=\"patch_title\">修补一下喵！</string>\n\n    <string name=\"patch_config_title\">补丁喵</string>\n    <string name=\"patch_mode_bootimg_patch\">模式：修补哒</string>\n    <string name=\"patch_mode_patch_and_install\">模式：修补完马上就装喵</string>\n    <string name=\"patch_mode_install_to_next_slot\">模式：装到没激活的槽位（OTA后）哒</string>\n    <string name=\"patch_mode_restore\">模式：恢复喵</string>\n    <string name=\"patch_mode_uninstall_patch\">模式：把KPatch卸掉喵</string>\n    <string name=\"patch_select_bootimg_btn\">选镜像喵！</string>\n    <string name=\"patch_embed_kpm_btn\">嵌KPM喵！</string>\n    <string name=\"patch_start_patch_btn\">开始啦喵！</string>\n    <string name=\"patch_start_unpatch_btn\">还原喵！</string>\n    <string name=\"patch_item_error\">!!呜哇！出错了啦!!</string>\n    <string name=\"patch_item_bootimg\">bootimg 喵</string>\n    <string name=\"patch_item_bootimg_slot\">槽位喵：</string>\n    <string name=\"patch_item_bootimg_dev\">设备喵：</string>\n    <string name=\"patch_item_kernel\">内核喵</string>\n    <string name=\"patch_item_kpimg\">kpimg 喵</string>\n    <string name=\"patch_item_kpimg_version\">版本喵：</string>\n    <string name=\"patch_item_kpimg_comile_time\">时间喵：</string>\n    <string name=\"patch_item_kpimg_config\">配置喵：</string>\n    <string name=\"patch_item_new_extra_kpm\">嵌新模块喵</string>\n    <string name=\"patch_item_existed_extra_kpm\">已经有了喵！</string>\n    <string name=\"patch_item_extra_name\">名字喵：</string>\n    <string name=\"patch_item_extra_version\">版本喵：</string>\n    <string name=\"patch_item_extra_author\">作者喵：</string>\n    <string name=\"patch_item_extra_kpm_license\">许可喵：</string>\n    <string name=\"patch_item_extra_kpm_desciption\">描述喵：</string>\n    <string name=\"patch_item_extra_args\">参数喵：</string>\n    <string name=\"patch_item_extra_event\">事件喵：</string>\n    <string name=\"patch_item_skey\">超级密钥喵</string>\n    <string name=\"patch_custom_superkey\">自定义超级密钥喵</string>\n    <string name=\"patch_custom_superkey_summary\">你仍然可以设置超级密钥来管理Root和内核喵，管理器已经由内置签名授权了喵</string>\n    <string name=\"patch_item_set_skey_label\">超级密钥要是8-63个字符喵，只有数字字母哦，绝对不要加奇怪符号啦喵！</string>\n    <string name=\"patch_confirm_superkey\">再确认一次超级密钥喵</string>\n    <string name=\"patch_skey_mismatch\">两次超级密钥不一样喵</string>\n\n    <string name=\"home_kernel\">内核版本哒</string>\n    <string name=\"home_manager_version\">管理器版本哒</string>\n    <string name=\"home_fingerprint\">指纹喵</string>\n\n    <string name=\"home_selinux_status\">SELinux状态喵</string>\n    <string name=\"home_selinux_status_disabled\">关掉了喵！</string>\n    <string name=\"home_selinux_status_enforcing\">强制开着喵！</string>\n    <string name=\"home_selinux_status_permissive\">宽松模式喵！</string>\n    <string name=\"home_selinux_status_unknown\">不知道喵...</string>\n\n    <string name=\"settings_selinux_mode\">SELinux模式喵</string>\n    <string name=\"settings_selinux_mode_summary\">选一下SELinux怎么执行嘛，不要乱选哦喵！</string>\n    <string name=\"settings_selinux_mode_enforcing\">强制（很严的）喵</string>\n    <string name=\"settings_selinux_mode_permissive\">宽松（睁只眼闭只眼）喵</string>\n    <string name=\"settings_selinux_current_mode\">现在是喵：%s</string>\n    <string name=\"settings_selinux_mode_enforcing_summary\">SELinux会严格按规则来哒，不要违规哦喵！</string>\n    <string name=\"settings_selinux_mode_permissive_summary\">SELinux只会记违规，不会拦着你的喵</string>\n\n    <string name=\"home_device_info\">设备信息喵</string>\n    <string name=\"home_system_version\">你的系统喵</string>\n    <string name=\"home_kpatch_version\">喵呜,内核补丁版本</string>\n    <string name=\"home_su_path\">su可执行文件喵</string>\n    <string name=\"home_apatch_version\">FolkPatch版本喵</string>\n\n    <string name=\"kpm\">内核模块</string>\n    <string name=\"kpm_kp_not_installed\">内核补丁还没装喵！</string>\n    <string name=\"kpm_add_kpm\">加KPM喵！</string>\n    <string name=\"kpm_load\">加载一下喵！</string>\n    <string name=\"kpm_install\">安装喵！</string>\n    <string name=\"kpm_embed\">嵌进去喵！</string>\n    <string name=\"kpm_load_toast_succ\">加载成功啦喵！</string>\n    <string name=\"kpm_load_toast_failed\">加载失败了喵...</string>\n    <string name=\"kpm_unload_confirm\">要把%s模块卸掉嘛喵？不要后悔哦</string>\n    <string name=\"kpm_unload\">卸掉喵！</string>\n    <string name=\"kpm_control\">控制喵！</string>\n    <string name=\"kpm_apm_empty\">没加载模块喵！</string>\n    <string name=\"kpm_version\">版本哒</string>\n    <string name=\"kpm_license\">许可哒</string>\n    <string name=\"kpm_author\">作者哒</string>\n    <string name=\"kpm_desc\">描述哒</string>\n    <string name=\"kpm_args\">参数哒</string>\n\n    <string name=\"su_title\">超级用户</string>\n    <string name=\"su_selinux_via_hook\">靠hook绕过去喵！</string>\n    <string name=\"su_pkg_excluded_label\">排除喵</string>\n    <string name=\"su_batch_exclude_title\">批量排除喵</string>\n    <string name=\"su_batch_exclude_content\">给所有没ROOT权限的软件排除注入，选一个嘛喵</string>\n    <string name=\"su_exclude_btn\">排除掉喵！</string>\n    <string name=\"su_exclude_reverse_btn\">反过来嘛喵！</string>\n\n    <!-- 超级用户应用操作弹窗 -->\n    <string name=\"su_app_action_title\">应用操作喵</string>\n    <string name=\"su_app_action_content\">请选择一个动作来对应用执行喵</string>\n    <string name=\"su_app_action_launch\">启动应用喵</string>\n    <string name=\"su_app_action_force_stop\">强行停止喵</string>\n    <string name=\"su_app_action_launch_success\">正在启动 %s 喵</string>\n    <string name=\"su_app_action_force_stop_success\">已经强行停止 %s 了喵</string>\n    <string name=\"su_app_action_failed\">操作失败了喵: %s</string>\n\n    <!-- SU Audit Log -->\n    <string name=\"su_audit_log_title\">授权日志</string>\n    <string name=\"su_audit_log_empty\">暂无授权记录</string>\n    <string name=\"su_audit_log_clear\">清除日志</string>\n    <string name=\"su_audit_log_clear_confirm\">确认清除所有授权记录？</string>\n    <string name=\"su_audit_action_grant\">已授权</string>\n    <string name=\"su_audit_action_revoke\">已撤销</string>\n    <string name=\"su_audit_action_exclude\">已排除</string>\n    <string name=\"su_audit_tab_usage\">使用记录</string>\n    <string name=\"su_audit_tab_operations\">操作记录</string>\n\n    <string name=\"su_pkg_excluded_setting_title\">改排除设置喵</string>\n    <string name=\"su_pkg_excluded_setting_summary\">开这个，FolkPatch就能恢复这应用改过的文件哒喵！</string>\n    <string name=\"su_pkg_root_setting_title\">超级用户喵</string>\n    <string name=\"su_pkg_root_setting_summary\">开这个为你的软件启动超级用户喵，软件能够使用SU命令喵</string>\n    <string name=\"su_pkg_normal_setting_title\">常规模式喵</string>\n    <string name=\"su_pkg_normal_setting_summary\">不授予 root 权限，应用在没有超级用户权限下运行喵。</string>\n    <string name=\"su_show_system_apps\">显示系统应用喵</string>\n    <string name=\"su_hide_system_apps\">藏起系统应用喵</string>\n    <string name=\"su_refresh\">刷新一下喵！</string>\n\n    <string name=\"apm\">系统模块</string>\n    <string name=\"apm_not_installed\">系统补丁没装喵！</string>\n    <string name=\"apm_failed_to_enable\">开模块失败了喵：%s</string>\n    <string name=\"apm_failed_to_disable\">关模块失败了喵：%s</string>\n    <string name=\"apm_empty\">没装模块喵，这里空空的</string>\n    <string name=\"apm_remove\">删掉喵！</string>\n    <string name=\"apm_undo\">恢复喵！</string>\n    <string name=\"apm_install\">安装喵！</string>\n    <string name=\"apm_uninstall_confirm\">要把%s模块卸了嘛喵？不要后悔哦</string>\n    <string name=\"apm_uninstall_success\">%s 卸掉了喵！</string>\n    <string name=\"apm_uninstall_failed\">卸载失败了喵：%s</string>\n    <string name=\"apm_undo_uninstall_success\">%s恢复成功了喵</string>\n    <string name=\"apm_undo_uninstall_failed\">恢复失败了喵：%s</string>\n    <string name=\"apm_version\">版本哒</string>\n    <string name=\"apm_author\">作者哒</string>\n    <string name=\"apm_desc\">描述哒</string>\n    <string name=\"apm_overlay_fs_not_available\">内核把OverlayFS关掉了，模块用不了喵！</string>\n    <string name=\"apm_magisk_conflict\">跟Magisk冲突了喵，模块用不了啦</string>\n    <string name=\"apm_mount_warning_title\">重要提醒喵！</string>\n    <string name=\"apm_mount_warning_message\">默认是不挂模块的，用内置挂载或者元模块喵！</string>\n    <string name=\"apm_mount_warning_button\">知道了喵！</string>\n    <string name=\"apm_reboot_to_apply\">重启一下才生效喵！</string>\n    <string name=\"apm_changelog\">更新日志喵</string>\n    <string name=\"apm_update\">更新喵！</string>\n    <string name=\"apm_downloading\">正在下模块喵：%s</string>\n    <string name=\"apm_start_downloading\">开始下载喵：%s</string>\n    <string name=\"apm_new_version_available\">有新版本 %s 了喵，戳人家升级吧</string>\n\n    <string name=\"hide_apatch_manager\">藏起APatch管理器喵</string>\n    <string name=\"hide_apatch_manager_summary\">装个随机包名自定义名字的代理应用喵！</string>\n    <string name=\"hide_apatch_dialog_new_manager_name\">新管理器名字喵</string>\n    <string name=\"hide_apatch_dialog_summary\">这就是启动器里显示的名字喵！</string>\n    <string name=\"hide_apatch_manager_failure\">藏失败了喵...报这个错哦！</string>\n\n    <string name=\"setting_reset_su_path\">重置su路径喵</string>\n    <string name=\"setting_reset_su_new_path\">新的完整路径喵</string>\n\n    <string name=\"apm_webui_open\">打开喵！</string>\n    <string name=\"apm_action\">操作喵</string>\n    <string name=\"module_shortcut_add\">快捷喵</string>\n    <string name=\"module_shortcut_not_supported\">启动器不支持桌面快捷方式喵！</string>\n    <string name=\"module_shortcut_created\">已在桌面创建快捷方式哒喵！</string>\n    <string name=\"module_shortcut_updated\">快捷方式已更新哒喵！</string>\n    <string name=\"module_shortcut_permission_tip_xiaomi\">请在小米设置中为本应用启用\"创建桌面快捷方式\"权限喵！</string>\n    <string name=\"module_shortcut_permission_tip_oppo\">请在OPPO设置中为本应用启用\"桌面快捷方式\"权限喵！</string>\n    <string name=\"module_shortcut_permission_tip_default\">若创建快捷方式失败，请在系统设置中为本应用启用桌面快捷方式权限喵！</string>\n    <string name=\"module_shortcut_name\">快捷喵名称</string>\n    <string name=\"module_shortcut_icon\">快捷喵图标</string>\n    <string name=\"module_shortcut_type\">快捷喵类型</string>\n    <string name=\"module_shortcut_type_webui\">WebUI</string>\n    <string name=\"module_shortcut_type_action\">Action</string>\n    <string name=\"module_shortcut_icon_default\">使用默认图标喵</string>\n    <string name=\"module_shortcut_icon_select\">选择图标喵</string>\n    <string name=\"enable_web_debugging\">开WebView调试喵</string>\n    <string name=\"enable_web_debugging_summary\">调WebUI用的哒，没事不要开哦喵！</string>\n    <string name=\"settings_apm_install_confirm\">装模块要确认喵</string>\n    <string name=\"settings_apm_install_confirm_summary\">装模块前弹个确认框问问你喵！</string>\n    <string name=\"settings_show_more_module_info\">显示模块详情喵</string>\n    <string name=\"settings_show_more_module_info_summary\">模块列表里显示ID和大小喵！</string>\n    <string name=\"settings_apm_stay_on_page\">留在操作页喵</string>\n    <string name=\"settings_apm_stay_on_page_summary\">系统模块操作完不要自动回去啦喵</string>\n    <string name=\"settings_show_disable_all_modules\">显示“关所有模块”按钮喵</string>\n    <string name=\"settings_enable_module_shortcut_add\">开快捷添加按钮喵</string>\n    <string name=\"settings_enable_module_shortcut_add_summary\">在系统模块页面显示WebUI快捷添加按钮喵！</string>\n    <string name=\"settings_show_disable_all_modules_summary\">模块页显示一键关所有模块的按钮喵！</string>\n    <string name=\"settings_disable_module_update_check\">关掉模块更新检查喵</string>\n    <string name=\"settings_disable_module_update_check_summary\">不要自动查系统模块更新喵！</string>\n    <string name=\"settings_module_sort_optimization\">模块排序优化喵</string>\n    <string name=\"settings_module_sort_optimization_summary\">带WebUI和Action的模块放前面喵！</string>\n    <string name=\"settings_fold_system_module\">折叠系统模块喵</string>\n    <string name=\"settings_fold_system_module_summary\">点模块卡片展开/收起操作按钮喵！</string>\n    <string name=\"settings_simple_list_bottom_bar\">简约列表底栏喵</string>\n    <string name=\"settings_simple_list_bottom_bar_summary\">模块操作用纯图标按钮喵，灵感来自APatch喵！</string>\n    <string name=\"settings_spliced_card_group\">拼接卡片组喵</string>\n    <string name=\"settings_spliced_card_group_summary\">模块列表用连续拼接卡片样式喵！</string>\n    <string name=\"apm_install_confirm_title\">装模块喵</string>\n    <string name=\"apm_install_confirm_content\">确定装 %s 嘛喵？不要装错哦！</string>\n    <string name=\"apm_disable_all_title\">关所有模块喵</string>\n    <string name=\"apm_disable_all_confirm\">确定关所有模块喵？一般只有OTA更新前才这么干哦</string>\n    <string name=\"apm_enable_module_banner\">开模块横幅喵</string>\n    <string name=\"apm_enable_module_banner_summary\">支持的模块显示横幅图片喵！</string>\n    <string name=\"apm_enable_folk_banner\">自定模块横幅喵</string>\n    <string name=\"apm_enable_folk_banner_summary\">长按模块卡片自选横幅图片喵！</string>\n    <string name=\"apm_folk_banner_title\">FolkBanner</string>\n    <string name=\"apm_folk_banner_select\">选图片喵</string>\n    <string name=\"apm_folk_banner_clear\">删掉 FolkBanner 喵</string>\n    <string name=\"apm_folk_banner_saved\">给 %s 装上 FolkBanner 啦喵！</string>\n    <string name=\"apm_folk_banner_cleared\">%s 的 FolkBanner 删掉啦喵！</string>\n    <string name=\"apm_folk_banner_failed\">给 %s 装 FolkBanner 失败了喵...</string>\n    <string name=\"apm_banner_api_mode\">API模式喵</string>\n    <string name=\"apm_banner_api_mode_summary\">用随机图API或本地目录给模块搞横幅喵</string>\n    <string name=\"apm_banner_api_source\">配置API源喵</string>\n    <string name=\"apm_banner_api_source_hint\">输入API地址或本地路径喵</string>\n    <string name=\"apm_banner_api_source_saved\">API源保存好了喵！</string>\n    <string name=\"apm_banner_api_source_not_configured\">还没配置喵</string>\n    <string name=\"apm_banner_api_url_configured\">API地址配置好了喵</string>\n    <string name=\"apm_banner_local_dir_configured\">本地目录配置好了喵</string>\n    <string name=\"apm_banner_clear_cache\">清除缓存喵</string>\n    <string name=\"apm_banner_cache_cleared\">横幅缓存清掉了喵！</string>\n    <string name=\"apm_banner_api_config_title\">配置API源喵</string>\n    <string name=\"apm_banner_api_config_desc\">输入随机图API地址或本地目录路径，每个模块都会有个特别的横幅喵！</string>\n    <string name=\"apm_banner_api_examples_title\">示例喵：</string>\n    <string name=\"apm_banner_api_examples\">API: https://picsum.photos/800/400\\n本地: /sdcard/Pictures/Banners</string>\n    <!-- API 接口市场喵 -->\n    <string name=\"apm_api_marketplace_title\">API 接口市场喵</string>\n    <string name=\"apm_api_preview\">预览喵</string>\n    <string name=\"apm_api_apply\">应用喵</string>\n    <string name=\"apm_api_preview_title\">API 预览喵</string>\n    <string name=\"apm_api_preview_failed\">预览加载失败喵</string>\n    <string name=\"apm_api_url_label\">API 地址喵：</string>\n    <string name=\"apm_api_apply_success\">API 源应用成功了喵！</string>\n    <string name=\"apm_api_verifying\">验证中喵…</string>\n    <string name=\"apm_api_retry\">重试喵</string>\n    <string name=\"apm_api_empty\">暂无 API 源喵</string>\n    <string name=\"settings_banner_custom_opacity\">横幅透明度喵</string>\n    <string name=\"settings_banner_custom_opacity_summary\">自定横幅透明度，不再跟随机箱模式哒喵！</string>\n    <string name=\"settings_banner_opacity\">横幅不透明度喵</string>\n\n    <string name=\"settings_donot_store_superkey\">本地不要存超级密钥喵</string>\n    <string name=\"settings_donot_store_superkey_summary\">每次开管理器都要验证密钥哒喵！</string>\n\n    <string name=\"mode_select_page_title\">安装喵！</string>\n    <string name=\"mode_select_page_patch_and_install\">修补完马上就装喵！</string>\n    <string name=\"mode_select_page_select_file\">选要修补的镜像喵！</string>\n    <string name=\"restore_select_file\">选要还原的分区引导文件喵！</string>\n    <string name=\"mode_select_page_install_inactive_slot\">装到没激活的槽位（OTA后）哒</string>\n    <string name=\"mode_select_page_install_inactive_slot_warning\">你设备重启后会**强制**进当前没激活的槽位喵！\n只有OTA完才用这选项哦喵！\n 要继续嘛？</string>\n    <string name=\"mode_select_page_select_kpimg\">用本地补丁文件喵 (KPimg)</string>\n    <string name=\"patch_custom_kpimg_label\">自定义 KPimg 喵</string>\n    <string name=\"patch_custom_kpimg_file\">文件: %s</string>\n    <string name=\"patch_select_kpimg_btn\">选择 KPimg 喵</string>\n\n    <string name=\"home_dialog_auth_fail_title\">认证失败了喵！</string>\n    <string name=\"home_dialog_auth_fail_content\">超级密钥认不了喵，导致 FolkPatch 激活失败喵！\n失败的原因大概是喵——请自己对号入座喵：\n\\n1. 你根本没使用 KernelPatch 来修补 boot.img，或者忘了自己到底做了啥喵。\n\\n2. 修补完的 boot.img 还躺在电脑里睡觉，根本没刷进设备喵。\n\\n3. 超级密钥输错了，或者夹杂了神秘符号，比如来自外星文的字符喵。\n\\n4. 你的设备可能并不兼容 FolkPatch 和 KernelPatch，强行折腾也是徒劳喵。\n\\n5. 你可能搞了一些神秘操作——比如用了某些排除包名的模块把 FolkPatch 给屏蔽了，结果把自己整出局了喵。\n\\n请先好好检查一遍，再试一次喵。如果问题依旧，欢迎到官方仓库的 issue 页面提问喵。\n毕竟，有些问题可能纯粹是您自己亲手造成的喵！\n祝你好运啊喵！顺带一提，你点击下面的文档按钮准没错喵！</string>\n\n    <string name=\"home_more_menu_feedback_or_suggestion\">反馈或者建议喵</string>\n    <string name=\"home_more_menu_about\">关于人家哒</string>\n    <string name=\"home_more_menu_document\">文档喵</string>\n\n    <string name=\"home_dialog_uninstall_title\">卸载喵！</string>\n    \n    <string name=\"home_dialog_uninstall_ap_only\">只卸补丁喵</string>\n    <string name=\"home_dialog_uninstall_all\">全都要卸掉喵</string>\n    <string name=\"home_dialog_uninstall_ap_only_desc\">只删系统补丁，留管理器喵！</string>\n    <string name=\"home_dialog_uninstall_all_desc\">删系统补丁，走全卸载流程喵！</string>\n\n    <string name=\"kpm_control_dialog_title\">控KPM喵！</string>\n    <string name=\"kpm_control_dialog_content\">输控制参数喵：</string>\n    <string name=\"kpm_control_paramters\">参数哒</string>\n    <string name=\"kpm_control_outMsg\">输出哒</string>\n    <string name=\"kpm_control_ok\">好了喵！</string>\n    <string name=\"kpm_control_failed\">搞砸了喵！</string>\n    \n    <string name=\"settings_night_mode_follow_sys\">夜色跟着系统走喵</string>\n    <string name=\"settings_night_mode_follow_sys_summary\">按系统设置自动切深色模式喵！</string>\n    <string name=\"settings_night_theme_enabled\">夜深人静喵</string>\n    \n    <string name=\"settings_use_system_color_theme\">我要变换颜色喵</string>\n    <string name=\"settings_use_system_color_theme_summary\">用系统按壁纸生成的颜色主题喵！</string>\n    <string name=\"settings_custom_color_theme\">颜色主题喵</string>\n\n    <string name=\"amber_theme\">琥珀色喵</string>\n    <string name=\"blue_theme\">蓝色喵</string>\n    <string name=\"blue_grey_theme\">蓝灰色喵</string>\n    <string name=\"brown_theme\">棕色喵</string>\n    <string name=\"cyan_theme\">青色喵</string>\n    <string name=\"deep_orange_theme\">深橙色喵</string>\n    <string name=\"deep_purple_theme\">深紫色喵</string>\n    <string name=\"green_theme\">绿色喵</string>\n    <string name=\"indigo_theme\">靛青色喵</string>\n    <string name=\"light_blue_theme\">浅蓝色喵</string>\n    <string name=\"light_green_theme\">浅绿色喵</string>\n    <string name=\"lime_theme\">酸橙色喵</string>\n    <string name=\"orange_theme\">橙色喵</string>\n    <string name=\"pink_theme\">粉色喵</string>\n    <string name=\"purple_theme\">紫色喵</string>\n    <string name=\"red_theme\">红色喵</string>\n    <string name=\"sakura_theme\">樱花粉色喵</string>\n    <string name=\"teal_theme\">蓝绿色喵</string>\n    <string name=\"yellow_theme\">黄色喵</string>\n    <string name=\"theme_color\">主题颜色喵</string>\n    <string name=\"theme_light\">浅色喵</string>\n    <string name=\"theme_dark\">深色喵</string>\n    <string name=\"theme_system\">系统喵</string>\n    <string name=\"loading_modules\">正在获取模块喵…</string>\n    <string name=\"loading_scripts\">正在查找脚本喵…</string>\n    <string name=\"loading_themes\">正在加载主题喵…</string>\n    <string name=\"loading_apis\">正在加载 API 源喵…</string>\n\n    <string name=\"about_app_desc\">基于内核的Root方案哒喵，支持内核模块和hook，不用重编内核哦喵！</string>\n    <string name=\"about_powered_by\">靠 %s 撑着哒喵！</string>\n    <string name=\"about_telegram_group\">Telegram群群喵</string>\n    \n    <!-- Online Module Strings -->\n    <string name=\"online_module_title\">给主人系统模块喵</string>\n    <string name=\"online_module_download_start\">开始下载喵：%s</string>\n    <string name=\"online_module_download_complete\">下载完成了喵：%s</string>\n    <string name=\"online_module_download_notification\">正在下 %s 喵！看通知栏进度，下完看下载文件夹哦喵</string>\n\n    <!-- Online KPM Strings -->\n    <string name=\"online_kpm_title\">给主人内核模块喵</string>\n    <string name=\"online_kpm_download_start\">开始下载喵：%s</string>\n    <string name=\"online_kpm_download_complete\">下载完成了喵：%s</string>\n    <string name=\"online_kpm_download_notification\">正在下 %s 喵！看通知栏进度，下完看下载文件夹哦喵</string>\n\n    <!-- Online Script Strings -->\n    <string name=\"online_script_title\">给主人脚本喵</string>\n    <string name=\"online_script_download_start\">开始下载喵：%s</string>\n    <string name=\"online_script_download_complete\">下载完成了喵：%s</string>\n    <string name=\"online_script_download_notification\">正在下 %s 喵</string>\n\n    <!-- Crash Handle Strings -->\n    <string name=\"crash_handle_title\">崩溃报告喵</string>\n    <string name=\"crash_handle_copy\">复制一下喵！</string>\n\n    <!-- About Screen Strings -->\n    <string name=\"about_app_version\">应用版本哒: %s</string>\n    <string name=\"about_github\">GitHub哒</string>\n    <string name=\"about_telegram_channel\">Telegram频道哒</string>\n\n    <string name=\"settings_app_dpi\">应用DPI喵</string>\n    <string name=\"dpi_apply_settings\">确定喵</string>\n    <string name=\"dpi_confirm_title\">确认改应用DPI喵</string>\n    <string name=\"dpi_confirm_message\">要把应用DPI从%1$s改成%2$s吗喵？</string>\n    <string name=\"settings_alt_icon\">备用图标喵</string>\n    <string name=\"alt_icon_summary\">用备选启动器图标喵</string>\n    <string name=\"settings_magic_mount\">我要挂载喵</string>\n    <string name=\"settings_magic_mount_summary\">开内置的模块挂载系统哒喵！</string>\n    <string name=\"settings_new_app_profile_mode\">新应用的默认模式喵</string>\n    <string name=\"settings_new_app_profile_normal\">普通模式喵</string>\n    <string name=\"settings_new_app_profile_root\">超级管理员喵</string>\n    <string name=\"settings_new_app_profile_exclude\">排除喵</string>\n    <string name=\"settings_hide_service\">FolkPatch Hide</string>\n    <string name=\"settings_hide_service_summary\">把Bootloader解锁状态藏起来喵~</string>\n    <string name=\"settings_app_title\">应用名字喵</string>\n    <string name=\"app_title_custom\">自定义喵</string>\n    <string name=\"settings_custom_app_title\">设置应用名字喵</string>\n    <string name=\"custom_app_title_dialog_title\">自定义应用名称喵</string>\n    <string name=\"custom_app_title_dialog_hint\">输入应用名字喵~</string>\n    <string name=\"custom_app_title_dialog_confirm\">好哒喵</string>\n    <string name=\"custom_app_title_dialog_empty\">名字不能是空的喵！</string>\n    <string name=\"cancel\">不要了喵</string>\n    <string name=\"settings_custom_background\">自选涩涩背景喵</string>\n    <string name=\"settings_custom_background_enabled\">自定义背景开了喵！</string>\n    <string name=\"settings_custom_background_opacity\">卡片透明度喵</string>\n    <string name=\"settings_custom_background_blur\">背景模糊喵</string>\n    <string name=\"settings_custom_background_dim\">背景暗度喵</string>\n    <string name=\"settings_custom_background_dual_dim\">开双切暗色适配喵</string>\n    <string name=\"settings_custom_background_dual_dim_desc\">根据亮色和暗色主题自动调整背景暗度喵</string>\n    <string name=\"settings_custom_background_day_dim\">白天模式暗度喵</string>\n    <string name=\"settings_custom_background_night_dim\">夜间模式暗度喵</string>\n    <string name=\"settings_multi_background_mode\">开多背景模式喵</string>\n    <string name=\"settings_multi_background_mode_summary\">不同页面设不同背景图喵！</string>\n    <string name=\"settings_select_home_background\">首页背景图喵</string>\n    <string name=\"settings_select_kernel_background\">内核模块背景图喵</string>\n    <string name=\"settings_select_superuser_background\">超级用户背景图喵</string>\n    <string name=\"settings_select_system_module_background\">系统模块背景图喵</string>\n    <string name=\"settings_select_settings_background\">设置页背景图喵</string>\n\n    <string name=\"settings_advanced_title_style\">高级标题样式喵</string>\n    <string name=\"settings_advanced_title_style_summary\">用自定义图片换掉顶部标题喵</string>\n    <string name=\"settings_advanced_title_style_enabled\">高级标题样式开起来了喵！</string>\n    <string name=\"settings_select_title_image\">挑标题图片喵！</string>\n    <string name=\"settings_title_image_selected\">标题图片挑好了喵！</string>\n    <string name=\"settings_title_image_saved\">标题图片存好了喵！</string>\n    <string name=\"settings_title_image_error\">标题图片存失败喵...</string>\n    <string name=\"settings_clear_title_image\">撤标题图片喵</string>\n    <string name=\"settings_clear_title_image_confirm\">真要撤标题图片吗喵？</string>\n    <string name=\"settings_title_image_cleared\">标题图片撤了喵</string>\n    <string name=\"settings_title_image_day_opacity\">白天透明度喵</string>\n    <string name=\"settings_title_image_night_opacity\">晚上透明度喵</string>\n    <string name=\"settings_title_image_dim\">背景暗度喵</string>\n    <string name=\"settings_title_image_offset_x\">水平位置喵</string>\n\n    <!-- Font Settings -->\n    <string name=\"settings_custom_font\">自定义字体喵</string>\n    <string name=\"settings_select_font_file\">选字体文件喵！</string>\n    <string name=\"settings_custom_font_enabled\">自定义字体开了喵！</string>\n    <string name=\"settings_custom_font_summary\">用自定义TTF字体哒喵！</string>\n    <string name=\"settings_font_selected\">选好自定义字体了喵！</string>\n    <string name=\"settings_custom_font_error\">存字体文件失败了喵...</string>\n    <string name=\"settings_custom_font_saved\">自定义字体存好了喵！</string>\n    <string name=\"settings_clear_font\">恢复默认字体喵</string>\n    <string name=\"settings_clear_font_confirm\">确定恢复默认字体嘛喵？</string>\n    <string name=\"settings_font_cleared\">恢复默认字体了喵！</string>\n    <string name=\"settings_font_select_hint\">选个TTF字体文件喵！</string>\n    <string name=\"settings_select_background_image\">选背景图喵！</string>\n    <string name=\"settings_custom_background_summary\">设自定义背景图喵！</string>\n    <string name=\"settings_custom_background_saved\">背景存好了喵！</string>\n    <string name=\"settings_custom_background_error\">存背景失败了喵...</string>\n    <string name=\"settings_background_selected\">选好背景了喵！</string>\n    <string name=\"settings_background_image_cleared\">背景删了喵！</string>\n    <string name=\"settings_clear_background\">清背景喵</string>\n    <string name=\"settings_clear_background_confirm\">确定清背景图嘛喵？</string>\n\n    <string name=\"settings_launcher_icon\">桌面图标喵</string>\n    <!-- Video Background Settings -->\n    <string name=\"settings_video_background\">视频背景喵</string>\n    <string name=\"settings_video_background_summary\">用视频当背景喵！</string>\n    <string name=\"settings_select_video\">选视频喵！</string>\n    <string name=\"settings_video_selected\">选好视频了喵！</string>\n    <string name=\"settings_clear_video_background\">清视频壁纸喵</string>\n    <string name=\"settings_clear_video_background_confirm\">确定清视频壁纸嘛喵？</string>\n    <string name=\"settings_video_background_enabled\">视频背景开了喵！</string>\n    <string name=\"settings_video_volume\">视频音量喵</string>\n\n    \n    <string name=\"su_exclude_all_title\">批量排除喵</string>\n    <string name=\"su_exclude_all_confirm\">确定把所有没授权Root的应用设为排除嘛喵？</string>\n\n    <!-- 隐藏设定字符串 -->\n    <string name=\"home_hide_su_path\">藏起su可执行文件路径喵</string>\n    <string name=\"home_hide_kpatch_version\">藏起内核版本补丁喵</string>\n    <string name=\"home_hide_fingerprint\">藏起指纹信息喵</string>\n    <string name=\"home_hide_zygisk\">藏起Zygisk实现喵</string>\n    <string name=\"home_hide_mount\">藏起挂载实现喵</string>\n    <string name=\"home_hide_su_path_summary\">信息卡片里藏起su可执行文件路径喵！</string>\n    <string name=\"home_hide_kpatch_version_summary\">信息卡片里藏起内核版本补丁信息喵！</string>\n    <string name=\"home_hide_zygisk_summary\">信息卡片里藏起Zygisk实现信息喵！</string>\n    <string name=\"home_hide_mount_summary\">信息卡片里藏起挂载实现信息喵！</string>\n    <string name=\"home_hide_fingerprint_summary\">信息卡片里藏起指纹信息喵！</string>\n    <!-- KPM AutoLoad Strings -->\n    <string name=\"kpm_autoload_title\">KPM自动加载喵</string>\n    <string name=\"kpm_autoload_enabled\">开自动加载喵</string>\n    <string name=\"kpm_autoload_enabled_summary\">开机就自动加载内核补丁模块哒喵！</string>\n    <string name=\"kpm_autoload_json_config\">JSON配置喵</string>\n    <string name=\"kpm_autoload_json_label\">JSON配置喵</string>\n    <string name=\"kpm_autoload_json_placeholder\">{\n  \"enabled\": true,\n  \"kpmPaths\": [\n    \"/path/to/module1.kpm\",\n    \"/path/to/module2.kpm\"\n  ]\n} 喵</string>\n    <string name=\"kpm_autoload_json_error\">JSON格式错了喵！</string>\n    <string name=\"kpm_autoload_json_helper\">输有效的JSON喵，里面得有KPM文件路径数组哦</string>\n    <string name=\"kpm_autoload_save\">存配置喵！</string>\n    <string name=\"kpm_autoload_save_confirm\">存自动加载配置嘛喵？</string>\n    <string name=\"kpm_autoload_save_success\">配置存好了喵！</string>\n    <string name=\"kpm_autoload_save_failed\">配置存失败了喵...</string>\n    <string name=\"kpm_autoload_visual_mode\">可视化模式喵</string>\n    <string name=\"kpm_autoload_json_mode\">JSON模式喵</string>\n\n    <!-- APM Bulk Install Strings -->\n    <string name=\"apm_bulk_install_title\">系统模块批量装喵</string>\n    <string name=\"apm_bulk_install_list_title\">模块列表喵</string>\n    <string name=\"apm_bulk_install_add\">加模块喵！</string>\n    <string name=\"apm_bulk_install_empty\">没加模块喵！</string>\n    <string name=\"apm_bulk_install_action\">批量刷喵！</string>\n    <string name=\"apm_bulk_install_log_title\">批量刷日志喵</string>\n    <string name=\"apm_bulk_install_log_start\">开始批量刷喵...</string>\n    <string name=\"apm_bulk_install_log_installing\">正在装 %s 喵！</string>\n    <string name=\"apm_batch_install_full_process\">完整批量装流程喵</string>\n    <string name=\"apm_batch_install_full_process_summary\">用完整流程装系统模块哒喵！</string>\n    <string name=\"next_module\">下一个模块喵！</string>\n    <string name=\"apm_bulk_install_log_installed\">%s 模块装完了喵！</string>\n    <string name=\"apm_bulk_install_log_done\">所有操作都做完了喵！</string>\n    <string name=\"apm_bulk_install_first_use_text\">这个功能可以一次刷多个模块哒喵，是快速刷哦！适合刷没按键操作的模块，要按音量键的去设置里开完整流程喵！</string>\n    <string name=\"apm_bulk_install_remove\">删掉喵！</string>\n    <string name=\"apm_first_use_title\">欢迎用系统模块喵</string>\n    <string name=\"apm_first_use_text\">欢迎用系统模块喵！这里用的是兼容Magisk的模块，点右下角装模块，也可以用顶部安装器批量装，还能一键备份所有模块喵！但这方案不一定都能用，自己要多备份哦</string>\n    <string name=\"kpm_autoload_add_kpm\">加KPM喵！</string>\n    <string name=\"kpm_autoload_remove_kpm\">删掉喵！</string>\n    <string name=\"kpm_autoload_edit_kpm\">编辑</string>\n    <string name=\"kpm_autoload_edit_dialog_title\">编辑 KPM</string>    <string name=\"kpm_autoload_event_label\">事件:</string>    <string name=\"kpm_autoload_args_label\">参数:</string>    <string name=\"kpm_autoload_event_service\">service</string>    <string name=\"kpm_autoload_event_post_fs_data\">post-fs-data</string>\n    <string name=\"kpm_autoload_kpm_list_title\">内核补丁模块列表喵</string>\n    <string name=\"kpm_autoload_no_kpm_added\">没加内核补丁模块喵！</string>\n    <string name=\"kpm_autoload_kpm_path\">KPM路径喵</string>\n    <string name=\"kpm_autoload_file_not_found\">文件没找到喵！</string>\n    <string name=\"kpm_autoload_select_kpm_file\">选KPM文件喵！</string>\n    <string name=\"kpm_autoload_first_time_title\">关于KPM自动加载喵</string>\n    <string name=\"kpm_autoload_first_time_message\">这个功能可以在开机的时候自动把配置里的所有KPM都加载起来哦喵！比嵌进内核方便多啦\n比如防分区被改的KPM，只能临时加载，嵌进去开不了机喵！不想改BOOT分区就用这个配置加载嘛\n要确保软件完全退出再进才执行命令，开机进一般也会加载喵！管理器黑屏一会儿是正常的，纯后台加载喵！记得手动上滑刷新看看加载没喵！</string>\n    <string name=\"kpm_autoload_first_time_confirm\">知道了喵！</string>\n    <string name=\"kpm_autoload_do_not_show_again\">不要再显示了喵！</string>\n\n    <string name=\"kpm_page_first_time_title\">警告喵！</string>\n    <string name=\"kpm_page_first_time_message\">内核模块是直接改Boot的喵！没系统模块的救砖机制，出问题只能进Fastboot修喵！先加载试试没问题再嵌Boot喵！只能加载的模块用自动加载功能喵！不懂的话不要用这个功能哦！</string>\n    <!-- Module Backup/Restore Strings -->\n    <string name=\"apm_backup_title\">备份模块喵！</string>\n    <string name=\"apm_restore_title\">恢复模块喵！</string>\n    <string name=\"apm_backup_success\">备份成功啦喵！</string>\n    <string name=\"apm_restore_success\">恢复成功啦喵！</string>\n    <string name=\"apm_backup_failed\">备份失败了喵...</string>\n    <string name=\"apm_restore_failed\">恢复失败了喵...</string>\n    <string name=\"apm_backup_failed_msg\">备份失败了喵：%s</string>\n    <string name=\"apm_restore_failed_msg\">恢复失败了喵：%s</string>\n    <string name=\"apm_copy_list_title\">抄录名录喵！</string>\n    <string name=\"apm_copy_list_success\">名录已抄录喵！</string>\n\n    <!-- Auto Backup Strings -->\n    <string name=\"settings_auto_backup_module\">自动备份系统模块喵</string>\n    <string name=\"settings_auto_backup_module_summary\">装模块的时候自动存到私有目录喵！</string>\n    <string name=\"settings_open_backup_dir\">开备份目录喵！</string>\n    <string name=\"backup_dir_empty\">备份目录是空的喵！</string>\n    <string name=\"backup_dir_open_failed\">开备份目录失败了喵...</string>\n    <string name=\"auto_backup_failed\">自动备份失败了喵：%s</string>\n    <string name=\"auto_backup_success\">自动备份成功了喵：%s</string>\n    <string name=\"settings_auto_backup_boot\">自动备份Boot喵</string>\n    <string name=\"settings_auto_backup_boot_summary\">自动把Boot备份到本地存储喵！(Downloads/FolkPatch/BootBackups)</string>\n\n    <!-- Home Layout Strings -->\n    <string name=\"settings_home_layout_style\">首页布局样式喵</string>\n    <string name=\"settings_home_layout_default\">ListUI</string>\n    <string name=\"settings_home_layout_grid\">GridUI</string>\n    <string name=\"settings_home_layout_focus\">FocusUI</string>\n    <string name=\"settings_home_layout_sign\">SignUI</string>\n    <string name=\"settings_home_layout_circle\">CircleUI</string>\n    <string name=\"settings_home_layout_dashboard_pro\">DashboardUI</string>\n    <string name=\"unofficial_version_title\">非官方版本喵！</string>\n    <string name=\"unofficial_version_message\">你用的FolkPatch管理器是第三方的喵！可能会坑你钱的哦！要下正版喵！</string>\n    <string name=\"go_to_github\">去Github喵！</string>\n    <string name=\"settings_save_theme\">存主题喵！</string>\n    <string name=\"settings_import_theme\">导主题喵！</string>\n    <string name=\"settings_theme_saved\">主题存好了喵！</string>\n    <string name=\"settings_theme_imported\">主题导好了喵！</string>\n    <string name=\"settings_theme_save_failed\">存主题失败了喵...</string>\n    <string name=\"settings_theme_import_failed\">导主题失败了喵...</string>\n    <string name=\"settings_reset_theme\">恢复主题喵！</string>\n    <string name=\"settings_reset_theme_confirm\">确定要把所有主题设置恢复成默认喵？这会清掉所有自定义背景、字体、音乐和音效喵~</string>\n    <string name=\"settings_theme_reset\">主题恢复好了喵！</string>\n    <string name=\"settings_theme_reset_failed\">恢复主题失败了喵...</string>\n\n    <!-- Theme Strings -->\n    <string name=\"theme_export_title\">导出主题喵！</string>\n    <string name=\"theme_import_title\">导入主题喵！</string>\n    <string name=\"theme_name\">主题名字喵</string>\n    <string name=\"theme_type\">主题类型喵</string>\n    <string name=\"theme_type_phone\">手机喵</string>\n    <string name=\"theme_type_tablet\">平板喵</string>\n    <string name=\"theme_version\">版本号喵</string>\n    <string name=\"theme_author\">作者喵</string>\n    <string name=\"theme_description\">描述喵</string>\n    <string name=\"theme_export_action\">导出喵！</string>\n    <string name=\"theme_import_action\">导入喵！</string>\n    <string name=\"theme_import_confirm\">确定导这个主题嘛喵？</string>\n    <string name=\"theme_info\">主题信息喵</string>\n\n    <!-- Grid Working Card Background -->\n    <string name=\"settings_grid_working_card_background\">工作卡片背景喵</string>\n    <string name=\"settings_grid_working_card_background_enabled\">自定义卡片背景开了喵！</string>\n    <string name=\"settings_grid_working_card_background_summary\">给工作卡片设自定义背景图喵！</string>\n    <string name=\"settings_grid_working_card_background_selected\">背景选好了喵！</string>\n    <string name=\"settings_grid_working_card_dual_opacity\">开双切透明度适配喵</string>\n    <string name=\"settings_grid_working_card_dual_opacity_desc\">根据亮色和暗色主题自动调整卡片透明度喵</string>\n    <string name=\"settings_grid_working_card_day_opacity\">白天模式透明度喵</string>\n    <string name=\"settings_grid_working_card_night_opacity\">夜间模式透明度喵</string>\n    <string name=\"settings_clear_grid_working_card_background\">清卡片背景喵</string>\n    <string name=\"settings_clear_grid_working_card_background_confirm\">确定清卡片背景图嘛喵？</string>\n    <string name=\"settings_grid_working_card_background_saved\">卡片背景存好了喵！</string>\n    <string name=\"settings_grid_working_card_background_error\">卡片背景存失败了喵...</string>\n    <string name=\"settings_grid_working_card_background_cleared\">卡片背景清了喵！</string>\n\n    <!-- Biometric Strings -->\n    <string name=\"action_biometric\">生物识别认证喵！</string>\n    <string name=\"msg_biometric\">验证你的指纹或者人脸喵</string>\n    <string name=\"settings_biometric_login\">生物识别认证喵</string>\n    <string name=\"settings_biometric_login_summary\">开应用要生物识别哒喵！</string>\n    <string name=\"settings_strong_biometric\">强力生物识别喵</string>\n    <string name=\"settings_strong_biometric_summary\">装、卸、关模块都要验证哒喵！</string>\n\n    <!-- Theme Store Strings -->\n    <string name=\"theme_store_title\">主题商店喵</string>\n    <string name=\"theme_source_official\">官方哒</string>\n    <string name=\"theme_source_third_party\">第三方哒</string>\n    <string name=\"theme_source_local\">本地哒</string>\n    <string name=\"theme_store_author\">作者喵：%s</string>\n    <string name=\"theme_store_version\">版本喵：%s</string>\n    <string name=\"theme_source\">来源喵</string>\n    <string name=\"theme_store_download_failed\">下失败了喵...</string>\n    <string name=\"theme_store_download\">下载喵！</string>\n    <string name=\"theme_store_search_hint\">搜主题喵...</string>\n\n    <!-- Update Check Strings -->\n    <string name=\"settings_auto_update_check\">自动查更新喵</string>\n    <string name=\"settings_auto_update_check_summary\">应用开了就自动查更新哒喵！</string>\n    <string name=\"settings_check_update\">查更新喵！</string>\n    <string name=\"update_available_title\">有更新喵！</string>\n    <string name=\"update_available_message\">你版本太老了喵！要下新版本嘛？</string>\n    <string name=\"update_action\">更新喵！</string>\n    <string name=\"update_close\">关掉喵！</string>\n    <string name=\"update_latest\">你已经是最新版本了喵！</string>\n    <string name=\"update_error\">查更新出错了喵...</string>\n    <!--折叠选项-->\n    <string name=\"settings_category_general\">常规喵</string>\n    <string name=\"superuser\">超级用户喵</string>\n    <string name=\"module\">模块喵</string>\n    <string name=\"settings_app_list_loading_scheme\">应用列表加载方案喵</string>\n    <string name=\"settings_use_legacy_su_page\">单页超级用户授权喵</string>\n    <string name=\"settings_use_legacy_su_page_summary\">超级用户页面改用单页授权设计喵</string>\n    <string name=\"settings_app_list_loading_scheme_summary\">选怎么加载应用列表嘛喵！</string>\n    <string name=\"app_list_loading_scheme_root_service\">RootService（默认）</string>\n    <string name=\"app_list_loading_scheme_package_manager\">PackageManager（系统API）</string>\n    <string name=\"app_list_loading_scheme_dialog_title\">加载方案喵</string>\n    <string name=\"settings_category_appearance\">外观喵</string>\n    <string name=\"settings_appearance_font\">字体设置喵</string>\n    <string name=\"settings_appearance_font_summary\">自定义字体配置喵</string>\n    <string name=\"settings_appearance_theme\">主题设置喵</string>\n    <string name=\"settings_appearance_theme_summary\">主题商店、保存、导入和重置喵</string>\n    <string name=\"settings_appearance_banner\">横幅设置喵</string>\n    <string name=\"settings_appearance_banner_summary\">模块横幅配置喵</string>\n    <string name=\"settings_appearance_layout\">布局设置喵</string>\n    <string name=\"settings_appearance_layout_summary\">首页布局、导航和卡片自定义喵</string>\n    <string name=\"settings_appearance_background\">背景设置喵</string>\n    <string name=\"settings_appearance_background_summary\">自定义背景、视频和多背景喵</string>\n    <string name=\"settings_appearance_night_mode\">夜间模式设置喵</string>\n    <string name=\"settings_appearance_night_mode_summary\">深色主题和颜色配置喵</string>\n    <string name=\"settings_amoled_theme\">AMOLED 纯黑主题喵</string>\n    <string name=\"settings_amoled_theme_desc\">为深色模式使用纯黑背景喵</string>\n    <string name=\"settings_switch_icon\">启用按钮指示器</string>\n    <string name=\"settings_switch_icon_desc\">为开关按钮添加状态指示图标</string>\n    <string name=\"settings_category_behavior\">行为喵</string>\n    <string name=\"settings_category_function\">功能喵</string>\n    <string name=\"settings_category_module\">模块喵</string>\n    <string name=\"settings_category_security\">安全喵</string>\n    \n    <!-- 背景音乐字符串 -->\n    <string name=\"settings_background_music\">背景音乐喵</string>\n    <string name=\"settings_background_music_summary\">应用在前台就放歌喵！</string>\n    <string name=\"settings_background_music_playing\">正在放着呢喵：%s</string>\n    <string name=\"settings_background_music_enabled\">背景音乐开了喵！</string>\n    <string name=\"settings_select_music_file\">选音乐文件喵！</string>\n    <string name=\"settings_music_selected\">选好音乐了喵！</string>\n    <string name=\"settings_clear_music\">清音乐喵！</string>\n    <string name=\"settings_clear_music_confirm\">确定清背景音乐嘛喵？</string>\n    <string name=\"settings_music_auto_play\">自动播放喵</string>\n    <string name=\"settings_music_auto_play_summary\">开应用就自动放歌哒喵！</string>\n    <string name=\"settings_music_looping\">循环播放喵</string>\n    <string name=\"settings_music_looping_summary\">单曲循环喵！</string>\n    <string name=\"settings_music_volume\">音量喵</string>\n    <string name=\"settings_music_saved\">音乐文件存好了喵！</string>\n    <string name=\"settings_music_save_error\">存音乐文件失败了喵...</string>\n    <string name=\"settings_music_cleared\">音乐清了喵！</string>\n    <string name=\"settings_category_multimedia\">多媒体喵</string>\n    <string name=\"settings_category_general_summary\">语言喵、更新喵、SELinux喵、系统调整喵</string>\n    <string name=\"settings_category_appearance_summary\">主题喵、颜色喵、布局喵、背景喵、字体喵</string>\n    <string name=\"settings_category_behavior_summary\">Web调试喵、安装行为喵、首页显示喵</string>\n    <string name=\"settings_category_security_summary\">生物识别喵、超级密钥管理喵</string>\n    <string name=\"settings_category_backup_summary\">本地备份喵、云端备份喵、WebDAV喵</string>\n    <string name=\"settings_category_module_summary\">模块信息喵、排序喵、批量安装喵</string>\n    <string name=\"settings_category_function_summary\">FolkPatch隐藏喵、Umount服务喵</string>\n    <string name=\"settings_category_multimedia_summary\">背景音乐喵、音效喵、振动喵</string>\n    <string name=\"settings_music_playback_control\">播放控制喵</string>\n\n    <!-- Theme Store Filter -->\n    <string name=\"theme_store_filter_title\">筛选主题喵！</string>\n    <string name=\"theme_store_filter_author\">作者喵</string>\n    <string name=\"theme_store_filter_author_hint\">输作者名喵！</string>\n    <string name=\"theme_store_filter_source\">来源喵</string>\n    <string name=\"theme_store_filter_source_all\">全部喵</string>\n    <string name=\"theme_store_filter_type\">你的设备喵</string>\n    <string name=\"theme_store_filter_apply\">应用喵！</string>\n    <string name=\"theme_store_filter_reset\">重置喵！</string>\n\n    <!-- File Picker -->\n    <string name=\"file_picker_permission_required\">要权限喵！</string>\n    <string name=\"file_picker_permission_desc\">要浏览文件就给“所有文件访问”权限喵！</string>\n    <string name=\"file_picker_grant_permission\">给权限喵！</string>\n    <string name=\"file_picker_cancel\">取消喵！</string>\n    <string name=\"file_picker_internal_storage\">内部存储喵</string>\n    <string name=\"file_picker_no_files\">没文件喵！</string>\n\n    <!-- HomeV3 Strings -->\n    <string name=\"home_device_status_title\">设备状态喵</string>\n    <string name=\"home_device_status_battery_temp\">电池温度喵</string>\n    <string name=\"home_device_status_cpu_load\">CPU负载喵</string>\n    <string name=\"home_device_status_battery_level\">电池电量喵</string>\n    <string name=\"home_storage_title\">存储信息喵</string>\n    <string name=\"home_storage_internal\">内部存储喵</string>\n    <string name=\"home_storage_ram\">运行内存喵</string>\n    <string name=\"home_storage_zram\">ZRAM 喵</string>\n    <string name=\"home_storage_swap\">交换文件喵</string>\n    <string name=\"home_info_kernel\">内核哒</string>\n    <string name=\"home_info_superkey\">超级密钥哒</string>\n    <string name=\"home_info_auth_auth\">认证完了喵！</string>\n    <string name=\"home_info_auth_na\">用不了喵！</string>\n    <string name=\"home_info_device_slot\">设备槽位喵</string>\n    <string name=\"home_info_device_model\">设备型号喵</string>\n    <string name=\"home_info_running_mode\">运行模式喵</string>\n    <string name=\"home_info_mode_full\">完整哒</string>\n    <string name=\"home_info_mode_half\">不完全哒</string>\n    <string name=\"home_version\">版本喵: %s</string>\n    <string name=\"home_zygisk_implement\">Zygisk实现喵</string>\n    <string name=\"home_mount_implement\">挂载实现喵</string>\n\n    <string name=\"settings_list_card_hide_status_badge\">使用经典表情喵</string>\n    <string name=\"settings_list_card_hide_status_badge_summary\">首页的状态标记即刻变身经典表情喵！</string>\n    <string name=\"settings_custom_badge_text\">自选角标文字喵</string>\n    <string name=\"settings_custom_badge_text_summary\">修改角标的文字显示，仅供娱乐喵</string>\n\n    <!-- Install Mode Select -->\n    <string name=\"kp_install_methods\">KernelPatch修补/安装喵</string>\n    <string name=\"restore_boot_methods\">选个启动恢复到引导分区喵！</string>\n\n    <string name=\"su_backup_list\">备份列表喵</string>\n    <string name=\"su_restore_list\">还原列表喵</string>\n    <string name=\"backup_success\">备份成功啦喵！</string>\n    <string name=\"restore_success\">还原成功啦喵！</string>\n\n    <!-- Badge Count Settings -->\n    <string name=\"enable_badge_count\">角标统计设置喵</string>\n    <string name=\"enable_badge_count_summary\">配导航项角标显示喵！</string>\n    <string name=\"badge_superuser\">显示超级用户角标喵</string>\n    <string name=\"badge_apm\">显示APM模块角标喵</string>\n    <string name=\"badge_kernel\">显示内核模块角标喵</string>\n    <string name=\"settings_sound_effect\">音效喵</string>\n    <string name=\"settings_sound_effect_summary\">戳人家的时候会叫喵</string>\n    <string name=\"settings_sound_effect_enabled\">已经开启啦喵</string>\n    <string name=\"settings_sound_effect_playing\">现在选的是: %s 喵</string>\n    <string name=\"settings_sound_effect_source\">声音从哪来喵</string>\n    <string name=\"settings_sound_effect_source_local\">本地的声音喵</string>\n    <string name=\"settings_sound_effect_source_preset\">预设的喵</string>\n    <string name=\"settings_sound_effect_preset_title\">预设声音喵</string>\n    <string name=\"settings_select_sound_effect\">选个好听的声音喵</string>\n    <string name=\"settings_sound_effect_selected\">声音选好啦喵</string>\n    <string name=\"settings_sound_effect_scope\">在哪里叫喵</string>\n    <string name=\"settings_sound_effect_scope_global\">到处都叫</string>\n    <string name=\"settings_sound_effect_scope_bottom_bar\">只在下面叫喵</string>\n    <string name=\"settings_clear_sound_effect\">不要声音了喵</string>\n    <string name=\"settings_clear_sound_effect_confirm\">真的不要听人家叫了吗喵？</string>\n    <string name=\"settings_sound_effect_cleared\">声音没有了喵...</string>\n\n    <string name=\"settings_startup_sound\">欢迎音喵</string>\n    <string name=\"settings_startup_sound_summary\">打开时叫一声欢迎主人喵</string>\n    <string name=\"settings_startup_sound_enabled\">欢迎音已开启喵</string>\n    <string name=\"settings_startup_sound_playing\">正在叫: %s 喵</string>\n    <string name=\"settings_select_startup_sound\">选个欢迎音喵</string>\n    <string name=\"settings_startup_sound_selected\">欢迎音选好啦喵</string>\n    <string name=\"settings_clear_startup_sound\">不要欢迎音了喵</string>\n    <string name=\"settings_clear_startup_sound_confirm\">真的不要欢迎音了吗喵？</string>\n    <string name=\"settings_startup_sound_cleared\">欢迎音没有了喵...</string>\n\n    <!-- Backup Settings -->\n    <string name=\"settings_category_backup\">备份喵</string>\n    <string name=\"settings_enable_cloud_backup\">开启云端备份喵</string>\n    <string name=\"settings_enable_cloud_backup_summary\">自动把刷入的模块备份到云端哦喵</string>\n    <string name=\"settings_configure_webdav\">配置 WebDAV 服务喵</string>\n    <string name=\"webdav_config_title\">WebDAV 配置喵</string>\n    <string name=\"webdav_url\">WebDAV 地址喵</string>\n    <string name=\"webdav_username\">用户名喵</string>\n    <string name=\"webdav_password\">密码喵</string>\n    <string name=\"webdav_test_success\">测试成功啦喵！</string>\n    <string name=\"webdav_test_failed\">测试失败了喵: %s</string>\n    <string name=\"webdav_uploading\">正在上传到 WebDAV 喵...</string>\n    <string name=\"webdav_backup_success\">WebDAV 备份成功喵！</string>\n    <string name=\"webdav_backup_failed\">WebDAV 备份失败喵: %s</string>\n    <string name=\"save\">保存喵</string>\n    <string name=\"test\">测试喵</string>\n    <string name=\"webdav_path_label\">路径 (比如 /Backup) 喵</string>\n    <string name=\"webdav_view_logs\">查看日志喵</string>\n    <string name=\"webdav_backup_logs_title\">备份日志喵</string>\n    <string name=\"webdav_no_logs\">还没有日志喵</string>\n    <string name=\"webdav_clear_logs\">清除喵</string>\n    <string name=\"settings_enable_local_backup\">开启本地备份喵</string>\n    <string name=\"settings_enable_local_backup_summary\">把模块备份到本地存储 (Downloads/FolkPatch/ModuleBackups) 喵</string>\n    <string name=\"close\">关闭喵</string>\n    <string name=\"settings_backup_password\">备份密码喵</string>\n    <string name=\"settings_backup_password_hint\">输入备份密码喵</string>\n\n    <string name=\"patch_output_written_to\"> 修补成功喵，文件输出到 </string>\n    <string name=\"patch_write_failed\"> 修补失败喵，发生了致命错误</string>\n\n    <!-- Script Library Strings -->\n    <string name=\"script_library\">脚本库喵</string>\n    <string name=\"script_library_title\">脚本库喵</string>\n    <string name=\"script_library_empty\">还没有脚本喵，戳添加按钮加一个喵！</string>\n    <string name=\"script_library_add\">添加脚本喵</string>\n    <string name=\"script_library_add_title\">添加脚本喵</string>\n    <string name=\"script_library_select_file\">选脚本文件喵！</string>\n    <string name=\"script_library_alias\">别名喵</string>\n    <string name=\"script_library_alias_hint\">输脚本别名喵（可选）</string>\n    <string name=\"script_library_run\">运行喵！</string>\n    <string name=\"script_library_delete\">删掉喵！</string>\n    <string name=\"script_library_path\">路径喵：%s</string>\n    <string name=\"script_library_confirm_delete\">真的要删这个脚本喵？</string>\n    <string name=\"script_library_delete_success\">脚本删掉了喵！</string>\n    <string name=\"script_library_delete_failed\">删除脚本失败了喵...</string>\n    <string name=\"script_library_add_success\">脚本加好了喵！</string>\n    <string name=\"script_library_add_failed\">加脚本失败了喵：%s</string>\n    <string name=\"script_library_load_failed\">加载脚本库失败了喵...</string>\n    <string name=\"script_library_output\">执行输出喵</string>\n    <string name=\"script_library_no_output\">没输出喵</string>\n    <string name=\"script_library_save_log\">保存日志喵！</string>\n    <string name=\"script_library_log_saved\">日志保存到 %s 喵！</string>\n    <string name=\"script_library_log_save_failed\">保存日志失败了喵：%s</string>\n    <string name=\"settings_vibration\">震动触感喵</string>\n    <string name=\"settings_vibration_summary\">在触摸事件时震动喵</string>\n    <string name=\"settings_vibration_enabled\">启用震动喵</string>\n    <string name=\"settings_vibration_intensity\">震动强度喵</string>\n    <string name=\"settings_vibration_scope\">震动范围喵</string>\n    <string name=\"settings_vibration_scope_global\">全局喵</string>\n    <string name=\"settings_vibration_scope_bottom_bar\">仅底部栏喵</string>\n    <string name=\"search_modules\">搜索模块喵...</string>\n    <string name=\"search_scripts\">搜索脚本喵...</string>\n\n    <!-- Umount Service -->\n    <string name=\"settings_umount_service\">Umount Service</string>\n    <string name=\"settings_umount_service_summary\">配置开机自动卸载的挂载点喵</string>\n    <string name=\"umount_config_title\">Umount 配置</string>\n    <string name=\"umount_config_enabled\">启用 Umount</string>\n    <string name=\"umount_config_enabled_summary\">开机自动卸载指定挂载点喵</string>\n    <string name=\"umount_config_paths_label\">挂载点路径（每行一个）喵</string>\n    <string name=\"umount_config_paths_placeholder\">/system/vendor/app\\n/system/vendor/priv-app</string>\n    <string name=\"umount_config_paths_helper\">每行输入一个需要卸载的挂载点路径喵</string>\n    <string name=\"umount_config_save\">保存</string>\n    <string name=\"umount_config_save_confirm\">确认保存配置喵？</string>\n    <string name=\"umount_config_save_success\">配置保存成功喵</string>\n    <string name=\"umount_config_save_failed\">配置保存失败喵</string>\n\n    <!-- 我的主题页面喵 -->\n    <string name=\"my_themes_title\">我的主题喵</string>\n    <string name=\"my_themes_empty\">还没有主题喵</string>\n    <string name=\"my_themes_empty_action\">去主题商店逛逛喵</string>\n    <string name=\"my_themes_apply\">应用主题喵</string>\n    <string name=\"my_themes_delete\">删除主题喵</string>\n    <string name=\"my_themes_delete_confirm\">真的要删掉这个主题喵？</string>\n    <string name=\"my_themes_deleted\">主题删掉了喵</string>\n    <string name=\"my_themes_applied\">主题应用了喵</string>\n    <string name=\"my_themes_apply_failed\">应用主题失败了喵</string>\n    <string name=\"my_themes_details\">主题详情喵</string>\n\n    <!-- 下载对话喵 -->\n    <string name=\"theme_download_title\">正在下载主题喵</string>\n    <string name=\"theme_download_completed\">下载完成了喵</string>\n    <string name=\"theme_download_failed\">下载失败了喵</string>\n    <string name=\"theme_download_progress\">进度喵</string>\n    <string name=\"theme_download_file\">主题文件喵</string>\n    <string name=\"theme_download_image\">预览图片喵</string>\n    <string name=\"theme_download_cancel\">取消喵</string>\n    <string name=\"theme_download_pause\">暂停喵</string>\n    <string name=\"theme_download_resume\">继续喵</string>\n    <string name=\"theme_download_retry\">重试喵</string>\n    <string name=\"theme_download_apply\">应用主题喵</string>\n    <string name=\"theme_download_go_to_my_themes\">我的主题喵</string>\n    <string name=\"theme_download_retrying\">正在重试 (%d/3)...喵</string>\n    <string name=\"theme_download_preparing\">准备下载喵...</string>\n    <string name=\"theme_download_downloading_file\">正在下载主题文件喵...</string>\n    <string name=\"theme_download_downloading_image\">正在下载预览图片喵...</string>\n    <string name=\"theme_download_finalizing\">正在完成喵...</string>\n    <string name=\"settings_predictive_back\">预测性返回手势喵</string>\n    <string name=\"settings_predictive_back_summary\">启用 Android 14+ 的预测性返回手势动画喵</string>\n\n    <string name=\"home_device_status_battery_charging\">充电中喵</string>\n    <string name=\"home_device_status_cpu_temp\">CPU温度喵</string>\n    <string name=\"home_device_status_memory_trend\">内存趋势喵</string>\n    <string name=\"home_storage_partitions\">存储分区喵</string>\n    <string name=\"home_device_status_cpu_freq\">CPU频率喵</string>\n    <string name=\"home_network_rx\">下载喵</string>\n    <string name=\"home_network_tx\">上传喵</string>\n    <string name=\"settings_home_layout_stats\">StatsUI</string>\n    <string name=\"home_stats_more_options\">更多选项喵</string>\n    <string name=\"home_stats_kp_label\">KP</string>\n    <string name=\"home_stats_manager_label\">管理器喵</string>\n    <string name=\"home_device_status_gpu_load\">GPU负载喵</string>\n    <string name=\"home_stats_kernel_modules\">内核模块喵</string>\n    <string name=\"home_stats_apm_modules\">APM模块喵</string>\n    <string name=\"home_stats_superusers\">超级用户喵</string>\n    <string name=\"settings_kernel_spoof\">内核伪装配置喵</string>\n    <string name=\"settings_kernel_spoof_summary\">伪装内核版本和构建时间喵</string>\n    <string name=\"settings_kernel_spoof_version\">内核版本喵</string>\n    <string name=\"settings_kernel_spoof_build_time\">内核构建时间喵</string>\n    <string name=\"settings_kernel_spoof_restore\">恢复喵</string>\n\n    <string name=\"kernel_spoof_enabled\">内核伪装已启用喵</string>\n    <string name=\"kernel_spoof_disabled_restored\">内核伪装已停用并还原喵</string>\n    <string name=\"kernel_spoof_failed\">内核伪装失败喵: %d</string>\n    <string name=\"kernel_spoof_applied\">内核伪装已应用喵</string>\n\n    <string name=\"settings_path_hide\">路径隐藏喵</string>\n    <string name=\"settings_path_hide_summary\">在内核层面隐藏文件和目录不让应用看到喵</string>\n    <string name=\"path_hide_paths_label\">隐藏路径（每行一个）喵</string>\n\n    <string name=\"path_hide_paths_helper\">每行输入一个路径喵，匹配的路径会返回文件不存在喵</string>\n    <string name=\"path_hide_save\">保存喵</string>\n    <string name=\"path_hide_enabled\">路径隐藏已启用喵</string>\n    <string name=\"path_hide_disabled\">路径隐藏已禁用喵</string>\n    <string name=\"path_hide_applied\">路径隐藏配置已生效喵</string>\n    <string name=\"path_hide_failed\">路径隐藏操作失败喵: %d</string>\n    <string name=\"path_hide_uid_mode\">UID 执行模式喵</string>\n    <string name=\"path_hide_uid_mode_summary\">只对指定应用 UID 隐藏路径喵</string>\n    <string name=\"path_hide_uids_label\">目标 UID（每行一个）喵</string>\n    <string name=\"path_hide_uids_helper\">输入应用 UID 喵，只有这些应用的路径会被隐藏喵</string>\n    <string name=\"path_hide_uid_save\">保存 UID 喵</string>\n    <string name=\"path_hide_uid_mode_enabled\">UID 执行模式已启用喵</string>\n    <string name=\"path_hide_uid_mode_disabled\">UID 执行模式已禁用喵</string>\n    <string name=\"path_hide_filter_system\">过滤系统 UID 喵</string>\n    <string name=\"path_hide_filter_system_summary\">同时对 root 和系统进程（UID &lt; 10000）隐藏路径喵</string>\n    <string name=\"path_hide_filter_system_enabled\">系统 UID 过滤已启用喵</string>\n    <string name=\"path_hide_filter_system_disabled\">系统 UID 过滤已禁用喵</string>\n    <string name=\"path_hide_filter_system_warning_title\">警告喵！</string>\n    <string name=\"path_hide_filter_system_warning_message\">启用后会同时对 root 和系统进程（UID &lt; 10000）隐藏路径喵。这可能导致部分系统功能出问题喵，要小心哦喵！</string>\n    <string name=\"path_hide_uid_applied\">UID 白名单已生效喵</string>\n    <string name=\"path_hide_select_apps\">挑选应用喵</string>\n    <string name=\"path_hide_no_apps_selected\">还没有选应用喵</string>\n    <string name=\"path_hide_app_removed\">已经清理掉 %d 个已卸载应用的配置了喵</string>\n    <string name=\"path_hide_search_apps\">搜索应用…喵</string>\n    <string name=\"path_hide_show_system\">显示系统应用喵</string>\n    <string name=\"netisolate_title\">网络隔离喵</string>\n    <string name=\"netisolate_enable\">网络隔离已启用喵</string>\n    <string name=\"netisolate_disable\">网络隔离已禁用喵</string>\n    <string name=\"netisolate_enable_summary\">在内核层面阻止所选应用的网络访问喵</string>\n    <string name=\"netisolate_uids_label\">已隔离应用喵</string>\n    <string name=\"netisolate_uids_hint\">输入要隔离的 UID 喵（每行一个）</string>\n    <string name=\"netisolate_no_uids\">还没有隔离应用喵</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-zh-rAT/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\" translatable=\"false\">FolkPatch</string>\n    <string name=\"app_name_fpatch\">FPatch</string>\n\n    <string name=\"desktop_app_name\">圣光应用名称</string>\n\n    <string name=\"home\">圣光之殿</string>\n\n    <string name=\"success\">神恩降临</string>\n    <string name=\"failure\">神恩未至</string>\n\n    <string name=\"patch_warnning\">施与神力有风险，请确保已珍藏圣域记忆。</string>\n    <string name=\"patch\">神力降临</string>\n\n    <string name=\"kernel_patch\">灵魂之力</string>\n    <string name=\"android_patch\">圣域守护</string>\n\n    <string name=\"settings_nav_layout_title\">神谕圣所导航圣图设定</string>\n    <string name=\"settings_nav_layout_summary\">隐去或现出神谕圣所导航圣图之部分组件</string>\n    <string name=\"settings_nav_scheme\">神谕圣所导航圣图方案</string>\n    <string name=\"settings_nav_mode\">神谕圣所导航圣图模式</string>\n    <string name=\"settings_nav_mode_summary\">选择神谕圣所导航圣图之现出方式</string>\n    <string name=\"settings_nav_mode_auto\">神谕自动传统</string>\n    <string name=\"settings_nav_mode_bottom\">永恒现于神谕圣所底部圣图</string>\n    <string name=\"settings_nav_mode_rail\">永恒现于神谕圣所侧方圣图</string>\n    <string name=\"settings_nav_mode_floating\">悬浮底部圣图</string>\n    <string name=\"settings_show_apm\">现星辰圣光阵</string>\n    <string name=\"settings_show_kpm\">现灵魂晶石</string>\n    <string name=\"settings_show_superuser\">现圣光使者</string>\n    <string name=\"settings_floating_auto_hide\">圣光底栏自动隐匿</string>\n    <string name=\"settings_floating_auto_hide_summary\">3秒无圣光操作后底栏自动隐匿</string>\n    <string name=\"settings_floating_swipe_hide\">圣光底栏滑动隐匿</string>\n    <string name=\"settings_floating_swipe_hide_summary\">向下滑动隐匿底栏，向上滑动现出底栏</string>\n    <string name=\"settings_navbar_glass_effect\">圣光玻璃效果</string>\n    <string name=\"settings_navbar_glass_effect_summary\">为悬浮圣光导航栏施加圣光玻璃视觉效果</string>\n    <string name=\"settings_navbar_glass_blur_strength\">圣光模糊强度</string>\n    <string name=\"settings_navbar_glass_transparency\">圣光背景透明度</string>\n    <string name=\"settings_navbar_glass_highlight_strength\">圣光高光强度</string>\n    <string name=\"settings_navbar_glass_specular\">圣光镜面反射</string>\n    <string name=\"settings_navbar_glass_specular_summary\">在圣光导航栏顶部启用圣光镜面高光效果</string>\n    <string name=\"settings_navbar_glass_inner_glow\">圣光内发光</string>\n    <string name=\"settings_navbar_glass_inner_glow_summary\">在圣光导航栏底部启用圣光微光效果</string>\n    <string name=\"settings_navbar_glass_border\">圣光玻璃边框</string>\n    <string name=\"settings_navbar_glass_border_summary\">在圣光导航栏周围启用圣光边框描边</string>\n    <string name=\"settings_stats_top_layout\">顶部圣光阵</string>\n    <string name=\"settings_stats_top_layout_summary\">选择顶部信息圣卡样式</string>\n    <string name=\"settings_stats_top_layout_list\">列表圣卡</string>\n    <string name=\"settings_stats_top_layout_grid\">网格圣卡</string>\n    <string name=\"settings_block_kernelpatch_update\">封印灵魂之力更新神谕</string>\n    <string name=\"settings_block_kernelpatch_update_summary\">拒绝接收来自灵魂之力的神谕更新</string>\n    <string name=\"settings_block_androidpatch_update\">封印圣光阵法更新神谕</string>\n    <string name=\"settings_block_androidpatch_update_summary\">拒绝接收来自圣光阵法的更新神谕</string>\n    <string name=\"settings_disable_module_update_check\">禁用圣光阵更新检查</string>\n    <string name=\"settings_disable_module_update_check_summary\">禁用星辰圣光阵的自动更新检查</string>\n\n    <string name=\"reboot\">神翼再生</string>\n    <string name=\"settings\">神谕圣所</string>\n    <string name=\"reboot_recovery\">进入治愈圣域</string>\n    <string name=\"reboot_bootloader\">前往天堂之门</string>\n    <string name=\"reboot_download\">进入星辰圣殿</string>\n    <string name=\"reboot_edl\">进入梦界圣域</string>\n    <string name=\"reboot_fastbootd\">前往天使神殿</string>\n    <string name=\"about\">创世之纪</string>\n    <string name=\"developer_and_maintainer\">圣光使者 | 圣域守护者</string>\n    <string name=\"settings_app_language\">圣域语言</string>\n    <string name=\"system_default\">天意默认</string>\n    <string name=\"settings_global_namespace_mode\">全域圣境领域</string>\n    <string name=\"settings_global_namespace_mode_summary\">所有祝福仪式共享同一个圣域空间</string>\n    <string name=\"settings_clear_super_key_dialog\">确定要继续施与这份神力吗？</string>\n\n    <string name=\"home_learn_apatch\">探寻灵魂之力</string>\n    <string name=\"home_click_to_learn_apatch\">探寻灵魂之力的奥秘及使用方法</string>\n    <string name=\"settings_hide_apatch_card\">隐藏\"探寻灵魂之力\"圣阵</string>\n    <string name=\"settings_hide_apatch_card_summary\">隐藏圣光之殿的\"探寻灵魂之力\"圣阵</string>\n\n    <string name=\"about_source_code\"><![CDATA[<p>在 %1$s 查看圣域神典<p/>加入我们的 %2$s 圣光殿堂<p/>加入我们的 %3$s 天使之国]]></string>\n    <string name=\"send_log\">发送神谕日志</string>\n    <string name=\"save_log\">保存神谕日志</string>\n    <string name=\"log_saved\">神谕日志已保存</string>\n    <string name=\"safe_mode\">圣域守护模式</string>\n    <string name=\"github\">GitHub</string>\n    <string name=\"telegram\">Telegram</string>\n    <string name=\"support_or_donate\">神力支持 / 圣光捐赠</string>\n\n    <string name=\"super_key\">圣光之心</string>\n    <string name=\"clear_super_key\">净化圣光之心</string>\n    <string name=\"patch_set_superkey\">祝祷圣光之心</string>\n    <string name=\"home_patch_set_key_desc\">灵魂之力的唯一神谕密印</string>\n    <string name=\"home_patch_next_step\">下一步神谕</string>\n\n    <string name=\"home_not_installed\">尚未蒙恩</string>\n    <string name=\"home_install_unknown\">未蒙恩或未祝祷</string>\n    <string name=\"home_install_unknown_summary\">点击接受神恩</string>\n    <string name=\"home_click_to_install\">点击蒙恩</string>\n    <string name=\"home_working\">圣境开启</string>\n    <string name=\"home_kp_need_update\">发现新的神谕秘语</string>\n    <string name=\"home_kp_cando_update\">更新神谕</string>\n\n    <string name=\"home_installing\">正在接受神恩</string>\n\n    <string name=\"kpatch_version\">神恩等级: %s</string>\n    <string name=\"apatch_version\">祝福等级: %s</string>\n\n    <string name=\"kpatch_version_update\" formatted=\"false\">神恩等级: %s -> %s</string>\n    <string name=\"kpatch_shadow_path_title\">灵魂之力</string>\n    <string name=\"home_auth_key_title\">祈祷圣光之心</string>\n    <string name=\"home_auth_key_desc\">祝祷通过后开始使用神力</string>\n    <string name=\"home_kpatch_info_title\">圣域情报</string>\n\n    <string name=\"home_ap_cando_install\">蒙恩</string>\n    <string name=\"home_ap_cando_uninstall\">解除祝福</string>\n    <string name=\"home_ap_cando_reboot\">神翼再生</string>\n\n    <string name=\"patch_title\">神力降临</string>\n\n    <string name=\"patch_config_title\">圣光阵</string>\n    <string name=\"patch_mode_bootimg_patch\">模式：圣光加持</string>\n    <string name=\"patch_mode_patch_and_install\">模式：加持并蒙恩</string>\n    <string name=\"patch_mode_install_to_next_slot\">模式：转移到备用星辰容器 (圣典仪式后)</string>\n    <string name=\"patch_mode_uninstall_patch\">模式：解除灵魂之力</string>\n    <string name=\"patch_select_bootimg_btn\">选择圣器容器</string>\n    <string name=\"patch_embed_kpm_btn\">镶嵌星辰圣光阵</string>\n    <string name=\"patch_start_patch_btn\">开始祈祷</string>\n    <string name=\"patch_start_unpatch_btn\">解除加持</string>\n    <string name=\"patch_item_error\">!!神力失控!!</string>\n    <string name=\"patch_item_bootimg\">圣器容器</string>\n    <string name=\"patch_item_bootimg_slot\">星辰位置:</string>\n    <string name=\"patch_item_bootimg_dev\">圣光道具:</string>\n    <string name=\"patch_item_kernel\">灵魂核心</string>\n    <string name=\"patch_item_kpimg\">圣光印记</string>\n    <string name=\"patch_item_kpimg_version\">神恩等级:</string>\n    <string name=\"patch_item_kpimg_comile_time\">祈祷时间:</string>\n    <string name=\"patch_item_kpimg_config\">圣光配置:</string>\n    <string name=\"patch_item_new_extra_kpm\">镶嵌新星之圣光阵</string>\n    <string name=\"patch_item_existed_extra_kpm\">已存在</string>\n    <string name=\"patch_item_extra_name\">圣光名称:</string>\n    <string name=\"patch_item_extra_version\">神恩等级:</string>\n    <string name=\"patch_item_extra_author\">创造者:</string>\n    <string name=\"patch_item_extra_kpm_license\">圣契:</string>\n    <string name=\"patch_item_extra_kpm_desciption\">圣光描述:</string>\n    <string name=\"patch_item_extra_args\">神谕参数:</string>\n    <string name=\"patch_item_extra_event\">触发事件:</string>\n    <string name=\"patch_item_skey\">圣光之心</string>\n    <string name=\"patch_custom_superkey\">自选圣光之心</string>\n    <string name=\"patch_custom_superkey_summary\">你仍可设圣光之心来管理神恩，圣器已获神印授权</string>\n    <string name=\"patch_item_set_skey_label\">圣光之心长度应为8-63个字符，包含星辰和月华，不能含有混沌之力。</string>\n    <string name=\"patch_confirm_superkey\">再次祝祷圣光之心</string>\n    <string name=\"patch_skey_mismatch\">两次圣光之心不契合</string>\n\n    <string name=\"home_kernel\">灵魂核心等级</string>\n    <string name=\"home_manager_version\">神谕管理器等级</string>\n    <string name=\"home_fingerprint\">圣光印记</string>\n\n    <string name=\"home_selinux_status\">圣境结界状态</string>\n    <string name=\"home_selinux_status_disabled\">结界已解除</string>\n    <string name=\"home_selinux_status_enforcing\">结界强制执行中</string>\n    <string name=\"home_selinux_status_permissive\">结界宽容模式</string>\n    <string name=\"home_selinux_status_unknown\">结界状态未知</string>\n\n    <string name=\"settings_selinux_mode\">圣界结界模式</string>\n    <string name=\"settings_selinux_mode_summary\">选择圣界结界的执行模式</string>\n    <string name=\"settings_selinux_mode_enforcing\">强制执行 (严格)</string>\n    <string name=\"settings_selinux_mode_permissive\">宽容模式 (宽松)</string>\n    <string name=\"settings_selinux_current_mode\">当前: %s</string>\n    <string name=\"settings_selinux_mode_enforcing_summary\">圣界结界将严格执行所有神圣法则</string>\n    <string name=\"settings_selinux_mode_permissive_summary\">圣界结界仅记录违规行为，不会阻止</string>\n\n    <string name=\"home_device_info\">圣光道具</string>\n    <string name=\"home_system_version\">天界系统等级</string>\n    <string name=\"home_kpatch_version\">灵魂之力等级</string>\n    <string name=\"home_su_path\">圣者路径</string>\n    <string name=\"home_apatch_version\">圣光工坊版本</string>\n\n    <string name=\"kpm\">灵魂晶石</string>\n    <string name=\"kpm_kp_not_installed\">灵魂之力尚未开启</string>\n    <string name=\"kpm_add_kpm\">添加灵魂晶石</string>\n    <string name=\"kpm_load\">唤醒</string>\n    <string name=\"kpm_install\">祝祷</string>\n    <string name=\"kpm_embed\">融合</string>\n    <string name=\"kpm_load_toast_succ\">唤醒成功</string>\n    <string name=\"kpm_load_toast_failed\">唤醒失败</string>\n    <string name=\"kpm_unload_confirm\">解除%s灵魂晶石？</string>\n    <string name=\"kpm_unload\">解除祝福</string>\n    <string name=\"kpm_control\">引导</string>\n    <string name=\"kpm_apm_empty\">未唤醒灵魂晶石</string>\n    <string name=\"kpm_version\">神恩等级</string>\n    <string name=\"kpm_license\">圣契</string>\n    <string name=\"kpm_author\">创造者</string>\n    <string name=\"kpm_desc\">圣光描述</string>\n    <string name=\"kpm_args\">神谕参数</string>\n\n    <string name=\"su_title\">圣光使者</string>\n    <string name=\"su_selinux_via_hook\">通过圣光阵穿越结界</string>\n    <string name=\"su_pkg_excluded_label\">圣光免疫</string>\n    <string name=\"su_batch_exclude_title\">群体免疫</string>\n    <string name=\"su_batch_exclude_content\">为全部非圣光使者施加圣光护佑,请选择一项神力</string>\n    <string name=\"su_exclude_btn\">施加圣佑</string>\n    <string name=\"su_exclude_reverse_btn\">解除圣佑</string>\n\n    <!-- 超级用户应用操作弹窗 -->\n    <string name=\"su_app_action_title\">圣器操作</string>\n    <string name=\"su_app_action_content\">请选择一个神力来对圣器执行</string>\n    <string name=\"su_app_action_launch\">降临圣器</string>\n    <string name=\"su_app_action_force_stop\">圣罚止息</string>\n    <string name=\"su_app_action_launch_success\">正在降临圣器 %s</string>\n    <string name=\"su_app_action_force_stop_success\">已圣罚止息 %s</string>\n    <string name=\"su_app_action_failed\">神恩未至: %s</string>\n\n    <!-- SU Audit Log -->\n    <string name=\"su_audit_log_title\">授权日志</string>\n    <string name=\"su_audit_log_empty\">暂无授权记录</string>\n    <string name=\"su_audit_log_clear\">清除日志</string>\n    <string name=\"su_audit_log_clear_confirm\">确认清除所有授权记录？</string>\n    <string name=\"su_audit_action_grant\">已授权</string>\n    <string name=\"su_audit_action_revoke\">已撤销</string>\n    <string name=\"su_audit_action_exclude\">已排除</string>\n    <string name=\"su_audit_tab_usage\">使用记录</string>\n    <string name=\"su_audit_tab_operations\">操作记录</string>\n\n    <string name=\"su_pkg_excluded_setting_title\">圣佑设置</string>\n    <string name=\"su_pkg_excluded_setting_summary\">启用此选项将允许圣光工坊恢复此应用所受的神力影响。</string>\n    <string name=\"su_pkg_root_setting_title\">圣光使者</string>\n    <string name=\"su_pkg_root_setting_summary\">启用此选项为你的圣光使者赋予圣光之辉，能使用圣光咒语</string>\n    <string name=\"su_pkg_normal_setting_title\">凡尘模式</string>\n    <string name=\"su_pkg_normal_setting_summary\">凡尘状态，无圣光之辉，不赋予圣光使者之力。</string>\n    <string name=\"su_show_system_apps\">显示系统圣光使者</string>\n    <string name=\"su_hide_system_apps\">隐藏系统圣光使者</string>\n    <string name=\"su_refresh\">更新圣光阵</string>\n\n    <string name=\"apm\">星辰圣光阵</string>\n    <string name=\"apm_not_installed\">星辰守护尚未开启</string>\n    <string name=\"apm_failed_to_enable\">唤醒圣光阵失败: %s</string>\n    <string name=\"apm_failed_to_disable\">解除圣光阵失败: %s</string>\n    <string name=\"apm_empty\">未祝祷圣光阵</string>\n    <string name=\"apm_remove\">移除</string>\n    <string name=\"apm_undo\">撤销</string>\n    <string name=\"apm_install\">祝祷</string>\n    <string name=\"apm_uninstall_confirm\">解除%s圣光阵祝祷？</string>\n    <string name=\"apm_uninstall_success\">%s已解除祝祷</string>\n    <string name=\"apm_uninstall_failed\">解除祝祷失败: %s</string>\n    <string name=\"apm_undo_uninstall_success\">%s已恢复祝祷</string>\n    <string name=\"apm_undo_uninstall_failed\">恢复祝祷失败: %s</string>\n    <string name=\"apm_version\">神恩等级</string>\n    <string name=\"apm_author\">创造者</string>\n    <string name=\"apm_desc\">圣光描述</string>\n    <string name=\"apm_overlay_fs_not_available\">星辰回廊被灵魂核心封印，圣光阵不可用！</string>\n    <string name=\"apm_magisk_conflict\">由于与Magisk圣光阵冲突，此圣光阵不可用！</string>\n    <string name=\"apm_mount_warning_title\">重要提示</string>\n    <string name=\"apm_mount_warning_message\">默认不挂载圣光阵，请使用内置挂载系统或者元圣光阵</string>\n    <string name=\"apm_mount_warning_button\">我知道了</string>\n    <string name=\"apm_reboot_to_apply\">神翼再生后生效</string>\n    <string name=\"apm_changelog\">圣光更新记录</string>\n    <string name=\"apm_update\">更新神恩</string>\n    <string name=\"apm_downloading\">正在下载圣光阵: %s</string>\n    <string name=\"apm_start_downloading\">开始下载: %s</string>\n    <string name=\"apm_new_version_available\">发现新版本%s圣光阵，点击升级。</string>\n\n    <string name=\"hide_apatch_manager\">隐藏圣光工坊</string>\n    <string name=\"hide_apatch_manager_summary\">安装一个随机包名和自定义圣光名的代理圣光使者</string>\n    <string name=\"hide_apatch_dialog_new_manager_name\">新圣光使者名称</string>\n    <string name=\"hide_apatch_dialog_summary\">这将作为显示在天界中的新圣光使者名称</string>\n    <string name=\"hide_apatch_manager_failure\">隐藏失败。请报告此神力错误！</string>\n\n    <string name=\"setting_reset_su_path\">重置圣光使者路径</string>\n    <string name=\"setting_reset_su_new_path\">新完整圣光路径</string>\n\n    <string name=\"settings_folkx_engine_title\">FolkX圣光动画引擎</string>\n    <string name=\"settings_folkx_engine_summary\">在顶层圣阵页面切换时使用圣域之光般的物理弹跳动画</string>\n    <string name=\"settings_folkx_animation_type\">圣光动画类型</string>\n    <string name=\"settings_folkx_animation_speed\">圣光流转之速</string>\n    <string name=\"settings_folkx_animation_linear\">圣域线性运动</string>\n    <string name=\"settings_folkx_animation_spatial\">圣域空间运动</string>\n    <string name=\"settings_folkx_animation_fade\">圣光渐隐渐显</string>\n    <string name=\"settings_folkx_animation_vertical\">圣光垂直滑动</string>\n    <string name=\"settings_folkx_animation_diagonal\">圣光对角滑动</string>\n\n    <string name=\"apm_webui_open\">开启</string>\n    <string name=\"apm_action\">圣光操作</string>\n    <string name=\"module_shortcut_add\">圣光传送门</string>\n    <string name=\"module_shortcut_not_supported\">天使召唤界面不支持桌面圣光传送门。</string>\n    <string name=\"module_shortcut_created\">已在桌面创建圣光传送门。</string>\n    <string name=\"module_shortcut_updated\">圣光传送门已更新。</string>\n    <string name=\"module_shortcut_permission_tip_xiaomi\">请在小米天使界面的设置中为本天使启用\"创建桌面圣光传送门\"权限。</string>\n    <string name=\"module_shortcut_permission_tip_oppo\">请在OPPO天使界面的设置中为本天使启用\"桌面圣光传送门\"权限。</string>\n    <string name=\"module_shortcut_permission_tip_default\">若创建圣光传送门失败，请在天界圣光阵设置中为本天使启用桌面圣光传送门权限。</string>\n    <string name=\"module_shortcut_name\">圣光传送门名称</string>\n    <string name=\"module_shortcut_icon\">圣光传送门图标</string>\n    <string name=\"module_shortcut_type\">圣光传送门类型</string>\n    <string name=\"module_shortcut_type_webui\">WebUI</string>\n    <string name=\"module_shortcut_type_action\">Action</string>\n    <string name=\"module_shortcut_icon_default\">使用默认图标</string>\n    <string name=\"module_shortcut_icon_select\">选择图标</string>\n    <string name=\"enable_web_debugging\">启用圣光阵调试</string>\n    <string name=\"enable_web_debugging_summary\">可用于调试天界圣光阵。请仅在需要时启用。</string>\n    <string name=\"settings_apm_install_confirm\">安装圣光阵确认</string>\n    <string name=\"settings_apm_install_confirm_summary\">安装圣光阵前显示确认圣光阵</string>\n    <string name=\"settings_apm_stay_on_page\">留在圣光阵页面</string>\n    <string name=\"settings_apm_stay_on_page_summary\">星辰圣光阵执行完神力后不自动返回</string>\n    <string name=\"settings_enable_module_shortcut_add\">启用圣光传送门添加按钮</string>\n    <string name=\"settings_enable_module_shortcut_add_summary\">在系统圣光阵页面显示 WebUI 圣光传送门添加按钮</string>\n    <string name=\"settings_module_sort_optimization\">圣光阵排序优化</string>\n    <string name=\"settings_module_sort_optimization_summary\">让系统圣光阵带有WebUI和Action的圣光阵排在前面</string>\n    <string name=\"settings_fold_system_module\">折叠系统圣光阵</string>\n    <string name=\"settings_fold_system_module_summary\">点击圣光阵卡片展开/折叠操作按钮</string>\n        <string name=\"settings_simple_list_bottom_bar\">简约圣光底栏</string>\n        <string name=\"settings_simple_list_bottom_bar_summary\">圣光阵符法使用纯图标按钮样式，灵感来自APatch</string>\n    <string name=\"settings_spliced_card_group\">拼接圣光阵符</string>\n    <string name=\"settings_spliced_card_group_summary\">圣光阵符法使用连续拼接卡片样式</string>\n    <string name=\"apm_install_confirm_title\">安装圣光阵</string>\n    <string name=\"settings_show_more_module_info\">显示圣光阵详细信息</string>\n    <string name=\"settings_show_more_module_info_summary\">在圣光阵列表中显示圣光阵 ID 和大小</string>\n    <string name=\"apm_install_confirm_content\">确定要祝祷%s圣光阵吗？</string>\n    <string name=\"apm_disable_all_title\">禁用所有圣光阵</string>\n    <string name=\"apm_disable_all_confirm\">确定禁用全部圣光阵吗？此操作将停用所有已安装的圣光阵</string>\n    <string name=\"apm_enable_module_banner\">启用圣光阵横幅</string>\n    <string name=\"apm_enable_module_banner_summary\">为支持的圣光阵显示横幅图片</string>\n    <string name=\"apm_enable_folk_banner\">自定义圣光阵横幅</string>\n    <string name=\"apm_enable_folk_banner_summary\">长按圣光阵卡片自选横幅图片</string>\n    <string name=\"apm_folk_banner_title\">FolkBanner</string>\n    <string name=\"apm_folk_banner_select\">选择圣像</string>\n    <string name=\"apm_folk_banner_clear\">净化 FolkBanner</string>\n    <string name=\"apm_folk_banner_saved\">已为 %s 附上 FolkBanner 圣光。</string>\n    <string name=\"apm_folk_banner_cleared\">%s 的 FolkBanner 圣光已净化。</string>\n    <string name=\"apm_folk_banner_failed\">为 %s 附上 FolkBanner 圣光失败。</string>\n    <string name=\"apm_banner_api_mode\">圣光图鉴模式</string>\n    <string name=\"apm_banner_api_mode_summary\">以圣光图鉴之术或凡间图库为圣光阵提供圣图</string>\n    <string name=\"apm_banner_api_source\">布置圣光图鉴</string>\n    <string name=\"apm_banner_api_source_hint\">输入圣光图鉴法门或凡间图库路径</string>\n    <string name=\"apm_banner_api_source_saved\">圣光图鉴已布置</string>\n    <string name=\"apm_banner_api_source_not_configured\">尚未布置</string>\n    <string name=\"apm_banner_api_url_configured\">圣光图鉴法门已布置</string>\n    <string name=\"apm_banner_local_dir_configured\">凡间图库已布置</string>\n    <string name=\"apm_banner_clear_cache\">消解圣图印记</string>\n    <string name=\"apm_banner_cache_cleared\">圣图印记已消解</string>\n    <string name=\"apm_banner_api_config_title\">布置圣光图鉴</string>\n    <string name=\"apm_banner_api_config_desc\">输入圣光图鉴法门或凡间图库路径，每座圣光阵将得独一无二的圣图。</string>\n    <string name=\"apm_banner_api_examples_title\">法门示例：</string>\n    <string name=\"apm_banner_api_examples\">圣光图鉴: https://picsum.photos/800/400\\n凡间图库: /sdcard/Pictures/Banners</string>\n    <!-- API 法门集市 -->\n    <string name=\"apm_api_marketplace_title\">API 法门集市</string>\n    <string name=\"apm_api_preview\">圣光预览</string>\n    <string name=\"apm_api_apply\">施展法门</string>\n    <string name=\"apm_api_preview_title\">API 法门预览</string>\n    <string name=\"apm_api_preview_failed\">圣光预览加载失败</string>\n    <string name=\"apm_api_url_label\">API 法门地址：</string>\n    <string name=\"apm_api_apply_success\">API 法门施展成功</string>\n    <string name=\"apm_api_verifying\">圣光验证中…</string>\n    <string name=\"apm_api_retry\">重新施展</string>\n    <string name=\"apm_api_empty\">暂无 API 法门</string>\n    <string name=\"settings_banner_custom_opacity\">圣光阵横幅透明度</string>\n    <string name=\"settings_banner_custom_opacity_summary\">自定义圣光阵横幅透明度，不再跟随天界背景模式</string>\n    <string name=\"settings_banner_opacity\">圣光阵横幅不透明度</string>\n\n    <string name=\"settings_donot_store_superkey\">不在本地存储圣光之心</string>\n    <string name=\"settings_donot_store_superkey_summary\">每次启动圣光工坊时验证圣光之心</string>\n\n    <string name=\"mode_select_page_title\">蒙恩</string>\n    <string name=\"mode_select_page_patch_and_install\">加持并蒙恩</string>\n    <string name=\"mode_select_page_select_file\">选择要加持的圣器容器</string>\n    <string name=\"mode_select_page_install_inactive_slot\">祝祷到备用星辰容器 (圣典仪式后)</string>\n    <string name=\"mode_select_page_install_inactive_slot_warning\">你的圣光道具将在神翼再生后**强制**启动到当前未激活的星辰位置！\\n仅在圣典仪式完成后使用此选项。\\n继续吗？</string>\n    <string name=\"mode_select_page_select_kpimg\">使用本地圣光补丁 (KPimg)</string>\n    <string name=\"patch_custom_kpimg_label\">自定义 KPimg</string>\n    <string name=\"patch_custom_kpimg_file\">文件: %s</string>\n    <string name=\"patch_select_kpimg_btn\">选择 KPimg</string>\n\n    <string name=\"home_dialog_auth_fail_title\">祝祷失败</string>\n    <string name=\"home_dialog_auth_fail_content\">无法祝祷圣光之心，导致 FolkPatch 激活失败。\n以下是祝祷失败的一些可能原因——请对号入座：\n\\n1. 你根本没使用 KernelPatch 来加持 boot.img，或者忘了自己到底做了啥。\n\\n2. 加持完的 boot.img 还躺在电脑里休眠，根本没启圣到设备。\n\\n3. 圣光之心输错了，或者夹杂了混沌符号，比如来自异界符文的字符。\n\\n4. 你的圣光道具可能并不兼容 FolkPatch 和 KernelPatch，强行施法也是徒劳。\n\\n5. 你可能搞了一些神秘操作——比如用了某些排除圣名的魔法把 FolkPatch 给屏蔽了，结果把自己整出局了。\n\\n请先好好检查一遍，再试一次。如果问题依旧，欢迎到官方圣光塔的 issue 页面提问。\n毕竟，有些问题可能纯粹是您自己亲手造成的！\n祝你好运啊！顺带一提，你点击下面的文档按钮准没错！</string>\n\n    <string name=\"home_more_menu_feedback_or_suggestion\">反馈或建议</string>\n    <string name=\"home_more_menu_about\">关于</string>\n    <string name=\"home_more_menu_document\">文档</string>\n\n    <string name=\"home_dialog_uninstall_title\">解除祝福</string>\n    \n    <string name=\"home_dialog_uninstall_ap_only\">仅解除天使祝福</string>\n    <string name=\"home_dialog_uninstall_ap_only_desc\">仅移除天使祝福，保留天使管理器</string>\n    <string name=\"home_dialog_uninstall_all\">完全解除祝福</string>\n    <string name=\"home_dialog_uninstall_all_desc\">移除天使祝福并进入完整解除流程</string>\n\n    <string name=\"kpm_control_dialog_title\">引导灵魂晶石</string>\n    <string name=\"kpm_control_dialog_content\">请输入神谕密语：</string>\n    <string name=\"kpm_control_paramters\">神谕参数</string>\n    <string name=\"kpm_control_outMsg\">神力输出</string>\n    <string name=\"kpm_control_ok\">神力生效！</string>\n    <string name=\"kpm_control_failed\">神力失效！</string>\n    \n    <string name=\"settings_night_mode_follow_sys\">星夜模式跟随天界</string>\n    <string name=\"settings_night_mode_follow_sys_summary\">根据天界设置自动切换星夜模式</string>\n    <string name=\"settings_night_theme_enabled\">星夜模式</string>\n    \n    <string name=\"settings_use_system_color_theme\">天界颜色主题</string>\n    <string name=\"settings_use_system_color_theme_summary\">使用天界根据圣光阵生成的颜色主题</string>\n    <string name=\"settings_custom_color_theme\">圣光颜色主题</string>\n\n    <string name=\"amber_theme\">圣光琥珀</string>\n    <string name=\"blue_theme\">天穹之蓝</string>\n    <string name=\"blue_grey_theme\">月影灰</string>\n    <string name=\"brown_theme\">大地棕</string>\n    <string name=\"cyan_theme\">净水之青</string>\n    <string name=\"deep_orange_theme\">圣焰之橙</string>\n    <string name=\"deep_purple_theme\">神秘紫</string>\n    <string name=\"green_theme\">森林绿</string>\n    <string name=\"indigo_theme\">靛蓝之夜</string>\n    <string name=\"light_blue_theme\">天空蓝</string>\n    <string name=\"light_green_theme\">新芽绿</string>\n    <string name=\"lime_theme\">柠檬光</string>\n    <string name=\"orange_theme\">晨光橙</string>\n    <string name=\"pink_theme\">圣樱粉</string>\n    <string name=\"purple_theme\">紫罗兰</string>\n    <string name=\"red_theme\">圣焰红</string>\n    <string name=\"sakura_theme\">圣樱飞舞</string>\n    <string name=\"teal_theme\">青蓝之韵</string>\n    <string name=\"yellow_theme\">阳光黄</string>\n    <string name=\"theme_color\">主题颜色</string>\n    <string name=\"theme_light\">浅色</string>\n    <string name=\"theme_dark\">深色</string>\n    <string name=\"theme_system\">系统</string>\n    <string name=\"loading_modules\">正在获取模块…</string>\n    <string name=\"loading_scripts\">正在查找脚本…</string>\n    <string name=\"loading_themes\">正在加载主题…</string>\n    <string name=\"loading_apis\">正在加载 API 法门…</string>\n\n    <string name=\"about_app_desc\">一个基于灵魂核心的圣光化身解决方案，支持灵魂晶石和灵魂函数hook，无需重新锻造灵魂核心。</string>\n    <string name=\"about_powered_by\">由%1$s提供圣光力量</string>\n    <string name=\"about_telegram_group\">圣光殿堂群组</string>\n    \n    <!-- Online Module Strings -->\n    <string name=\"online_module_title\">在线星辰圣光阵</string>\n    <string name=\"online_module_download_start\">开始祈召: %s</string>\n    <string name=\"online_module_download_complete\">祈召完成: %s</string>\n    <string name=\"online_module_download_notification\">正在祈召%s。请查看圣光水晶球了解进度，祈召完成后请查看圣光仓库。</string>\n\n    <!-- Online KPM Strings -->\n    <string name=\"online_kpm_title\">在线灵魂晶石</string>\n    <string name=\"online_kpm_download_start\">开始祈召: %s</string>\n    <string name=\"online_kpm_download_complete\">祈召完成: %s</string>\n    <string name=\"online_kpm_download_notification\">正在祈召%s。请查看圣光水晶球了解进度，祈召完成后请查看圣光仓库。</string>\n\n    <!-- Online Script Strings -->\n    <string name=\"online_script_title\">在线圣光咒语</string>\n    <string name=\"online_script_download_start\">开始祈召: %s</string>\n    <string name=\"online_script_download_complete\">祈召完成: %s</string>\n    <string name=\"online_script_download_notification\">正在祈召%s</string>\n\n    <!-- Crash Handle Strings -->\n    <string name=\"crash_handle_title\">圣境事故报告</string>\n    <string name=\"crash_handle_copy\">复制</string>\n\n    <!-- About Screen Strings -->\n    <string name=\"about_app_version\">应用魔法等级: %s</string>\n    <string name=\"about_github\">天使书仓库</string>\n    <string name=\"about_telegram_channel\">天使圣堂频道</string>\n\n    <string name=\"settings_app_dpi\">天使使清晰度</string>\n    <string name=\"dpi_apply_settings\">确认</string>\n    <string name=\"dpi_confirm_title\">确认更改天使使清晰度</string>\n    <string name=\"dpi_confirm_message\">将天使使清晰度从%1$s更改为%2$s？</string>\n    <string name=\"settings_magic_mount\">Folk Mount API</string>\n    <string name=\"settings_magic_mount_summary\">启用内置的圣光阵挂载系统</string>\n    <string name=\"settings_new_app_profile_mode\">新天使使默认职阶</string>\n    <string name=\"settings_new_app_profile_normal\">凡人</string>\n    <string name=\"settings_new_app_profile_root\">圣者</string>\n    <string name=\"settings_new_app_profile_exclude\">驱逐</string>\n    <string name=\"settings_hide_service\">FolkPatch Hide</string>\n    <string name=\"settings_hide_service_summary\">以圣光遮蔽Bootloader解锁状态</string>\n    <string name=\"settings_app_title\">天使使名称</string>\n    <string name=\"app_title_custom\">自定义圣名</string>\n    <string name=\"settings_custom_app_title\">设置天使使名称</string>\n    <string name=\"custom_app_title_dialog_title\">自定义天使使名称</string>\n    <string name=\"custom_app_title_dialog_hint\">请赐予圣名</string>\n    <string name=\"custom_app_title_dialog_confirm\">赐名</string>\n    <string name=\"custom_app_title_dialog_empty\">圣名不可为空</string>\n    <string name=\"cancel\">取消赐名</string>\n    <string name=\"settings_custom_background\">自定义天使背景</string>\n    <string name=\"settings_custom_background_enabled\">已启用自定义天使背景</string>\n    <string name=\"settings_custom_background_opacity\">魔法阵透明度</string>\n    <string name=\"settings_custom_background_blur\">天使背景模糊</string>\n    <string name=\"settings_custom_background_dim\">背景暗度</string>\n    <string name=\"settings_select_background_image\">选择天使背景图片</string>\n    <string name=\"settings_custom_background_summary\">设置自定义天使背景图片</string>\n    <string name=\"settings_custom_background_saved\">天使背景保存成功</string>\n    <string name=\"settings_custom_background_error\">保存天使背景失败</string>\n    <string name=\"settings_alt_icon\">備用圖標</string>\n    <string name=\"alt_icon_summary\">使用備選啟動器圖標</string>\n    <string name=\"settings_background_selected\">已选择天使背景</string>\n    <string name=\"settings_background_image_cleared\">天使背景已清除</string>\n    <string name=\"settings_clear_background\">清除天使背景</string>\n    <string name=\"settings_clear_background_confirm\">确定要清除天使背景图片吗？</string>\n    <string name=\"settings_multi_background_mode\">次元多魔法</string>\n    <string name=\"settings_multi_background_mode_summary\">为不同页面设置不同的天使背景图片</string>\n    <string name=\"settings_select_home_background\">首页天使背景</string>\n    <string name=\"settings_select_kernel_background\">内核模块天使背景</string>\n    <string name=\"settings_select_superuser_background\">超级用户天使背景</string>\n    <string name=\"settings_select_system_module_background\">系统模块天使背景</string>\n    <string name=\"settings_select_settings_background\">设置页面天使背景</string>\n\n    <string name=\"settings_advanced_title_style\">高级标题天使样式</string>\n    <string name=\"settings_advanced_title_style_summary\">用自定义图片替换顶部天使标题</string>\n    <string name=\"settings_advanced_title_style_enabled\">高级标题天使样式已开启</string>\n    <string name=\"settings_select_title_image\">选择标题天使图片</string>\n    <string name=\"settings_title_image_selected\">已选择标题天使图片</string>\n    <string name=\"settings_title_image_saved\">标题天使图片保存成功</string>\n    <string name=\"settings_title_image_error\">保存标题天使图片失败</string>\n    <string name=\"settings_clear_title_image\">清除标题天使图片</string>\n    <string name=\"settings_clear_title_image_confirm\">确定要清除标题天使图片吗？</string>\n    <string name=\"settings_title_image_cleared\">标题天使图片已清除</string>\n    <string name=\"settings_title_image_day_opacity\">白天模式透明度</string>\n    <string name=\"settings_title_image_night_opacity\">夜间模式透明度</string>\n    <string name=\"settings_title_image_dim\">背景天使暗度</string>\n    <string name=\"settings_title_image_offset_x\">水平位置</string>\n\n    <string name=\"settings_launcher_icon\">天使使徽章</string>\n\n    <!-- 隐藏设定字符串 -->\n    <string name=\"home_hide_su_path\">隐藏天使使路径</string>\n    <string name=\"home_hide_kpatch_version\">隐藏心之魔法祝福等级</string>\n    <string name=\"home_hide_fingerprint\">隐藏魔法指纹</string>\n    <string name=\"home_hide_zygisk\">隐藏 Zygisk 圣化</string>\n    <string name=\"home_hide_mount\">隐藏圣光结界</string>\n    <string name=\"home_hide_su_path_summary\">在天使情报卡片中隐藏天使使路径</string>\n    <string name=\"home_hide_kpatch_version_summary\">在天使情报卡片中隐藏心之魔法祝福等级信息</string>\n    <string name=\"home_hide_zygisk_summary\">在天使情报卡片中隐藏 Zygisk 圣化信息</string>\n    <string name=\"home_hide_mount_summary\">在天使情报卡片中隐藏圣光结界信息</string>\n    <string name=\"home_hide_fingerprint_summary\">在天使情报卡片中隐藏魔法指纹信息</string>\n    \n    <string name=\"settings_grid_working_card_hide_check\">隐藏天使光环</string>\n    <string name=\"settings_grid_working_card_hide_check_summary\">隐藏天使卡片上的天使印记或警示标记</string>\n    <string name=\"settings_grid_working_card_hide_text\">隐藏天使状态文字</string>\n    <string name=\"settings_grid_working_card_hide_text_summary\">隐藏天使卡片上的\"祈祷中\"或\"未赐福\"文字</string>\n    <string name=\"settings_grid_working_card_hide_mode\">隐藏天使工作模式</string>\n    <string name=\"settings_grid_working_card_hide_mode_summary\">隐藏天使卡片上的\"Full\"或\"Half\"文字</string>\n    <string name=\"settings_list_card_hide_status_badge\">使用经典表情</string>\n    <string name=\"settings_list_card_hide_status_badge_summary\">首页的神圣印记即刻变身经典表情</string>\n    <string name=\"settings_custom_badge_text\">自选神恩印记文字</string>\n    <string name=\"settings_custom_badge_text_summary\">修改神恩印记之文字显示，仅供契约者娱乐</string>\n    <string name=\"settings_custom_background_dual_dim\">启用白天/夜晚双模式祝福</string>\n    <string name=\"settings_custom_background_dual_dim_desc\">根据亮色和暗色主题自动调整背景暗度</string>\n    <string name=\"settings_custom_background_day_dim\">白天模式祝福暗度</string>\n    <string name=\"settings_custom_background_night_dim\">夜晚模式祝福暗度</string>\n    <string name=\"settings_grid_working_card_dual_opacity\">双向祝福透明</string>\n    <string name=\"settings_grid_working_card_dual_opacity_desc\">根据亮色和暗色主题自动调整卡片透明度</string>\n    <string name=\"settings_grid_working_card_day_opacity\">星之卡片透明</string>\n    <string name=\"settings_grid_working_card_night_opacity\">月之卡片透明</string>\n    <!-- KPM AutoLoad Strings -->\n    <string name=\"kpm_autoload_title\">心之结晶自动觉醒</string>\n    <string name=\"kpm_autoload_enabled\">启用结晶自主觉醒</string>\n    <string name=\"kpm_autoload_enabled_summary\">开机时自动激活心之结晶</string>\n    <string name=\"kpm_autoload_json_config\">星界咒语配置</string>\n    <string name=\"kpm_autoload_json_label\">星界咒语</string>\n    <string name=\"kpm_autoload_json_placeholder\">{\n  \"enabled\": true,\n  \"kpmPaths\": [\n    \"/path/to/crystal1.kpm\",\n    \"/path/to/crystal2.kpm\"\n  ]\n}</string>\n    <string name=\"kpm_autoload_json_error\">无效的星界咒语格式</string>\n    <string name=\"kpm_autoload_json_helper\">输入有效的星界咒语，包含心之结晶文件路径数组</string>\n    <string name=\"kpm_autoload_save\">保存咒语配置</string>\n    <string name=\"kpm_autoload_save_confirm\">保存自动激活咒语配置？</string>\n    <string name=\"kpm_autoload_save_success\">咒语配置保存成功</string>\n    <string name=\"kpm_autoload_save_failed\">咒语配置保存失败</string>\n    <string name=\"kpm_autoload_visual_mode\">可视化咒语模式</string>\n    <string name=\"kpm_autoload_json_mode\">星界咒语模式</string>\n    <string name=\"kpm_autoload_add_kpm\">添加心之结晶</string>\n    <string name=\"kpm_autoload_remove_kpm\">解除</string>\n    <string name=\"kpm_autoload_edit_kpm\">编辑</string>\n    <string name=\"kpm_autoload_edit_dialog_title\">编辑 KPM</string>    <string name=\"kpm_autoload_event_label\">事件:</string>    <string name=\"kpm_autoload_args_label\">参数:</string>    <string name=\"kpm_autoload_event_service\">service</string>    <string name=\"kpm_autoload_event_post_fs_data\">post-fs-data</string>\n    <string name=\"kpm_autoload_kpm_list_title\">心之结晶列表</string>\n    <string name=\"kpm_autoload_no_kpm_added\">未添加心之结晶</string>\n    <string name=\"kpm_autoload_kpm_path\">心之结晶路径</string>\n    <string name=\"kpm_autoload_file_not_found\">未找到魔法文件</string>\n    <string name=\"kpm_autoload_select_kpm_file\">选择心之结晶文件</string>\n    <string name=\"kpm_autoload_first_time_title\">关于心之结晶自动觉醒</string>\n    <string name=\"kpm_autoload_first_time_message\">这个功能可以让你觉醒时自动临时激活咒语配置文件的全部心之结晶，这种方案相比于直接融合到心之核心更加方便。\n\n例如，功能为防止魔法容器被修改的心之结晶，这种心之结晶只能临时激活使用，但是融合会导致无法觉醒。或者你不想破坏心之核心就可以使用这个咒语配置快速激活结晶。\n\n需要确保天使工坊完全退出进入才会执行咒语，一般来说觉醒进入也是会激活的，工坊黑屏一段时间属于正常现象，使用的是纯后台的咒语激活方案，记得手动刷新看看是否正确激活！</string>\n    <string name=\"kpm_autoload_first_time_confirm\">了解了</string>\n    <string name=\"kpm_autoload_do_not_show_again\">不再显示</string>\n\n    <!-- APM Bulk Install Strings -->\n    <string name=\"apm_bulk_install_title\">星之魔法阵批量祝福</string>\n    <string name=\"apm_bulk_install_list_title\">魔法阵列表</string>\n    <string name=\"apm_bulk_install_add\">添加魔法阵</string>\n    <string name=\"apm_bulk_install_empty\">未添加魔法阵</string>\n    <string name=\"apm_bulk_install_action\">批量祝福</string>\n    <string name=\"apm_bulk_install_log_title\">批量祝福记录</string>\n    <string name=\"apm_bulk_install_log_start\">开始批量祝福...</string>\n    <string name=\"apm_bulk_install_log_installing\">正在祝福 %1$s</string>\n    <string name=\"apm_batch_install_full_process\">完整批量祝福仪式</string>\n    <string name=\"apm_batch_install_full_process_summary\">使用完整仪式进行星之魔法阵批量祝福</string>\n    <string name=\"next_module\">下一个魔法阵</string>\n    <string name=\"apm_bulk_install_log_installed\">魔法阵 %s 祝福完成。</string>\n    <string name=\"apm_bulk_install_log_done\">所有魔法仪式已完成。</string>\n    <string name=\"apm_bulk_install_first_use_text\">这个功能允许你一次性施展多个魔法阵，属于快速施法，适合施展一些过程中没有涉及咒语咏唱的魔法阵，涉及咏唱古代咒语的请去设置启用完整仪式模式</string>\n    <string name=\"apm_bulk_install_remove\">移除</string>\n    <string name=\"apm_first_use_title\">欢迎使用星之魔法阵</string>\n    <string name=\"apm_first_use_text\">欢迎使用星之魔法阵，这里使用的是兼容Magisk生态的魔法阵，点击右下角的按钮可以施展魔法阵，你也可以使用顶部的施法器批量施展魔法阵，还提供了一个功能一键备份全部魔法阵，但是注意这种方案不一定适用全部魔法阵，还是自己多加备份</string>\n\n    <string name=\"kpm_page_first_time_title\">魔法警告</string>\n    <string name=\"kpm_page_first_time_message\">心之结晶是直接修改心之核心的实现，它不像星之魔法阵一样有很好的恢复机制，出问题只能进入天使之门去修复。建议先激活结晶没有问题再融合到心之核心。如果是只能激活使用的结晶，可以尝试使用自动心之结晶激活功能。如果你不了解心之结晶，请不要使用本功能！</string>\n    \n    <!-- Module Backup/Restore Strings -->\n    <string name=\"apm_backup_title\">封印魔法记忆</string>\n    <string name=\"apm_restore_title\">唤醒魔法记忆</string>\n    <string name=\"apm_backup_success\">记忆封印成功</string>\n    <string name=\"apm_restore_success\">记忆唤醒成功</string>\n    <string name=\"apm_backup_failed\">记忆封印失败</string>\n    <string name=\"apm_restore_failed\">记忆唤醒失败</string>\n    <string name=\"apm_backup_failed_msg\">记忆封印失败：%s</string>\n    <string name=\"apm_restore_failed_msg\">记忆唤醒失败：%s</string>\n    <string name=\"apm_copy_list_title\">抄录圣光名录</string>\n    <string name=\"apm_copy_list_success\">名录已抄录完毕</string>\n\n    <!-- Font Settings -->\n    <string name=\"settings_custom_font\">天使变身</string>\n    <string name=\"settings_select_font_file\">选择星尘卷轴(TTF)</string>\n    <string name=\"settings_custom_font_enabled\">已使用星尘卷轴</string>\n    <string name=\"settings_custom_font_summary\">使用星尘卷轴</string>\n    <string name=\"settings_font_selected\">已选择星尘卷轴</string>\n    <string name=\"settings_custom_font_error\">使用星尘卷轴失败</string>\n    <string name=\"settings_custom_font_saved\">星尘卷轴召唤成功</string>\n    <string name=\"settings_clear_font\">恢复变身前</string>\n    <string name=\"settings_clear_font_confirm\">确定要解除变身吗？</string>\n    <string name=\"settings_font_cleared\">已恢复变身前的状态</string>\n    <string name=\"settings_font_select_hint\">请选择一个星尘卷轴(TTF)</string>\n    \n    <!-- Auto Backup Strings -->\n    <string name=\"settings_auto_backup_module\">魔法星阵自动护存</string>\n    <string name=\"settings_auto_backup_module_summary\">觉醒时自动将星之魔法阵保存到私人魔法宝箱</string>\n    <string name=\"settings_open_backup_dir\">打开魔法宝箱</string>\n    <string name=\"backup_dir_empty\">魔法宝箱空空如也</string>\n    <string name=\"backup_dir_open_failed\">无法打开魔法宝箱</string>\n    <string name=\"auto_backup_failed\">星之魔法阵保存失败: %s</string>\n    <string name=\"auto_backup_success\">星之魔法阵保存成功: %s</string>\n    <string name=\"settings_auto_backup_boot\">自动保存Boot圣器</string>\n    <string name=\"settings_auto_backup_boot_summary\">自动将Boot圣器保存至神圣宝库 (Downloads/FolkPatch/BootBackups)</string>\n\n    <!-- Auto-added missing strings -->\n\n    <!-- Home Layout Strings -->\n    <string name=\"settings_home_layout_style\">天使殿堂布局样式</string>\n    <string name=\"settings_home_layout_default\">ListUI</string>\n    <string name=\"settings_home_layout_grid\">GridUI</string>\n    <string name=\"settings_home_layout_focus\">FocusUI</string>\n    <string name=\"settings_home_layout_sign\">SignUI</string>\n    <string name=\"settings_home_layout_circle\">CircleUI</string>\n    <string name=\"settings_home_layout_dashboard_pro\">DashboardUI</string>\n\n    <!-- Unofficial Version Strings -->\n    <string name=\"unofficial_version_title\">非正统天使工坊✨</string>\n    <string name=\"unofficial_version_message\">你所使用的FolkPatch并非星界认证的正统天使工坊~ 请前往官方天使屋获取正版工坊，以避免魔法反噬或祝福失效！</string>\n    <string name=\"go_to_github\">前往Github天使屋~☆</string>\n    <string name=\"settings_save_theme\">封印魔法主题</string>\n    <string name=\"settings_import_theme\">唤醒魔法主题</string>\n    <string name=\"settings_theme_saved\">魔法主题已封印</string>\n    <string name=\"settings_theme_imported\">魔法主题已唤醒</string>\n    <string name=\"settings_theme_save_failed\">封印魔法主题失败</string>\n    <string name=\"settings_theme_import_failed\">唤醒魔法主题失败</string>\n    <string name=\"settings_reset_theme\">恢复天使主题</string>\n    <string name=\"settings_reset_theme_confirm\">确定要将所有魔法主题设置重置为默认吗？这将清除所有自定义背景、字体、音乐和音效。</string>\n    <string name=\"settings_theme_reset\">天使主题已恢复</string>\n    <string name=\"settings_theme_reset_failed\">恢复天使主题失败</string>\n\n    <!-- Theme Strings -->\n    <string name=\"theme_export_title\">封印魔法主题</string>\n    <string name=\"theme_import_title\">唤醒魔法主题</string>\n    <string name=\"theme_name\">魔法主题真名</string>\n    <string name=\"theme_type\">魔法类型</string>\n    <string name=\"theme_type_phone\">天使手机</string>\n    <string name=\"theme_type_tablet\">天使石板</string>\n    <string name=\"theme_version\">魔法等级</string>\n    <string name=\"theme_author\">创造者</string>\n    <string name=\"theme_description\">魔法描述</string>\n    <string name=\"theme_export_action\">封印</string>\n    <string name=\"theme_import_action\">唤醒</string>\n    <string name=\"theme_import_confirm\">确定要唤醒此魔法主题吗？</string>\n    <string name=\"theme_info\">魔法主题情报</string>\n\n    <!-- Grid Working Card Background -->\n    <string name=\"settings_grid_working_card_background\">魔法卡片背景</string>\n    <string name=\"settings_grid_working_card_background_enabled\">魔法卡片背景已生效</string>\n    <string name=\"settings_grid_working_card_background_summary\">为卡片施加自定义幻象</string>\n    <string name=\"settings_grid_working_card_background_selected\">幻象已选定</string>\n    <string name=\"settings_clear_grid_working_card_background\">解除卡片幻象</string>\n    <string name=\"settings_clear_grid_working_card_background_confirm\">确定要解除卡片上的幻象吗？</string>\n    <string name=\"settings_grid_working_card_background_saved\">卡片幻象保存成功</string>\n    <string name=\"settings_grid_working_card_background_error\">卡片幻象保存失败</string>\n    <string name=\"settings_grid_working_card_background_cleared\">卡片幻象已解除</string>\n\n    <!-- Biometric Strings -->\n    <string name=\"action_biometric\">天使印鉴认证</string>\n    <string name=\"msg_biometric\">请验证您的天使印鉴</string>\n    <string name=\"settings_biometric_login\">天使印鉴认证</string>\n    <string name=\"settings_biometric_login_summary\">开启天使工坊时需要进行天使印鉴认证</string>\n    <string name=\"settings_strong_biometric\">强力天使印鉴</string>\n    <string name=\"settings_strong_biometric_summary\">施展、解除或封印魔法阵时需要认证</string>\n\n    <!-- Theme Store Strings -->\n    <string name=\"theme_store_title\">绮幻衣装魔盒</string>\n    <string name=\"theme_store_search_hint\">搜索魔法主题...</string>\n    <string name=\"theme_source_official\">天使工坊直供</string>\n    <string name=\"theme_source_third_party\">同好魔法分享</string>\n    <string name=\"theme_source_local\">本地</string>\n    <string name=\"theme_store_author\">编织师: %s</string>\n    <string name=\"theme_store_version\">咒语版本: %s</string>\n    <string name=\"theme_source\">魔力源流</string>\n    <string name=\"theme_store_download_failed\">呜哇！魔力传输失败啦</string>\n    <string name=\"theme_store_download\">点亮魔法下载</string>\n\n    <!-- Update Check Strings -->\n    <string name=\"settings_auto_update_check\">自动魔法升级检查</string>\n    <string name=\"settings_auto_update_check_summary\">天使工坊启动时自动检查新魔法咒语</string>\n    <string name=\"settings_check_update\">检查魔法升级</string>\n    <string name=\"update_available_title\">发现新魔法</string>\n    <string name=\"update_available_message\">检测到您的魔法等级过低，是否要下载新版本的魔法？</string>\n    <string name=\"update_action\">升级魔法</string>\n    <string name=\"update_close\">关闭</string>\n    <string name=\"update_latest\">您已掌握最新的魔法</string>\n    <string name=\"update_error\">检查魔法升级时出错</string>\n    \n    <!--折叠选项-->\n    <string name=\"settings_category_general\">天使基础</string>\n    <string name=\"superuser\">圣光使者</string>\n    <string name=\"module\">星辰圣光阵</string>\n    <string name=\"settings_use_legacy_su_page\">单页圣光使者授权</string>\n    <string name=\"settings_use_legacy_su_page_summary\">圣光使者页面改用单页神恩设计</string>\n    <string name=\"settings_category_appearance\">圣光外观</string>\n    <string name=\"settings_appearance_font\">圣域字体设置</string>\n    <string name=\"settings_appearance_font_summary\">自定义圣域文字样式</string>\n    <string name=\"settings_appearance_theme\">圣殿主题设置</string>\n    <string name=\"settings_appearance_theme_summary\">主题商店、保存、导入和重置</string>\n    <string name=\"settings_appearance_banner\">圣光横幅设置</string>\n    <string name=\"settings_appearance_banner_summary\">模块横幅配置</string>\n    <string name=\"settings_appearance_layout\">圣所布局设置</string>\n    <string name=\"settings_appearance_layout_summary\">首页布局、导航和卡片自定义</string>\n    <string name=\"settings_appearance_background\">圣域背景设置</string>\n    <string name=\"settings_appearance_background_summary\">自定义背景、视频和多背景</string>\n    <string name=\"settings_appearance_night_mode\">暗夜模式设置</string>\n    <string name=\"settings_appearance_night_mode_summary\">深色主题和颜色配置</string>\n    <string name=\"settings_amoled_theme\">AMOLED 纯黑主题</string>\n    <string name=\"settings_amoled_theme_desc\">为深色模式使用纯黑背景</string>\n    <string name=\"settings_switch_icon\">启用按钮指示器</string>\n    <string name=\"settings_switch_icon_desc\">为开关按钮添加状态指示图标</string>\n    <string name=\"settings_category_behavior\">神谕指令</string>\n    <string name=\"settings_category_function\">圣光功能</string>\n    <string name=\"settings_category_module\">魔法模组</string>\n    <string name=\"settings_category_security\">结界守护</string>\n    \n    <!-- Multimedia Category Strings -->\n    <string name=\"settings_category_multimedia\">天使幻音</string>\n    <string name=\"settings_category_general_summary\">天使语韵、圣光更新、结界律令、神域调整</string>\n    <string name=\"settings_category_appearance_summary\">圣光主题、天使色彩、神殿布局、圣域背景、神文字体</string>\n    <string name=\"settings_category_behavior_summary\">万维除错、降临仪式、圣殿展示</string>\n    <string name=\"settings_category_security_summary\">天使印记、圣光密钥管理</string>\n    <string name=\"settings_category_backup_summary\">本地圣典、云端圣典、WebDAV</string>\n    <string name=\"settings_category_module_summary\">魔法信息、天使排列、批量降临</string>\n    <string name=\"settings_category_function_summary\">FolkPatch隐匿、卸载圣侍</string>\n    <string name=\"settings_category_multimedia_summary\">背景圣歌、天使音效、圣光振动</string>\n    \n    <!-- Background Music Strings -->\n    <string name=\"settings_background_music\">背景音乐</string>\n    <string name=\"settings_background_music_summary\">应用在前台时播放背景音乐</string>\n    <string name=\"settings_background_music_playing\">正在播放: %s</string>\n    <string name=\"settings_background_music_enabled\">背景音乐已启用</string>\n    <string name=\"settings_select_music_file\">选择音乐文件</string>\n    <string name=\"settings_music_selected\">音乐已选择</string>\n    <string name=\"settings_clear_music\">清除音乐</string>\n    <string name=\"settings_clear_music_confirm\">确定要清除背景音乐吗？</string>\n    <string name=\"settings_music_auto_play\">自动播放</string>\n    <string name=\"settings_music_auto_play_summary\">应用打开时自动播放音乐</string>\n    <string name=\"settings_music_volume\">音量</string>\n    <string name=\"settings_music_saved\">音乐文件已保存</string>\n    <string name=\"settings_music_save_error\">音乐文件保存失败</string>\n    <string name=\"settings_music_cleared\">音乐已清除</string>\n    <string name=\"settings_music_playback_control\">播放控制</string>\n    <string name=\"settings_music_looping\">圣乐循环</string>\n    <string name=\"settings_music_looping_summary\">圣乐回响永恒不息</string>\n    \n    <!-- Install Mode Select -->\n    <string name=\"kp_install_methods\">天使祝福修补/安装</string>\n    <string name=\"restore_boot_methods\">选择一个启动镜像来恢复到引导分区</string>\n    <string name=\"restore_select_file\">选择一个要还原的分区引导文件</string>\n    \n    <!-- Theme Store Filter -->\n    <string name=\"theme_store_filter_title\">筛选圣光主题</string>\n    <string name=\"theme_store_filter_author\">创造者</string>\n    <string name=\"theme_store_filter_author_hint\">输入创造者名称</string>\n    <string name=\"theme_store_filter_source\">圣光源流</string>\n    <string name=\"theme_store_filter_source_all\">全圣光</string>\n    <string name=\"theme_store_filter_type\">圣光器类型</string>\n    <string name=\"theme_store_filter_apply\">神恩应用</string>\n    <string name=\"theme_store_filter_reset\">神恩重置</string>\n\n    <!-- Video Background Settings -->\n    <string name=\"settings_video_background\">圣光影像背景</string>\n    <string name=\"settings_video_background_summary\">使用圣光影像作为背景</string>\n    <string name=\"settings_select_video\">选择圣光影像</string>\n    <string name=\"settings_video_selected\">已选择圣光影像</string>\n    <string name=\"settings_video_background_enabled\">圣光影像背景已启用</string>\n    <string name=\"settings_clear_video_background\">清除圣光影像</string>\n    <string name=\"settings_clear_video_background_confirm\">确定要清除圣光影像吗？</string>\n    <string name=\"settings_video_volume\">圣光影像音量</string>\n    <!-- File Picker -->\n    <string name=\"file_picker_permission_required\">需要神恩</string>\n    <string name=\"file_picker_permission_desc\">为了浏览圣域典籍，请授予\\'所有文件访问\\'神恩。</string>\n    <string name=\"file_picker_grant_permission\">授予神恩</string>\n    <string name=\"file_picker_cancel\">撤销</string>\n    <string name=\"file_picker_internal_storage\">圣域记忆</string>\n    <string name=\"file_picker_no_files\">无典籍</string>\n    <!-- HomeV3 Strings -->\n    <string name=\"home_device_status_title\">圣域状态</string>\n    <string name=\"home_device_status_battery_temp\">圣光温度</string>\n    <string name=\"home_device_status_cpu_load\">神念负载</string>\n    <string name=\"home_device_status_battery_level\">圣光能量</string>\n    <string name=\"home_storage_title\">圣域存储</string>\n    <string name=\"home_storage_internal\">圣域记忆</string>\n    <string name=\"home_storage_ram\">神念内存</string>\n    <string name=\"home_storage_zram\">圣域压缩</string>\n    <string name=\"home_storage_swap\">神念交换</string>\n    <string name=\"home_info_kernel\">灵魂内核</string>\n    <string name=\"home_info_superkey\">圣光之心</string>\n    <string name=\"home_info_auth_auth\">已认证</string>\n    <string name=\"home_info_auth_na\">不可用</string>\n    <string name=\"home_info_device_slot\">神念槽位</string>\n    <string name=\"home_info_device_model\">圣域形态</string>\n    <string name=\"home_info_running_mode\">运行模式</string>\n    <string name=\"home_info_mode_full\">完整</string>\n    <string name=\"home_info_mode_half\">不完整</string>\n    <string name=\"home_version\">圣迹: %s</string>\n    <string name=\"home_zygisk_implement\">Zygisk 圣化</string>\n    <string name=\"home_mount_implement\">圣光结界</string>\n\n    <string name=\"settings_app_list_loading_scheme\">圣光使召唤方案</string>\n    <string name=\"settings_app_list_loading_scheme_summary\">选择如何召唤圣光使列表</string>\n    <string name=\"app_list_loading_scheme_root_service\">圣光阵召唤 (默认)</string>\n    <string name=\"app_list_loading_scheme_package_manager\">天界召唤 (天界 API)</string>\n    <string name=\"app_list_loading_scheme_dialog_title\">召唤方案</string>\n    <string name=\"su_backup_list\">封存圣光记忆</string>\n    <string name=\"su_restore_list\">取出圣光记忆</string>\n    <string name=\"backup_success\">圣光记忆封存成功</string>\n    <string name=\"restore_success\">圣光记忆取出成功</string>\n    <!-- Badge Count Settings -->\n    <string name=\"enable_badge_count\">神恩印记设定</string>\n    <string name=\"enable_badge_count_summary\">配置神谕圣所的神恩印记显示</string>\n    <string name=\"badge_superuser\">显示圣光使者印记</string>\n    <string name=\"badge_apm\">显示星辰圣光阵印记</string>\n    <string name=\"badge_kernel\">显示灵魂晶石印记</string>\n    <string name=\"settings_sound_effect\">圣音</string>\n    <string name=\"settings_sound_effect_summary\">点击时回响圣音</string>\n    <string name=\"settings_sound_effect_enabled\">圣音已降临</string>\n    <string name=\"settings_sound_effect_playing\">当前圣音: %s</string>\n    <string name=\"settings_sound_effect_source\">圣音来源</string>\n    <string name=\"settings_sound_effect_source_local\">凡间法器</string>\n    <string name=\"settings_sound_effect_source_preset\">圣光预设</string>\n    <string name=\"settings_sound_effect_preset_title\">圣光音库</string>\n    <string name=\"settings_select_sound_effect\">选择圣音法器</string>\n    <string name=\"settings_sound_effect_selected\">圣音法器已选</string>\n    <string name=\"settings_sound_effect_scope\">圣音回响范围</string>\n    <string name=\"settings_sound_effect_scope_global\">全域 (圣光普照)</string>\n    <string name=\"settings_sound_effect_scope_bottom_bar\">仅基座圣坛</string>\n    <string name=\"settings_clear_sound_effect\">净化圣音</string>\n    <string name=\"settings_clear_sound_effect_confirm\">确定要净化此圣音吗？</string>\n    <string name=\"settings_sound_effect_cleared\">圣音已净化</string>\n\n    <string name=\"settings_startup_sound\">降临圣音</string>\n    <string name=\"settings_startup_sound_summary\">圣域开启时奏响降临圣音</string>\n    <string name=\"settings_startup_sound_enabled\">降临圣音已降临</string>\n    <string name=\"settings_startup_sound_playing\">正在奏响: %s</string>\n    <string name=\"settings_select_startup_sound\">选择降临圣音</string>\n    <string name=\"settings_startup_sound_selected\">降临圣音已选</string>\n    <string name=\"settings_clear_startup_sound\">净化降临圣音</string>\n    <string name=\"settings_clear_startup_sound_confirm\">确定要净化降临圣音吗？</string>\n    <string name=\"settings_startup_sound_cleared\">降临圣音已净化</string>\n\n    <!-- Backup Settings -->\n    <string name=\"settings_category_backup\">圣域记忆</string>\n    <string name=\"settings_enable_cloud_backup\">开启云端圣域</string>\n    <string name=\"settings_enable_cloud_backup_summary\">自动将铭刻的圣物投影至云端圣域</string>\n    <string name=\"settings_configure_webdav\">连接异界通道</string>\n    <string name=\"webdav_config_title\">异界通道配置</string>\n    <string name=\"webdav_url\">异界坐标</string>\n    <string name=\"webdav_username\">契约者真名</string>\n    <string name=\"webdav_password\">灵魂印记</string>\n    <string name=\"webdav_test_success\">通道共鸣成功</string>\n    <string name=\"webdav_test_failed\">通道共鸣失败: %s</string>\n    <string name=\"webdav_uploading\">正在传送至异界...</string>\n    <string name=\"webdav_backup_success\">圣域投影成功</string>\n    <string name=\"webdav_backup_failed\">圣域投影失败: %s</string>\n    <string name=\"save\">铭刻</string>\n    <string name=\"test\">共鸣</string>\n    <string name=\"webdav_path_label\">虚空路径 (如 /Backup)</string>\n    <string name=\"webdav_view_logs\">查阅神谕</string>\n    <string name=\"webdav_backup_logs_title\">投影神谕</string>\n    <string name=\"webdav_no_logs\">暂无神谕</string>\n    <string name=\"webdav_clear_logs\">净化</string>\n    <string name=\"settings_enable_local_backup\">开启现世留存</string>\n    <string name=\"settings_enable_local_backup_summary\">将圣物留存于现世 (Downloads/FolkPatch/ModuleBackups)</string>\n    <string name=\"close\">封印</string>\n    <string name=\"settings_backup_password\">封印咒语</string>\n    <string name=\"settings_backup_password_hint\">吟唱封印咒语</string>\n\n    <string name=\"patch_output_written_to\"> 圣光降临，圣物铭刻至 </string>\n    <string name=\"patch_write_failed\"> 圣光黯淡，深渊降临</string>\n\n    <!-- Script Library Strings -->\n    <string name=\"script_library\">圣光咒语卷轴</string>\n    <string name=\"script_library_title\">圣光咒语卷轴</string>\n    <string name=\"script_library_empty\">暂无咒语，点击添加咒语卷轴</string>\n    <string name=\"script_library_add\">添加咒语</string>\n    <string name=\"script_library_add_title\">添加咒语</string>\n    <string name=\"script_library_select_file\">选择咒语卷轴</string>\n    <string name=\"script_library_alias\">咒名</string>\n    <string name=\"script_library_alias_hint\">输入咒名（可选）</string>\n    <string name=\"script_library_run\">咏唱</string>\n    <string name=\"script_library_delete\">净化</string>\n    <string name=\"script_library_path\">圣域路径：%s</string>\n    <string name=\"script_library_confirm_delete\">确认净化此咒语？</string>\n    <string name=\"script_library_delete_success\">咒语已净化</string>\n    <string name=\"script_library_delete_failed\">净化咒语失败</string>\n    <string name=\"script_library_add_success\">咒语添加成功</string>\n    <string name=\"script_library_add_failed\">添加咒语失败：%s</string>\n    <string name=\"script_library_load_failed\">打开咒语卷轴失败</string>\n    <string name=\"script_library_output\">咒语显化</string>\n    <string name=\"script_library_no_output\">无显化</string>\n    <string name=\"script_library_save_log\">保存咏唱记录</string>\n    <string name=\"script_library_log_saved\">咏唱记录已保存至 %s</string>\n    <string name=\"script_library_log_save_failed\">保存咏唱记录失败：%s</string>\n\n    <string name=\"settings_vibration\">圣光之触</string>\n    <string name=\"settings_vibration_summary\">感知神圣的震颤</string>\n    <string name=\"settings_vibration_enabled\">开启神圣震动</string>\n    <string name=\"settings_vibration_intensity\">神恩强度</string>\n    <string name=\"settings_vibration_scope\">圣光范围</string>\n    <string name=\"settings_vibration_scope_global\">普世(任意位置)</string>\n    <string name=\"settings_vibration_scope_bottom_bar\">仅限祭坛(底部栏)</string>\n    <string name=\"search_modules\">搜索圣光阵...</string>\n    <string name=\"search_scripts\">搜索神谕圣言...</string>\n\n    <!-- Umount Service -->\n    <string name=\"settings_umount_service\">Umount Service</string>\n    <string name=\"settings_umount_service_summary\">配置祷告自动卸载的神圣连接点</string>\n    <string name=\"umount_config_title\">Umount 圣言</string>\n    <string name=\"umount_config_enabled\">启用 Umount</string>\n    <string name=\"umount_config_enabled_summary\">祷告自动卸载指定的神圣连接点</string>\n    <string name=\"umount_config_paths_label\">神圣连接点路径（每行一个）</string>\n    <string name=\"umount_config_paths_placeholder\">/system/vendor/app\\n/system/vendor/priv-app</string>\n    <string name=\"umount_config_paths_helper\">每行输入一个需要卸载的神圣连接点路径</string>\n    <string name=\"umount_config_save\">圣言保存</string>\n    <string name=\"umount_config_save_confirm\">确认以圣言保存配置？</string>\n    <string name=\"umount_config_save_success\">圣言已传达，配置保存成功</string>\n    <string name=\"umount_config_save_failed\">圣言传达失败，配置保存失败</string>\n\n    <!-- 我的圣域页面 -->\n    <string name=\"my_themes_title\">我的圣域</string>\n    <string name=\"my_themes_empty\">圣域尚未装饰</string>\n    <string name=\"my_themes_empty_action\">前往圣光商店</string>\n    <string name=\"my_themes_apply\">施展主题</string>\n    <string name=\"my_themes_delete\">净化主题</string>\n    <string name=\"my_themes_delete_confirm\">确定要净化此主题圣物吗？</string>\n    <string name=\"my_themes_deleted\">主题已净化</string>\n    <string name=\"my_themes_applied\">主题已施展</string>\n    <string name=\"my_themes_apply_failed\">施展主题失败</string>\n    <string name=\"my_themes_details\">主题圣物详情</string>\n\n    <!-- 下载圣光阵 -->\n    <string name=\"theme_download_title\">正在祈召主题</string>\n    <string name=\"theme_download_completed\">祈召完成</string>\n    <string name=\"theme_download_failed\">祈召失败</string>\n    <string name=\"theme_download_progress\">圣光进度</string>\n    <string name=\"theme_download_file\">主题圣物</string>\n    <string name=\"theme_download_image\">圣光预览</string>\n    <string name=\"theme_download_cancel\">终止祈召</string>\n    <string name=\"theme_download_pause\">暂停祈召</string>\n    <string name=\"theme_download_resume\">继续祈召</string>\n    <string name=\"theme_download_retry\">重新祈召</string>\n    <string name=\"theme_download_apply\">施展主题</string>\n    <string name=\"theme_download_go_to_my_themes\">我的圣域</string>\n    <string name=\"theme_download_retrying\">重新祈召中 (%d/3)...</string>\n    <string name=\"theme_download_preparing\">准备祈召...</string>\n    <string name=\"theme_download_downloading_file\">正在下载主题圣物...</string>\n    <string name=\"theme_download_downloading_image\">正在下载圣光预览...</string>\n    <string name=\"theme_download_finalizing\">圣光凝聚中...</string>\n    <string name=\"settings_predictive_back\">圣预返回手势</string>\n    <string name=\"settings_predictive_back_summary\">启用圣域 Android 14+ 的预知返回圣光动画</string>\n\n    <string name=\"home_device_status_battery_charging\">圣光充能中</string>\n    <string name=\"home_device_status_cpu_temp\">神念温度</string>\n    <string name=\"home_device_status_memory_trend\">神念趋势</string>\n    <string name=\"home_storage_partitions\">圣域分区</string>\n    <string name=\"home_device_status_cpu_freq\">神念频率</string>\n    <string name=\"home_network_rx\">圣光汲取</string>\n    <string name=\"home_network_tx\">圣光传送</string>\n    <string name=\"settings_home_layout_stats\">StatsUI</string>\n    <string name=\"home_stats_more_options\">更多圣光</string>\n    <string name=\"home_stats_kp_label\">KP</string>\n    <string name=\"home_stats_manager_label\">圣管</string>\n    <string name=\"home_device_status_gpu_load\">圣光负载</string>\n    <string name=\"home_stats_kernel_modules\">内核圣物</string>\n    <string name=\"home_stats_apm_modules\">APM 圣物</string>\n    <string name=\"home_stats_superusers\">至高权限</string>\n    <string name=\"settings_kernel_spoof\">内核圣伪装</string>\n    <string name=\"settings_kernel_spoof_summary\">伪装内核版本和神圣时刻</string>\n    <string name=\"settings_kernel_spoof_version\">内核圣版本</string>\n    <string name=\"settings_kernel_spoof_build_time\">内核神圣时刻</string>\n    <string name=\"settings_kernel_spoof_restore\">恢复神圣</string>\n\n    <string name=\"kernel_spoof_enabled\">内核圣伪装已开启</string>\n    <string name=\"kernel_spoof_disabled_restored\">内核圣伪装已关闭并恢复</string>\n    <string name=\"kernel_spoof_failed\">内核圣伪装失败: %d</string>\n    <string name=\"kernel_spoof_applied\">内核圣伪装已生效</string>\n\n    <string name=\"settings_path_hide\">路径圣隐匿</string>\n    <string name=\"settings_path_hide_summary\">于灵魂之力层面施展圣隐匿，令凡俗应用无法感知文件与目录</string>\n    <string name=\"path_hide_paths_label\">隐匿路径（每行一道）</string>\n\n    <string name=\"path_hide_paths_helper\">每行书写一个路径，匹配之路径将被圣光笼罩，查无可寻</string>\n    <string name=\"path_hide_save\">圣光封存</string>\n    <string name=\"path_hide_enabled\">路径圣隐匿已开启</string>\n    <string name=\"path_hide_disabled\">路径圣隐匿已关闭</string>\n    <string name=\"path_hide_applied\">路径圣隐匿已降临生效</string>\n    <string name=\"path_hide_failed\">路径圣隐匿失败: %d</string>\n    <string name=\"path_hide_uid_mode\">UID 圣裁模式</string>\n    <string name=\"path_hide_uid_mode_summary\">仅对受圣光选中之 UID 施以圣隐匿</string>\n    <string name=\"path_hide_uids_label\">圣选 UID（每行一道）</string>\n    <string name=\"path_hide_uids_helper\">书写应用 UID，唯蒙圣光眷顾者方受圣隐匿庇护</string>\n    <string name=\"path_hide_uid_save\">圣光封存 UID</string>\n    <string name=\"path_hide_uid_mode_enabled\">UID 圣裁模式已开启</string>\n    <string name=\"path_hide_uid_mode_disabled\">UID 圣裁模式已关闭</string>\n    <string name=\"path_hide_filter_system\">净化系统 UID</string>\n    <string name=\"path_hide_filter_system_summary\">同时对 root 和系统进程（UID &lt; 10000）遮蔽路径</string>\n    <string name=\"path_hide_filter_system_enabled\">系统 UID 净化已开启</string>\n    <string name=\"path_hide_filter_system_disabled\">系统 UID 净化已关闭</string>\n    <string name=\"path_hide_filter_system_warning_title\">神谕警示</string>\n    <string name=\"path_hide_filter_system_warning_message\">开启后将同时对 root 和系统进程（UID &lt; 10000）遮蔽路径。这可能导致部分圣域功能异常，请谨慎施与神力。</string>\n    <string name=\"path_hide_uid_applied\">UID 圣选名录已降临生效</string>\n    <string name=\"path_hide_select_apps\">遴选应用</string>\n    <string name=\"path_hide_no_apps_selected\">未遴选任何应用</string>\n    <string name=\"path_hide_app_removed\">已涤除 %d 个已废黜应用之旧制</string>\n    <string name=\"path_hide_search_apps\">搜寻应用…</string>\n    <string name=\"path_hide_show_system\">昭示系统应用</string>\n    <string name=\"netisolate_title\">圣光网络封印</string>\n    <string name=\"netisolate_enable\">网络封印已施展</string>\n    <string name=\"netisolate_disable\">网络封印已解除</string>\n    <string name=\"netisolate_enable_summary\">于灵魂之力层面封印所选应用之网络连接</string>\n    <string name=\"netisolate_uids_label\">已封印应用</string>\n    <string name=\"netisolate_uids_hint\">输入要封印的 UID（每行一道）</string>\n    <string name=\"netisolate_no_uids\">暂无被封印应用</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-zh-rCK/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\" translatable=\"false\">FolkPatch</string>\n    <string name=\"app_name_fpatch\">FPatch</string>\n\n    <string name=\"desktop_app_name\">餐厅招牌菜</string>\n\n    <string name=\"home\">后厨操作台</string>\n\n    <string name=\"success\">搞定了</string>\n    <string name=\"failure\">翻车了</string>\n\n    <string name=\"patch_warnning\">火大无王，小心别把自己给烫着！</string>\n    <string name=\"patch\">动火</string>\n\n    <string name=\"kernel_patch\">秘酱料</string>\n    <string name=\"android_patch\">大菜谱</string>\n\n    <string name=\"settings_nav_layout_title\">菜单编排风格</string>\n    <string name=\"settings_nav_layout_summary\">想秀啥就秀啥</string>\n    <string name=\"settings_nav_scheme\">菜单编排方案</string>\n    <string name=\"settings_nav_mode\">菜单展示模式</string>\n    <string name=\"settings_nav_mode_summary\">挑喜欢的菜单样式</string>\n    <string name=\"settings_nav_mode_auto\">自动传统</string>\n    <string name=\"settings_nav_mode_bottom\">老在下面</string>\n    <string name=\"settings_nav_mode_rail\">老在左边</string>\n    <string name=\"settings_nav_mode_floating\">飘在下边</string>\n    <string name=\"settings_show_apm\">摆出来招牌菜</string>\n    <string name=\"settings_show_kpm\">摆出来特酱料</string>\n    <string name=\"settings_show_superuser\">摆出来首席大厨</string>\n    <string name=\"settings_floating_auto_hide\">底栏自动收起来</string>\n    <string name=\"settings_floating_auto_hide_summary\">3秒没操作就把底栏藏起来</string>\n    <string name=\"settings_floating_swipe_hide\">滑动收起底栏</string>\n    <string name=\"settings_floating_swipe_hide_summary\">往下滑的时候藏底栏，往上滑的时候亮出来</string>\n    <string name=\"settings_navbar_glass_effect\">毛玻璃效果</string>\n    <string name=\"settings_navbar_glass_effect_summary\">给悬浮底栏加个毛玻璃特效</string>\n    <string name=\"settings_navbar_glass_blur_strength\">模糊程度</string>\n    <string name=\"settings_navbar_glass_transparency\">背景透明度</string>\n    <string name=\"settings_navbar_glass_highlight_strength\">高光程度</string>\n    <string name=\"settings_navbar_glass_specular\">镜面反光</string>\n    <string name=\"settings_navbar_glass_specular_summary\">在底栏顶部开个镜面高光</string>\n    <string name=\"settings_navbar_glass_inner_glow\">内发光</string>\n    <string name=\"settings_navbar_glass_inner_glow_summary\">在底栏底部加个微光效果</string>\n    <string name=\"settings_navbar_glass_border\">玻璃边框</string>\n    <string name=\"settings_navbar_glass_border_summary\">给底栏加个微妙的边框描边</string>\n    <string name=\"settings_stats_top_layout\">顶部摆盘</string>\n    <string name=\"settings_stats_top_layout_summary\">选择顶部信息卡片摆盘样式</string>\n    <string name=\"settings_stats_top_layout_list\">列表摆盘</string>\n    <string name=\"settings_stats_top_layout_grid\">网格摆盘</string>\n    <string name=\"settings_block_kernelpatch_update\">别嚷嚷秘酱料更新</string>\n    <string name=\"settings_block_kernelpatch_update_summary\">专心做菜，别老提示秘酱料换口味</string>\n    <string name=\"settings_block_androidpatch_update\">别嚷嚷招牌菜更新</string>\n    <string name=\"settings_block_androidpatch_update_summary\">专心做菜，别老提示招牌菜换配方</string>\n\n    <string name=\"reboot\">重新起火</string>\n    <string name=\"settings\">烹饪秘籍</string>\n    <string name=\"reboot_recovery\">去后厨</string>\n    <string name=\"reboot_bootloader\">去备料间</string>\n    <string name=\"reboot_download\">去进货</string>\n    <string name=\"reboot_edl\">去抢救</string>\n    <string name=\"reboot_fastbootd\">换换地儿</string>\n    <string name=\"about\">咱家底</string>\n    <string name=\"developer_and_maintainer\">掌勺大厨 | 传人</string>\n    <string name=\"settings_app_language\">方言</string>\n    <string name=\"system_default\">照旧</string>\n    <string name=\"settings_global_namespace_mode\">共用备料区</string>\n    <string name=\"settings_global_namespace_mode_summary\">大家伙用同一块地儿备料</string>\n    <string name=\"settings_clear_super_key_dialog\">大厨，真要继续干？</string>\n\n    <string name=\"home_learn_apatch\">了解 FolkPatch 餐厅</string>\n    <string name=\"home_click_to_learn_apatch\">了解 FolkPatch 餐厅的功能及烹饪使用方法</string>\n    <string name=\"settings_hide_apatch_card\">隐藏\"了解 FolkPatch 餐厅\"菜单</string>\n    <string name=\"settings_hide_apatch_card_summary\">隐藏主厨台面的\"了解 FolkPatch 餐厅\"菜单</string>\n    <string name=\"settings_grid_working_card_hide_check\">不显对勾</string>\n    <string name=\"settings_grid_working_card_hide_check_summary\">工牌上的小勾勾或警告全藏起</string>\n    <string name=\"settings_grid_working_card_hide_text\">不显状态字</string>\n    <string name=\"settings_grid_working_card_hide_text_summary\">工牌上那些\"火大无王\"啊\"翻车啦\"啊的字儿全藏起</string>\n    <string name=\"settings_grid_working_card_hide_mode\">不显模式字</string>\n    <string name=\"settings_grid_working_card_hide_mode_summary\">工牌上那个\"Full\"啊\"Half\"啊模式标识收起</string>\n\n    <string name=\"settings_folkx_engine_title\">FolkX 烹饪动画</string>\n    <string name=\"settings_folkx_engine_summary\">在主页面切换时使用丝滑的物理弹簧动画</string>\n    <string name=\"settings_folkx_animation_type\">动画类型</string>\n    <string name=\"settings_folkx_animation_speed\">动画速度</string>\n    <string name=\"settings_folkx_animation_linear\">直线运动</string>\n    <string name=\"settings_folkx_animation_spatial\">空间运动</string>\n    <string name=\"settings_folkx_animation_fade\">渐隐渐显</string>\n    <string name=\"settings_folkx_animation_vertical\">垂直滑动</string>\n    <string name=\"settings_folkx_animation_diagonal\">对角滑动</string>\n\n    <string name=\"about_source_code\"><![CDATA[<p>在 %1$s 查看菜谱源码<p/>加入我们的 %2$s 频道<p/>加入我们的 %3$s 群组]]></string>\n    <string name=\"send_log\">发送烹饪日志</string>\n    <string name=\"save_log\">保存烹饪日志</string>\n    <string name=\"log_saved\">烹饪日志已保存</string>\n    <string name=\"safe_mode\">安全烹饪模式</string>\n    <string name=\"github\">GitHub</string>\n    <string name=\"telegram\">Telegram</string>\n    <string name=\"support_or_donate\">支持 / 打赏</string>\n\n    <string name=\"super_key\">传世秘方</string>\n    <string name=\"clear_super_key\">销毁传世秘方</string>\n    <string name=\"patch_set_superkey\">设置传世秘方</string>\n    <string name=\"home_patch_set_key_desc\">KernelPatch 的唯一烹饪凭证</string>\n    <string name=\"home_patch_next_step\">下一步烹饪</string>\n\n    <string name=\"home_not_installed\">未上菜</string>\n    <string name=\"home_install_unknown\">未上菜或未验证</string>\n    <string name=\"home_install_unknown_summary\">点击上菜</string>\n    <string name=\"home_click_to_install\">点击上菜</string>\n    <string name=\"home_working\">烹饪中</string>\n    <string name=\"home_version\">菜谱版本: %s</string>\n    <string name=\"home_kp_need_update\">有新菜谱可用</string>\n    <string name=\"home_kp_cando_update\">更新菜谱</string>\n\n    <string name=\"home_installing\">正在烹饪</string>\n\n    <string name=\"kpatch_version\">酱料版本: %s</string>\n    <string name=\"apatch_version\">餐厅版本: %s</string>\n\n    <string name=\"kpatch_version_update\" formatted=\"false\">酱料版本: %s -&gt; %s</string>\n    <string name=\"kpatch_shadow_path_title\">秘制酱料</string>\n    <string name=\"home_auth_key_title\">输入传世秘方</string>\n    <string name=\"home_auth_key_desc\">验证后开始烹饪</string>\n    <string name=\"home_kpatch_info_title\">菜谱信息</string>\n\n    <string name=\"home_ap_cando_install\">上菜</string>\n\n    <string name=\"home_ap_cando_uninstall\">撤下菜品</string>\n    <string name=\"home_ap_cando_reboot\">重新开火</string>\n\n    <string name=\"patch_title\">烹饪</string>\n\n    <string name=\"patch_config_title\">菜谱</string>\n    <string name=\"patch_mode_bootimg_patch\">模式：烹饪</string>\n    <string name=\"patch_mode_patch_and_install\">模式：烹饪并上菜</string>\n    <string name=\"patch_mode_install_to_next_slot\">模式：烹饪到备用灶台 (OTA 后)</string>\n    <string name=\"patch_mode_restore\">模式：恢复原味</string>\n    <string name=\"patch_mode_uninstall_patch\">模式：撤下酱料</string>\n    <string name=\"patch_select_bootimg_btn\">选择食材</string>\n    <string name=\"patch_embed_kpm_btn\">添加特制酱料</string>\n    <string name=\"patch_start_patch_btn\">开始烹饪</string>\n    <string name=\"patch_start_unpatch_btn\">恢复原味</string>\n    <string name=\"patch_item_error\">!!厨房翻车!!</string>\n    <string name=\"patch_item_bootimg\">食材镜像</string>\n    <string name=\"patch_item_bootimg_slot\">灶台:</string>\n    <string name=\"patch_item_bootimg_dev\">厨房设备:</string>\n    <string name=\"patch_item_kernel\">核心食材</string>\n    <string name=\"patch_item_kpimg\">酱料包</string>\n    <string name=\"patch_item_kpimg_version\">版本:</string>\n    <string name=\"patch_item_kpimg_comile_time\">烹饪时间:</string>\n    <string name=\"patch_item_kpimg_config\">菜谱配置:</string>\n    <string name=\"patch_item_new_extra_kpm\">添加新调料</string>\n    <string name=\"patch_item_existed_extra_kpm\">已有调料</string>\n    <string name=\"patch_item_extra_name\">菜名:</string>\n    <string name=\"patch_item_extra_version\">版本:</string>\n    <string name=\"patch_item_extra_author\">主厨:</string>\n    <string name=\"patch_item_extra_kpm_license\">许可:</string>\n    <string name=\"patch_item_extra_kpm_desciption\">菜品描述:</string>\n    <string name=\"patch_item_extra_args\">调料参数:</string>\n    <string name=\"patch_item_extra_event\">烹饪事件:</string>\n    <string name=\"patch_item_skey\">传世秘方</string>\n    <string name=\"patch_custom_superkey\">自定传世秘方</string>\n    <string name=\"patch_custom_superkey_summary\">你仍可设秘方来管理后厨，掌柜已获内务府印信授权</string>\n    <string name=\"patch_item_set_skey_label\">传世秘方长度应为 8-63 个字符，包含数字和字母，不能包含特殊符号。</string>\n    <string name=\"patch_confirm_superkey\">再次确认传世秘方</string>\n    <string name=\"patch_skey_mismatch\">两次传世秘方不一致</string>\n\n    <string name=\"home_kernel\">核心食材版本</string>\n    <string name=\"home_manager_version\">餐厅经理版本</string>\n    <string name=\"home_fingerprint\">菜品指纹</string>\n\n    <string name=\"home_selinux_status\">厨房卫生状态</string>\n    <string name=\"home_selinux_status_disabled\">已关闭</string>\n    <string name=\"home_selinux_status_enforcing\">严格检查</string>\n    <string name=\"home_selinux_status_permissive\">宽松模式</string>\n    <string name=\"home_selinux_status_unknown\">未知状态</string>\n\n    <string name=\"settings_selinux_mode\">厨房卫生模式</string>\n    <string name=\"settings_selinux_mode_summary\">选择厨房卫生检查模式</string>\n    <string name=\"settings_selinux_mode_enforcing\">严格检查 (高标准)</string>\n    <string name=\"settings_selinux_mode_permissive\">宽松模式 (低标准)</string>\n    <string name=\"settings_selinux_current_mode\">当前: %s</string>\n    <string name=\"settings_selinux_mode_enforcing_summary\">完全严格执行厨房卫生规则</string>\n    <string name=\"settings_selinux_mode_permissive_summary\">仅记录卫生违规，不会拒绝</string>\n\n    <string name=\"home_device_info\">厨房设备</string>\n    <string name=\"home_system_version\">餐厅系统版本</string>\n    <string name=\"home_kpatch_version\">酱料版本</string>\n    <string name=\"home_su_path\">su 可执行菜谱</string>\n    <string name=\"home_apatch_version\">FolkPatch 餐厅版本</string>\n\n    <string name=\"kpm\">特制酱料</string>\n    <string name=\"kpm_kp_not_installed\">未添加秘制酱料</string>\n    <string name=\"kpm_add_kpm\">添加酱料</string>\n    <string name=\"kpm_load\">添加</string>\n    <string name=\"kpm_install\">安装</string>\n    <string name=\"kpm_embed\">融入</string>\n    <string name=\"kpm_load_toast_succ\">酱料添加成功</string>\n    <string name=\"kpm_load_toast_failed\">酱料添加失败</string>\n    <string name=\"kpm_unload_confirm\">撤下 %s 酱料？</string>\n    <string name=\"kpm_unload\">撤下</string>\n    <string name=\"kpm_control\">控制</string>\n    <string name=\"kpm_apm_empty\">未添加酱料</string>\n    <string name=\"kpm_version\">版本</string>\n    <string name=\"kpm_license\">许可</string>\n    <string name=\"kpm_author\">主厨</string>\n    <string name=\"kpm_desc\">描述</string>\n    <string name=\"kpm_args\">参数</string>\n\n    <string name=\"su_title\">首席大厨</string>\n    <string name=\"su_selinux_via_hook\">通过后门绕过</string>\n    <string name=\"su_pkg_excluded_label\">排除</string>\n    <string name=\"su_batch_exclude_title\">批量排除</string>\n    <string name=\"su_batch_exclude_content\">为全部非大厨权限餐厅软件排除注入,请选择一个烹饪操作</string>\n    <string name=\"su_exclude_btn\">排除</string>\n    <string name=\"su_exclude_reverse_btn\">反排除</string>\n    <string name=\"su_pkg_excluded_setting_title\">排除修改</string>\n    <string name=\"su_pkg_excluded_setting_summary\">启用此选项将允许 FolkPatch 恢复此餐厅招牌菜修改的任何菜谱。</string>\n    <string name=\"su_pkg_root_setting_title\">首席大厨权限</string>\n    <string name=\"su_pkg_root_setting_summary\">启用该选项为你的餐厅软件启动首席大厨,餐厅软件能够使用 SU 命令</string>\n    <string name=\"su_pkg_normal_setting_title\">学徒模式</string>\n    <string name=\"su_pkg_normal_setting_summary\">普通学徒状态，无首席大厨权限，不能使用特殊菜谱。</string>\n    <string name=\"su_show_system_apps\">显示餐厅菜谱</string>\n    <string name=\"su_hide_system_apps\">隐藏餐厅菜谱</string>\n    <string name=\"su_refresh\">刷新</string>\n\n    <string name=\"apm\">招牌菜</string>\n    <string name=\"apm_not_installed\">未添加招牌菜</string>\n    <string name=\"apm_failed_to_enable\">启用招牌菜失败: %s</string>\n    <string name=\"apm_failed_to_disable\">禁用招牌菜失败: %s</string>\n    <string name=\"apm_empty\">菜单空白</string>\n    <string name=\"apm_remove\">移除</string>\n    <string name=\"apm_undo\">还要吃</string>\n    <string name=\"apm_install\">上菜</string>\n    <string name=\"apm_uninstall_confirm\">撤下 %s 这道招牌菜？</string>\n    <string name=\"apm_uninstall_success\">%s 已撤下</string>\n    <string name=\"apm_uninstall_failed\">撤下失败: %s</string>\n    <string name=\"apm_undo_uninstall_success\">%s 已恢复</string>\n    <string name=\"apm_undo_uninstall_failed\">恢复失败: %s</string>\n    <string name=\"apm_version\">版本</string>\n    <string name=\"apm_author\">主厨</string>\n    <string name=\"apm_desc\">描述</string>\n    <string name=\"apm_overlay_fs_not_available\">OverlayFS 被核心食材禁用，招牌菜不可用！</string>\n    <string name=\"apm_magisk_conflict\">由于与 Magisk 冲突，招牌菜不可用！</string>\n    <string name=\"apm_mount_warning_title\">重要提示</string>\n    <string name=\"apm_mount_warning_message\">默认不挂载招牌菜，请使用内置上菜系统或者元招牌菜</string>\n    <string name=\"apm_mount_warning_button\">我知道了</string>\n    <string name=\"apm_reboot_to_apply\">重新开火后生效</string>\n    <string name=\"apm_changelog\">新菜谱日志</string>\n    <string name=\"apm_update\">更新菜谱</string>\n    <string name=\"apm_downloading\">正在采购招牌菜: %s</string>\n    <string name=\"apm_start_downloading\">开始采购: %s</string>\n    <string name=\"apm_new_version_available\">新菜谱版本 %s 可用，点击升级烹饪。</string>\n\n    <string name=\"hide_apatch_manager\">隐藏 FolkPatch 管理器</string>\n    <string name=\"hide_apatch_manager_summary\">烹饪一个随机包名和自定义餐厅名称的代理餐厅软件</string>\n    <string name=\"hide_apatch_dialog_new_manager_name\">新管理器名称</string>\n    <string name=\"hide_apatch_dialog_summary\">这将作为显示在启动器中的新餐厅软件名称</string>\n    <string name=\"hide_apatch_manager_failure\">隐藏失败。请报告这个厨房事故！</string>\n\n    <string name=\"setting_reset_su_path\">重置 su 菜谱路径</string>\n    <string name=\"setting_reset_su_new_path\">新完整路径</string>\n\n    <string name=\"apm_webui_open\">菜单</string>\n    <string name=\"apm_action\">烹饪操作</string>\n    <string name=\"module_shortcut_add\">快捷</string>\n    <string name=\"module_shortcut_not_supported\">启动器不支持桌面快捷方式。</string>\n    <string name=\"module_shortcut_created\">已在桌面烹饪快捷方式。</string>\n    <string name=\"module_shortcut_updated\">快捷方式已更新。</string>\n    <string name=\"module_shortcut_permission_tip_xiaomi\">请在 Xiaomi 烹饪设置中为本餐厅软件启用\"烹饪桌面快捷方式\"厨房权限。</string>\n    <string name=\"module_shortcut_permission_tip_oppo\">请在 OPPO 烹饪设置中为本餐厅软件启用\"桌面快捷方式\"厨房权限。</string>\n    <string name=\"module_shortcut_permission_tip_default\">若烹饪快捷方式失败，请在餐厅系统设置中为本餐厅软件启用桌面快捷方式厨房权限。</string>\n    <string name=\"enable_web_debugging\">启用 WebView 调试</string>\n    <string name=\"enable_web_debugging_summary\">可用于调试 WebUI。请仅在烹饪需要时启用。</string>\n    <string name=\"settings_apm_install_confirm\">烹饪招牌菜确认</string>\n    <string name=\"settings_apm_install_confirm_summary\">烹饪招牌菜前显示确认对话框</string>\n    <string name=\"settings_show_more_module_info\">显示招牌菜详细信息</string>\n    <string name=\"settings_show_more_module_info_summary\">在招牌菜列表中显示招牌菜 ID 和大小</string>\n    <string name=\"settings_apm_stay_on_page\">留在烹饪操作页面</string>\n    <string name=\"settings_apm_stay_on_page_summary\">招牌菜执行完烹饪操作后不自动返回</string>\n    <string name=\"settings_enable_module_shortcut_add\">启用快捷添加按钮</string>\n    <string name=\"settings_enable_module_shortcut_add_summary\">显示 WebUI 快捷方式添加按钮</string>\n    <string name=\"settings_disable_module_update_check\">禁用招牌菜菜谱更新检查</string>\n    <string name=\"settings_disable_module_update_check_summary\">禁用招牌菜的自动菜谱更新检查</string>\n    <string name=\"settings_module_sort_optimization\">招牌菜排序优化</string>\n    <string name=\"module_shortcut_name\">快捷方式名称</string>\n    <string name=\"module_shortcut_icon\">快捷方式图标</string>\n    <string name=\"module_shortcut_type\">快捷方式类型</string>\n    <string name=\"module_shortcut_type_webui\">WebUI</string>\n    <string name=\"module_shortcut_type_action\">烹饪操作</string>\n    <string name=\"module_shortcut_icon_default\">使用默认图标</string>\n    <string name=\"module_shortcut_icon_select\">选择图标</string>\n    <string name=\"settings_module_sort_optimization_summary\">让招牌菜带有 WebUI 和 烹饪操作的招牌菜排在前面</string>\n    <string name=\"settings_fold_system_module\">折叠餐厅系统招牌菜</string>\n    <string name=\"settings_fold_system_module_summary\">点击招牌菜卡片展开/折叠烹饪操作按钮</string>\n    <string name=\"settings_simple_list_bottom_bar\">简约列表底栏</string>\n    <string name=\"settings_simple_list_bottom_bar_summary\">招牌菜烹饪操作使用纯图标按钮样式，灵感来自 FolkPatch</string>\n    <string name=\"settings_spliced_card_group\">拼接招牌菜</string>\n    <string name=\"settings_spliced_card_group_summary\">招牌菜列表使用连续拼接卡片样式</string>\n    <string name=\"apm_install_confirm_title\">烹饪招牌菜</string>\n    <string name=\"apm_install_confirm_content\">确定要烹饪 %s 吗？</string>\n    <string name=\"apm_disable_all_title\">禁用所有招牌菜</string>\n    <string name=\"apm_disable_all_confirm\">确定禁用全部招牌菜吗？此操作将停用所有已安装的招牌菜</string>\n    <string name=\"apm_enable_module_banner\">启用招牌菜横幅</string>\n    <string name=\"apm_enable_module_banner_summary\">为支持的招牌菜显示横幅图片</string>\n    <string name=\"apm_enable_folk_banner\">自定义招牌菜横幅</string>\n    <string name=\"apm_enable_folk_banner_summary\">长按招牌菜卡片自选横幅图片</string>\n    <string name=\"apm_folk_banner_title\">FolkBanner</string>\n    <string name=\"apm_folk_banner_select\">选择图片</string>\n    <string name=\"apm_folk_banner_clear\">清除 FolkBanner</string>\n    <string name=\"apm_folk_banner_saved\">已为 %s 注入 FolkBanner。</string>\n    <string name=\"apm_folk_banner_cleared\">已为 %s 清除 FolkBanner。</string>\n    <string name=\"apm_folk_banner_failed\">为 %s 更新 FolkBanner 失败。</string>\n    <string name=\"apm_banner_api_mode\">配菜图鉴模式</string>\n    <string name=\"apm_banner_api_mode_summary\">用随机配菜图库或本地食材库给每道菜配装饰图</string>\n    <string name=\"apm_banner_api_source\">配置配菜源</string>\n    <string name=\"apm_banner_api_source_hint\">输入配菜图库地址或食材库路径</string>\n    <string name=\"apm_banner_api_source_saved\">配菜源已入库</string>\n    <string name=\"apm_banner_api_source_not_configured\">还没配菜呢</string>\n    <string name=\"apm_banner_api_url_configured\">配菜图库已入库</string>\n    <string name=\"apm_banner_local_dir_configured\">本地食材库已入库</string>\n    <string name=\"apm_banner_clear_cache\">清理备菜台</string>\n    <string name=\"apm_banner_cache_cleared\">备菜台已清空</string>\n    <string name=\"apm_banner_api_config_title\">配置配菜源</string>\n    <string name=\"apm_banner_api_config_desc\">输入配菜图库地址或本地食材库路径，每道菜都能配上独一无二的装饰图！</string>\n    <string name=\"apm_banner_api_examples_title\">配菜示例：</string>\n    <string name=\"apm_banner_api_examples\">图库: https://picsum.photos/800/400\\n食材: /sdcard/Pictures/Banners</string>\n    <!-- API 配菜市场 -->\n    <string name=\"apm_api_marketplace_title\">API 配菜市场</string>\n    <string name=\"apm_api_preview\">试菜</string>\n    <string name=\"apm_api_apply\">上菜</string>\n    <string name=\"apm_api_preview_title\">API 试菜</string>\n    <string name=\"apm_api_preview_failed\">试菜失败</string>\n    <string name=\"apm_api_url_label\">API 地址：</string>\n    <string name=\"apm_api_apply_success\">API 配菜已上桌</string>\n    <string name=\"apm_api_verifying\">尝菜中…</string>\n    <string name=\"apm_api_retry\">重新配菜</string>\n    <string name=\"apm_api_empty\">暂无 API 配菜</string>\n    <string name=\"settings_banner_custom_opacity\">横幅透明度</string>\n    <string name=\"settings_banner_custom_opacity_summary\">自定义横幅透明度，不再跟随壁纸模式</string>\n    <string name=\"settings_banner_opacity\">横幅不透明度</string>\n\n    <string name=\"settings_donot_store_superkey\">不在本地存储传世秘方</string>\n    <string name=\"settings_donot_store_superkey_summary\">每次启动管理器时验证传世秘方</string>\n\n    <string name=\"mode_select_page_title\">烹饪</string>\n    <string name=\"mode_select_page_patch_and_install\">烹饪并上菜</string>\n    <string name=\"mode_select_page_select_file\">选择要烹饪的食材</string>\n    <string name=\"restore_select_file\">选择一个要恢复原味的分区引导文件</string>\n    <string name=\"mode_select_page_install_inactive_slot\">烹饪到备用灶台 (OTA 后)</string>\n    <string name=\"mode_select_page_install_inactive_slot_warning\">您的设备将在重新开火后**强制**启动到当前未激活的灶台！\\n仅在 OTA 完成后使用此烹饪选项。\\n继续烹饪吗？</string>\n    <string name=\"mode_select_page_select_kpimg\">使用本地秘方补丁 (KPimg)</string>\n    <string name=\"patch_custom_kpimg_label\">自定义 KPimg</string>\n    <string name=\"patch_custom_kpimg_file\">文件: %s</string>\n    <string name=\"patch_select_kpimg_btn\">选择 KPimg</string>\n\n    <string name=\"home_dialog_auth_fail_title\">验证失败</string>\n    <string name=\"home_dialog_auth_fail_content\">无法验证传世秘方，导致 FolkPatch 激活失败。\n以下是验证失败的一些可能原因——请对号入座：\n\\n1. 您根本没使用 KernelPatch 来烹饪 boot.img，或者忘了自己到底做了啥烹饪操作。\n\\n2. 烹饪完的 boot.img 还躺在电脑里睡觉，根本没放进厨房。\n\\n3. 传世秘方输错了,或者夹杂了神秘符号，比如来自外星文的字符。\n\\n4. 您的厨房设备可能并不兼容 FolkPatch 和 KernelPatch，强行折腾也是徒劳。\n\\n5. 您可能搞了一些神秘烹饪操作——比如用了某些排除包名的招牌菜把 FolkPatch 给屏蔽了，结果把自己整出局了。\n\\n请先好好检查一遍，再试一次烹饪。如果问题依旧，欢迎到官方仓库的 issue 页面提问。\n毕竟，有些问题可能纯粹是您自己亲手造成的烹饪事故!\n祝你好运 啊!顺带一提,您点击下面的文档按钮准没错!\n</string>\n\n    <string name=\"home_more_menu_feedback_or_suggestion\">反馈或建议</string>\n    <string name=\"home_more_menu_about\">关于</string>\n    <string name=\"home_more_menu_document\">菜谱文档</string>\n\n    <string name=\"home_dialog_uninstall_title\">撤下菜品</string>\n\n    <string name=\"home_dialog_uninstall_ap_only\">仅撤下配方</string>\n    <string name=\"home_dialog_uninstall_all\">完全撤下</string>\n    <string name=\"home_dialog_uninstall_ap_only_desc\">仅移除主菜配方，保留管理器。</string>\n    <string name=\"home_dialog_uninstall_all_desc\">移除主菜配方并进入完整撤下烹饪流程。</string>\n\n    <string name=\"kpm_control_dialog_title\">控制酱料</string>\n    <string name=\"kpm_control_dialog_content\">请输入控制烹饪参数：</string>\n    <string name=\"kpm_control_paramters\">参数</string>\n    <string name=\"kpm_control_outMsg\">输出</string>\n    <string name=\"kpm_control_ok\">烹饪成功！</string>\n    <string name=\"kpm_control_failed\">烹饪失败！</string>\n\n    <string name=\"settings_night_mode_follow_sys\">深色模式跟随餐厅系统</string>\n    <string name=\"settings_night_mode_follow_sys_summary\">根据餐厅系统烹饪设置自动切换深色模式</string>\n    <string name=\"settings_night_theme_enabled\">深色模式</string>\n\n    <string name=\"settings_use_system_color_theme\">餐厅系统颜色主题</string>\n    <string name=\"settings_use_system_color_theme_summary\">使用餐厅系统根据壁纸生成的颜色主题</string>\n    <string name=\"settings_custom_color_theme\">颜色主题</string>\n\n    <string name=\"amber_theme\">琥珀色</string>\n    <string name=\"blue_theme\">蓝色</string>\n    <string name=\"blue_grey_theme\">蓝灰色</string>\n    <string name=\"brown_theme\">棕色</string>\n    <string name=\"cyan_theme\">青色</string>\n    <string name=\"deep_orange_theme\">深橙色</string>\n    <string name=\"deep_purple_theme\">深紫色</string>\n    <string name=\"green_theme\">绿色</string>\n    <string name=\"indigo_theme\">靛青色</string>\n    <string name=\"light_blue_theme\">浅蓝色</string>\n    <string name=\"light_green_theme\">浅绿色</string>\n    <string name=\"lime_theme\">酸橙色</string>\n    <string name=\"orange_theme\">橙色</string>\n    <string name=\"pink_theme\">粉色</string>\n    <string name=\"purple_theme\">紫色</string>\n    <string name=\"red_theme\">红色</string>\n    <string name=\"sakura_theme\">樱花粉</string>\n    <string name=\"teal_theme\">蓝绿色</string>\n    <string name=\"yellow_theme\">黄色</string>\n    <string name=\"theme_color\">主题颜色</string>\n    <string name=\"theme_light\">浅色</string>\n    <string name=\"theme_dark\">深色</string>\n    <string name=\"theme_system\">系统</string>\n    <string name=\"loading_modules\">正在获取模块…</string>\n    <string name=\"loading_scripts\">正在查找脚本…</string>\n    <string name=\"loading_themes\">正在加载主题…</string>\n    <string name=\"loading_apis\">正在加载 API 配菜…</string>\n\n    <string name=\"about_app_desc\">基于 KernelPatch 的 Root 实现，无需重新烹饪内核的同时，允许 Hook 内核函数。</string>\n    <string name=\"about_powered_by\">由 %1$s 提供支持</string>\n    <string name=\"about_telegram_group\">Telegram 群组</string>\n\n    <!-- Online Module Strings -->\n    <string name=\"online_module_title\">在线招牌菜</string>\n    <string name=\"online_module_download_start\">开始弄: %s</string>\n    <string name=\"online_module_download_complete\">弄好了: %s</string>\n    <string name=\"online_module_download_notification\">正在弄 %s，瞧瞧通知栏的进度，弄完记得去下菜文件夹瞅一眼</string>\n\n    <!-- Online KPM Strings -->\n    <string name=\"online_kpm_title\">在线酱料</string>\n    <string name=\"online_kpm_download_start\">开始弄: %s</string>\n    <string name=\"online_kpm_download_complete\">弄好了: %s</string>\n    <string name=\"online_kpm_download_notification\">正在弄 %s，瞧瞧通知栏的进度，弄完记得去下菜文件夹瞅一眼</string>\n\n    <!-- Online Script Strings -->\n    <string name=\"online_script_title\">在线烹饪菜谱</string>\n    <string name=\"online_script_download_start\">开始弄: %s</string>\n    <string name=\"online_script_download_complete\">弄好了: %s</string>\n    <string name=\"online_script_download_notification\">正在弄 %s</string>\n\n    <!-- Crash Handle Strings -->\n    <string name=\"crash_handle_title\">厨房事故报告</string>\n    <string name=\"crash_handle_copy\">复制</string>\n\n    <!-- About Screen Strings -->\n    <string name=\"about_app_version\">店里的版本: %s</string>\n    <string name=\"about_github\">代码仓库</string>\n    <string name=\"about_telegram_channel\">Telegram 频道</string>\n\n    <string name=\"settings_app_dpi\">软件 DPI</string>\n    <string name=\"dpi_apply_settings\">确定</string>\n    <string name=\"dpi_confirm_title\">确认更改软件DPI</string>\n    <string name=\"dpi_confirm_message\">将软件DPI从%1$s更改为%2$s？</string>\n    <string name=\"settings_magic_mount\">Folk Mount API</string>\n    <string name=\"settings_magic_mount_summary\">启用招牌菜挂载系统</string>\n    <string name=\"settings_new_app_profile_mode\">新菜默认做法</string>\n    <string name=\"settings_new_app_profile_normal\">家常</string>\n    <string name=\"settings_new_app_profile_root\">招牌</string>\n    <string name=\"settings_new_app_profile_exclude\">退菜</string>\n    <string name=\"settings_hide_service\">FolkPatch Hide</string>\n    <string name=\"settings_hide_service_summary\">把Bootloader解锁状态藏起来呗</string>\n    <string name=\"settings_app_title\">软件名</string>\n    <string name=\"app_title_custom\">自个儿起</string>\n    <string name=\"settings_custom_app_title\">设置软件名</string>\n    <string name=\"custom_app_title_dialog_title\">自定义软件名</string>\n    <string name=\"custom_app_title_dialog_hint\">写个名儿呗</string>\n    <string name=\"custom_app_title_dialog_confirm\">中</string>\n    <string name=\"custom_app_title_dialog_empty\">咋能没名儿呢</string>\n    <string name=\"cancel\">算了</string>\n    <string name=\"settings_custom_background\">自定背景</string>\n    <string name=\"settings_custom_background_enabled\">已开自定背景</string>\n    <string name=\"settings_custom_background_opacity\">卡片透明度</string>\n    <string name=\"settings_custom_background_blur\">背景虚化</string>\n    <string name=\"settings_custom_background_dim\">背景暗度</string>\n    <string name=\"settings_custom_background_dual_dim\">明暗双切</string>\n    <string name=\"settings_custom_background_dual_dim_desc\">根据亮色和暗色主题自动调整背景暗度</string>\n    <string name=\"settings_custom_background_day_dim\">白天暗度</string>\n    <string name=\"settings_custom_background_night_dim\">夜间暗度</string>\n    <string name=\"settings_multi_background_mode\">开多背景模式</string>\n    <string name=\"settings_multi_background_mode_summary\">不同页面用不同背景图</string>\n    <string name=\"settings_select_home_background\">后厨背景</string>\n    <string name=\"settings_select_kernel_background\">酱料背景</string>\n    <string name=\"settings_select_superuser_background\">首席大厨背景</string>\n    <string name=\"settings_select_system_module_background\">招牌菜背景</string>\n    <string name=\"settings_select_settings_background\">秘籍背景</string>\n\n    <string name=\"settings_advanced_title_style\">高级标题样式</string>\n    <string name=\"settings_advanced_title_style_summary\">用自定义图片替换顶部标题</string>\n    <string name=\"settings_advanced_title_style_enabled\">高级标题样式已开启</string>\n    <string name=\"settings_select_title_image\">挑标题图片</string>\n    <string name=\"settings_title_image_selected\">标题图片挑好了</string>\n    <string name=\"settings_title_image_saved\">标题图片存好了</string>\n    <string name=\"settings_title_image_error\">标题图片存失败</string>\n    <string name=\"settings_clear_title_image\">撤标题图片</string>\n    <string name=\"settings_clear_title_image_confirm\">真要撤标题图片吗？</string>\n    <string name=\"settings_title_image_cleared\">标题图片撤了</string>\n    <string name=\"settings_title_image_day_opacity\">白天透明度</string>\n    <string name=\"settings_title_image_night_opacity\">晚上透明度</string>\n    <string name=\"settings_title_image_dim\">背景暗度</string>\n    <string name=\"settings_title_image_offset_x\">水平位置</string>\n\n    <!-- Font Settings -->\n    <string name=\"settings_custom_font\">自定字体</string>\n    <string name=\"settings_select_font_file\">挑字体文件</string>\n    <string name=\"settings_custom_font_enabled\">已开自定字体</string>\n    <string name=\"settings_custom_font_summary\">用自定 TTF 字体</string>\n    <string name=\"settings_font_selected\">已选好自定字体</string>\n    <string name=\"settings_custom_font_error\">存字体失败</string>\n    <string name=\"settings_custom_font_saved\">自定字体存好了</string>\n    <string name=\"settings_clear_font\">用回默认字体</string>\n    <string name=\"settings_clear_font_confirm\">真要恢复默认字体吗？</string>\n    <string name=\"settings_font_cleared\">已用回默认字体</string>\n    <string name=\"settings_font_select_hint\">请挑一个 TTF 字体文件</string>\n    <string name=\"settings_select_background_image\">挑背景图</string>\n    <string name=\"settings_custom_background_summary\">设背景图</string>\n    <string name=\"settings_custom_background_saved\">背景存好了</string>\n    <string name=\"settings_custom_background_error\">存背景失败</string>\n    <string name=\"settings_background_selected\">背景已选好</string>\n    <string name=\"settings_background_image_cleared\">背景已撤了</string>\n    <string name=\"settings_clear_background\">撤背景</string>\n    <string name=\"settings_clear_background_confirm\">真要撤背景图吗？</string>\n\n    <string name=\"settings_alt_icon\">备用图标</string>\n    <string name=\"alt_icon_summary\">用备选启动器图标</string>\n\n    <!-- Video Background Settings -->\n    <string name=\"settings_video_background\">视频背景</string>\n    <string name=\"settings_video_background_summary\">使用视频作为背景</string>\n    <string name=\"settings_select_video\">选择视频</string>\n    <string name=\"settings_video_selected\">已选择视频</string>\n    <string name=\"settings_clear_video_background\">清除视频壁纸</string>\n    <string name=\"settings_clear_video_background_confirm\">确定要清除视频壁纸吗？</string>\n    <string name=\"settings_video_background_enabled\">视频背景已启用</string>\n    <string name=\"settings_video_volume\">视频音量</string>\n\n    <string name=\"su_exclude_all_title\">批量排除</string>\n    <string name=\"su_exclude_all_confirm\">确定要将所有未授权大厨的餐厅软件设为排除吗？</string>\n\n    <!-- 隐藏设定字符串 -->\n    <string name=\"home_hide_su_path\">隐藏 su 可执行菜谱路径</string>\n    <string name=\"home_hide_kpatch_version\">隐藏酱料版本</string>\n    <string name=\"home_hide_fingerprint\">隐藏菜品指纹信息</string>\n    <string name=\"home_hide_zygisk\">隐藏 Zygisk 实现</string>\n    <string name=\"home_hide_mount\">隐藏上菜实现</string>\n    <string name=\"home_hide_su_path_summary\">在信息卡片中隐藏 su 可执行菜谱路径</string>\n    <string name=\"home_hide_kpatch_version_summary\">在信息卡片中隐藏酱料版本信息</string>\n    <string name=\"home_hide_fingerprint_summary\">在信息卡片中隐藏菜品指纹信息</string>\n    <string name=\"home_hide_zygisk_summary\">在信息卡片中隐藏 Zygisk 实现信息</string>\n    <string name=\"home_hide_mount_summary\">在信息卡片中隐藏上菜实现信息</string>\n    <!-- KPM AutoLoad Strings -->\n    <string name=\"kpm_autoload_title\">酱料自动添加</string>\n    <string name=\"kpm_autoload_enabled\">启用自动添加</string>\n    <string name=\"kpm_autoload_enabled_summary\">开餐时自动添加酱料</string>\n    <string name=\"kpm_autoload_json_config\">JSON 菜谱配置</string>\n    <string name=\"kpm_autoload_json_label\">JSON 菜谱配置</string>\n    <string name=\"kpm_autoload_json_placeholder\">{\n  \"enabled\": true,\n  \"kpmPaths\": [\n    \"/path/to/module1.kpm\",\n    \"/path/to/module2.kpm\"\n  ]\n}</string>\n    <string name=\"kpm_autoload_json_error\">无效的 JSON 菜谱格式</string>\n    <string name=\"kpm_autoload_json_helper\">输入有效的 JSON 菜谱，包含酱料文件路径数组</string>\n    <string name=\"kpm_autoload_save\">保存菜谱配置</string>\n    <string name=\"kpm_autoload_save_confirm\">保存自动添加菜谱配置？</string>\n    <string name=\"kpm_autoload_save_success\">菜谱配置保存成功</string>\n    <string name=\"kpm_autoload_save_failed\">菜谱配置保存失败</string>\n    <string name=\"kpm_autoload_visual_mode\">可视化烹饪模式</string>\n    <string name=\"kpm_autoload_json_mode\">JSON 菜谱模式</string>\n\n    <!-- APM Bulk Install Strings -->\n    <string name=\"apm_bulk_install_title\">招牌菜批量烹饪</string>\n    <string name=\"apm_bulk_install_list_title\">招牌菜列表</string>\n    <string name=\"apm_bulk_install_add\">添加招牌菜</string>\n    <string name=\"apm_bulk_install_empty\">未添加招牌菜</string>\n    <string name=\"apm_bulk_install_action\">批量烹饪</string>\n    <string name=\"apm_bulk_install_log_title\">批量烹饪日志</string>\n    <string name=\"apm_bulk_install_log_start\">开始批量烹饪...</string>\n    <string name=\"apm_bulk_install_log_installing\">正在烹饪 %1$s</string>\n    <string name=\"apm_batch_install_full_process\">完整批量烹饪流程</string>\n    <string name=\"apm_batch_install_full_process_summary\">使用完整流程进行招牌菜批量烹饪</string>\n    <string name=\"next_module\">下一道招牌菜</string>\n    <string name=\"apm_bulk_install_log_installed\">招牌菜 %s 烹饪完成。</string>\n    <string name=\"apm_bulk_install_log_done\">所有烹饪操作已完成。</string>\n    <string name=\"apm_bulk_install_first_use_text\">这个功能允许你一次性烹饪多道招牌菜,属于快速烹饪,适合烹饪一些过程中没有涉及按键操作的招牌菜,涉及点按音量键等操作的请去烹饪设置启用完整流程烹饪模式</string>\n    <string name=\"apm_bulk_install_remove\">撤菜</string>\n    <string name=\"apm_first_use_title\">欢迎使用招牌菜</string>\n    <string name=\"apm_first_use_text\">欢迎使用招牌菜，这里使用的是兼容 Magisk 生态的招牌菜，点击右下角的按钮可以烹饪招牌菜，你也可以使用顶部的烹饪器批量烹饪招牌菜，还提供了一个功能一键备份全部招牌菜，但是注意这种方案不一定适用全部招牌菜，还是自己多加备份</string>\n    <string name=\"kpm_autoload_add_kpm\">添加酱料</string>\n    <string name=\"kpm_autoload_remove_kpm\">删除</string>\n    <string name=\"kpm_autoload_edit_kpm\">编辑</string>\n    <string name=\"kpm_autoload_edit_dialog_title\">编辑 KPM</string>    <string name=\"kpm_autoload_event_label\">事件:</string>    <string name=\"kpm_autoload_args_label\">参数:</string>    <string name=\"kpm_autoload_event_service\">service</string>    <string name=\"kpm_autoload_event_post_fs_data\">post-fs-data</string>\n    <string name=\"kpm_autoload_kpm_list_title\">酱料列表</string>\n    <string name=\"kpm_autoload_no_kpm_added\">未添加酱料</string>\n    <string name=\"kpm_autoload_kpm_path\">酱料路径</string>\n    <string name=\"kpm_autoload_file_not_found\">菜谱文件未找到</string>\n    <string name=\"kpm_autoload_select_kpm_file\">选择酱料菜谱文件</string>\n    <string name=\"kpm_autoload_first_time_title\">关于酱料自动添加</string>\n    <string name=\"kpm_autoload_first_time_message\">这个功能可以让你开机自动临时添加菜谱配置文件的全部酱料，这种方案相比于直接融入核心食材更加方便。\n\n例如，功能为防止分区被修改的酱料，这种酱料只能临时添加使用，但是融入会导致不开机。或者你不想破坏 BOOT 分区就可以使用这个菜谱配置快速载入酱料。\n\n需要确保餐厅软件完全退出进入才会执行命令，一般来说开机进入也是会添加的，管理器黑屏一段时间属于正常现象,使用的是纯后台的记添加方案,记得手动上滑刷新看看是否正确添加！</string>\n    <string name=\"kpm_autoload_first_time_confirm\">知道了</string>\n    <string name=\"kpm_autoload_do_not_show_again\">不再显示</string>\n\n    <string name=\"kpm_page_first_time_title\">警告</string>\n    <string name=\"kpm_page_first_time_message\">核心酱料是直接修改 Boot 的实现，它不像招牌菜一样有很好的救砖机制，出问题只能进入 Fastboot 去修复。建议先添加酱料没有问题再融入到 Boot。如果是只能添加使用的酱料，可以尝试使用自动酱料添加功能。如果你不了解核心酱料，请不要使用本烹饪功能！</string>\n    <!-- Module Backup/Restore Strings -->\n    <string name=\"apm_backup_title\">备份招牌菜</string>\n    <string name=\"apm_restore_title\">恢复招牌菜</string>\n    <string name=\"apm_backup_success\">备份成功</string>\n    <string name=\"apm_restore_success\">恢复成功</string>\n    <string name=\"apm_backup_failed\">备份失败</string>\n    <string name=\"apm_restore_failed\">恢复失败</string>\n    <string name=\"apm_backup_failed_msg\">备份失败：%s</string>\n    <string name=\"apm_restore_failed_msg\">恢复失败：%s</string>\n    <string name=\"apm_copy_list_title\">复制列表</string>\n    <string name=\"apm_copy_list_success\">招牌菜列表已复制</string>\n\n    <!-- Auto Backup Strings -->\n    <string name=\"settings_auto_backup_module\">招牌菜自动留份</string>\n    <string name=\"settings_auto_backup_module_summary\">招牌菜上桌后，自动留份到储物柜</string>\n    <string name=\"settings_open_backup_dir\">打开备菜柜</string>\n    <string name=\"backup_dir_empty\">备菜柜是空的</string>\n    <string name=\"backup_dir_open_failed\">打不开备菜柜</string>\n    <string name=\"auto_backup_failed\">留份失败: %s</string>\n    <string name=\"auto_backup_success\">留份成功: %s</string>\n    <string name=\"settings_auto_backup_boot\">Boot 自动留份</string>\n    <string name=\"settings_auto_backup_boot_summary\">自动把 Boot 留份到本地储物柜 (Downloads/FolkPatch/BootBackups)</string>\n\n    <!-- Home Layout Strings -->\n    <string name=\"settings_home_layout_style\">主厨台面风格</string>\n    <string name=\"settings_home_layout_default\">ListUI</string>\n    <string name=\"settings_home_layout_grid\">GridUI</string>\n    <string name=\"settings_home_layout_focus\">FocusUI</string>\n    <string name=\"settings_home_layout_sign\">SignUI</string>\n    <string name=\"settings_home_layout_circle\">CircleUI</string>\n    <string name=\"settings_home_layout_dashboard_pro\">DashboardUI</string>\n    <string name=\"unofficial_version_title\">非官方版本</string>\n    <string name=\"unofficial_version_message\">您正在用的是第三方 FolkPatch 管理器，可能会出岔子。要吃正宗的，请去官方渠道下正版！</string>\n    <string name=\"go_to_github\">去 Github 瞧瞇瞇</string>\n    <string name=\"settings_save_theme\">存起主题</string>\n    <string name=\"settings_import_theme\">导入主题</string>\n    <string name=\"settings_theme_saved\">主题已存起</string>\n    <string name=\"settings_theme_imported\">主题已导入</string>\n    <string name=\"settings_theme_save_failed\">存起主题失败</string>\n    <string name=\"settings_theme_import_failed\">导入主题失败</string>\n    <string name=\"settings_reset_theme\">恢复装修</string>\n    <string name=\"settings_reset_theme_confirm\">确定要把装修风格恢复成默认吗？这会清掉所有自定义背景、字体、音乐和音效。</string>\n    <string name=\"settings_theme_reset\">装修已恢复</string>\n    <string name=\"settings_theme_reset_failed\">恢复装修失败</string>\n\n    <!-- Theme Strings -->\n    <string name=\"theme_export_title\">打包装</string>\n    <string name=\"theme_import_title\">导入风格</string>\n    <string name=\"theme_name\">装修名称</string>\n    <string name=\"theme_type\">店面类型</string>\n    <string name=\"theme_type_phone\">小桌</string>\n    <string name=\"theme_type_tablet\">大桌</string>\n    <string name=\"theme_version\">版本号</string>\n    <string name=\"theme_author\">设计师</string>\n    <string name=\"theme_description\">装修说明</string>\n    <string name=\"theme_export_action\">打包</string>\n    <string name=\"theme_import_action\">导入</string>\n    <string name=\"theme_import_confirm\">真要导入这个装修风格吗？</string>\n    <string name=\"theme_info\">装修信息</string>\n\n    <!-- Grid Working Card Background -->\n    <string name=\"settings_grid_working_card_background\">工牌背景</string>\n    <string name=\"settings_grid_working_card_background_enabled\">已启用自定义工牌背景</string>\n    <string name=\"settings_grid_working_card_background_summary\">给工牌设置自定义背景图</string>\n    <string name=\"settings_grid_working_card_background_selected\">背景已选好</string>\n    <string name=\"settings_grid_working_card_dual_opacity\">启用明暗双透明度</string>\n    <string name=\"settings_grid_working_card_dual_opacity_desc\">根据亮色和暗色主题自动调整卡片透明度</string>\n    <string name=\"settings_grid_working_card_day_opacity\">白天透明度</string>\n    <string name=\"settings_grid_working_card_night_opacity\">夜间透明度</string>\n    <string name=\"settings_clear_grid_working_card_background\">清除工牌背景</string>\n    <string name=\"settings_clear_grid_working_card_background_confirm\">确定要清除工牌背景图吗？</string>\n    <string name=\"settings_grid_working_card_background_saved\">工牌背景存好了</string>\n    <string name=\"settings_grid_working_card_background_error\">工牌背景存失败了</string>\n    <string name=\"settings_grid_working_card_background_cleared\">工牌背景已清除</string>\n\n    <!-- Biometric Strings -->\n    <string name=\"action_biometric\">生物识别认证</string>\n    <string name=\"msg_biometric\">请验证您的生物识别身份</string>\n    <string name=\"settings_biometric_login\">生物识别认证</string>\n    <string name=\"settings_biometric_login_summary\">打开餐厅软件时要求生物识别认证</string>\n    <string name=\"settings_strong_biometric\">强力生物识别</string>\n    <string name=\"settings_strong_biometric_summary\">烹饪、撤下或禁用招牌菜时需要验证</string>\n\n    <!-- Theme Store Strings -->\n    <string name=\"theme_store_title\">主题商店</string>\n    <string name=\"theme_source_official\">官方</string>\n    <string name=\"theme_source_third_party\">第三方</string>\n    <string name=\"theme_source_local\">本地</string>\n    <string name=\"theme_store_author\">主厨: %s</string>\n    <string name=\"theme_store_version\">版本: %s</string>\n    <string name=\"theme_source\">来源</string>\n    <string name=\"theme_store_download_failed\">采购失败</string>\n    <string name=\"theme_store_download\">采购</string>\n    <string name=\"theme_store_search_hint\">搜索主题...</string>\n\n    <!-- Update Check Strings -->\n    <string name=\"settings_auto_update_check\">自动看新菜谱</string>\n    <string name=\"settings_auto_update_check_summary\">开张时自动瞧瞧有嘛新菜谱没</string>\n    <string name=\"settings_check_update\">看新菜谱</string>\n    <string name=\"update_available_title\">有新菜谱啦</string>\n    <string name=\"update_available_message\">菜谱有点旧了，要搞点新菜谱不？</string>\n    <string name=\"update_action\">搞新菜谱</string>\n    <string name=\"update_close\">关咯</string>\n    <string name=\"update_latest\">菜谱挺新的</string>\n    <string name=\"update_error\">看新菜谱时出岔子</string>\n    <!--折叠选项-->\n    <string name=\"settings_category_general\">餐厅规矩</string>\n    <string name=\"settings_app_list_loading_scheme\">餐厅软件列表加载方案</string>\n    <string name=\"settings_app_list_loading_scheme_summary\">选择如何加载餐厅软件列表</string>\n    <string name=\"app_list_loading_scheme_root_service\">RootService (默认)</string>\n    <string name=\"app_list_loading_scheme_package_manager\">PackageManager (餐厅系统 API)</string>\n    <string name=\"app_list_loading_scheme_dialog_title\">加载方案</string>\n    <string name=\"superuser\">首席大厨</string>\n    <string name=\"module\">招牌菜</string>\n    <string name=\"settings_category_appearance\">餐厅布局</string>\n    <string name=\"settings_appearance_font\">字体样式</string>\n    <string name=\"settings_appearance_font_summary\">自定义字体配置</string>\n    <string name=\"settings_appearance_theme\">主题装修</string>\n    <string name=\"settings_appearance_theme_summary\">主题商店、保存、导入和重置</string>\n    <string name=\"settings_appearance_banner\">横幅挂牌</string>\n    <string name=\"settings_appearance_banner_summary\">模块横幅配置</string>\n    <string name=\"settings_appearance_layout\">后厨布局</string>\n    <string name=\"settings_appearance_layout_summary\">首页布局、导航和卡片自定义</string>\n    <string name=\"settings_appearance_background\">后厨背景</string>\n    <string name=\"settings_appearance_background_summary\">自定义背景、视频和多背景</string>\n    <string name=\"settings_appearance_night_mode\">夜间打烊模式</string>\n    <string name=\"settings_appearance_night_mode_summary\">深色主题和颜色配置</string>\n    <string name=\"settings_amoled_theme\">AMOLED 纯黑主题</string>\n    <string name=\"settings_amoled_theme_desc\">为深色模式使用纯黑背景</string>\n    <string name=\"settings_switch_icon\">启用按钮指示器</string>\n    <string name=\"settings_switch_icon_desc\">为开关按钮添加状态指示图标</string>\n    <string name=\"settings_category_behavior\">吃饭行为</string>\n    <string name=\"settings_category_function\">餐厅功能</string>\n    <string name=\"settings_use_legacy_su_page\">单页首席大厨授权</string>\n    <string name=\"settings_use_legacy_su_page_summary\">首席大厨页面改用单页授权设计</string>\n    <string name=\"settings_category_module\">招牌菜肴</string>\n    <string name=\"settings_category_security\">饭菜安全</string>\n\n    <!-- 背景音乐字符串 -->\n    <string name=\"settings_background_music\">背景音乐</string>\n    <string name=\"settings_background_music_summary\">餐厅软件在前台时播放背景音乐</string>\n    <string name=\"settings_background_music_playing\">正在播放: %s</string>\n    <string name=\"settings_background_music_enabled\">背景音乐已启用</string>\n    <string name=\"settings_select_music_file\">选择音乐文件</string>\n    <string name=\"settings_music_selected\">背景音乐已选</string>\n    <string name=\"settings_clear_music\">清除音乐</string>\n    <string name=\"settings_clear_music_confirm\">确定要清除背景音乐吗？</string>\n    <string name=\"settings_music_auto_play\">自动开唱</string>\n    <string name=\"settings_music_auto_play_summary\">开门迎客时自动播放音乐</string>\n    <string name=\"settings_music_looping\">循环播放</string>\n    <string name=\"settings_music_looping_summary\">重复播放同一曲</string>\n    <string name=\"settings_music_volume\">音量大小</string>\n    <string name=\"settings_music_saved\">音乐文件已存</string>\n    <string name=\"settings_music_save_error\">保存音乐失败</string>\n    <string name=\"settings_music_cleared\">音乐已删</string>\n    <string name=\"settings_category_multimedia\">餐厅喇叭</string>\n    <string name=\"settings_category_general_summary\">菜单语言、新菜更新、厨房规矩、餐厅调整</string>\n    <string name=\"settings_category_appearance_summary\">菜单主题、颜色、布局、背景、字体</string>\n    <string name=\"settings_category_behavior_summary\">Web调试、上菜行为、招牌展示</string>\n    <string name=\"settings_category_security_summary\">顾客认证、传世秘方管理</string>\n    <string name=\"settings_category_backup_summary\">本地留份、云端留份、WebDAV</string>\n    <string name=\"settings_category_module_summary\">招牌菜信息、排序、批量上菜</string>\n    <string name=\"settings_category_function_summary\">FolkPatch隐身、Umount服务</string>\n    <string name=\"settings_category_multimedia_summary\">背景音乐、音效、振动</string>\n    <string name=\"settings_music_playback_control\">音乐控制</string>\n\n    <!-- Theme Store Filter -->\n    <string name=\"theme_store_filter_title\">挑装修风格</string>\n    <string name=\"theme_store_filter_author\">设计师</string>\n    <string name=\"theme_store_filter_author_hint\">输入设计师名字</string>\n    <string name=\"theme_store_filter_source\">来源地</string>\n    <string name=\"theme_store_filter_source_all\">全摆上</string>\n    <string name=\"theme_store_filter_type\">店面类型</string>\n    <string name=\"theme_store_filter_apply\">整</string>\n    <string name=\"theme_store_filter_reset\">恢复原样</string>\n\n    <!-- File Picker -->\n    <string name=\"file_picker_permission_required\">需要厨房权限</string>\n    <string name=\"file_picker_permission_desc\">为了浏览菜谱文件，请授予\\'所有菜谱文件访问\\'厨房权限。</string>\n    <string name=\"file_picker_grant_permission\">授予厨房权限</string>\n    <string name=\"file_picker_cancel\">取消</string>\n    <string name=\"file_picker_internal_storage\">内部存储</string>\n    <string name=\"file_picker_no_files\">无菜谱文件</string>\n\n    <!-- HomeV3 Strings -->\n    <string name=\"home_device_status_title\">厨房设备状态</string>\n    <string name=\"home_device_status_battery_temp\">烤箱温度</string>\n    <string name=\"home_device_status_cpu_load\">灶台负载</string>\n    <string name=\"home_device_status_battery_level\">电力剩余</string>\n    <string name=\"home_storage_title\">储物柜信息</string>\n    <string name=\"home_storage_internal\">主储物柜</string>\n    <string name=\"home_storage_ram\">操作台内存</string>\n    <string name=\"home_storage_zram\">ZRAM</string>\n    <string name=\"home_storage_swap\">交换空间</string>\n    <string name=\"home_info_kernel\">核心食材</string>\n    <string name=\"home_info_superkey\">传世秘方</string>\n    <string name=\"home_info_auth_auth\">已验证</string>\n    <string name=\"home_info_auth_na\">不可用</string>\n    <string name=\"home_info_device_slot\">设备灶台</string>\n    <string name=\"home_info_device_model\">设备型号</string>\n    <string name=\"home_info_running_mode\">运行模式</string>\n    <string name=\"home_info_mode_full\">完整</string>\n    <string name=\"home_info_mode_half\">不完全</string>\n    <string name=\"home_zygisk_implement\">Zygisk 实现</string>\n    <string name=\"home_mount_implement\">上菜实现</string>\n\n    <string name=\"settings_list_card_hide_status_badge\">使用经典表情</string>\n    <string name=\"settings_list_card_hide_status_badge_summary\">主厨台面的状态徽章即刻变身经典表情</string>\n    <string name=\"settings_custom_badge_text\">自选角标文字</string>\n    <string name=\"settings_custom_badge_text_summary\">修改角标的文本显示，仅供娱乐</string>\n\n    <!-- Install Mode Select -->\n    <string name=\"kp_install_methods\">KernelPatch 烹饪/烹饪</string>\n    <string name=\"restore_boot_methods\">选择一个启动来恢复到引导分区</string>\n\n    <string name=\"su_backup_list\">备份列表</string>\n    <string name=\"su_restore_list\">恢复列表</string>\n    <string name=\"backup_success\">备份成功</string>\n    <string name=\"restore_success\">恢复成功</string>\n\n    <!-- 超级用户应用操作弹窗 -->\n    <string name=\"su_app_action_title\">餐厅软件烹饪操作</string>\n    <string name=\"su_app_action_content\">请选择一个烹饪操作来对餐厅软件执行</string>\n    <string name=\"su_app_action_launch\">启动餐厅软件</string>\n    <string name=\"su_app_action_force_stop\">强行停止</string>\n    <string name=\"su_app_action_launch_success\">正在启动 %s</string>\n    <string name=\"su_app_action_force_stop_success\">已强行停止 %s</string>\n    <string name=\"su_app_action_failed\">烹饪操作失败: %s</string>\n\n    <!-- SU Audit Log -->\n    <string name=\"su_audit_log_title\">授权日志</string>\n    <string name=\"su_audit_log_empty\">暂无授权记录</string>\n    <string name=\"su_audit_log_clear\">清除日志</string>\n    <string name=\"su_audit_log_clear_confirm\">确认清除所有授权记录？</string>\n    <string name=\"su_audit_action_grant\">已授权</string>\n    <string name=\"su_audit_action_revoke\">已撤销</string>\n    <string name=\"su_audit_action_exclude\">已排除</string>\n    <string name=\"su_audit_tab_usage\">使用记录</string>\n    <string name=\"su_audit_tab_operations\">操作记录</string>\n\n    <string name=\"patch_output_written_to\"> 烹饪成功，菜品输出到 </string>\n    <string name=\"patch_write_failed\"> 烹饪失败，发生了厨房事故</string>\n\n    <!-- Badge Count Settings -->\n    <string name=\"enable_badge_count\">角标统计烹饪设置</string>\n    <string name=\"enable_badge_count_summary\">配置菜单项的角标统计显示</string>\n    <string name=\"badge_superuser\">显示首席大厨角标</string>\n    <string name=\"badge_apm\">显示招牌菜角标</string>\n    <string name=\"badge_kernel\">显示特制酱料角标</string>\n\n    <string name=\"settings_sound_effect\">厨房声响</string>\n    <string name=\"settings_sound_effect_summary\">操作时发出厨房声响</string>\n    <string name=\"settings_sound_effect_enabled\">声响已开</string>\n    <string name=\"settings_sound_effect_playing\">当前声响: %s</string>\n    <string name=\"settings_sound_effect_source\">声响来源</string>\n    <string name=\"settings_sound_effect_source_local\">本地食材</string>\n    <string name=\"settings_sound_effect_source_preset\">主厨预设</string>\n    <string name=\"settings_sound_effect_preset_title\">主厨音效库</string>\n    <string name=\"settings_select_sound_effect\">选择声响文件</string>\n    <string name=\"settings_sound_effect_selected\">已选声响文件</string>\n    <string name=\"settings_sound_effect_scope\">声响范围</string>\n    <string name=\"settings_sound_effect_scope_global\">整个餐厅</string>\n    <string name=\"settings_sound_effect_scope_bottom_bar\">仅操作台底部</string>\n    <string name=\"settings_clear_sound_effect\">清除声响</string>\n    <string name=\"settings_clear_sound_effect_confirm\">确定要清除声响吗？</string>\n    <string name=\"settings_sound_effect_cleared\">声响已关</string>\n\n    <string name=\"settings_startup_sound\">开张声响</string>\n    <string name=\"settings_startup_sound_summary\">餐厅开门时播放迎宾声响</string>\n    <string name=\"settings_startup_sound_enabled\">开张声响已开</string>\n    <string name=\"settings_startup_sound_playing\">正在播放: %s</string>\n    <string name=\"settings_select_startup_sound\">选择开张声响</string>\n    <string name=\"settings_startup_sound_selected\">已选开张声响</string>\n    <string name=\"settings_clear_startup_sound\">关闭开张声响</string>\n    <string name=\"settings_clear_startup_sound_confirm\">确定要关闭开张声响吗？</string>\n    <string name=\"settings_startup_sound_cleared\">开张声响已关</string>\n\n    <string name=\"settings_vibration\">触感反馈</string>\n    <string name=\"settings_vibration_summary\">操作时震动提示</string>\n    <string name=\"settings_vibration_enabled\">开启触感</string>\n    <string name=\"settings_vibration_intensity\">震动力度</string>\n    <string name=\"settings_vibration_scope\">触感范围</string>\n    <string name=\"settings_vibration_scope_global\">整个餐厅</string>\n    <string name=\"settings_vibration_scope_bottom_bar\">仅操作台底部</string>\n\n    <!-- Backup Settings -->\n    <string name=\"settings_category_backup\">菜品留份</string>\n    <string name=\"settings_enable_cloud_backup\">启用云端留份</string>\n    <string name=\"settings_enable_cloud_backup_summary\">自动将烹饪过的招牌菜留份到云端仓库</string>\n    <string name=\"settings_configure_webdav\">配置云端仓库</string>\n    <string name=\"webdav_config_title\">云端仓库配置</string>\n    <string name=\"webdav_url\">仓库地址</string>\n    <string name=\"webdav_username\">账号</string>\n    <string name=\"webdav_password\">钥匙</string>\n    <string name=\"webdav_test_success\">仓库连接正常</string>\n    <string name=\"webdav_test_failed\">连接失败: %s</string>\n    <string name=\"webdav_uploading\">正在送到云端仓库...</string>\n    <string name=\"webdav_backup_success\">云端留份成功</string>\n    <string name=\"webdav_backup_failed\">云端留份失败: %s</string>\n    <string name=\"save\">保存</string>\n    <string name=\"test\">测试</string>\n    <string name=\"webdav_path_label\">路径 (例如 /Backup)</string>\n    <string name=\"webdav_view_logs\">查看留份记录</string>\n    <string name=\"webdav_backup_logs_title\">留份记录</string>\n    <string name=\"webdav_no_logs\">暂无留份记录</string>\n    <string name=\"webdav_clear_logs\">清空记录</string>\n    <string name=\"settings_enable_local_backup\">启用本地留份</string>\n    <string name=\"settings_enable_local_backup_summary\">将招牌菜留份到本地储物柜 (Downloads/FolkPatch/ModuleBackups)</string>\n    <string name=\"close\">关闭</string>\n\n    <!-- Script Library Strings -->\n    <string name=\"script_library\">烹饪菜谱库</string>\n    <string name=\"script_library_title\">烹饪菜谱库</string>\n    <string name=\"script_library_empty\">暂无菜谱，点击添加按钮添加一个</string>\n    <string name=\"script_library_add\">添加菜谱</string>\n    <string name=\"script_library_add_title\">添加菜谱</string>\n    <string name=\"script_library_select_file\">选择菜谱文件</string>\n    <string name=\"script_library_alias\">别名</string>\n    <string name=\"script_library_alias_hint\">输入菜谱别名（可选）</string>\n    <string name=\"script_library_run\">烹饪</string>\n    <string name=\"script_library_delete\">删除</string>\n    <string name=\"script_library_path\">路径：%s</string>\n    <string name=\"script_library_confirm_delete\">确认删除菜谱？</string>\n    <string name=\"script_library_delete_success\">菜谱删除成功</string>\n    <string name=\"script_library_delete_failed\">删除菜谱失败</string>\n    <string name=\"script_library_add_success\">菜谱添加成功</string>\n    <string name=\"script_library_add_failed\">添加菜谱失败：%s</string>\n    <string name=\"script_library_load_failed\">加载菜谱库失败</string>\n    <string name=\"script_library_output\">烹饪输出</string>\n    <string name=\"script_library_no_output\">无输出</string>\n    <string name=\"script_library_save_log\">保存烹饪日志</string>\n    <string name=\"script_library_log_saved\">烹饪日志已保存至 %s</string>\n    <string name=\"script_library_log_save_failed\">保存烹饪日志失败：%s</string>\n    <string name=\"search_modules\">搜索菜品...</string>\n    <string name=\"search_scripts\">搜索后厨秘籍...</string>\n\n    <!-- Umount Service -->\n    <string name=\"settings_umount_service\">Umount Service</string>\n    <string name=\"settings_umount_service_summary\">配置开炉自动撤回的摆盘位置</string>\n    <string name=\"umount_config_title\">Umount 食谱</string>\n    <string name=\"umount_config_enabled\">启用 Umount</string>\n    <string name=\"umount_config_enabled_summary\">开炉自动撤回指定的摆盘位置</string>\n    <string name=\"umount_config_paths_label\">摆盘位置路径（每行一个）</string>\n    <string name=\"umount_config_paths_placeholder\">/system/vendor/app\\n/system/vendor/priv-app</string>\n    <string name=\"umount_config_paths_helper\">每行输入一个需要撤回的摆盘位置路径</string>\n    <string name=\"umount_config_save\">开吃</string>\n    <string name=\"umount_config_save_confirm\">确认开吃并保存食谱？</string>\n    <string name=\"umount_config_save_success\">开炉成功，食谱保存成功</string>\n    <string name=\"umount_config_save_failed\">厨师长反对，食谱保存失败</string>\n\n    <!-- 我的餐厅页面 -->\n    <string name=\"my_themes_title\">我的餐厅</string>\n    <string name=\"my_themes_empty\">餐厅还没装修呢</string>\n    <string name=\"my_themes_empty_action\">去主题商店看看</string>\n    <string name=\"my_themes_apply\">应用主题</string>\n    <string name=\"my_themes_delete\">删除主题</string>\n    <string name=\"my_themes_delete_confirm\">确定要删除这个主题吗？</string>\n    <string name=\"my_themes_deleted\">主题已删除</string>\n    <string name=\"my_themes_applied\">主题已应用</string>\n    <string name=\"my_themes_apply_failed\">应用主题失败</string>\n    <string name=\"my_themes_details\">主题详情</string>\n\n    <!-- 下载对话 -->\n    <string name=\"theme_download_title\">正在采购主题</string>\n    <string name=\"theme_download_completed\">采购完成</string>\n    <string name=\"theme_download_failed\">采购失败</string>\n    <string name=\"theme_download_progress\">采购进度</string>\n    <string name=\"theme_download_file\">主题菜谱</string>\n    <string name=\"theme_download_image\">菜品预览</string>\n    <string name=\"theme_download_cancel\">取消采购</string>\n    <string name=\"theme_download_pause\">暂停采购</string>\n    <string name=\"theme_download_resume\">继续采购</string>\n    <string name=\"theme_download_retry\">重新采购</string>\n    <string name=\"theme_download_apply\">应用主题</string>\n    <string name=\"theme_download_go_to_my_themes\">我的餐厅</string>\n    <string name=\"theme_download_retrying\">重新采购中 (%d/3)...</string>\n    <string name=\"theme_download_preparing\">准备采购...</string>\n    <string name=\"theme_download_downloading_file\">正在下载主题菜谱...</string>\n    <string name=\"theme_download_downloading_image\">正在下载菜品预览...</string>\n    <string name=\"theme_download_finalizing\">准备上菜...</string>\n    <string name=\"settings_predictive_back\">预测性返回手势</string>\n    <string name=\"settings_predictive_back_summary\">启用 Android 14+ 的预测性返回手势动画</string>\n\n    <string name=\"home_device_status_battery_charging\">烤箱加热中</string>\n    <string name=\"home_device_status_cpu_temp\">灶台温度</string>\n    <string name=\"home_device_status_memory_trend\">备料趋势</string>\n    <string name=\"home_storage_partitions\">储物分区</string>\n    <string name=\"home_device_status_cpu_freq\">灶台频率</string>\n    <string name=\"home_network_rx\">进货</string>\n    <string name=\"home_network_tx\">出餐</string>\n    <string name=\"settings_home_layout_stats\">StatsUI</string>\n    <string name=\"home_stats_more_options\">更多菜谱</string>\n    <string name=\"home_stats_kp_label\">KP</string>\n    <string name=\"home_stats_manager_label\">厨管</string>\n    <string name=\"home_device_status_gpu_load\">烤箱负载</string>\n    <string name=\"home_stats_kernel_modules\">内核菜谱</string>\n    <string name=\"home_stats_apm_modules\">APM 菜谱</string>\n    <string name=\"home_stats_superusers\">主厨权限</string>\n    <string name=\"settings_kernel_spoof\">内核伪装配方</string>\n    <string name=\"settings_kernel_spoof_summary\">伪装内核版本和出炉时间</string>\n    <string name=\"settings_kernel_spoof_version\">内核版本</string>\n    <string name=\"settings_kernel_spoof_build_time\">内核出炉时间</string>\n    <string name=\"settings_kernel_spoof_restore\">重新出炉</string>\n\n    <string name=\"kernel_spoof_enabled\">内核伪装配方已启用</string>\n    <string name=\"kernel_spoof_disabled_restored\">内核伪装配方已停用并重新出炉</string>\n    <string name=\"kernel_spoof_failed\">内核伪装配方失败: %d</string>\n    <string name=\"kernel_spoof_applied\">内核伪装配方已上菜</string>\n\n    <string name=\"settings_path_hide\">路径藏起来</string>\n    <string name=\"settings_path_hide_summary\">在内核层面把文件和目录藏起来，不让别的应用看到</string>\n    <string name=\"path_hide_paths_label\">要藏的路径（每行一个）</string>\n    <string name=\"path_hide_paths_helper\">每行写一个路径，匹配的路径就跟不存在一样</string>\n    <string name=\"path_hide_save\">保存</string>\n    <string name=\"path_hide_enabled\">路径隐藏已开火</string>\n    <string name=\"path_hide_disabled\">路径隐藏已熄火</string>\n    <string name=\"path_hide_applied\">路径隐藏已上菜</string>\n    <string name=\"path_hide_failed\">路径隐藏翻车了: %d</string>\n    <string name=\"path_hide_uid_mode\">UID 精准火候模式</string>\n    <string name=\"path_hide_uid_mode_summary\">只对指定应用的 UID 下隐藏路径</string>\n    <string name=\"path_hide_uids_label\">目标 UID（每行一个，列好清单）</string>\n    <string name=\"path_hide_uids_helper\">每行写一个应用 UID，只有这些应用的路径会被藏起来</string>\n    <string name=\"path_hide_uid_save\">保存 UID 配方</string>\n    <string name=\"path_hide_uid_mode_enabled\">UID 精准火候模式已开火</string>\n    <string name=\"path_hide_uid_mode_disabled\">UID 精准火候模式已熄火</string>\n    <string name=\"path_hide_filter_system\">过滤系统 UID 菜品</string>\n    <string name=\"path_hide_filter_system_summary\">同时对 root 和系统进程（UID &lt; 10000）隐藏食材路径</string>\n    <string name=\"path_hide_filter_system_enabled\">系统 UID 过滤已开灶</string>\n    <string name=\"path_hide_filter_system_disabled\">系统 UID 过滤已熄火</string>\n    <string name=\"path_hide_filter_system_warning_title\">厨房警告</string>\n    <string name=\"path_hide_filter_system_warning_message\">开灶后将同时对 root 和系统进程（UID &lt; 10000）隐藏食材路径。这可能导致部分厨房功能异常，请谨慎操作。</string>\n    <string name=\"path_hide_uid_applied\">UID 白名单已上菜</string>\n    <string name=\"path_hide_select_apps\">点选应用</string>\n    <string name=\"path_hide_no_apps_selected\">莫得应用被选中</string>\n    <string name=\"path_hide_app_removed\">已铲掉 %d 个卸载了的应用的锅气</string>\n    <string name=\"path_hide_search_apps\">找应用…</string>\n    <string name=\"path_hide_show_system\">亮出系统应用</string>\n    <string name=\"netisolate_title\">断网隔离</string>\n    <string name=\"netisolate_enable\">断网隔离已开火</string>\n    <string name=\"netisolate_disable\">断网隔离已熄火</string>\n    <string name=\"netisolate_enable_summary\">在内核层面断掉所选应用的网络连接</string>\n    <string name=\"netisolate_uids_label\">已断网应用</string>\n    <string name=\"netisolate_uids_hint\">输入要断网的 UID（每行一个）</string>\n    <string name=\"netisolate_no_uids\">莫得断网应用</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-zh-rCN/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\" translatable=\"false\">FolkPatch</string>\n    <string name=\"app_name_fpatch\">FPatch</string>\n\n    <string name=\"desktop_app_name\">桌面应用名称</string>\n\n    <string name=\"home\">首页</string>\n\n    <string name=\"success\">成功</string>\n    <string name=\"failure\">失败</string>\n\n    <string name=\"patch_warnning\">修补有风险，请确保已备份数据。</string>\n    <string name=\"patch\">修补</string>\n\n    <string name=\"kernel_patch\">内核补丁</string>\n    <string name=\"android_patch\">系统补丁</string>\n\n    <string name=\"settings_nav_layout_title\">导航布局设置</string>\n    <string name=\"settings_nav_layout_summary\">隐藏或显示导航的部分组件</string>\n    <string name=\"settings_nav_scheme\">导航方案设置</string>\n    <string name=\"settings_nav_mode\">导航栏模式</string>\n    <string name=\"settings_nav_mode_summary\">选择导航栏的显示方式</string>\n    <string name=\"settings_nav_mode_auto\">自动传统导航栏</string>\n    <string name=\"settings_nav_mode_bottom\">始终底部导航栏</string>\n    <string name=\"settings_nav_mode_rail\">始终侧边导航栏</string>\n    <string name=\"settings_nav_mode_floating\">悬浮底部导航栏</string>\n    <string name=\"settings_show_apm\">显示系统模块</string>\n    <string name=\"settings_show_kpm\">显示内核模块</string>\n    <string name=\"settings_show_superuser\">显示超级用户</string>\n    <string name=\"settings_floating_auto_hide\">自动隐藏悬浮底栏</string>\n    <string name=\"settings_floating_auto_hide_summary\">无操作3秒后自动隐藏悬浮底栏</string>\n    <string name=\"settings_floating_swipe_hide\">滑动隐藏悬浮底栏</string>\n    <string name=\"settings_floating_swipe_hide_summary\">向下滑动时隐藏底栏，向上滑动时显示底栏</string>\n    <string name=\"settings_navbar_glass_effect\">毛玻璃效果</string>\n    <string name=\"settings_navbar_glass_effect_summary\">为悬浮导航栏应用毛玻璃视觉效果</string>\n    <string name=\"settings_navbar_glass_blur_strength\">模糊强度</string>\n    <string name=\"settings_navbar_glass_transparency\">背景透明度</string>\n    <string name=\"settings_navbar_glass_highlight_strength\">高光强度</string>\n    <string name=\"settings_navbar_glass_specular\">镜面反射</string>\n    <string name=\"settings_navbar_glass_specular_summary\">在导航栏顶部启用镜面高光效果</string>\n    <string name=\"settings_navbar_glass_inner_glow\">内发光</string>\n    <string name=\"settings_navbar_glass_inner_glow_summary\">在导航栏底部启用微光效果</string>\n    <string name=\"settings_navbar_glass_border\">玻璃边框</string>\n    <string name=\"settings_navbar_glass_border_summary\">在导航栏周围启用微妙的边框描边</string>\n    <string name=\"settings_stats_top_layout\">顶部布局</string>\n    <string name=\"settings_stats_top_layout_summary\">选择顶部信息卡片样式</string>\n    <string name=\"settings_stats_top_layout_list\">列表样式</string>\n    <string name=\"settings_stats_top_layout_grid\">网格样式</string>\n    <string name=\"settings_block_kernelpatch_update\">屏蔽内核补丁更新提示</string>\n    <string name=\"settings_block_kernelpatch_update_summary\">不显示内核补丁的更新通知</string>\n    <string name=\"settings_block_androidpatch_update\">屏蔽系统补丁更新提示</string>\n    <string name=\"settings_block_androidpatch_update_summary\">不显示系统补丁(APD)的更新通知</string>\n\n    <string name=\"reboot\">重启</string>\n    <string name=\"settings\">设置</string>\n    <string name=\"reboot_recovery\">重启到 Recovery</string>\n    <string name=\"reboot_bootloader\">重启到 Bootloader</string>\n    <string name=\"reboot_download\">重启到 Download</string>\n    <string name=\"reboot_edl\">重启到 EDL</string>\n    <string name=\"reboot_fastbootd\">重启到 FastbootD</string>\n    <string name=\"about\">关于</string>\n    <string name=\"developer_and_maintainer\">开发者 | 维护者</string>\n    <string name=\"settings_app_language\">语言</string>\n    <string name=\"system_default\">系统默认</string>\n    <string name=\"settings_global_namespace_mode\">全局命名空间模式</string>\n    <string name=\"settings_global_namespace_mode_summary\">所有 root 会话使用全局挂载命名空间</string>\n    <string name=\"settings_clear_super_key_dialog\">确定要继续吗？</string>\n\n    <string name=\"home_learn_apatch\">了解 FolkPatch</string>\n    <string name=\"home_click_to_learn_apatch\">了解 FolkPatch 的功能及使用方法</string>\n    <string name=\"settings_hide_apatch_card\">隐藏\"了解 FolkPatch\"卡片</string>\n    <string name=\"settings_hide_apatch_card_summary\">隐藏首页的\"了解 FolkPatch\"卡片</string>\n    <string name=\"settings_grid_working_card_hide_check\">隐藏状态图标</string>\n    <string name=\"settings_grid_working_card_hide_check_summary\">隐藏工作卡片上的对号或警告图标</string>\n    <string name=\"settings_grid_working_card_hide_text\">隐藏状态文字</string>\n    <string name=\"settings_grid_working_card_hide_text_summary\">隐藏工作卡片上的\"工作中\"或\"未安装\"文字</string>\n    <string name=\"settings_grid_working_card_hide_mode\">隐藏工作模式</string>\n    <string name=\"settings_grid_working_card_hide_mode_summary\">隐藏工作卡片上的\"Full\"或\"Half\"文字</string>\n\n    <string name=\"settings_folkx_engine_title\">FolkX动画引擎</string>\n    <string name=\"settings_folkx_engine_summary\">在顶层页面切换时使用丝滑的物理弹簧动画</string>\n    <string name=\"settings_folkx_animation_type\">动画类型</string>\n    <string name=\"settings_folkx_animation_speed\">动画速度</string>\n    <string name=\"settings_folkx_animation_linear\">线性运动</string>\n    <string name=\"settings_folkx_animation_spatial\">空间运动</string>\n    <string name=\"settings_folkx_animation_fade\">渐隐渐显</string>\n    <string name=\"settings_folkx_animation_vertical\">垂直滑动</string>\n    <string name=\"settings_folkx_animation_diagonal\">对角滑动</string>\n\n    <string name=\"about_source_code\"><![CDATA[<p>在 %1$s 查看源代码<p/>加入我们的 %2$s 频道<p/>加入我们的 %3$s 群组]]></string>\n    <string name=\"send_log\">发送日志</string>\n    <string name=\"save_log\">保存日志</string>\n    <string name=\"log_saved\">日志已保存</string>\n    <string name=\"safe_mode\">安全模式</string>\n    <string name=\"github\">GitHub</string>\n    <string name=\"telegram\">Telegram</string>\n    <string name=\"support_or_donate\">支持 / 捐赠</string>\n\n    <string name=\"super_key\">超级密钥</string>\n    <string name=\"clear_super_key\">清除超级密钥</string>\n    <string name=\"patch_set_superkey\">设置超级密钥</string>\n    <string name=\"home_patch_set_key_desc\">KernelPatch 的唯一凭证</string>\n    <string name=\"home_patch_next_step\">下一步</string>\n\n    <string name=\"home_not_installed\">未安装</string>\n    <string name=\"home_install_unknown\">内核补丁未安装</string>\n    <string name=\"home_install_unknown_summary\">当前无法获取 Root，可能尚未修补或未刷入内核。</string>\n    <string name=\"home_click_to_install\">点击安装</string>\n    <string name=\"home_working\">工作中</string>\n    <string name=\"home_version\">版本: %s</string>\n    <string name=\"home_kp_need_update\">有新版本可用</string>\n    <string name=\"home_kp_cando_update\">更新</string>\n\n    <string name=\"home_installing\">正在安装</string>\n\n    <string name=\"kpatch_version\">版本: %s</string>\n    <string name=\"apatch_version\">版本: %s</string>\n\n    <string name=\"kpatch_version_update\" formatted=\"false\">版本: %s -&gt; %s</string>\n    <string name=\"kpatch_shadow_path_title\">kpatch</string>\n    <string name=\"home_auth_key_title\">输入超级密钥</string>\n    <string name=\"home_auth_key_desc\">认证后开始使用</string>\n    <string name=\"home_kpatch_info_title\">信息</string>\n\n    <string name=\"home_ap_cando_install\">安装</string>\n\n    <string name=\"home_ap_cando_uninstall\">卸载</string>\n    <string name=\"home_ap_cando_reboot\">重启</string>\n\n    <string name=\"patch_title\">修补</string>\n\n    <string name=\"patch_config_title\">补丁</string>\n    <string name=\"patch_mode_bootimg_patch\">模式：修补</string>\n    <string name=\"patch_mode_patch_and_install\">模式：修补并安装</string>\n    <string name=\"patch_mode_install_to_next_slot\">模式：安装到未激活槽位 (OTA 后)</string>\n    <string name=\"patch_mode_restore\">模式：恢复</string>\n    <string name=\"patch_mode_uninstall_patch\">模式：卸载 KPatch</string>\n    <string name=\"patch_select_bootimg_btn\">选择镜像</string>\n    <string name=\"patch_embed_kpm_btn\">嵌入 KPM</string>\n    <string name=\"patch_start_patch_btn\">开始</string>\n    <string name=\"patch_start_unpatch_btn\">还原</string>\n    <string name=\"patch_item_error\">!!错误!!</string>\n    <string name=\"patch_item_bootimg\">bootimg</string>\n    <string name=\"patch_item_bootimg_slot\">槽位:</string>\n    <string name=\"patch_item_bootimg_dev\">设备:</string>\n    <string name=\"patch_item_kernel\">内核</string>\n    <string name=\"patch_item_kpimg\">kpimg</string>\n    <string name=\"patch_item_kpimg_version\">版本:</string>\n    <string name=\"patch_item_kpimg_comile_time\">时间:</string>\n    <string name=\"patch_item_kpimg_config\">配置:</string>\n    <string name=\"patch_item_new_extra_kpm\">嵌入新模块</string>\n    <string name=\"patch_item_existed_extra_kpm\">已存在</string>\n    <string name=\"patch_item_extra_name\">名称:</string>\n    <string name=\"patch_item_extra_version\">版本:</string>\n    <string name=\"patch_item_extra_author\">作者:</string>\n    <string name=\"patch_item_extra_kpm_license\">许可:</string>\n    <string name=\"patch_item_extra_kpm_desciption\">描述:</string>\n    <string name=\"patch_item_extra_args\">参数:</string>\n    <string name=\"patch_item_extra_event\">事件:</string>\n    <string name=\"patch_item_skey\">超级密钥</string>\n    <string name=\"patch_custom_superkey\">自定义超级密钥</string>\n    <string name=\"patch_custom_superkey_summary\">你仍可以设置超级密钥来管理 Root 以及内核，管理器已由内置签名授权</string>\n    <string name=\"patch_item_set_skey_label\">超级密钥长度应为 8-63 个字符，包含数字和字母，不能包含特殊字符。</string>\n    <string name=\"patch_confirm_superkey\">确认超级密钥</string>\n    <string name=\"patch_skey_mismatch\">两次输入的超级密钥不一致</string>\n\n    <string name=\"home_kernel\">内核版本</string>\n    <string name=\"home_manager_version\">管理器版本</string>\n    <string name=\"home_fingerprint\">指纹</string>\n\n    <string name=\"home_selinux_status\">SELinux 状态</string>\n    <string name=\"home_selinux_status_disabled\">已禁用</string>\n    <string name=\"home_selinux_status_enforcing\">强制执行</string>\n    <string name=\"home_selinux_status_permissive\">宽容模式</string>\n    <string name=\"home_selinux_status_unknown\">未知</string>\n\n    <string name=\"settings_selinux_mode\">SELinux 模式</string>\n    <string name=\"settings_selinux_mode_summary\">选择 SELinux 强制执行模式</string>\n    <string name=\"settings_selinux_mode_enforcing\">强制执行 (严格)</string>\n    <string name=\"settings_selinux_mode_permissive\">宽容模式 (宽松)</string>\n    <string name=\"settings_selinux_current_mode\">当前: %s</string>\n    <string name=\"settings_selinux_mode_enforcing_summary\">SELinux 完全强制执行访问规则</string>\n    <string name=\"settings_selinux_mode_permissive_summary\">SELinux 仅记录违规操作,不会拒绝</string>\n\n    <string name=\"home_device_info\">设备</string>\n    <string name=\"home_system_version\">系统版本</string>\n    <string name=\"home_kpatch_version\">内核补丁版本</string>\n    <string name=\"home_su_path\">su 可执行文件</string>\n    <string name=\"home_apatch_version\">FolkPatch 版本</string>\n\n    <string name=\"kpm\">内核模块</string>\n    <string name=\"kpm_kp_not_installed\">未安装内核补丁</string>\n    <string name=\"kpm_add_kpm\">添加 KPM</string>\n    <string name=\"kpm_load\">加载</string>\n    <string name=\"kpm_install\">安装</string>\n    <string name=\"kpm_embed\">嵌入</string>\n    <string name=\"kpm_load_toast_succ\">加载成功</string>\n    <string name=\"kpm_load_toast_failed\">加载失败</string>\n    <string name=\"kpm_unload_confirm\">卸载 %s 模块？</string>\n    <string name=\"kpm_unload\">卸载</string>\n    <string name=\"kpm_control\">控制</string>\n    <string name=\"kpm_apm_empty\">未加载模块</string>\n    <string name=\"kpm_version\">版本</string>\n    <string name=\"kpm_license\">许可</string>\n    <string name=\"kpm_author\">作者</string>\n    <string name=\"kpm_desc\">描述</string>\n    <string name=\"kpm_args\">参数</string>\n\n    <string name=\"su_title\">超级用户</string>\n    <string name=\"su_selinux_via_hook\">通过 hook 绕过</string>\n    <string name=\"su_pkg_excluded_label\">排除</string>\n    <string name=\"su_batch_exclude_title\">批量排除</string>\n    <string name=\"su_batch_exclude_content\">为全部非ROOT权限软件排除注入,请选择一个操作</string>\n    <string name=\"su_exclude_btn\">排除</string>\n    <string name=\"su_exclude_reverse_btn\">反排除</string>\n    <string name=\"su_pkg_excluded_setting_title\">排除修改</string>\n    <string name=\"su_pkg_excluded_setting_summary\">启用此选项将允许 FolkPatch 恢复此应用模块修改的任何文件。</string>\n    <string name=\"su_pkg_root_setting_title\">超级用户</string>\n    <string name=\"su_pkg_root_setting_summary\">启用该选项为你的软件启动超级用户,软件能够使用SU命令</string>\n    <string name=\"su_pkg_normal_setting_title\">常规模式</string>\n    <string name=\"su_pkg_normal_setting_summary\">不授予 root 权限，应用在没有超级用户权限下运行。</string>\n    <string name=\"su_show_system_apps\">显示系统应用</string>\n    <string name=\"su_hide_system_apps\">隐藏系统应用</string>\n    <string name=\"su_refresh\">刷新</string>\n\n    <string name=\"apm\">系统模块</string>\n    <string name=\"apm_not_installed\">未安装系统补丁</string>\n    <string name=\"apm_failed_to_enable\">启用模块失败: %s</string>\n    <string name=\"apm_failed_to_disable\">禁用模块失败: %s</string>\n    <string name=\"apm_empty\">未安装模块</string>\n    <string name=\"apm_remove\">移除</string>\n    <string name=\"apm_undo\">撤销</string>\n    <string name=\"apm_install\">安装</string>\n    <string name=\"apm_uninstall_confirm\">卸载 %s 模块？</string>\n    <string name=\"apm_uninstall_success\">%s 已卸载</string>\n    <string name=\"apm_uninstall_failed\">卸载失败: %s</string>\n    <string name=\"apm_undo_uninstall_success\">%s 已恢复</string>\n    <string name=\"apm_undo_uninstall_failed\">恢复失败: %s</string>\n    <string name=\"apm_version\">版本</string>\n    <string name=\"apm_author\">作者</string>\n    <string name=\"apm_desc\">描述</string>\n    <string name=\"apm_overlay_fs_not_available\">OverlayFS 被内核禁用，模块不可用！</string>\n    <string name=\"apm_magisk_conflict\">由于与 Magisk 冲突，模块不可用！</string>\n    <string name=\"apm_mount_warning_title\">重要提示</string>\n    <string name=\"apm_mount_warning_message\">默认不挂载模块，请使用内置挂载系统或者元模块</string>\n    <string name=\"apm_mount_warning_button\">我知道了</string>\n    <string name=\"apm_reboot_to_apply\">重启生效</string>\n    <string name=\"apm_changelog\">更新日志</string>\n    <string name=\"apm_update\">更新</string>\n    <string name=\"apm_downloading\">正在下载模块: %s</string>\n    <string name=\"apm_start_downloading\">开始下载: %s</string>\n    <string name=\"apm_new_version_available\">新版本 %s 可用，点击升级。</string>\n\n    <string name=\"hide_apatch_manager\">隐藏 APatch 管理器</string>\n    <string name=\"hide_apatch_manager_summary\">安装一个随机包名和自定义应用名称的代理应用</string>\n    <string name=\"hide_apatch_dialog_new_manager_name\">新管理器名称</string>\n    <string name=\"hide_apatch_dialog_summary\">这将作为显示在启动器中的新应用名称</string>\n    <string name=\"hide_apatch_manager_failure\">隐藏失败。请报告此错误！</string>\n\n    <string name=\"setting_reset_su_path\">重置 su 路径</string>\n    <string name=\"setting_reset_su_new_path\">新完整路径</string>\n\n    <string name=\"apm_webui_open\">打开</string>\n    <string name=\"apm_action\">操作</string>\n    <string name=\"module_shortcut_add\">快捷</string>\n    <string name=\"module_shortcut_not_supported\">启动器不支持桌面快捷方式。</string>\n    <string name=\"module_shortcut_created\">已在桌面创建快捷方式。</string>\n    <string name=\"module_shortcut_updated\">快捷方式已更新。</string>\n    <string name=\"module_shortcut_permission_tip_xiaomi\">请在 Xiaomi 设置中为本应用启用“创建桌面快捷方式”权限。</string>\n    <string name=\"module_shortcut_permission_tip_oppo\">请在 OPPO 设置中为本应用启用“桌面快捷方式”权限。</string>\n    <string name=\"module_shortcut_permission_tip_default\">若创建快捷方式失败，请在系统设置中为本应用启用桌面快捷方式权限。</string>\n    <string name=\"enable_web_debugging\">启用 WebView 调试</string>\n    <string name=\"enable_web_debugging_summary\">可用于调试 WebUI。请仅在需要时启用。</string>\n    <string name=\"settings_apm_install_confirm\">安装模块确认</string>\n    <string name=\"settings_apm_install_confirm_summary\">安装模块前显示确认对话框</string>\n    <string name=\"settings_show_more_module_info\">显示模块详细信息</string>\n    <string name=\"settings_show_more_module_info_summary\">在模块列表中显示模块 ID 和大小</string>\n    <string name=\"settings_apm_stay_on_page\">留在操作页面</string>\n    <string name=\"settings_apm_stay_on_page_summary\">系统模块执行完操作后不自动返回</string>\n    <string name=\"settings_enable_module_shortcut_add\">启用快捷添加按钮</string>\n    <string name=\"settings_enable_module_shortcut_add_summary\">显示 WebUI 快捷方式添加按钮</string>\n    <string name=\"settings_disable_module_update_check\">禁用模块更新检查</string>\n    <string name=\"settings_disable_module_update_check_summary\">禁用系统模块的自动更新检查</string>\n    <string name=\"settings_module_sort_optimization\">模块排序优化</string>\n    <string name=\"module_shortcut_name\">快捷方式名称</string>\n    <string name=\"module_shortcut_icon\">快捷方式图标</string>\n    <string name=\"module_shortcut_type\">快捷方式类型</string>\n    <string name=\"module_shortcut_type_webui\">WebUI</string>\n    <string name=\"module_shortcut_type_action\">Action</string>\n    <string name=\"module_shortcut_icon_default\">使用默认图标</string>\n    <string name=\"module_shortcut_icon_select\">选择图标</string>\n    <string name=\"settings_module_sort_optimization_summary\">让系统模块带有WebUI和Action的模块排在前面</string>\n    <string name=\"settings_fold_system_module\">折叠系统模块</string>\n    <string name=\"settings_fold_system_module_summary\">点击模块卡片展开/折叠操作按钮</string>\n    <string name=\"settings_simple_list_bottom_bar\">简约列表底栏</string>\n    <string name=\"settings_simple_list_bottom_bar_summary\">模块操作使用纯图标按钮样式，灵感来自APatch</string>\n    <string name=\"settings_spliced_card_group\">拼接卡片组</string>\n    <string name=\"settings_spliced_card_group_summary\">模块列表使用 M3E 连续拼接卡片样式</string>\n    <string name=\"apm_install_confirm_title\">安装模块</string>\n    <string name=\"apm_install_confirm_content\">确定要安装 %s 吗？</string>\n    <string name=\"apm_disable_all_title\">禁用所有模块</string>\n    <string name=\"apm_disable_all_confirm\">确定禁用全部模块吗？此操作将停用所有已安装的模块</string>\n    <string name=\"apm_enable_module_banner\">启用模块横幅</string>\n    <string name=\"apm_enable_module_banner_summary\">为支持的模块显示横幅图片</string>\n    <string name=\"apm_enable_folk_banner\">自定义模块横幅</string>\n    <string name=\"apm_enable_folk_banner_summary\">长按模块卡片自选横幅图片</string>\n    <string name=\"apm_folk_banner_title\">FolkBanner</string>\n    <string name=\"apm_folk_banner_select\">选择图片</string>\n    <string name=\"apm_folk_banner_clear\">清除 FolkBanner</string>\n    <string name=\"apm_folk_banner_saved\">已为 %s 注入 FolkBanner。</string>\n    <string name=\"apm_folk_banner_cleared\">已为 %s 清除 FolkBanner。</string>\n    <string name=\"apm_folk_banner_failed\">为 %s 更新 FolkBanner 失败。</string>\n    <string name=\"apm_banner_api_mode\">API模式</string>\n    <string name=\"apm_banner_api_mode_summary\">使用随机图API或本地目录为模块提供横幅</string>\n    <string name=\"apm_banner_api_source\">配置API源</string>\n    <string name=\"apm_banner_api_source_hint\">输入API地址或本地路径</string>\n    <string name=\"apm_banner_api_source_saved\">API源已保存</string>\n    <string name=\"apm_banner_api_source_not_configured\">未配置</string>\n    <string name=\"apm_banner_api_url_configured\">API地址已配置</string>\n    <string name=\"apm_banner_local_dir_configured\">本地目录已配置</string>\n    <string name=\"apm_banner_clear_cache\">清除缓存</string>\n    <string name=\"apm_banner_cache_cleared\">横幅缓存已清除</string>\n    <string name=\"apm_banner_api_config_title\">配置API源</string>\n    <string name=\"apm_banner_api_config_desc\">输入随机图API地址或本地目录路径，每个模块将获得唯一的横幅图片。</string>\n    <string name=\"apm_banner_api_examples_title\">示例：</string>\n    <string name=\"apm_banner_api_examples\">API: https://picsum.photos/800/400\\n本地: /sdcard/Pictures/Banners</string>\n    <!-- API 接口市场 -->\n    <string name=\"apm_api_marketplace_title\">API 接口市场</string>\n    <string name=\"apm_api_preview\">预览</string>\n    <string name=\"apm_api_apply\">应用</string>\n    <string name=\"apm_api_preview_title\">API 预览</string>\n    <string name=\"apm_api_preview_failed\">预览加载失败</string>\n    <string name=\"apm_api_url_label\">API 地址：</string>\n    <string name=\"apm_api_apply_success\">API 源已成功应用</string>\n    <string name=\"apm_api_verifying\">验证中…</string>\n    <string name=\"apm_api_retry\">重试</string>\n    <string name=\"apm_api_empty\">暂无 API 源</string>\n    <string name=\"settings_banner_custom_opacity\">横幅透明度</string>\n    <string name=\"settings_banner_custom_opacity_summary\">自定义横幅透明度，不再跟随壁纸模式</string>\n    <string name=\"settings_banner_opacity\">横幅不透明度</string>\n\n    <string name=\"settings_donot_store_superkey\">不在本地存储超级密钥</string>\n    <string name=\"settings_donot_store_superkey_summary\">每次启动管理器时验证超级密钥</string>\n\n    <string name=\"mode_select_page_title\">安装</string>\n    <string name=\"mode_select_page_patch_and_install\">修补并安装</string>\n    <string name=\"mode_select_page_select_file\">选择要修补的镜像</string>\n    <string name=\"restore_select_file\">选择一个要还原的分区引导文件</string>\n    <string name=\"mode_select_page_install_inactive_slot\">安装到未激活槽位 (OTA 后)</string>\n    <string name=\"mode_select_page_install_inactive_slot_warning\">您的设备将在重启后**强制**启动到当前未激活的槽位！\\n仅在 OTA 完成后使用此选项。\\n继续吗？</string>\n    <string name=\"mode_select_page_select_kpimg\">使用本地补丁文件(KPimg)</string>\n    <string name=\"patch_custom_kpimg_label\">自定义 KPimg</string>\n    <string name=\"patch_custom_kpimg_file\">文件: %s</string>\n    <string name=\"patch_select_kpimg_btn\">选择 KPimg</string>\n\n    <string name=\"home_dialog_auth_fail_title\">认证失败</string>\n    <string name=\"home_dialog_auth_fail_content\">无法认证超级密钥，导致 FolkPatch 激活失败。  \n以下是认证失败的一些可能原因——请对号入座：\n\\n1. 您根本没使用 KernelPatch 来修补 boot.img，或者忘了自己到底做了啥。\n\\n2. 修补完的 boot.img 还躺在电脑里睡觉，根本没刷进设备。\n\\n3. 超级密钥输错了,或者夹杂了神秘符号，比如来自外星文的字符。\n\\n4. 您的设备可能并不兼容 FolkPatch 和 KernelPatch，强行折腾也是徒劳。\n\\n5. 您可能搞了一些神秘操作——比如用了某些排除包名的模块把 FolkPatch 给屏蔽了，结果把自己整出局了。\n\\n请先好好检查一遍，再试一次。如果问题依旧，欢迎到官方仓库的 issue 页面提问。  \n毕竟，有些问题可能纯粹是您自己亲手造成的!\n祝你好运 啊!顺带一提,您点击下面的文档按钮准没错!\n</string>\n\n    <string name=\"home_more_menu_feedback_or_suggestion\">反馈或建议</string>\n    <string name=\"home_more_menu_about\">关于</string>\n    <string name=\"home_more_menu_document\">文档</string>\n\n    <string name=\"home_dialog_uninstall_title\">卸载</string>\n    \n    <string name=\"home_dialog_uninstall_ap_only\">仅卸载补丁</string>\n    <string name=\"home_dialog_uninstall_all\">完全卸载</string>\n    <string name=\"home_dialog_uninstall_ap_only_desc\">仅移除系统补丁，保留管理器。</string>\n    <string name=\"home_dialog_uninstall_all_desc\">移除系统补丁并进入完整卸载流程。</string>\n\n    <string name=\"kpm_control_dialog_title\">控制 KPM</string>\n    <string name=\"kpm_control_dialog_content\">请输入控制参数：</string>\n    <string name=\"kpm_control_paramters\">参数</string>\n    <string name=\"kpm_control_outMsg\">输出</string>\n    <string name=\"kpm_control_ok\">成功！</string>\n    <string name=\"kpm_control_failed\">失败！</string>\n    \n    <string name=\"settings_night_mode_follow_sys\">深色模式跟随系统</string>\n    <string name=\"settings_night_mode_follow_sys_summary\">根据系统设置自动切换深色模式</string>\n    <string name=\"settings_night_theme_enabled\">深色模式</string>\n    \n    <string name=\"settings_use_system_color_theme\">系统颜色主题</string>\n    <string name=\"settings_use_system_color_theme_summary\">使用系统根据壁纸生成的颜色主题</string>\n    <string name=\"settings_custom_color_theme\">颜色主题</string>\n\n    <string name=\"amber_theme\">琥珀色</string>\n    <string name=\"blue_theme\">蓝色</string>\n    <string name=\"blue_grey_theme\">蓝灰色</string>\n    <string name=\"brown_theme\">大地</string>\n    <string name=\"cyan_theme\">碧湖</string>\n    <string name=\"deep_orange_theme\">夕阳</string>\n    <string name=\"deep_purple_theme\">星夜</string>\n    <string name=\"green_theme\">森林</string>\n    <string name=\"indigo_theme\">深海</string>\n    <string name=\"light_blue_theme\">海洋</string>\n    <string name=\"light_green_theme\">春草</string>\n    <string name=\"lime_theme\">新叶</string>\n    <string name=\"orange_theme\">晨曦</string>\n    <string name=\"pink_theme\">桃夭</string>\n    <string name=\"purple_theme\">薰衣</string>\n    <string name=\"red_theme\">烈焰</string>\n    <string name=\"sakura_theme\">樱花</string>\n    <string name=\"teal_theme\">翡翠</string>\n    <string name=\"yellow_theme\">金穗</string>\n    <string name=\"ink_wash_theme\">水墨风</string>\n\n    <string name=\"theme_color\">主题颜色</string>\n    <string name=\"theme_light\">浅色</string>\n    <string name=\"theme_dark\">深色</string>\n    <string name=\"theme_system\">系统</string>\n\n    <string name=\"about_app_desc\">基于 KernelPatch 的 Root 实现，无需重新编译内核的同时，允许 Hook 内核函数。</string>\n    <string name=\"about_powered_by\">由 %1$s 提供支持</string>\n    <string name=\"about_telegram_group\">Telegram 群组</string>\n    \n    <!-- Loading Strings -->\n    <string name=\"loading_modules\">正在获取模块…</string>\n    <string name=\"loading_scripts\">正在查找脚本…</string>\n    <string name=\"loading_themes\">正在加载主题…</string>\n    <string name=\"loading_apis\">正在加载 API 源…</string>\n    <string name=\"retry\">重试</string>\n\n    <!-- Online Module Strings -->\n    <string name=\"online_module_title\">在线系统模块</string>\n    <string name=\"online_module_download_start\">开始下载: %s</string>\n    <string name=\"online_module_download_complete\">下载完成: %s</string>\n    <string name=\"online_module_download_notification\">正在下载 %s。请查看通知栏了解进度，下载完成后请查看下载文件夹。</string>\n\n    <!-- Online KPM Strings -->\n    <string name=\"online_kpm_title\">在线内核模块</string>\n    <string name=\"online_kpm_download_start\">开始下载: %s</string>\n    <string name=\"online_kpm_download_complete\">下载完成: %s</string>\n    <string name=\"online_kpm_download_notification\">正在下载 %s。请查看通知栏了解进度，下载完成后请查看下载文件夹。</string>\n\n    <!-- Online Script Strings -->\n    <string name=\"online_script_title\">在线脚本</string>\n    <string name=\"online_script_download_start\">开始下载: %s</string>\n    <string name=\"online_script_download_complete\">下载完成: %s</string>\n    <string name=\"online_script_download_notification\">正在下载 %s</string>\n\n    <!-- Crash Handle Strings -->\n    <string name=\"crash_handle_title\">崩溃报告</string>\n    <string name=\"crash_handle_copy\">复制</string>\n\n    <!-- About Screen Strings -->\n    <string name=\"about_app_version\">应用版本: %s</string>\n    <string name=\"about_github\">GitHub</string>\n    <string name=\"about_telegram_channel\">Telegram 频道</string>\n\n    <string name=\"settings_app_dpi\">应用 DPI</string>\n    <string name=\"dpi_apply_settings\">应用</string>\n    <string name=\"dpi_confirm_title\">确认更改 DPI</string>\n    <string name=\"dpi_confirm_message\">将应用 DPI 从 %1$s 更改为 %2$s？</string>\n    <string name=\"settings_magic_mount\">Folk Mount API</string>\n    <string name=\"settings_magic_mount_summary\">启用内置的模块挂载系统</string>\n    <string name=\"settings_new_app_profile_mode\">新软件默认模式</string>\n    <string name=\"settings_new_app_profile_normal\">常规</string>\n    <string name=\"settings_new_app_profile_root\">超级用户</string>\n    <string name=\"settings_new_app_profile_exclude\">排除</string>\n    <string name=\"settings_hide_service\">FolkPatch Hide</string>\n    <string name=\"settings_hide_service_summary\">一定程度上隐藏Bootloader锁解锁状态</string>\n    <string name=\"settings_app_title\">应用名称</string>\n    <string name=\"app_title_custom\">自定义</string>\n    <string name=\"settings_custom_app_title\">设置应用名字</string>\n    <string name=\"custom_app_title_dialog_title\">自定义应用名称</string>\n    <string name=\"custom_app_title_dialog_hint\">请输入应用名称</string>\n    <string name=\"custom_app_title_dialog_confirm\">确定</string>\n    <string name=\"custom_app_title_dialog_empty\">名称不能为空</string>\n    <string name=\"cancel\">取消</string>\n    <string name=\"settings_custom_background\">自定义背景</string>\n    <string name=\"settings_custom_background_enabled\">已启用自定义背景</string>\n    <string name=\"settings_custom_background_opacity\">卡片不透明度</string>\n    <string name=\"settings_custom_background_blur\">背景模糊</string>\n    <string name=\"settings_custom_background_dim\">背景暗度</string>\n    <string name=\"settings_custom_background_dual_dim\">启用双切暗色适配</string>\n    <string name=\"settings_custom_background_dual_dim_desc\">根据亮色和暗色主题自动调整背景暗度</string>\n    <string name=\"settings_custom_background_day_dim\">白天模式暗度</string>\n    <string name=\"settings_custom_background_night_dim\">夜间模式暗度</string>\n    <string name=\"settings_multi_background_mode\">启用多背景模式</string>\n    <string name=\"settings_multi_background_mode_summary\">为不同页面设置不同的背景图片</string>\n    <string name=\"settings_select_home_background\">首页背景</string>\n    <string name=\"settings_select_kernel_background\">内核模块背景</string>\n    <string name=\"settings_select_superuser_background\">超级用户背景</string>\n    <string name=\"settings_select_system_module_background\">系统模块背景</string>\n    <string name=\"settings_select_settings_background\">设置页面背景</string>\n\n    <string name=\"settings_advanced_title_style\">高级标题样式</string>\n    <string name=\"settings_advanced_title_style_summary\">使用自定义图片替换顶部标题</string>\n    <string name=\"settings_advanced_title_style_enabled\">高级标题样式已启用</string>\n    <string name=\"settings_select_title_image\">选择标题图片</string>\n    <string name=\"settings_title_image_selected\">已选择标题图片</string>\n    <string name=\"settings_title_image_saved\">标题图片保存成功</string>\n    <string name=\"settings_title_image_error\">保存标题图片失败</string>\n    <string name=\"settings_clear_title_image\">清除标题图片</string>\n    <string name=\"settings_clear_title_image_confirm\">确定要清除标题图片吗？</string>\n    <string name=\"settings_title_image_cleared\">标题图片已清除</string>\n    <string name=\"settings_title_image_day_opacity\">白天模式透明度</string>\n    <string name=\"settings_title_image_night_opacity\">夜间模式透明度</string>\n    <string name=\"settings_title_image_dim\">背景暗度</string>\n    <string name=\"settings_title_image_offset_x\">水平位置</string>\n\n    <!-- Font Settings -->\n    <string name=\"settings_custom_font\">自定义字体</string>\n    <string name=\"settings_select_font_file\">选择字体文件</string>\n    <string name=\"settings_custom_font_enabled\">已启用自定义字体</string>\n    <string name=\"settings_custom_font_summary\">使用自定义 TTF 字体</string>\n    <string name=\"settings_font_selected\">已选择自定义字体</string>\n    <string name=\"settings_custom_font_error\">保存字体文件失败</string>\n    <string name=\"settings_custom_font_saved\">自定义字体保存成功</string>\n    <string name=\"settings_clear_font\">恢复默认字体</string>\n    <string name=\"settings_clear_font_confirm\">确定要恢复默认字体吗？</string>\n    <string name=\"settings_font_cleared\">已恢复默认字体</string>\n    <string name=\"settings_font_select_hint\">请选择一个 TTF 字体文件</string>\n    <string name=\"settings_select_background_image\">选择背景图片</string>\n    <string name=\"settings_custom_background_summary\">设置自定义背景图片</string>\n    <string name=\"settings_custom_background_saved\">背景保存成功</string>\n    <string name=\"settings_custom_background_error\">保存背景失败</string>\n    <string name=\"settings_background_selected\">已选择背景</string>\n    <string name=\"settings_background_image_cleared\">已移除背景</string>\n    <string name=\"settings_clear_background\">清除背景</string>\n    <string name=\"settings_clear_background_confirm\">确定要清除背景图片吗？</string>\n\n    <string name=\"settings_alt_icon\">备用图标</string>\n    <string name=\"alt_icon_summary\">使用备选启动器图标</string>\n\n    <!-- Video Background Settings -->\n    <string name=\"settings_video_background\">视频背景</string>\n    <string name=\"settings_video_background_summary\">使用视频作为背景</string>\n    <string name=\"settings_select_video\">选择视频</string>\n    <string name=\"settings_video_selected\">已选择视频</string>\n    <string name=\"settings_clear_video_background\">清除视频壁纸</string>\n    <string name=\"settings_clear_video_background_confirm\">确定要清除视频壁纸吗？</string>\n    <string name=\"settings_video_background_enabled\">视频背景已启用</string>\n    <string name=\"settings_video_volume\">视频音量</string>\n    \n    <string name=\"su_exclude_all_title\">批量排除</string>\n    <string name=\"su_exclude_all_confirm\">确定要将所有未授权Root的应用设为排除吗？</string>\n\n    <!-- 隐藏设定字符串 -->\n    <string name=\"home_hide_su_path\">隐藏 su 可执行文件路径</string>\n    <string name=\"home_hide_kpatch_version\">隐藏内核版本补丁</string>\n    <string name=\"home_hide_fingerprint\">隐藏指纹信息</string>\n    <string name=\"home_hide_zygisk\">隐藏 Zygisk 实现</string>\n    <string name=\"home_hide_mount\">隐藏挂载实现</string>\n    <string name=\"home_hide_su_path_summary\">在信息卡片中隐藏 su 可执行文件路径</string>\n    <string name=\"home_hide_kpatch_version_summary\">在信息卡片中隐藏内核版本补丁信息</string>\n    <string name=\"home_hide_fingerprint_summary\">在信息卡片中隐藏指纹信息</string>\n    <string name=\"home_hide_zygisk_summary\">在信息卡片中隐藏 Zygisk 实现信息</string>\n    <string name=\"home_hide_mount_summary\">在信息卡片中隐藏挂载实现信息</string>\n    <!-- KPM AutoLoad Strings -->\n    <string name=\"kpm_autoload_title\">KPM自动加载</string>\n    <string name=\"kpm_autoload_enabled\">启用自动加载</string>\n    <string name=\"kpm_autoload_enabled_summary\">开机时自动加载内核补丁模块</string>\n    <string name=\"kpm_autoload_json_config\">JSON配置</string>\n    <string name=\"kpm_autoload_json_label\">JSON配置</string>\n    <string name=\"kpm_autoload_json_placeholder\">{\n  \"enabled\": true,\n  \"kpmPaths\": [\n    \"/path/to/module1.kpm\",\n    \"/path/to/module2.kpm\"\n  ]\n}</string>\n    <string name=\"kpm_autoload_json_error\">无效的JSON格式</string>\n    <string name=\"kpm_autoload_json_helper\">输入有效的JSON，包含KPM文件路径数组</string>\n    <string name=\"kpm_autoload_save\">保存配置</string>\n    <string name=\"kpm_autoload_save_confirm\">保存自动加载配置？</string>\n    <string name=\"kpm_autoload_save_success\">配置保存成功</string>\n    <string name=\"kpm_autoload_save_failed\">配置保存失败</string>\n    <string name=\"kpm_autoload_visual_mode\">可视化模式</string>\n    <string name=\"kpm_autoload_json_mode\">JSON模式</string>\n\n    <!-- APM Bulk Install Strings -->\n    <string name=\"apm_bulk_install_title\">系统模块批量安装</string>\n    <string name=\"apm_bulk_install_list_title\">模块列表</string>\n    <string name=\"apm_bulk_install_add\">添加模块</string>\n    <string name=\"apm_bulk_install_empty\">未添加模块</string>\n    <string name=\"apm_bulk_install_action\">批量刷入</string>\n    <string name=\"apm_bulk_install_log_title\">批量刷入日志</string>\n    <string name=\"apm_bulk_install_log_start\">开始批量刷入...</string>\n    <string name=\"apm_bulk_install_log_installing\">正在安装 %1$s</string>\n    <string name=\"apm_batch_install_full_process\">完整批量安装流程</string>\n    <string name=\"apm_batch_install_full_process_summary\">使用完整流程进行系统模块批量安装</string>\n    <string name=\"next_module\">下一个模块</string>\n    <string name=\"apm_bulk_install_log_installed\">模块 %s 安装完成。</string>\n    <string name=\"apm_bulk_install_log_done\">所有操作已完成。</string>\n    <string name=\"apm_bulk_install_first_use_text\">这个功能允许你一次性刷入多个模块,属于快速刷入,适合刷一些过程中没有涉及按键操作的模块,涉及点按音量键等操作的请去设置启用完整流程刷入模式</string>\n    <string name=\"apm_bulk_install_remove\">移除</string>\n    <string name=\"apm_first_use_title\">欢迎使用系统模块</string>\n    <string name=\"apm_first_use_text\">欢迎使用系统模块，这里使用的是兼容Magisk生态的模块，点击右下角的按钮可以安装模块，你也可以使用顶部的安装器批量安装模块，还提供了一个功能一键备份全部模块，但是注意这种方案不一定适用全部模块，还是自己多加备份</string>\n    <string name=\"kpm_autoload_add_kpm\">添加KPM</string>\n    <string name=\"kpm_autoload_remove_kpm\">删除</string>\n    <string name=\"kpm_autoload_edit_kpm\">编辑</string>\n    <string name=\"kpm_autoload_edit_dialog_title\">编辑 KPM</string>    <string name=\"kpm_autoload_event_label\">事件:</string>    <string name=\"kpm_autoload_args_label\">参数:</string>    <string name=\"kpm_autoload_event_service\">service</string>    <string name=\"kpm_autoload_event_post_fs_data\">post-fs-data</string>\n    <string name=\"kpm_autoload_kpm_list_title\">内核补丁模块列表</string>\n    <string name=\"kpm_autoload_no_kpm_added\">未添加内核补丁模块</string>\n    <string name=\"kpm_autoload_kpm_path\">KPM路径</string>\n    <string name=\"kpm_autoload_file_not_found\">文件未找到</string>\n    <string name=\"kpm_autoload_select_kpm_file\">选择KPM文件</string>\n    <string name=\"kpm_autoload_first_time_title\">关于KPM自动加载</string>\n    <string name=\"kpm_autoload_first_time_message\">这个功能可以让你开机自动临时加载配置文件的全部KPM，这种方案相比于直接嵌入内核更加方便。\n\n例如，功能为防止分区被修改的KPM，这种KPM只能临时加载使用，但是嵌入会导致不开机。或者你不想破坏BOOT分区就可以使用这个配置快速载入模块。\n\n需要确保软件完全退出进入才会执行命令，一般来说开机进入也是会加载的，管理器黑屏一段时间属于正常现象,使用的是纯后台的记加载方案,记得手动上滑刷新看看是否正确加载！</string>\n    <string name=\"kpm_autoload_first_time_confirm\">知道了</string>\n    <string name=\"kpm_autoload_do_not_show_again\">不再显示</string>\n\n    <string name=\"kpm_page_first_time_title\">警告</string>\n    <string name=\"kpm_page_first_time_message\">内核模块是直接修改Boot的实现，它不像系统模块一样有很好的救砖机制，出问题只能进入Fastboot去修复。建议先加载模块没有问题再嵌入到Boot。如果是只能加载使用的模块，可以尝试使用自动内核补丁模块加载功能。如果你不了解内核模块，请不要使用本功能！</string>\n    <!-- Module Backup/Restore Strings -->\n    <string name=\"apm_backup_title\">备份模块</string>\n    <string name=\"apm_restore_title\">恢复模块</string>\n    <string name=\"apm_backup_success\">备份成功</string>\n    <string name=\"apm_restore_success\">恢复成功</string>\n    <string name=\"apm_backup_failed\">备份失败</string>\n    <string name=\"apm_restore_failed\">恢复失败</string>\n    <string name=\"apm_backup_failed_msg\">备份失败：%s</string>\n    <string name=\"apm_restore_failed_msg\">恢复失败：%s</string>\n    <string name=\"apm_copy_list_title\">复制列表</string>\n    <string name=\"apm_copy_list_success\">模块列表已复制</string>\n\n    <!-- Auto Backup Strings -->\n    <string name=\"settings_auto_backup_module\">自动备份系统模块</string>\n    <string name=\"settings_auto_backup_module_summary\">安装模块时自动备份文件到私有目录</string>\n    <string name=\"settings_open_backup_dir\">打开备份目录</string>\n    <string name=\"backup_dir_empty\">备份目录为空</string>\n    <string name=\"backup_dir_open_failed\">打开备份目录失败</string>\n    <string name=\"auto_backup_failed\">自动备份失败: %s</string>\n    <string name=\"auto_backup_success\">自动备份成功: %s</string>\n    <string name=\"settings_auto_backup_boot\">自动备份Boot</string>\n    <string name=\"settings_auto_backup_boot_summary\">自动将Boot备份到本地存储 (Downloads/FolkPatch/BootBackups)</string>\n\n    <!-- Home Layout Strings -->\n    <string name=\"settings_home_layout_style\">首页布局样式</string>\n    <string name=\"settings_home_layout_default\">ListUI</string>\n    <string name=\"settings_home_layout_grid\">GridUI</string>\n    <string name=\"settings_home_layout_focus\">FocusUI</string>\n    <string name=\"settings_home_layout_sign\">SignUI</string>\n    <string name=\"settings_home_layout_circle\">CircleUI</string>\n    <string name=\"settings_home_layout_dashboard_pro\">DashboardUI</string>\n    <string name=\"unofficial_version_title\">非官方版本</string>\n    <string name=\"unofficial_version_message\">您正在使用的 FolkPatch 管理器为第三方版本，可能会造成财产损失，请下载正版APP使用！</string>\n    <string name=\"go_to_github\">前往 Github</string>\n    <string name=\"settings_save_theme\">保存主题</string>\n    <string name=\"settings_import_theme\">导入主题</string>\n    <string name=\"settings_theme_saved\">主题已保存</string>\n    <string name=\"settings_theme_imported\">主题已导入</string>\n    <string name=\"settings_theme_save_failed\">保存主题失败</string>\n    <string name=\"settings_theme_import_failed\">导入主题失败</string>\n    <string name=\"settings_reset_theme\">恢复主题</string>\n    <string name=\"settings_reset_theme_confirm\">确定要将所有主题设置恢复为默认值吗？这将清除所有自定义背景、字体、音乐和音效。</string>\n    <string name=\"settings_theme_reset\">主题已恢复默认</string>\n    <string name=\"settings_theme_reset_failed\">恢复主题失败</string>\n\n    <!-- Theme Strings -->\n    <string name=\"theme_export_title\">导出主题</string>\n    <string name=\"theme_import_title\">导入主题</string>\n    <string name=\"theme_name\">主题名称</string>\n    <string name=\"theme_type\">主题类型</string>\n    <string name=\"theme_type_phone\">手机</string>\n    <string name=\"theme_type_tablet\">平板</string>\n    <string name=\"theme_version\">版本</string>\n    <string name=\"theme_author\">作者</string>\n    <string name=\"theme_description\">描述</string>\n    <string name=\"theme_export_action\">导出</string>\n    <string name=\"theme_import_action\">导入</string>\n    <string name=\"theme_import_confirm\">确定要导入此主题吗？</string>\n    <string name=\"theme_info\">主题信息</string>\n\n    <!-- Grid Working Card Background -->\n    <string name=\"settings_grid_working_card_background\">工作卡片背景</string>\n    <string name=\"settings_grid_working_card_background_enabled\">自定义卡片背景已启用</string>\n    <string name=\"settings_grid_working_card_background_summary\">为工作卡片设置自定义背景图片</string>\n    <string name=\"settings_grid_working_card_background_selected\">背景已选择</string>\n    <string name=\"settings_grid_working_card_dual_opacity\">启用双切透明度适配</string>\n    <string name=\"settings_grid_working_card_dual_opacity_desc\">根据亮色和暗色主题自动调整卡片透明度</string>\n    <string name=\"settings_grid_working_card_day_opacity\">白天模式透明度</string>\n    <string name=\"settings_grid_working_card_night_opacity\">夜间模式透明度</string>\n    <string name=\"settings_clear_grid_working_card_background\">清除卡片背景</string>\n    <string name=\"settings_clear_grid_working_card_background_confirm\">确定要清除卡片背景图片吗？</string>\n    <string name=\"settings_grid_working_card_background_saved\">卡片背景保存成功</string>\n    <string name=\"settings_grid_working_card_background_error\">卡片背景保存失败</string>\n    <string name=\"settings_grid_working_card_background_cleared\">卡片背景已清除</string>\n\n    <!-- Biometric Strings -->\n    <string name=\"action_biometric\">生物识别认证</string>\n    <string name=\"msg_biometric\">请验证您的生物识别身份</string>\n    <string name=\"settings_biometric_login\">生物识别认证</string>\n    <string name=\"settings_biometric_login_summary\">打开应用时要求生物识别认证</string>\n    <string name=\"settings_strong_biometric\">强力生物识别</string>\n    <string name=\"settings_strong_biometric_summary\">安装、卸载或禁用模块时需要验证</string>\n\n    <!-- Theme Store Strings -->\n    <string name=\"theme_store_title\">主题商店</string>\n    <string name=\"theme_source_official\">官方</string>\n    <string name=\"theme_source_third_party\">第三方</string>\n    <string name=\"theme_source_local\">本地</string>\n    <string name=\"theme_store_author\">作者: %s</string>\n    <string name=\"theme_store_version\">版本: %s</string>\n    <string name=\"theme_source\">来源</string>\n    <string name=\"theme_store_download_failed\">下载失败</string>\n    <string name=\"theme_store_download\">下载</string>\n    <string name=\"theme_store_search_hint\">搜索主题...</string>\n    <string name=\"search_modules\">搜索模块...</string>\n    <string name=\"search_scripts\">搜索脚本...</string>\n\n    <!-- Update Check Strings -->\n    <string name=\"settings_auto_update_check\">自动检查更新</string>\n    <string name=\"settings_auto_update_check_summary\">应用启动时自动检查更新</string>\n    <string name=\"settings_check_update\">检查更新</string>\n    <string name=\"update_available_title\">有可用更新</string>\n    <string name=\"update_available_message\">检测到您的版本过低，是否要下载新版本？</string>\n    <string name=\"update_action\">更新</string>\n    <string name=\"update_close\">关闭</string>\n    <string name=\"update_latest\">您已是最新版本</string>\n    <string name=\"update_error\">检查更新时出错</string>\n    <!--折叠选项-->\n    <string name=\"settings_category_general\">常规</string>\n    <string name=\"settings_app_list_loading_scheme\">应用列表加载方案</string>\n    <string name=\"settings_app_list_loading_scheme_summary\">选择如何加载应用列表</string>\n    <string name=\"app_list_loading_scheme_root_service\">RootService (默认)</string>\n    <string name=\"app_list_loading_scheme_package_manager\">PackageManager (系统 API)</string>\n    <string name=\"app_list_loading_scheme_dialog_title\">加载方案</string>\n    <string name=\"superuser\">超级用户</string>\n    <string name=\"module\">系统模块</string>\n    <string name=\"settings_category_appearance\">外观</string>\n    <string name=\"settings_category_behavior\">行为</string>\n    \n    <!-- Appearance Sub-Categories -->\n    <string name=\"settings_appearance_font\">字体设置</string>\n    <string name=\"settings_appearance_font_summary\">自定义字体配置</string>\n    <string name=\"settings_appearance_theme\">主题设置</string>\n    <string name=\"settings_appearance_theme_summary\">主题商店、保存、导入和重置</string>\n    <string name=\"settings_appearance_banner\">横幅设置</string>\n    <string name=\"settings_appearance_banner_summary\">模块横幅配置</string>\n    <string name=\"settings_appearance_layout\">布局设置</string>\n    <string name=\"settings_appearance_layout_summary\">首页布局、导航和卡片自定义</string>\n    <string name=\"settings_appearance_background\">背景设置</string>\n    <string name=\"settings_appearance_background_summary\">自定义背景、视频和多背景</string>\n    <string name=\"settings_appearance_night_mode\">夜间模式设置</string>\n    <string name=\"settings_appearance_night_mode_summary\">深色主题和颜色配置</string>\n    <string name=\"settings_amoled_theme\">AMOLED 纯黑主题</string>\n    <string name=\"settings_amoled_theme_desc\">为深色模式使用纯黑背景</string>\n    <string name=\"settings_switch_icon\">启用按钮指示器</string>\n    <string name=\"settings_switch_icon_desc\">为开关按钮添加状态指示图标</string>\n    <string name=\"settings_use_legacy_su_page\">单页超级用户授权</string>\n    <string name=\"settings_use_legacy_su_page_summary\">超级用户页面改用单页授权设计</string>\n    <string name=\"settings_category_module\">模块</string>\n    <string name=\"settings_category_security\">安全</string>\n    <string name=\"settings_category_function\">功能</string>\n\n    <!-- 背景音乐字符串 -->\n    <string name=\"settings_background_music\">背景音乐</string>\n    <string name=\"settings_background_music_summary\">应用在前台时播放背景音乐</string>\n    <string name=\"settings_background_music_playing\">正在播放: %s</string>\n    <string name=\"settings_background_music_enabled\">背景音乐已启用</string>\n    <string name=\"settings_select_music_file\">选择音乐文件</string>\n    <string name=\"settings_music_selected\">音乐已选择</string>\n    <string name=\"settings_clear_music\">清除音乐</string>\n    <string name=\"settings_clear_music_confirm\">确定要清除背景音乐吗？</string>\n    <string name=\"settings_music_auto_play\">自动播放</string>\n    <string name=\"settings_music_auto_play_summary\">应用打开时自动播放音乐</string>\n    <string name=\"settings_music_looping\">循环播放</string>\n    <string name=\"settings_music_looping_summary\">循环播放当前歌曲</string>\n    <string name=\"settings_music_volume\">音量</string>\n    <string name=\"settings_music_saved\">音乐文件已保存</string>\n    <string name=\"settings_music_save_error\">保存音乐文件失败</string>\n    <string name=\"settings_music_cleared\">音乐已清除</string>\n    <string name=\"settings_category_multimedia\">多媒体</string>\n    <string name=\"settings_category_general_summary\">语言、更新、SELinux、系统调整</string>\n    <string name=\"settings_category_appearance_summary\">主题、颜色、布局、背景、字体</string>\n    <string name=\"settings_category_behavior_summary\">Web调试、安装行为、首页显示</string>\n    <string name=\"settings_category_security_summary\">生物识别、超级密钥管理</string>\n    <string name=\"settings_category_backup_summary\">本地备份、云端备份、WebDAV</string>\n    <string name=\"settings_category_module_summary\">模块信息、排序、批量安装</string>\n    <string name=\"settings_category_function_summary\">FolkPatch隐藏、Umount服务</string>\n    <string name=\"settings_category_multimedia_summary\">背景音乐、音效、振动</string>\n    <string name=\"settings_music_playback_control\">播放控制</string>\n\n    <!-- Theme Store Filter -->\n    <string name=\"theme_store_filter_title\">筛选主题</string>\n    <string name=\"theme_store_filter_author\">作者</string>\n    <string name=\"theme_store_filter_author_hint\">输入作者名称</string>\n    <string name=\"theme_store_filter_source\">来源</string>\n    <string name=\"theme_store_filter_source_all\">全部</string>\n    <string name=\"theme_store_filter_type\">设备类型</string>\n    <string name=\"theme_store_filter_apply\">应用</string>\n    <string name=\"theme_store_filter_reset\">重置</string>\n\n    <!-- My Themes Page -->\n    <string name=\"my_themes_title\">我的主题</string>\n    <string name=\"my_themes_empty\">暂无主题</string>\n    <string name=\"my_themes_empty_action\">去逛逛</string>\n    <string name=\"my_themes_apply\">应用主题</string>\n    <string name=\"my_themes_delete\">删除主题</string>\n    <string name=\"my_themes_delete_confirm\">确定要删除此主题吗？</string>\n    <string name=\"my_themes_deleted\">主题已删除</string>\n    <string name=\"my_themes_applied\">主题已应用</string>\n    <string name=\"my_themes_apply_failed\">应用主题失败</string>\n    <string name=\"my_themes_details\">主题详情</string>\n\n    <!-- Download Dialog -->\n    <string name=\"theme_download_title\">正在下载主题</string>\n    <string name=\"theme_download_completed\">下载完成</string>\n    <string name=\"theme_download_failed\">下载失败</string>\n    <string name=\"theme_download_progress\">进度</string>\n    <string name=\"theme_download_file\">主题文件</string>\n    <string name=\"theme_download_image\">预览图片</string>\n    <string name=\"theme_download_cancel\">取消</string>\n    <string name=\"theme_download_pause\">暂停</string>\n    <string name=\"theme_download_resume\">继续</string>\n    <string name=\"theme_download_retry\">重试</string>\n    <string name=\"theme_download_apply\">应用主题</string>\n    <string name=\"theme_download_go_to_my_themes\">我的主题</string>\n    <string name=\"theme_download_retrying\">正在重试 (%d/3)...</string>\n    <string name=\"theme_download_preparing\">准备下载...</string>\n    <string name=\"theme_download_downloading_file\">正在下载主题文件...</string>\n    <string name=\"theme_download_downloading_image\">正在下载预览图片...</string>\n    <string name=\"theme_download_finalizing\">正在完成...</string>\n\n    <!-- File Picker -->\n    <string name=\"file_picker_permission_required\">需要权限</string>\n    <string name=\"file_picker_permission_desc\">为了浏览文件，请授予\\'所有文件访问\\'权限。</string>\n    <string name=\"file_picker_grant_permission\">授予权限</string>\n    <string name=\"file_picker_cancel\">取消</string>\n    <string name=\"file_picker_internal_storage\">内部存储</string>\n    <string name=\"file_picker_no_files\">无文件</string>\n\n    <!-- HomeV3 Strings -->\n    <string name=\"home_device_status_title\">设备状态</string>\n    <string name=\"home_device_status_battery_temp\">电池温度</string>\n    <string name=\"home_device_status_cpu_load\">CPU 负载</string>\n    <string name=\"home_device_status_battery_level\">电池电量</string>\n    <string name=\"home_storage_title\">存储信息</string>\n    <string name=\"home_storage_internal\">内部存储</string>\n    <string name=\"home_storage_ram\">运行内存</string>\n    <string name=\"home_storage_zram\">ZRAM</string>\n    <string name=\"home_storage_swap\">交换文件</string>\n    <string name=\"home_info_kernel\">内核</string>\n    <string name=\"home_info_superkey\">超级密钥</string>\n    <string name=\"home_info_auth_auth\">已认证</string>\n    <string name=\"home_info_auth_na\">不可用</string>\n    <string name=\"home_info_device_slot\">设备槽位</string>\n    <string name=\"home_info_device_model\">设备型号</string>\n    <string name=\"home_info_running_mode\">运行模式</string>\n    <string name=\"home_info_mode_full\">完整</string>\n    <string name=\"home_info_mode_half\">不完全</string>\n    <string name=\"home_zygisk_implement\">Zygisk 实现</string>\n    <string name=\"home_mount_implement\">挂载实现</string>\n\n    <string name=\"settings_list_card_hide_status_badge\">使用经典表情</string>\n    <string name=\"settings_list_card_hide_status_badge_summary\">首页的状态徽章即刻变身经典表情</string>\n    <string name=\"settings_custom_badge_text\">自选角标文字</string>\n    <string name=\"settings_custom_badge_text_summary\">修改角标的文本显示，仅供娱乐</string>\n\n    <!-- Install Mode Select -->\n    <string name=\"kp_install_methods\">KernelPatch 修补/安装</string>\n    <string name=\"restore_boot_methods\">选择一个启动来恢复到引导分区</string>\n\n    <string name=\"su_backup_list\">备份列表</string>\n    <string name=\"su_restore_list\">还原列表</string>\n    <string name=\"backup_success\">备份成功</string>\n    <string name=\"restore_success\">还原成功</string>\n\n    <!-- 超级用户应用操作弹窗 -->\n    <string name=\"su_app_action_title\">应用操作</string>\n    <string name=\"su_app_action_content\">请选择一个操作来对应用执行</string>\n    <string name=\"su_app_action_launch\">启动应用</string>\n    <string name=\"su_app_action_force_stop\">强行停止</string>\n    <string name=\"su_app_action_launch_success\">正在启动 %s</string>\n    <string name=\"su_app_action_force_stop_success\">已强行停止 %s</string>\n    <string name=\"su_app_action_failed\">操作失败: %s</string>\n\n    <!-- SU Audit Log -->\n    <string name=\"su_audit_log_title\">授权日志</string>\n    <string name=\"su_audit_log_empty\">暂无授权记录</string>\n    <string name=\"su_audit_log_clear\">清除日志</string>\n    <string name=\"su_audit_log_clear_confirm\">确认清除所有授权记录？</string>\n    <string name=\"su_audit_action_grant\">已授权</string>\n    <string name=\"su_audit_action_revoke\">已撤销</string>\n    <string name=\"su_audit_action_exclude\">已排除</string>\n    <string name=\"su_audit_tab_usage\">使用记录</string>\n    <string name=\"su_audit_tab_operations\">操作记录</string>\n\n    <string name=\"patch_output_written_to\"> 修补成功，文件输出到 </string>\n    <string name=\"patch_write_failed\"> 修补失败，发生了致命错误</string>\n\n    <!-- Badge Count Settings -->\n    <string name=\"enable_badge_count\">角标统计设置</string>\n    <string name=\"enable_badge_count_summary\">配置导航项的角标统计显示</string>\n    <string name=\"badge_superuser\">显示超级用户角标</string>\n    <string name=\"badge_apm\">显示 APM 模块角标</string>\n    <string name=\"badge_kernel\">显示内核模块角标</string>\n    \n    <string name=\"settings_sound_effect\">点击音效</string>\n    <string name=\"settings_sound_effect_summary\">点击时播放音效</string>\n    <string name=\"settings_sound_effect_enabled\">已启用</string>\n    <string name=\"settings_sound_effect_playing\">当前选择: %s</string>\n    <string name=\"settings_sound_effect_source\">音效来源</string>\n    <string name=\"settings_sound_effect_source_local\">本地文件</string>\n    <string name=\"settings_sound_effect_source_preset\">预设</string>\n    <string name=\"settings_sound_effect_preset_title\">预设音效</string>\n    <string name=\"settings_select_sound_effect\">选择音效文件</string>\n    <string name=\"settings_sound_effect_selected\">已选择音效文件</string>\n    <string name=\"settings_sound_effect_scope\">生效范围</string>\n    <string name=\"settings_sound_effect_scope_global\">全局 (任何位置)</string>\n    <string name=\"settings_sound_effect_scope_bottom_bar\">仅底栏</string>\n    <string name=\"settings_clear_sound_effect\">清除音效</string>\n    <string name=\"settings_clear_sound_effect_confirm\">确定要清除音效吗？</string>\n    <string name=\"settings_sound_effect_cleared\">音效已清除</string>\n\n    <string name=\"settings_startup_sound\">启动音效</string>\n    <string name=\"settings_startup_sound_summary\">应用启动时播放音效</string>\n    <string name=\"settings_startup_sound_enabled\">已启用启动音效</string>\n    <string name=\"settings_startup_sound_playing\">正在播放: %s</string>\n    <string name=\"settings_select_startup_sound\">选择启动音效</string>\n    <string name=\"settings_startup_sound_selected\">已选择启动音效</string>\n    <string name=\"settings_clear_startup_sound\">清除启动音效</string>\n    <string name=\"settings_clear_startup_sound_confirm\">确定要清除启动音效吗？</string>\n    <string name=\"settings_startup_sound_cleared\">启动音效已清除</string>\n\n    <string name=\"settings_vibration\">震动触感</string>\n    <string name=\"settings_vibration_summary\">在触摸事件时震动</string>\n    <string name=\"settings_vibration_enabled\">启用震动</string>\n    <string name=\"settings_vibration_intensity\">震动强度</string>\n    <string name=\"settings_vibration_scope\">震动范围</string>\n    <string name=\"settings_vibration_scope_global\">全局(任意位置)</string>\n    <string name=\"settings_vibration_scope_bottom_bar\">仅底部栏</string>\n\n    <!-- Backup Settings -->\n    <string name=\"settings_category_backup\">备份</string>\n    <string name=\"settings_enable_cloud_backup\">启用云端备份</string>\n    <string name=\"settings_enable_cloud_backup_summary\">自动备份刷入过的模块到云端服务</string>\n    <string name=\"settings_configure_webdav\">配置 WebDAV 服务</string>\n    <string name=\"webdav_config_title\">WebDAV 配置</string>\n    <string name=\"webdav_url\">WebDAV 地址</string>\n    <string name=\"webdav_username\">用户名</string>\n    <string name=\"webdav_password\">密码</string>\n    <string name=\"webdav_test_success\">测试成功</string>\n    <string name=\"webdav_test_failed\">测试失败: %s</string>\n    <string name=\"webdav_uploading\">正在上传到 WebDAV...</string>\n    <string name=\"webdav_backup_success\">WebDAV 备份成功</string>\n    <string name=\"webdav_backup_failed\">WebDAV 备份失败: %s</string>\n    <string name=\"save\">保存</string>\n    <string name=\"test\">测试</string>\n    <string name=\"webdav_path_label\">路径 (例如 /Backup)</string>\n    <string name=\"webdav_view_logs\">查看日志</string>\n    <string name=\"webdav_backup_logs_title\">备份日志</string>\n    <string name=\"webdav_no_logs\">暂无日志</string>\n    <string name=\"webdav_clear_logs\">清除</string>\n    <string name=\"settings_enable_local_backup\">启用本地备份</string>\n    <string name=\"settings_enable_local_backup_summary\">将模块备份到本地存储 (Downloads/FolkPatch/ModuleBackups)</string>\n    <string name=\"close\">关闭</string>\n\n    <!-- Script Library Strings -->\n    <string name=\"script_library\">脚本库</string>\n    <string name=\"script_library_title\">脚本库</string>\n    <string name=\"script_library_empty\">暂无脚本，点击添加按钮添加一个</string>\n    <string name=\"script_library_add\">添加脚本</string>\n    <string name=\"script_library_add_title\">添加脚本</string>\n    <string name=\"script_library_select_file\">选择脚本文件</string>\n    <string name=\"script_library_alias\">别名</string>\n    <string name=\"script_library_alias_hint\">输入脚本别名（可选）</string>\n    <string name=\"script_library_run\">运行</string>\n    <string name=\"script_library_delete\">删除</string>\n    <string name=\"script_library_path\">路径：%s</string>\n    <string name=\"script_library_confirm_delete\">确认删除脚本？</string>\n    <string name=\"script_library_delete_success\">脚本删除成功</string>\n    <string name=\"script_library_delete_failed\">删除脚本失败</string>\n    <string name=\"script_library_add_success\">脚本添加成功</string>\n    <string name=\"script_library_add_failed\">添加脚本失败：%s</string>\n    <string name=\"script_library_load_failed\">加载脚本库失败</string>\n    <string name=\"script_library_output\">执行输出</string>\n    <string name=\"script_library_no_output\">无输出</string>\n    <string name=\"script_library_save_log\">保存日志</string>\n    <string name=\"script_library_log_saved\">日志已保存至 %s</string>\n    <string name=\"script_library_log_save_failed\">保存日志失败：%s</string>\n\n    <!-- Umount Service -->\n    <string name=\"settings_kernel_spoof\">内核伪装配置</string>\n    <string name=\"settings_kernel_spoof_summary\">伪装内核版本和构建时间</string>\n    <string name=\"settings_kernel_spoof_version\">内核版本</string>\n    <string name=\"settings_kernel_spoof_build_time\">内核构建时间</string>\n    <string name=\"settings_kernel_spoof_restore\">恢复</string>\n    <string name=\"settings_umount_service\">Umount Service</string>\n    <string name=\"settings_umount_service_summary\">配置开机自动卸载的挂载点</string>\n    <string name=\"umount_config_title\">Umount 配置</string>\n    <string name=\"umount_config_enabled\">启用 Umount</string>\n    <string name=\"umount_config_enabled_summary\">开机自动卸载指定挂载点</string>\n    <string name=\"umount_config_paths_label\">挂载点路径（每行一个）</string>\n    <string name=\"umount_config_paths_placeholder\">/system/vendor/app\\n/system/vendor/priv-app</string>\n    <string name=\"umount_config_paths_helper\">每行输入一个需要卸载的挂载点路径</string>\n    <string name=\"umount_config_save\">保存</string>\n    <string name=\"umount_config_save_confirm\">确认保存配置？</string>\n    <string name=\"umount_config_save_success\">配置保存成功</string>\n    <string name=\"umount_config_save_failed\">配置保存失败</string>\n    <string name=\"settings_predictive_back\">预测性返回手势</string>\n    <string name=\"settings_predictive_back_summary\">启用 Android 14+ 的预测性返回手势动画</string>\n\n    <string name=\"home_device_status_battery_charging\">充电中</string>\n    <string name=\"home_device_status_cpu_temp\">CPU 温度</string>\n    <string name=\"home_device_status_memory_trend\">内存趋势</string>\n    <string name=\"home_storage_partitions\">存储分区</string>\n    <string name=\"home_device_status_cpu_freq\">CPU 频率</string>\n    <string name=\"home_network_rx\">下载</string>\n    <string name=\"home_network_tx\">上传</string>\n    <string name=\"settings_home_layout_stats\">StatsUI</string>\n    <string name=\"home_stats_more_options\">更多选项</string>\n    <string name=\"home_stats_kp_label\">KP</string>\n    <string name=\"home_stats_manager_label\">管理器</string>\n    <string name=\"home_device_status_gpu_load\">GPU 负载</string>\n    <string name=\"home_stats_kernel_modules\">内核模块</string>\n    <string name=\"home_stats_apm_modules\">系统模块</string>\n    <string name=\"home_stats_superusers\">超级用户</string>\n\n    <string name=\"kernel_spoof_enabled\">内核伪装已启用</string>\n    <string name=\"kernel_spoof_disabled_restored\">内核伪装已停用并还原</string>\n    <string name=\"kernel_spoof_failed\">内核伪装失败: %d</string>\n    <string name=\"kernel_spoof_applied\">内核伪装已应用</string>\n\n    <string name=\"settings_path_hide\">路径隐藏</string>\n    <string name=\"settings_path_hide_summary\">在内核层面向应用隐藏文件和目录</string>\n    <string name=\"path_hide_paths_label\">隐藏路径（每行一个）</string>\n\n    <string name=\"path_hide_paths_helper\">每行输入一个路径。匹配的路径将返回 ENOENT。</string>\n    <string name=\"path_hide_save\">保存</string>\n    <string name=\"path_hide_enabled\">路径隐藏已启用</string>\n    <string name=\"path_hide_disabled\">路径隐藏已禁用</string>\n    <string name=\"path_hide_applied\">路径隐藏配置已应用</string>\n    <string name=\"path_hide_failed\">路径隐藏操作失败：%d</string>\n    <string name=\"path_hide_uid_mode\">UID 执行模式</string>\n    <string name=\"path_hide_uid_mode_summary\">仅对指定应用 UID 隐藏路径</string>\n    <string name=\"path_hide_uids_label\">目标 UID（每行一个）</string>\n    <string name=\"path_hide_uids_helper\">输入应用 UID，仅这些应用的路径会被隐藏。</string>\n    <string name=\"path_hide_uid_save\">保存 UID</string>\n    <string name=\"path_hide_uid_mode_enabled\">UID 执行模式已启用</string>\n    <string name=\"path_hide_uid_mode_disabled\">UID 执行模式已禁用</string>\n    <string name=\"path_hide_filter_system\">过滤系统 UID</string>\n    <string name=\"path_hide_filter_system_summary\">同时对 root 和系统进程（UID &lt; 10000）隐藏路径</string>\n    <string name=\"path_hide_filter_system_enabled\">系统 UID 过滤已启用</string>\n    <string name=\"path_hide_filter_system_disabled\">系统 UID 过滤已禁用</string>\n    <string name=\"path_hide_filter_system_warning_title\">警告</string>\n    <string name=\"path_hide_filter_system_warning_message\">启用后将同时对 root 和系统进程（UID &lt; 10000）隐藏路径。这可能导致部分系统功能异常，请谨慎操作。</string>\n    <string name=\"path_hide_uid_applied\">UID 白名单已应用</string>\n    <string name=\"path_hide_select_apps\">选择应用</string>\n    <string name=\"path_hide_no_apps_selected\">未选择应用</string>\n    <string name=\"path_hide_app_removed\">已移除 %d 个已卸载应用的配置</string>\n    <string name=\"path_hide_search_apps\">搜索应用…</string>\n    <string name=\"path_hide_show_system\">显示系统应用</string>\n    <string name=\"netisolate_title\">网络隔离</string>\n    <string name=\"netisolate_enable\">网络隔离已启用</string>\n    <string name=\"netisolate_disable\">网络隔离已禁用</string>\n    <string name=\"netisolate_enable_summary\">在内核层面阻止所选应用的网络访问</string>\n    <string name=\"netisolate_uids_label\">已隔离应用</string>\n    <string name=\"netisolate_uids_hint\">输入要隔离的 UID（每行一个）</string>\n    <string name=\"netisolate_no_uids\">暂无隔离应用</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-zh-rMC/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\" translatable=\"false\">FolkPatch</string>\n    <string name=\"app_name_fpatch\">FPatch</string>\n\n    <string name=\"desktop_app_name\">出生点名牌</string>\n\n    <string name=\"home\">主世界</string>\n\n    <string name=\"success\">成就已解锁！</string>\n    <string name=\"failure\">你死了！游戏结束！</string>\n\n    <string name=\"patch_warnning\">合成与锻造有风险，请确保已备份存档到末影箱！</string>\n    <string name=\"patch\">锻造</string>\n\n    <string name=\"kernel_patch\">基岩核心补丁</string>\n    <string name=\"android_patch\">红石系统补丁</string>\n\n    <string name=\"settings_nav_layout_title\">快捷栏布局</string>\n    <string name=\"settings_nav_layout_summary\">隐藏或显示快捷栏的部分格子</string>\n    <string name=\"settings_nav_scheme\">物品栏方案</string>\n    <string name=\"settings_nav_mode\">物品栏模式</string>\n    <string name=\"settings_nav_mode_summary\">选择物品栏的排列方式</string>\n    <string name=\"settings_nav_mode_auto\">自动快捷栏</string>\n    <string name=\"settings_nav_mode_bottom\">始终底部快捷栏</string>\n    <string name=\"settings_nav_mode_rail\">始终侧边物品栏</string>\n    <string name=\"settings_nav_mode_floating\">悬浮底部快捷栏</string>\n    <string name=\"settings_show_apm\">显示模组</string>\n    <string name=\"settings_show_kpm\">显示红石组件</string>\n    <string name=\"settings_show_superuser\">显示OP权限</string>\n    <string name=\"settings_floating_auto_hide\">自动隐藏底栏（自动收起快捷栏）</string>\n    <string name=\"settings_floating_auto_hide_summary\">3秒没操作就把底栏藏起来（就像物品栏自动收起）</string>\n    <string name=\"settings_floating_swipe_hide\">滑动隐藏底栏（滑动收起快捷栏）</string>\n    <string name=\"settings_floating_swipe_hide_summary\">往下滑藏起来，往上滑亮出来（像切换物品栏一样）</string>\n    <string name=\"settings_navbar_glass_effect\">毛玻璃效果（磨砂玻璃特效）</string>\n    <string name=\"settings_navbar_glass_effect_summary\">为悬浮导航栏应用毛玻璃视觉效果（让底栏看起来像磨砂玻璃）</string>\n    <string name=\"settings_navbar_glass_blur_strength\">模糊强度（背景虚化程度）</string>\n    <string name=\"settings_navbar_glass_transparency\">背景透明度（底栏背景的透明程度）</string>\n    <string name=\"settings_navbar_glass_highlight_strength\">高光强度（反光亮度）</string>\n    <string name=\"settings_navbar_glass_specular\">镜面反射（镜面反光效果）</string>\n    <string name=\"settings_navbar_glass_specular_summary\">在导航栏顶部启用镜面高光效果（顶部反光）</string>\n    <string name=\"settings_navbar_glass_inner_glow\">内发光（内部发光效果）</string>\n    <string name=\"settings_navbar_glass_inner_glow_summary\">在导航栏底部启用微光效果（底部柔和发光）</string>\n    <string name=\"settings_navbar_glass_border\">玻璃边框（玻璃质感边框）</string>\n    <string name=\"settings_navbar_glass_border_summary\">在导航栏周围启用微妙的边框描边（给底栏加一圈边框线）</string>\n    <string name=\"settings_stats_top_layout\">顶部布局（顶部快捷栏样式）</string>\n    <string name=\"settings_stats_top_layout_summary\">选择顶部信息卡片样式（快捷栏摆法）</string>\n    <string name=\"settings_stats_top_layout_list\">列表样式</string>\n    <string name=\"settings_stats_top_layout_grid\">网格样式</string>\n    <string name=\"settings_block_kernelpatch_update\">屏蔽基岩补丁更新</string>\n    <string name=\"settings_block_kernelpatch_update_summary\">别再弹基岩补丁的更新提示了，烦得像苦力怕一样</string>\n    <string name=\"settings_block_androidpatch_update\">屏蔽红石系统更新</string>\n    <string name=\"settings_block_androidpatch_update_summary\">别再弹红石系统的更新提示了，关掉它</string>\n    <string name=\"settings_disable_module_update_check\">关闭模组自动更新检查</string>\n    <string name=\"settings_disable_module_update_check_summary\">别自动检查模组更新了，刷存在感没意思</string>\n\n    <string name=\"reboot\">重生一下</string>\n    <string name=\"reboot_recovery\">回到备份点重生</string>\n    <string name=\"reboot_bootloader\">回出生点重生</string>\n    <string name=\"reboot_download\">传送到下界</string>\n    <string name=\"reboot_edl\">传送到末地</string>\n    <string name=\"reboot_fastbootd\">切到创造模式</string>\n    <string name=\"about\">关于这个世界</string>\n    <string name=\"developer_and_maintainer\">世界创造者 | 守护者</string>\n    <string name=\"settings_app_language\">语言资源包</string>\n    <string name=\"system_default\">默认资源包</string>\n    <string name=\"settings_global_namespace_mode\">全局区块加载</string>\n    <string name=\"settings_global_namespace_mode_summary\">所有OP会话共享全局区块挂载空间</string>\n    <string name=\"settings_clear_super_key_dialog\">你确定吗？这操作比扔末影珍珠还刺激，不可逆的！</string>\n\n    <string name=\"home_learn_apatch\">探索 FolkPatch 这片大陆</string>\n    <string name=\"home_click_to_learn_apatch\">看看 FolkPatch 能在你的世界里搞出什么花样</string>\n    <string name=\"settings_hide_apatch_card\">拆掉\"了解 FolkPatch\"告示牌</string>\n    <string name=\"settings_hide_apatch_card_summary\">把主世界那块\"了解 FolkPatch\"告示牌拆了吧，碍事</string>\n    <string name=\"settings_grid_working_card_hide_check\">藏起状态图标</string>\n    <string name=\"settings_grid_working_card_hide_check_summary\">把合成台面板上的对勾和苦力怕脸藏起来</string>\n    <string name=\"settings_grid_working_card_hide_text\">藏起状态文字</string>\n    <string name=\"home_grid_working_card_hide_text_summary\">把合成台面板上的\"世界正常运行中\"或\"这片世界还是空的\"文字藏起来</string>\n    <string name=\"settings_grid_working_card_hide_mode\">藏起游戏模式</string>\n    <string name=\"settings_grid_working_card_hide_mode_summary\">把合成台面板上的模式标识藏起来</string>\n\n    <string name=\"settings_folkx_engine_title\">FolkX 物理引擎</string>\n    <string name=\"settings_folkx_engine_summary\">在切换维度页面时使用丝滑的物理弹簧动画</string>\n    <string name=\"settings_folkx_animation_type\">动画类型</string>\n    <string name=\"settings_folkx_animation_speed\">动画速度（TPS）</string>\n    <string name=\"settings_folkx_animation_linear\">线性弹道</string>\n    <string name=\"settings_folkx_animation_spatial\">空间弹道</string>\n    <string name=\"settings_folkx_animation_fade\">渐隐渐显</string>\n    <string name=\"settings_folkx_animation_vertical\">Y轴滑动</string>\n    <string name=\"settings_folkx_animation_diagonal\">对角弹道</string>\n\n    <string name=\"about_source_code\"><![CDATA[<p>在 %1$s 查看源代码<p/>加入我们的 %2$s 频道<p/>加入我们的 %3$s 服务器群组]]></string>\n    <string name=\"send_log\">发送崩溃报告</string>\n    <string name=\"save_log\">保存崩溃报告</string>\n    <string name=\"log_saved\">崩溃报告已存入末影箱</string>\n    <string name=\"safe_mode\">和平难度</string>\n    <string name=\"github\">GitHub</string>\n    <string name=\"telegram\">Telegram</string>\n    <string name=\"support_or_donate\">投喂绿宝石 / 赞助</string>\n\n    <string name=\"super_key\">OP密钥</string>\n    <string name=\"clear_super_key\">注销OP密钥</string>\n    <string name=\"patch_set_superkey\">绑定OP密钥</string>\n    <string name=\"home_patch_set_key_desc\">KernelPatch 的唯一身份凭证</string>\n    <string name=\"home_patch_next_step\">下一步</string>\n\n    <string name=\"home_not_installed\">这片世界还是空的</string>\n    <string name=\"home_install_unknown\">不确定有没有装好</string>\n    <string name=\"home_install_unknown_summary\">点一下试试</string>\n    <string name=\"home_click_to_install\">点这里装一下</string>\n    <string name=\"home_working\">服务器在线</string>\n    <string name=\"home_version\">版本: %s</string>\n    <string name=\"home_kp_need_update\">有新版出来了</string>\n    <string name=\"home_kp_cando_update\">去更新</string>\n\n    <string name=\"home_installing\">合成中，稍等…</string>\n\n    <string name=\"kpatch_version\">基岩核心版本: %s</string>\n    <string name=\"apatch_version\">FolkPatch版本: %s</string>\n\n    <string name=\"kpatch_version_update\" formatted=\"false\">基岩核心版本: %s -&gt; %s</string>\n    <string name=\"kpatch_shadow_path_title\">kpatch</string>\n    <string name=\"home_auth_key_title\">输入OP密钥</string>\n    <string name=\"home_auth_key_desc\">验证OP身份后才能拿到创造模式的权限</string>\n    <string name=\"home_kpatch_info_title\">世界情报</string>\n\n    <string name=\"home_ap_cando_install\">装一下</string>\n\n    <string name=\"home_ap_cando_uninstall\">卸掉模组</string>\n    <string name=\"home_ap_cando_reboot\">重生一下</string>\n\n    <string name=\"patch_title\">锻造</string>\n\n    <string name=\"patch_config_title\">锻造配方</string>\n    <string name=\"patch_mode_bootimg_patch\">模式：锻造合成</string>\n    <string name=\"patch_mode_patch_and_install\">模式：锻造并安装到设备</string>\n    <string name=\"patch_mode_install_to_next_slot\">模式：安装到备用槽位（OTA后）</string>\n    <string name=\"patch_mode_restore\">模式：从存档备份恢复</string>\n    <string name=\"patch_mode_uninstall_patch\">模式：拆除基岩补丁</string>\n    <string name=\"patch_select_bootimg_btn\">选择基岩镜像文件</string>\n    <string name=\"patch_embed_kpm_btn\">嵌入红石组件</string>\n    <string name=\"patch_start_patch_btn\">开始锻造！</string>\n    <string name=\"patch_start_unpatch_btn\">还原基岩原样</string>\n    <string name=\"patch_item_error\">!!合成失败：材料不足!!</string>\n    <string name=\"patch_item_bootimg\">bootimg 基岩镜像</string>\n    <string name=\"patch_item_bootimg_slot\">槽位:</string>\n    <string name=\"patch_item_bootimg_dev\">设备:</string>\n    <string name=\"patch_item_kernel\">基岩核心</string>\n    <string name=\"patch_item_kpimg\">kpimg 补丁文件</string>\n    <string name=\"patch_item_kpimg_version\">版本:</string>\n    <string name=\"patch_item_kpimg_comile_time\">锻造时间:</string>\n    <string name=\"patch_item_kpimg_config\">配方:</string>\n    <string name=\"patch_item_new_extra_kpm\">嵌入新红石组件</string>\n    <string name=\"patch_item_existed_extra_kpm\">已经嵌过了</string>\n    <string name=\"patch_item_extra_name\">名称:</string>\n    <string name=\"patch_item_extra_version\">版本:</string>\n    <string name=\"patch_item_extra_author\">作者:</string>\n    <string name=\"patch_item_extra_kpm_license\">许可协议:</string>\n    <string name=\"patch_item_extra_kpm_desciption\">描述:</string>\n    <string name=\"patch_item_extra_args\">NBT参数:</string>\n    <string name=\"patch_item_extra_event\">触发事件:</string>\n    <string name=\"patch_item_skey\">OP密钥</string>\n    <string name=\"patch_custom_superkey\">自定义OP密钥</string>\n    <string name=\"patch_custom_superkey_summary\">你还能设OP密钥来管Root和内核，管理器已经内置签名授权了</string>\n    <string name=\"patch_item_set_skey_label\">OP密钥需要8-63个字符，只能用数字和字母，别夹带特殊符号进去</string>\n    <string name=\"patch_confirm_superkey\">再输一次OP密钥</string>\n    <string name=\"patch_skey_mismatch\">两次OP密钥对不上啊</string>\n\n    <string name=\"home_kernel\">基岩核心</string>\n    <string name=\"home_manager_version\">管理器版本</string>\n    <string name=\"home_fingerprint\">世界种子</string>\n\n    <string name=\"home_selinux_status\">游戏规则</string>\n    <string name=\"home_selinux_status_disabled\">游戏规则: 关了</string>\n    <string name=\"home_selinux_status_enforcing\">允许作弊：关</string>\n    <string name=\"home_selinux_status_permissive\">允许作弊：开</string>\n    <string name=\"home_selinux_status_unknown\">搞不清</string>\n\n    <string name=\"settings_selinux_mode\">游戏规则</string>\n    <string name=\"settings_selinux_mode_summary\">选一个游戏规则的执行模式</string>\n    <string name=\"settings_selinux_mode_enforcing\">允许作弊：关</string>\n    <string name=\"settings_selinux_mode_permissive\">允许作弊：开</string>\n    <string name=\"settings_selinux_current_mode\">当前: %s</string>\n    <string name=\"settings_selinux_mode_enforcing_summary\">游戏规则严格执行，作弊？没门！</string>\n    <string name=\"settings_selinux_mode_permissive_summary\">游戏规则只记违规不惩罚，等于作弊不管</string>\n\n    <string name=\"home_device_info\">设备信息</string>\n    <string name=\"home_system_version\">系统版本</string>\n    <string name=\"home_kpatch_version\">基岩补丁</string>\n    <string name=\"home_su_path\">OP指令</string>\n    <string name=\"home_apatch_version\">FolkPatch</string>\n\n    <string name=\"kpm\">红石组件</string>\n    <string name=\"kpm_kp_not_installed\">基岩核心还没打补丁呢</string>\n    <string name=\"kpm_add_kpm\">扔进红石组件</string>\n    <string name=\"kpm_load\">给红石充能</string>\n    <string name=\"kpm_install\">安装红石</string>\n    <string name=\"kpm_embed\">把红石嵌进基岩</string>\n    <string name=\"kpm_load_toast_succ\">红石充能成功！亮了！</string>\n    <string name=\"kpm_load_toast_failed\">红石充能失败…没反应</string>\n    <string name=\"kpm_unload_confirm\">把 %s 红石组件拆了？</string>\n    <string name=\"kpm_unload\">拆除红石</string>\n    <string name=\"kpm_control\">红石信号控制台</string>\n    <string name=\"kpm_apm_empty\">红石组件栏空空如也</string>\n    <string name=\"kpm_version\">版本</string>\n    <string name=\"kpm_license\">许可协议</string>\n    <string name=\"kpm_author\">制作者</string>\n    <string name=\"kpm_desc\">物品描述</string>\n    <string name=\"kpm_args\">NBT参数</string>\n\n    <string name=\"su_title\">OP权限</string>\n    <string name=\"su_selinux_via_hook\">通过红石信号绕过游戏规则</string>\n    <string name=\"su_pkg_excluded_label\">已屏蔽</string>\n    <string name=\"su_batch_exclude_title\">批量拉黑</string>\n    <string name=\"su_batch_exclude_content\">把所有没OP权限的应用都屏蔽掉红石信号，选一个操作吧</string>\n    <string name=\"su_exclude_btn\">屏蔽</string>\n    <string name=\"su_exclude_reverse_btn\">解除屏蔽</string>\n    <string name=\"su_pkg_excluded_setting_title\">屏蔽修改</string>\n    <string name=\"su_pkg_excluded_setting_summary\">启用后，FolkPatch 会恢复此应用被模组篡改过的文件</string>\n    <string name=\"su_pkg_root_setting_title\">授予OP权限</string>\n    <string name=\"su_pkg_root_setting_summary\">给应用发个OP，让它能跑管理员指令</string>\n    <string name=\"su_pkg_normal_setting_title\">生存模式</string>\n    <string name=\"su_pkg_normal_setting_summary\">不给OP，让它在生存模式里自生自灭吧</string>\n    <string name=\"su_show_system_apps\">显示系统自带应用</string>\n    <string name=\"su_hide_system_apps\">隐藏系统自带应用</string>\n    <string name=\"su_refresh\">刷新区块</string>\n\n    <string name=\"apm\">模组</string>\n    <string name=\"apm_not_installed\">红石系统还没装呢</string>\n    <string name=\"apm_failed_to_enable\">启用模组炸了: %s</string>\n    <string name=\"apm_failed_to_disable\">禁用模组炸了: %s</string>\n    <string name=\"apm_empty\">背包空空，一个模组都没有</string>\n    <string name=\"apm_remove\">从背包扔掉</string>\n    <string name=\"apm_undo\">捡回来！</string>\n    <string name=\"apm_install\">把模组放进背包</string>\n    <string name=\"apm_uninstall_confirm\">把 %s 模组扔掉？</string>\n    <string name=\"apm_uninstall_success\">%s 已被扔进虚空</string>\n    <string name=\"apm_uninstall_failed\">扔不掉: %s</string>\n    <string name=\"apm_undo_uninstall_success\">%s 从虚空捡回来了</string>\n    <string name=\"apm_undo_uninstall_failed\">从虚空捡不回来: %s</string>\n    <string name=\"apm_version\">版本</string>\n    <string name=\"apm_author\">制作者</string>\n    <string name=\"apm_desc\">物品描述</string>\n    <string name=\"apm_overlay_fs_not_available\">OverlayFS被基岩核心锁死了，模组全挂了！</string>\n    <string name=\"apm_magisk_conflict\">Magisk模组跟咱的打架呢，模组全挂了！</string>\n    <string name=\"apm_mount_warning_title\">注意了！</string>\n    <string name=\"apm_mount_warning_message\">默认不挂载模组，想用的话得开内置挂载系统或者装个元模组</string>\n    <string name=\"apm_mount_warning_button\">明白了</string>\n    <string name=\"apm_reboot_to_apply\">重生一下，模组就生效了</string>\n    <string name=\"apm_changelog\">模组更新日志</string>\n    <string name=\"apm_update\">升级模组版本</string>\n    <string name=\"apm_downloading\">正在从远方搬运模组: %s</string>\n    <string name=\"apm_start_downloading\">开始搬运: %s</string>\n    <string name=\"apm_new_version_available\">新版本 %s 来了，赶紧升级吧</string>\n\n    <string name=\"hide_apatch_manager\">伪装管理器</string>\n    <string name=\"hide_apatch_manager_summary\">生成一个随机包名和自定义名字的伪装应用，藏在众目睽睽之下</string>\n    <string name=\"hide_apatch_dialog_new_manager_name\">伪装后的名字</string>\n    <string name=\"hide_apatch_dialog_summary\">这个假名字会显示在桌面上</string>\n    <string name=\"hide_apatch_manager_failure\">伪装失败！快去issue里告诉服务器主！</string>\n\n    <string name=\"setting_reset_su_path\">重置OP指令位置</string>\n    <string name=\"setting_reset_su_new_path\">新的完整路径</string>\n\n    <string name=\"apm_webui_open\">打开</string>\n    <string name=\"apm_action\">操作</string>\n    <string name=\"module_shortcut_add\">在桌面放个快捷栏</string>\n    <string name=\"module_shortcut_not_supported\">你的启动器不支持在桌面放快捷栏</string>\n    <string name=\"module_shortcut_created\">已经在桌面放好快捷栏了</string>\n    <string name=\"module_shortcut_updated\">快捷栏已更新</string>\n    <string name=\"module_shortcut_permission_tip_xiaomi\">去小米系统设置里，给本应用开\"桌面快捷栏\"权限</string>\n    <string name=\"module_shortcut_permission_tip_oppo\">去OPPO系统设置里，给本应用开\"桌面快捷栏\"权限</string>\n    <string name=\"module_shortcut_permission_tip_default\">放不了的话，去系统设置里给本应用开桌面快捷栏权限</string>\n    <string name=\"module_shortcut_name\">快捷栏名称</string>\n    <string name=\"module_shortcut_icon\">快捷栏图标</string>\n    <string name=\"module_shortcut_type\">快捷栏类型</string>\n    <string name=\"module_shortcut_type_webui\">WebUI</string>\n    <string name=\"module_shortcut_type_action\">Action</string>\n    <string name=\"module_shortcut_icon_default\">使用默认图标</string>\n    <string name=\"module_shortcut_icon_select\">选择图标</string>\n    <string name=\"enable_web_debugging\">启用 WebView 调试</string>\n    <string name=\"enable_web_debugging_summary\">可用于调试 WebUI。请仅在需要时启用</string>\n    <string name=\"settings_apm_install_confirm\">安装模组前确认</string>\n    <string name=\"settings_apm_install_confirm_summary\">往背包里塞模组之前弹个确认框</string>\n    <string name=\"settings_show_more_module_info\">显示模组详细信息</string>\n    <string name=\"settings_show_more_module_info_summary\">在模组列表里显示模组ID和占用的箱子空间</string>\n    <string name=\"settings_apm_stay_on_page\">别自动返回</string>\n    <string name=\"settings_apm_stay_on_page_summary\">模组操作完之后留在当前页面，别自己跑回去</string>\n    <string name=\"settings_enable_module_shortcut_add\">启用快捷添加按钮</string>\n    <string name=\"settings_enable_module_shortcut_add_summary\">显示 WebUI 快捷方式添加按钮</string>\n    <string name=\"settings_module_sort_optimization\">模组排序</string>\n    <string name=\"settings_module_sort_optimization_summary\">带WebUI和Action的模组排到前面去</string>\n    <string name=\"settings_fold_system_module\">折叠模组卡片</string>\n    <string name=\"settings_fold_system_module_summary\">点模组卡片来展开/收起操作按钮</string>\n    <string name=\"settings_simple_list_bottom_bar\">简约模组底栏</string>\n    <string name=\"settings_simple_list_bottom_bar_summary\">模组操作只用纯图标按钮，跟APatch学的</string>\n    <string name=\"settings_spliced_card_group\">拼接模组卡片</string>\n    <string name=\"settings_spliced_card_group_summary\">模组列表用连续拼接卡片样式</string>\n    <string name=\"apm_install_confirm_title\">确认安装模组</string>\n    <string name=\"apm_install_confirm_content\">真的要把 %s 装进背包吗？</string>\n    <string name=\"apm_disable_all_title\">把模组全禁了</string>\n    <string name=\"apm_disable_all_confirm\">确定要把所有模组都禁了吗？全部停用哦</string>\n    <string name=\"apm_enable_module_banner\">显示模组横幅</string>\n    <string name=\"apm_enable_module_banner_summary\">给支持的模组加个炫酷的横幅图</string>\n    <string name=\"apm_enable_folk_banner\">自定义模组横幅</string>\n    <string name=\"apm_enable_folk_banner_summary\">长按模组卡片挑一张横幅图</string>\n    <string name=\"apm_folk_banner_title\">FolkBanner</string>\n    <string name=\"apm_folk_banner_select\">选择图片</string>\n    <string name=\"apm_folk_banner_clear\">清除 FolkBanner</string>\n    <string name=\"apm_folk_banner_saved\">已为 %s 设置 FolkBanner</string>\n    <string name=\"apm_folk_banner_cleared\">已为 %s 清除 FolkBanner</string>\n    <string name=\"apm_folk_banner_failed\">为 %s 设置 FolkBanner 失败</string>\n    <string name=\"apm_banner_api_mode\">API模式</string>\n    <string name=\"apm_banner_api_mode_summary\">使用随机图API或本地目录为模组提供横幅</string>\n    <string name=\"apm_banner_api_source\">配置API源</string>\n    <string name=\"apm_banner_api_source_hint\">输入API地址或本地路径</string>\n    <string name=\"apm_banner_api_source_saved\">API源已保存</string>\n    <string name=\"apm_banner_api_source_not_configured\">未配置</string>\n    <string name=\"apm_banner_api_url_configured\">API地址已配置</string>\n    <string name=\"apm_banner_local_dir_configured\">本地目录已配置</string>\n    <string name=\"apm_banner_clear_cache\">清除缓存</string>\n    <string name=\"apm_banner_cache_cleared\">横幅缓存已清除</string>\n    <string name=\"apm_banner_api_config_title\">配置API源</string>\n    <string name=\"apm_banner_api_config_desc\">输入随机图API地址或本地目录路径，每个模组将获得唯一的横幅图片</string>\n    <string name=\"apm_banner_api_examples_title\">示例：</string>\n    <string name=\"apm_banner_api_examples\">API: https://picsum.photos/800/400\\n本地: /sdcard/Pictures/Banners</string>\n    <!-- API 接口市场 -->\n    <string name=\"apm_api_marketplace_title\">API 接口市场</string>\n    <string name=\"apm_api_preview\">预览</string>\n    <string name=\"apm_api_apply\">应用</string>\n    <string name=\"apm_api_preview_title\">API 预览</string>\n    <string name=\"apm_api_preview_failed\">预览加载失败</string>\n    <string name=\"apm_api_url_label\">API 地址：</string>\n    <string name=\"apm_api_apply_success\">API 源已成功应用</string>\n    <string name=\"apm_api_verifying\">验证中…</string>\n    <string name=\"apm_api_retry\">重试</string>\n    <string name=\"apm_api_empty\">暂无 API 源</string>\n    <string name=\"settings_banner_custom_opacity\">横幅透明度</string>\n    <string name=\"settings_banner_custom_opacity_summary\">自定义横幅透明度，不再跟随壁纸模式</string>\n    <string name=\"settings_banner_opacity\">横幅不透明度</string>\n\n    <string name=\"settings_donot_store_superkey\">不把OP密钥存本地箱子</string>\n    <string name=\"settings_donot_store_superkey_summary\">每次打开管理器都得重新验证OP密钥，安全第一</string>\n\n    <string name=\"mode_select_page_title\">合成安装</string>\n    <string name=\"mode_select_page_patch_and_install\">锻造并安装到设备</string>\n    <string name=\"mode_select_page_select_file\">选一个要锻造的基岩镜像</string>\n    <string name=\"restore_select_file\">选一个存档备份文件来恢复</string>\n    <string name=\"mode_select_page_install_inactive_slot\">装到备用槽位（OTA后用这个）</string>\n    <string name=\"mode_select_page_install_inactive_slot_warning\">重生后会**强制**进入当前没用的那个槽位！\\n只在OTA完成后用这个。\\n继续吗？</string>\n    <string name=\"mode_select_page_select_kpimg\">使用本地补丁文件(KPimg)</string>\n    <string name=\"patch_custom_kpimg_label\">自定义 KPimg</string>\n    <string name=\"patch_custom_kpimg_file\">文件: %s</string>\n    <string name=\"patch_select_kpimg_btn\">选择 KPimg</string>\n\n    <string name=\"home_dialog_auth_fail_title\">身份验证失败</string>\n    <string name=\"home_dialog_auth_fail_content\">OP密钥验证失败，FolkPatch 无法激活！\n以下是失败的可能原因——请逐一排查：\n\n\\n1. 您可能根本没用 KernelPatch 锻造过 boot.img，或者忘了做过什么。\n\\n2. 锻造好的 boot.img 还躺在电脑里，根本没刷进设备。\n\\n3. OP密钥输错了，或者夹杂了来自末地的特殊字符。\n\\n4. 您的设备可能不兼容 FolkPatch 和 KernelPatch。\n\\n5. 某些模组可能屏蔽了 FolkPatch，导致它被踢出了世界。\n\\n请仔细检查后重试。如果仍然不行，到官方仓库的 issue 页面提问。\n祝你好运！点击下方的Wiki按钮也没错！\n</string>\n\n    <string name=\"home_more_menu_feedback_or_suggestion\">反馈 / 建议</string>\n    <string name=\"home_more_menu_about\">世界概况</string>\n    <string name=\"home_more_menu_document\">Wiki百科</string>\n\n    <string name=\"home_dialog_uninstall_title\">卸载模组</string>\n\n    <string name=\"home_dialog_uninstall_ap_only\">只卸补丁</string>\n    <string name=\"home_dialog_uninstall_all\">全卸了</string>\n    <string name=\"home_dialog_uninstall_ap_only_desc\">只卸掉红石系统补丁，管理器留着</string>\n    <string name=\"home_dialog_uninstall_all_desc\">卸掉红石系统补丁，走完整卸载流程</string>\n\n    <string name=\"kpm_control_dialog_title\">红石信号控制台</string>\n    <string name=\"kpm_control_dialog_content\">输入红石信号参数：</string>\n    <string name=\"kpm_control_paramters\">红石参数</string>\n    <string name=\"kpm_control_outMsg\">信号输出</string>\n    <string name=\"kpm_control_ok\">红石亮了！</string>\n    <string name=\"kpm_control_failed\">红石没反应！</string>\n\n    <string name=\"settings_night_mode_follow_sys\">夜间模式跟着系统走</string>\n    <string name=\"settings_night_mode_follow_sys_summary\">系统切夜间模式就跟着切</string>\n    <string name=\"settings_night_theme_enabled\">夜间模式</string>\n\n    <string name=\"settings_use_system_color_theme\">系统颜色主题</string>\n    <string name=\"settings_use_system_color_theme_summary\">使用系统根据壁纸生成的颜色</string>\n    <string name=\"settings_custom_color_theme\">自定义颜色主题</string>\n\n    <string name=\"amber_theme\">烛光琥珀</string>\n    <string name=\"blue_theme\">青金石蓝</string>\n    <string name=\"blue_grey_theme\">蓝冰灰</string>\n    <string name=\"brown_theme\">泥土棕</string>\n    <string name=\"cyan_theme\">海晶灯青</string>\n    <string name=\"deep_orange_theme\">下界砖橙</string>\n    <string name=\"deep_purple_theme\">末地紫</string>\n    <string name=\"green_theme\">苦力怕绿</string>\n    <string name=\"indigo_theme\">靛蓝染料</string>\n    <string name=\"light_blue_theme\">淡蓝色染料</string>\n    <string name=\"light_green_theme\">荧光墨囊绿</string>\n    <string name=\"lime_theme\">史莱姆绿</string>\n    <string name=\"orange_theme\">橙色染料</string>\n    <string name=\"pink_theme\">粉红色染料</string>\n    <string name=\"purple_theme\">紫色染料</string>\n    <string name=\"red_theme\">红石粉红</string>\n    <string name=\"sakura_theme\">樱花染料</string>\n    <string name=\"teal_theme\">青绿色染料</string>\n    <string name=\"yellow_theme\">蒲公英黄</string>\n    <string name=\"ink_wash_theme\">墨囊水墨</string>\n    <string name=\"theme_color\">主题颜色</string>\n    <string name=\"theme_light\">浅色</string>\n    <string name=\"theme_dark\">深色</string>\n    <string name=\"theme_system\">系统</string>\n    <string name=\"loading_modules\">正在获取模块…</string>\n    <string name=\"loading_scripts\">正在查找脚本…</string>\n    <string name=\"loading_themes\">正在加载主题…</string>\n    <string name=\"loading_apis\">正在加载 API 源…</string>\n\n    <string name=\"about_app_desc\">基于 KernelPatch 的 Root 实现，不用重编基岩核心就能Hook内核函数，挺好用的。</string>\n    <string name=\"about_powered_by\">由 %1$s 驱动</string>\n    <string name=\"about_telegram_group\">Telegram 群组</string>\n\n    <!-- 在线模组 -->\n    <string name=\"online_module_title\">在线模组仓库</string>\n    <string name=\"online_module_download_start\">开始搬运模组: %s</string>\n    <string name=\"online_module_download_complete\">搬运完成: %s</string>\n    <string name=\"online_module_download_notification\">正在搬运 %s。看看通知栏的进度，下好了去下载文件夹找</string>\n\n    <!-- 在线红石组件 -->\n    <string name=\"online_kpm_title\">在线红石组件仓库</string>\n    <string name=\"online_kpm_download_start\">开始搬运红石: %s</string>\n    <string name=\"online_kpm_download_complete\">搬运完成: %s</string>\n    <string name=\"online_kpm_download_notification\">正在搬运 %s。看看通知栏的进度，下好了去下载文件夹找</string>\n\n    <!-- 在线数据包 -->\n    <string name=\"online_script_title\">在线数据包市场</string>\n    <string name=\"online_script_download_start\">开始搬运数据包: %s</string>\n    <string name=\"online_script_download_complete\">搬运完成: %s</string>\n    <string name=\"online_script_download_notification\">正在搬运 %s</string>\n\n    <!-- 崩溃报告 -->\n    <string name=\"crash_handle_title\">崩溃日志（死亡记录）</string>\n    <string name=\"crash_handle_copy\">复制死亡记录</string>\n\n    <!-- 关于页面 -->\n    <string name=\"about_app_version\">应用版本: %s</string>\n    <string name=\"about_github\">GitHub</string>\n    <string name=\"about_telegram_channel\">Telegram 频道</string>\n\n    <string name=\"settings_app_dpi\">渲染距离</string>\n    <string name=\"dpi_apply_settings\">确定</string>\n    <string name=\"dpi_confirm_title\">确认更改DPI</string>\n    <string name=\"dpi_confirm_message\">将渲染距离从%1$s更改为%2$s？</string>\n    <string name=\"settings_magic_mount\">Folk Mount API</string>\n    <string name=\"settings_magic_mount_summary\">开启内置的模组区块挂载系统</string>\n    <string name=\"settings_new_app_profile_mode\">新模组默认模式</string>\n    <string name=\"settings_new_app_profile_normal\">生存</string>\n    <string name=\"settings_new_app_profile_root\">创造</string>\n    <string name=\"settings_new_app_profile_exclude\">移除</string>\n    <string name=\"settings_hide_service\">FolkPatch Hide</string>\n    <string name=\"settings_hide_service_summary\">把Bootloader解锁状态藏起来，别让别人看到</string>\n    <string name=\"settings_app_title\">应用名称</string>\n    <string name=\"app_title_custom\">自定义</string>\n    <string name=\"settings_custom_app_title\">设置应用名字</string>\n    <string name=\"custom_app_title_dialog_title\">自定义应用名称</string>\n    <string name=\"custom_app_title_dialog_hint\">输入应用名称</string>\n    <string name=\"custom_app_title_dialog_confirm\">确定</string>\n    <string name=\"custom_app_title_dialog_empty\">名称不能为空</string>\n    <string name=\"cancel\">取消</string>\n    <string name=\"settings_custom_background\">自定义背景皮肤</string>\n    <string name=\"settings_custom_background_enabled\">背景皮肤已装备</string>\n    <string name=\"settings_custom_background_opacity\">卡片不透明度</string>\n    <string name=\"settings_custom_background_blur\">背景迷雾浓度</string>\n    <string name=\"settings_custom_background_dim\">背景暗度</string>\n    <string name=\"settings_custom_background_dual_dim\">昼夜双切暗度</string>\n    <string name=\"settings_custom_background_dual_dim_desc\">根据亮色和暗色主题自动调整背景暗度</string>\n    <string name=\"settings_custom_background_day_dim\">白天模式暗度</string>\n    <string name=\"settings_custom_background_night_dim\">夜间模式暗度</string>\n    <string name=\"settings_multi_background_mode\">多维度背景</string>\n    <string name=\"settings_multi_background_mode_summary\">不同维度页面用不同的背景皮肤</string>\n    <string name=\"settings_select_home_background\">主世界背景</string>\n    <string name=\"settings_select_kernel_background\">下界背景</string>\n    <string name=\"settings_select_superuser_background\">末地背景</string>\n    <string name=\"settings_select_system_module_background\">模组背景</string>\n    <string name=\"settings_select_settings_background\">附魔台背景</string>\n\n    <string name=\"settings_advanced_title_style\">高级标题样式</string>\n    <string name=\"settings_advanced_title_style_summary\">使用自定义图片替换顶部标题</string>\n    <string name=\"settings_advanced_title_style_enabled\">高级标题样式已启用</string>\n    <string name=\"settings_select_title_image\">选择标题图片</string>\n    <string name=\"settings_title_image_selected\">已选择标题图片</string>\n    <string name=\"settings_title_image_saved\">标题图片保存成功</string>\n    <string name=\"settings_title_image_error\">保存标题图片失败</string>\n    <string name=\"settings_clear_title_image\">清除标题图片</string>\n    <string name=\"settings_clear_title_image_confirm\">确定要清除标题图片吗？</string>\n    <string name=\"settings_title_image_cleared\">标题图片已清除</string>\n    <string name=\"settings_title_image_day_opacity\">白天模式透明度</string>\n    <string name=\"settings_title_image_night_opacity\">夜间模式透明度</string>\n    <string name=\"settings_title_image_dim\">背景暗度</string>\n    <string name=\"settings_title_image_offset_x\">水平位置</string>\n\n    <!-- 字体设置 -->\n    <string name=\"settings_custom_font\">自定义字体</string>\n    <string name=\"settings_select_font_file\">选择字体文件</string>\n    <string name=\"settings_custom_font_enabled\">已启用自定义字体</string>\n    <string name=\"settings_custom_font_summary\">使用自定义TTF字体</string>\n    <string name=\"settings_font_selected\">已选择自定义字体</string>\n    <string name=\"settings_custom_font_error\">保存字体文件失败</string>\n    <string name=\"settings_custom_font_saved\">自定义字体保存成功</string>\n    <string name=\"settings_clear_font\">恢复默认字体</string>\n    <string name=\"settings_clear_font_confirm\">确定要恢复默认字体吗？</string>\n    <string name=\"settings_font_cleared\">已恢复默认字体</string>\n    <string name=\"settings_font_select_hint\">请选择一个TTF字体文件</string>\n    <string name=\"settings_select_background_image\">选择背景图片</string>\n    <string name=\"settings_custom_background_summary\">设置自定义背景皮肤</string>\n    <string name=\"settings_custom_background_saved\">背景保存成功</string>\n    <string name=\"settings_custom_background_error\">保存背景失败</string>\n    <string name=\"settings_background_selected\">已选择背景</string>\n    <string name=\"settings_background_image_cleared\">已移除背景</string>\n    <string name=\"settings_clear_background\">清除背景</string>\n    <string name=\"settings_clear_background_confirm\">确定要清除背景图片吗？</string>\n\n    <string name=\"settings_alt_icon\">备用图标</string>\n    <string name=\"alt_icon_summary\">使用备选启动器图标</string>\n\n    <!-- 视频背景 -->\n    <string name=\"settings_video_background\">视频背景</string>\n    <string name=\"settings_video_background_summary\">使用视频作为背景</string>\n    <string name=\"settings_select_video\">选择视频</string>\n    <string name=\"settings_video_selected\">已选择视频</string>\n    <string name=\"settings_clear_video_background\">清除视频壁纸</string>\n    <string name=\"settings_clear_video_background_confirm\">确定要清除视频壁纸吗？</string>\n    <string name=\"settings_video_background_enabled\">视频背景已启用</string>\n    <string name=\"settings_video_volume\">视频音量</string>\n\n    <string name=\"su_exclude_all_title\">批量屏蔽</string>\n    <string name=\"su_exclude_all_confirm\">确定要将所有未授权OP的应用设为屏蔽吗？</string>\n\n    <!-- 隐藏设定 -->\n    <string name=\"home_hide_su_path\">藏起OP指令位置</string>\n    <string name=\"home_hide_kpatch_version\">藏起基岩补丁版本</string>\n    <string name=\"home_hide_fingerprint\">藏起世界种子</string>\n    <string name=\"home_hide_zygisk\">藏起Zygisk实现</string>\n    <string name=\"home_hide_mount\">藏起挂载实现</string>\n    <string name=\"home_hide_su_path_summary\">在信息面板里藏起OP指令位置</string>\n    <string name=\"home_hide_kpatch_version_summary\">在信息面板里藏起基岩补丁版本</string>\n    <string name=\"home_hide_fingerprint_summary\">在信息面板里藏起世界种子</string>\n    <string name=\"home_hide_zygisk_summary\">在信息面板里藏起Zygisk实现</string>\n    <string name=\"home_hide_mount_summary\">在信息面板里藏起挂载实现</string>\n    <!-- KPM 自动加载 -->\n    <string name=\"kpm_autoload_title\">红石组件自动充能</string>\n    <string name=\"kpm_autoload_enabled\">开启自动充能</string>\n    <string name=\"kpm_autoload_enabled_summary\">世界加载时自动给红石组件充能</string>\n    <string name=\"kpm_autoload_json_config\">JSON配置</string>\n    <string name=\"kpm_autoload_json_label\">JSON配置</string>\n    <string name=\"kpm_autoload_json_placeholder\">{\n  \"enabled\": true,\n  \"kpmPaths\": [\n    \"/path/to/module1.kpm\",\n    \"/path/to/module2.kpm\"\n  ]\n}</string>\n    <string name=\"kpm_autoload_json_error\">无效的JSON格式</string>\n    <string name=\"kpm_autoload_json_helper\">输入有效的JSON，包含红石组件文件路径数组</string>\n    <string name=\"kpm_autoload_save\">保存配置</string>\n    <string name=\"kpm_autoload_save_confirm\">保存自动充能配方？</string>\n    <string name=\"kpm_autoload_save_success\">配方保存成功</string>\n    <string name=\"kpm_autoload_save_failed\">配方保存失败</string>\n    <string name=\"kpm_autoload_visual_mode\">可视化模式</string>\n    <string name=\"kpm_autoload_json_mode\">JSON配方模式</string>\n\n    <!-- 模组批量安装 -->\n    <string name=\"apm_bulk_install_title\">模组批量合成</string>\n    <string name=\"apm_bulk_install_list_title\">模组背包</string>\n    <string name=\"apm_bulk_install_add\">往背包里加模组</string>\n    <string name=\"apm_bulk_install_empty\">背包是空的</string>\n    <string name=\"apm_bulk_install_action\">全部合成！</string>\n    <string name=\"apm_bulk_install_log_title\">批量合成日志</string>\n    <string name=\"apm_bulk_install_log_start\">合成台上叮叮当当…</string>\n    <string name=\"apm_bulk_install_log_installing\">正在合成 %1$s</string>\n    <string name=\"apm_batch_install_full_process\">完整批量合成流程</string>\n    <string name=\"apm_batch_install_full_process_summary\">用完整流程来批量合成安装模组</string>\n    <string name=\"next_module\">下一个模组</string>\n    <string name=\"apm_bulk_install_log_installed\">%s 合成完毕！</string>\n    <string name=\"apm_bulk_install_log_done\">全部合成完毕！大功告成！</string>\n    <string name=\"apm_bulk_install_first_use_text\">这个功能让你一次性把多个模组全部合成安装，适合不需要按键操作的模组。如果有需要按音量键之类的模组，去设置里开启完整流程模式</string>\n    <string name=\"apm_bulk_install_remove\">从背包扔掉</string>\n    <string name=\"apm_first_use_title\">欢迎来到模组世界</string>\n    <string name=\"apm_first_use_text\">欢迎来到模组世界！这里用的是兼容Magisk生态的模组，右下角按钮可以安装单个模组，顶部有批量安装器。还有一键备份全部模组到末影箱的功能，不过不是所有模组都能用这个方式备份，建议自己多留几份</string>\n    <string name=\"kpm_autoload_add_kpm\">添加红石组件</string>\n    <string name=\"kpm_autoload_remove_kpm\">删除</string>\n    <string name=\"kpm_autoload_edit_kpm\">编辑</string>\n    <string name=\"kpm_autoload_edit_dialog_title\">编辑 KPM</string>    <string name=\"kpm_autoload_event_label\">事件:</string>    <string name=\"kpm_autoload_args_label\">参数:</string>    <string name=\"kpm_autoload_event_service\">service</string>    <string name=\"kpm_autoload_event_post_fs_data\">post-fs-data</string>\n    <string name=\"kpm_autoload_kpm_list_title\">红石组件列表</string>\n    <string name=\"kpm_autoload_no_kpm_added\">未添加红石组件</string>\n    <string name=\"kpm_autoload_kpm_path\">红石组件路径</string>\n    <string name=\"kpm_autoload_file_not_found\">文件未找到</string>\n    <string name=\"kpm_autoload_select_kpm_file\">选择红石组件文件</string>\n    <string name=\"kpm_autoload_first_time_title\">关于红石组件自动充能</string>\n    <string name=\"kpm_autoload_first_time_message\">这个功能可以让你重生时自动给配置里的所有红石组件充能，比直接嵌进基岩核心方便多了。\n\n比如，防止区块被修改的红石组件只能临时充能用，嵌进去会导致设备变砖无法重生。或者你不想破坏BOOT区块，也可以用这个方式快速充能。\n\n注意：必须完全退出应用再重新进入才会触发充能。重生时一般也会自动充能的，管理器黑屏一会儿是正常的，用的纯后台充能方案，记得下拉刷新区块看看红石亮没亮！</string>\n    <string name=\"kpm_autoload_first_time_confirm\">懂了</string>\n    <string name=\"kpm_autoload_do_not_show_again\">别再弹了</string>\n\n    <string name=\"kpm_page_first_time_title\">警告：危险操作！</string>\n    <string name=\"kpm_page_first_time_message\">红石组件是直接改Boot区块的硬核操作，不像模组那样有救砖机制。出了问题只能进Fastboot修。建议先临时充能测试没问题再嵌进Boot。如果是只能临时充能的组件，可以用自动充能功能。如果你不懂红石组件，千万别碰！</string>\n    <!-- 模组备份/恢复 -->\n    <string name=\"apm_backup_title\">备份模组到末影箱</string>\n    <string name=\"apm_restore_title\">从末影箱恢复模组</string>\n    <string name=\"apm_backup_success\">备份成功！物品已存入末影箱</string>\n    <string name=\"apm_restore_success\">恢复成功！物品已从末影箱取出</string>\n    <string name=\"apm_backup_failed\">备份失败！末影箱装不下了</string>\n    <string name=\"apm_restore_failed\">恢复失败！末影箱打不开</string>\n    <string name=\"apm_backup_failed_msg\">备份失败：%s</string>\n    <string name=\"apm_restore_failed_msg\">恢复失败：%s</string>\n    <string name=\"apm_copy_list_title\">复制列表</string>\n    <string name=\"apm_copy_list_success\">模组列表已复制</string>\n\n    <!-- 自动备份 -->\n    <string name=\"settings_auto_backup_module\">自动备份模组到末影箱</string>\n    <string name=\"settings_auto_backup_module_summary\">安装模组时自动备份到末影箱</string>\n    <string name=\"settings_open_backup_dir\">打开末影箱</string>\n    <string name=\"backup_dir_empty\">末影箱是空的</string>\n    <string name=\"backup_dir_open_failed\">打不开末影箱</string>\n    <string name=\"auto_backup_failed\">自动备份失败: %s</string>\n    <string name=\"auto_backup_success\">自动备份成功: %s</string>\n    <string name=\"settings_auto_backup_boot\">自动备份Boot区块</string>\n    <string name=\"settings_auto_backup_boot_summary\">自动将Boot区块备份到本地存储 (Downloads/FolkPatch/BootBackups)</string>\n\n    <!-- 主世界布局 -->\n    <string name=\"settings_home_layout_style\">主世界布局</string>\n    <string name=\"settings_home_layout_default\">ListUI</string>\n    <string name=\"settings_home_layout_grid\">GridUI</string>\n    <string name=\"settings_home_layout_focus\">FocusUI</string>\n    <string name=\"settings_home_layout_sign\">SignUI</string>\n    <string name=\"settings_home_layout_circle\">CircleUI</string>\n    <string name=\"settings_home_layout_dashboard_pro\">DashboardUI</string>\n    <string name=\"unofficial_version_title\">这不是正版！</string>\n    <string name=\"unofficial_version_message\">你用的这个 FolkPatch 不是官方的，可能不安全，建议去官方渠道下正版。</string>\n    <string name=\"go_to_github\">前往Github</string>\n    <string name=\"settings_save_theme\">保存资源包</string>\n    <string name=\"settings_import_theme\">导入资源包</string>\n    <string name=\"settings_theme_saved\">资源包已保存</string>\n    <string name=\"settings_theme_imported\">资源包已导入</string>\n    <string name=\"settings_theme_save_failed\">保存资源包失败</string>\n    <string name=\"settings_theme_import_failed\">导入资源包失败</string>\n    <string name=\"settings_reset_theme\">恢复默认资源包</string>\n    <string name=\"settings_reset_theme_confirm\">确定要将所有资源包设置恢复为默认值吗？这将清除所有自定义背景、字体、音乐和音效。</string>\n    <string name=\"settings_theme_reset\">资源包已恢复默认</string>\n    <string name=\"settings_theme_reset_failed\">恢复资源包失败</string>\n\n    <!-- 主题（资源包） -->\n    <string name=\"theme_export_title\">导出资源包</string>\n    <string name=\"theme_import_title\">导入资源包</string>\n    <string name=\"theme_name\">资源包名称</string>\n    <string name=\"theme_type\">适用平台</string>\n    <string name=\"theme_type_phone\">手机版（Java版）</string>\n    <string name=\"theme_type_tablet\">平板版（基岩版）</string>\n    <string name=\"theme_version\">版本</string>\n    <string name=\"theme_author\">作者</string>\n    <string name=\"theme_description\">描述</string>\n    <string name=\"theme_export_action\">导出</string>\n    <string name=\"theme_import_action\">导入</string>\n    <string name=\"theme_import_confirm\">确定要导入此资源包吗？</string>\n    <string name=\"theme_info\">资源包信息</string>\n\n    <!-- 工作卡片背景 -->\n    <string name=\"settings_grid_working_card_background\">合成台面板背景</string>\n    <string name=\"settings_grid_working_card_background_enabled\">自定义面板背景已装备</string>\n    <string name=\"settings_grid_working_card_background_summary\">给合成台面板换个自定义背景</string>\n    <string name=\"settings_grid_working_card_background_selected\">背景已选择</string>\n    <string name=\"settings_grid_working_card_dual_opacity\">启用昼夜双切透明度</string>\n    <string name=\"settings_grid_working_card_dual_opacity_desc\">根据亮色和暗色主题自动调整卡片透明度</string>\n    <string name=\"settings_grid_working_card_day_opacity\">白天模式透明度</string>\n    <string name=\"settings_grid_working_card_night_opacity\">夜间模式透明度</string>\n    <string name=\"settings_clear_grid_working_card_background\">清除卡片背景</string>\n    <string name=\"settings_clear_grid_working_card_background_confirm\">确定要清除卡片背景图片吗？</string>\n    <string name=\"settings_grid_working_card_background_saved\">卡片背景保存成功</string>\n    <string name=\"settings_grid_working_card_background_error\">卡片背景保存失败</string>\n    <string name=\"settings_grid_working_card_background_cleared\">卡片背景已清除</string>\n\n    <!-- 生物识别 -->\n    <string name=\"action_biometric\">生物验证（就像村民交易确认）</string>\n    <string name=\"msg_biometric\">请验证你的身份</string>\n    <string name=\"settings_biometric_login\">生物验证</string>\n    <string name=\"settings_biometric_login_summary\">打开管理器时要求生物验证</string>\n    <string name=\"settings_strong_biometric\">高强度生物验证</string>\n    <string name=\"settings_strong_biometric_summary\">安装、拆除或禁用模组时都得验证</string>\n\n    <!-- 主题商店（资源包商店） -->\n    <string name=\"theme_store_title\">资源包商店</string>\n    <string name=\"theme_source_official\">官方</string>\n    <string name=\"theme_source_third_party\">社区</string>\n    <string name=\"theme_source_local\">本地</string>\n    <string name=\"theme_store_author\">作者: %s</string>\n    <string name=\"theme_store_version\">版本: %s</string>\n    <string name=\"theme_source\">来源</string>\n    <string name=\"theme_store_download_failed\">搬运失败，掉进虚空了</string>\n    <string name=\"theme_store_download\">搬运</string>\n    <string name=\"theme_store_search_hint\">搜索资源包...</string>\n    <string name=\"search_modules\">搜索模组...</string>\n    <string name=\"search_scripts\">搜索数据包...</string>\n\n    <!-- 更新检查 -->\n    <string name=\"settings_auto_update_check\">自动检查更新</string>\n    <string name=\"settings_auto_update_check_summary\">打开应用时自动检查是否有新版本</string>\n    <string name=\"settings_check_update\">检查更新</string>\n    <string name=\"update_available_title\">有新版本了</string>\n    <string name=\"update_available_message\">你的版本有点旧了，要不要更新一下？</string>\n    <string name=\"update_action\">更新</string>\n    <string name=\"update_close\">算了</string>\n    <string name=\"update_latest\">已经是最新版了</string>\n    <string name=\"update_error\">检查更新炸了</string>\n    <!--折叠选项-->\n    <string name=\"settings_category_general\">游戏规则</string>\n    <string name=\"settings_app_list_loading_scheme\">应用列表加载方案</string>\n    <string name=\"settings_app_list_loading_scheme_summary\">选择如何加载应用列表</string>\n    <string name=\"app_list_loading_scheme_root_service\">RootService (默认)</string>\n    <string name=\"app_list_loading_scheme_package_manager\">PackageManager (系统API)</string>\n    <string name=\"app_list_loading_scheme_dialog_title\">加载方案</string>\n    <string name=\"superuser\">OP权限</string>\n    <string name=\"module\">模组</string>\n    <string name=\"settings_category_appearance\">外观皮肤</string>\n    <string name=\"settings_category_behavior\">玩家行为</string>\n\n    <!-- 外观子分类 -->\n    <string name=\"settings_appearance_font\">字体设置</string>\n    <string name=\"settings_appearance_font_summary\">自定义字体配置</string>\n    <string name=\"settings_appearance_theme\">资源包主题</string>\n    <string name=\"settings_appearance_theme_summary\">资源包商店、保存、导入和重置</string>\n    <string name=\"settings_appearance_banner\">横幅设置</string>\n    <string name=\"settings_appearance_banner_summary\">模组横幅配置</string>\n    <string name=\"settings_appearance_layout\">布局设置</string>\n    <string name=\"settings_appearance_layout_summary\">主世界布局、导航和卡片自定义</string>\n    <string name=\"settings_appearance_background\">背景皮肤</string>\n    <string name=\"settings_appearance_background_summary\">自定义背景、视频和多维度背景</string>\n    <string name=\"settings_appearance_night_mode\">夜间模式</string>\n    <string name=\"settings_appearance_night_mode_summary\">夜间主题和颜色配置</string>\n    <string name=\"settings_amoled_theme\">AMOLED 纯黑主题</string>\n    <string name=\"settings_amoled_theme_desc\">为深色模式使用纯黑背景</string>\n    <string name=\"settings_switch_icon\">启用按钮指示器</string>\n    <string name=\"settings_switch_icon_desc\">为开关按钮添加状态指示图标</string>\n    <string name=\"settings_use_legacy_su_page\">单页OP权限授权</string>\n    <string name=\"settings_use_legacy_su_page_summary\">OP权限页面改用单页授权设计</string>\n    <string name=\"settings_category_module\">模组</string>\n    <string name=\"settings_category_security\">服务器安全</string>\n    <string name=\"settings_category_function\">服务器功能</string>\n\n    <!-- 背景音乐（唱片机） -->\n    <string name=\"settings_background_music\">唱片机</string>\n    <string name=\"settings_background_music_summary\">应用在前台时播放唱片</string>\n    <string name=\"settings_background_music_playing\">正在播放: %s</string>\n    <string name=\"settings_background_music_enabled\">唱片机已开</string>\n    <string name=\"settings_select_music_file\">选择唱片</string>\n    <string name=\"settings_music_selected\">唱片已选择</string>\n    <string name=\"settings_clear_music\">取出唱片</string>\n    <string name=\"settings_clear_music_confirm\">确定要从唱片机中取出唱片吗？</string>\n    <string name=\"settings_music_auto_play\">自动播放</string>\n    <string name=\"settings_music_auto_play_summary\">进入主世界时自动播放唱片</string>\n    <string name=\"settings_music_looping\">循环播放</string>\n    <string name=\"settings_music_looping_summary\">唱片循环播放</string>\n    <string name=\"settings_music_volume\">音量</string>\n    <string name=\"settings_music_saved\">唱片已保存</string>\n    <string name=\"settings_music_save_error\">保存唱片失败</string>\n    <string name=\"settings_music_cleared\">唱片已取出</string>\n    <string name=\"settings_category_multimedia\">唱片机与音效</string>\n    <string name=\"settings_category_general_summary\">语言、更新、SELinux、服务器调整</string>\n    <string name=\"settings_category_appearance_summary\">资源包主题、颜色、布局、背景皮肤、字体</string>\n    <string name=\"settings_category_behavior_summary\">Web调试、安装行为、主世界显示</string>\n    <string name=\"settings_category_security_summary\">生物识别、OP密钥管理</string>\n    <string name=\"settings_category_backup_summary\">本地存档、云端存档、WebDAV</string>\n    <string name=\"settings_category_module_summary\">模组信息、排序、批量安装</string>\n    <string name=\"settings_category_function_summary\">FolkPatch隐藏、Umount服务</string>\n    <string name=\"settings_category_multimedia_summary\">背景唱片、音效、振动</string>\n    <string name=\"settings_music_playback_control\">唱片机控制</string>\n\n    <!-- 主题商店筛选 -->\n    <string name=\"theme_store_filter_title\">筛选资源包</string>\n    <string name=\"theme_store_filter_author\">作者</string>\n    <string name=\"theme_store_filter_author_hint\">输入作者名称</string>\n    <string name=\"theme_store_filter_source\">来源</string>\n    <string name=\"theme_store_filter_source_all\">全部</string>\n    <string name=\"theme_store_filter_type\">设备类型</string>\n    <string name=\"theme_store_filter_apply\">应用</string>\n    <string name=\"theme_store_filter_reset\">重置</string>\n\n    <!-- 我的资源包 -->\n    <string name=\"my_themes_title\">我的资源包</string>\n    <string name=\"my_themes_empty\">背包里没有资源包</string>\n    <string name=\"my_themes_empty_action\">去商店逛逛</string>\n    <string name=\"my_themes_apply\">应用资源包</string>\n    <string name=\"my_themes_delete\">删除资源包</string>\n    <string name=\"my_themes_delete_confirm\">确定要删除此资源包吗？</string>\n    <string name=\"my_themes_deleted\">资源包已删除</string>\n    <string name=\"my_themes_applied\">资源包已应用</string>\n    <string name=\"my_themes_apply_failed\">应用资源包失败</string>\n    <string name=\"my_themes_details\">资源包详情</string>\n\n    <!-- 下载对话框 -->\n    <string name=\"theme_download_title\">正在下载资源包</string>\n    <string name=\"theme_download_completed\">下载完成</string>\n    <string name=\"theme_download_failed\">下载失败</string>\n    <string name=\"theme_download_progress\">进度</string>\n    <string name=\"theme_download_file\">资源包文件</string>\n    <string name=\"theme_download_image\">预览图片</string>\n    <string name=\"theme_download_cancel\">取消</string>\n    <string name=\"theme_download_pause\">暂停</string>\n    <string name=\"theme_download_resume\">继续</string>\n    <string name=\"theme_download_retry\">重试</string>\n    <string name=\"theme_download_apply\">应用资源包</string>\n    <string name=\"theme_download_go_to_my_themes\">我的资源包</string>\n    <string name=\"theme_download_retrying\">正在重试 (%d/3)...</string>\n    <string name=\"theme_download_preparing\">准备下载...</string>\n    <string name=\"theme_download_downloading_file\">正在下载资源包文件...</string>\n    <string name=\"theme_download_downloading_image\">正在下载预览图片...</string>\n    <string name=\"theme_download_finalizing\">正在完成...</string>\n\n    <!-- 文件选择器 -->\n    <string name=\"file_picker_permission_required\">需要权限！</string>\n    <string name=\"file_picker_permission_desc\">想翻箱子的话得先给我\"所有文件访问\"权限</string>\n    <string name=\"file_picker_grant_permission\">给权限</string>\n    <string name=\"file_picker_cancel\">算了</string>\n    <string name=\"file_picker_internal_storage\">内部存储</string>\n    <string name=\"file_picker_no_files\">箱子是空的，啥也没有</string>\n\n    <!-- 主世界V3 -->\n    <string name=\"home_device_status_title\">设备状态</string>\n    <string name=\"home_device_status_battery_temp\">电池温度（小心别炸了）</string>\n    <string name=\"home_device_status_cpu_load\">CPU负载（服务器TPS）</string>\n    <string name=\"home_device_status_battery_level\">电池电量（生命值）</string>\n    <string name=\"home_storage_title\">箱子存储</string>\n    <string name=\"home_storage_internal\">主世界大箱子</string>\n    <string name=\"home_storage_ram\">运行内存（经验槽）</string>\n    <string name=\"home_storage_zram\">ZRAM（压缩空间）</string>\n    <string name=\"home_storage_swap\">交换空间（末影箱暂存）</string>\n    <string name=\"home_info_kernel\">基岩核心</string>\n    <string name=\"home_info_superkey\">OP密钥</string>\n    <string name=\"home_info_auth_auth\">已验证</string>\n    <string name=\"home_info_auth_na\">还没验证</string>\n    <string name=\"home_info_device_slot\">设备槽位</string>\n    <string name=\"home_info_device_model\">设备型号</string>\n    <string name=\"home_info_running_mode\">运行模式</string>\n    <string name=\"home_info_mode_full\">创造模式</string>\n    <string name=\"home_info_mode_half\">冒险模式</string>\n    <string name=\"home_zygisk_implement\">Zygisk实现</string>\n    <string name=\"home_mount_implement\">挂载实现</string>\n\n    <string name=\"settings_list_card_hide_status_badge\">使用经典表情</string>\n    <string name=\"settings_list_card_hide_status_badge_summary\">状态徽章即刻变身经典方块表情</string>\n    <string name=\"settings_custom_badge_text\">自定义徽章文字</string>\n    <string name=\"settings_custom_badge_text_summary\">修改状态徽章的文本，仅供娱乐</string>\n\n    <!-- 安装模式选择 -->\n    <string name=\"kp_install_methods\">KernelPatch 锻造/安装</string>\n    <string name=\"restore_boot_methods\">选择一个启动来恢复到引导分区</string>\n\n    <string name=\"su_backup_list\">备份列表</string>\n    <string name=\"su_restore_list\">还原列表</string>\n    <string name=\"backup_success\">备份成功</string>\n    <string name=\"restore_success\">还原成功</string>\n\n    <!-- 应用操作弹窗 -->\n    <string name=\"su_app_action_content\">选一个操作来搞</string>\n    <string name=\"su_app_action_launch\">启动应用</string>\n    <string name=\"su_app_action_force_stop\">强制关掉</string>\n    <string name=\"su_app_action_launch_success\">正在启动 %s</string>\n    <string name=\"su_app_action_force_stop_success\">%s 已经被关掉了</string>\n    <string name=\"su_app_action_failed\">搞砸了: %s</string>\n\n    <!-- SU Audit Log -->\n    <string name=\"su_audit_log_title\">授权日志</string>\n    <string name=\"su_audit_log_empty\">暂无授权记录</string>\n    <string name=\"su_audit_log_clear\">清除日志</string>\n    <string name=\"su_audit_log_clear_confirm\">确认清除所有授权记录？</string>\n    <string name=\"su_audit_action_grant\">已授权</string>\n    <string name=\"su_audit_action_revoke\">已撤销</string>\n    <string name=\"su_audit_action_exclude\">已排除</string>\n    <string name=\"su_audit_tab_usage\">使用记录</string>\n    <string name=\"su_audit_tab_operations\">操作记录</string>\n\n    <string name=\"patch_output_written_to\"> 锻造完成！输出到了 </string>\n    <string name=\"patch_write_failed\"> 合成台炸了！检查一下材料够不够</string>\n\n    <!-- 角标统计 -->\n    <string name=\"enable_badge_count\">角标统计设置</string>\n    <string name=\"enable_badge_count_summary\">配置导航项的角标统计显示</string>\n    <string name=\"badge_superuser\">显示OP权限角标</string>\n    <string name=\"badge_apm\">显示模组角标</string>\n    <string name=\"badge_kernel\">显示红石组件角标</string>\n\n    <string name=\"settings_sound_effect\">方块交互音效</string>\n    <string name=\"settings_sound_effect_summary\">操作时播放方块交互音效</string>\n    <string name=\"settings_sound_effect_enabled\">方块音效已开</string>\n    <string name=\"settings_sound_effect_playing\">当前音效: %s</string>\n    <string name=\"settings_sound_effect_source\">音效来源</string>\n    <string name=\"settings_sound_effect_source_local\">本地音效包</string>\n    <string name=\"settings_sound_effect_source_preset\">游戏内置音效</string>\n    <string name=\"settings_sound_effect_preset_title\">内置音效库</string>\n    <string name=\"settings_select_sound_effect\">选一个音效</string>\n    <string name=\"settings_sound_effect_selected\">已选好音效</string>\n    <string name=\"settings_sound_effect_scope\">生效范围</string>\n    <string name=\"settings_sound_effect_scope_global\">全局（走到哪响到哪）</string>\n    <string name=\"settings_sound_effect_scope_bottom_bar\">仅快捷栏</string>\n    <string name=\"settings_clear_sound_effect\">关掉方块音效</string>\n    <string name=\"settings_clear_sound_effect_confirm\">确定关掉方块音效？</string>\n    <string name=\"settings_sound_effect_cleared\">方块音效已关</string>\n\n    <string name=\"settings_startup_sound\">启动音效</string>\n    <string name=\"settings_startup_sound_summary\">打开应用时放一段启动音效</string>\n    <string name=\"settings_startup_sound_enabled\">启动音效已开</string>\n    <string name=\"settings_startup_sound_playing\">正在播放: %s</string>\n    <string name=\"settings_select_startup_sound\">选一个进入音效</string>\n    <string name=\"settings_startup_sound_selected\">已选好进入音效</string>\n    <string name=\"settings_clear_startup_sound\">关掉进入音效</string>\n    <string name=\"settings_clear_startup_sound_confirm\">确定关掉进入音效？</string>\n    <string name=\"settings_startup_sound_cleared\">进入音效已关</string>\n\n    <string name=\"settings_vibration\">震动反馈</string>\n    <string name=\"settings_vibration_summary\">操作的时候震一下</string>\n    <string name=\"settings_vibration_enabled\">已开</string>\n    <string name=\"settings_vibration_intensity\">触感强度</string>\n    <string name=\"settings_vibration_scope\">触感范围</string>\n    <string name=\"settings_vibration_scope_global\">全局</string>\n    <string name=\"settings_vibration_scope_bottom_bar\">仅快捷栏</string>\n\n    <!-- 备份设置 -->\n    <string name=\"settings_category_backup\">存档备份</string>\n    <string name=\"settings_enable_cloud_backup\">开启云端备份</string>\n    <string name=\"settings_enable_cloud_backup_summary\">装过的模组自动备份到云端</string>\n    <string name=\"settings_configure_webdav\">配置 WebDAV</string>\n    <string name=\"webdav_config_title\">WebDAV 配置</string>\n    <string name=\"webdav_url\">WebDAV 地址</string>\n    <string name=\"webdav_test_success\">连接成功</string>\n    <string name=\"webdav_test_failed\">连接失败: %s</string>\n    <string name=\"webdav_uploading\">正在往云端扔东西…</string>\n    <string name=\"webdav_backup_success\">云端备份搞定！</string>\n    <string name=\"webdav_backup_failed\">云端备份失败: %s</string>\n    <string name=\"save\">保存</string>\n    <string name=\"test\">测试</string>\n    <string name=\"webdav_path_label\">路径 (例如 /Backup)</string>\n    <string name=\"webdav_view_logs\">查看日志</string>\n    <string name=\"webdav_backup_logs_title\">备份日志</string>\n    <string name=\"webdav_no_logs\">暂无日志</string>\n    <string name=\"webdav_clear_logs\">清除</string>\n    <string name=\"settings_enable_local_backup\">开启本地备份</string>\n    <string name=\"settings_enable_local_backup_summary\">把模组备份到本地 (Downloads/FolkPatch/ModuleBackups)</string>\n    <string name=\"close\">关了</string>\n\n    <!-- 脚本库（数据包脚本库） -->\n    <string name=\"script_library\">数据包脚本库</string>\n    <string name=\"script_library_title\">数据包脚本库</string>\n    <string name=\"script_library_empty\">箱子里没数据包脚本，点添加按钮扔一个进去</string>\n    <string name=\"script_library_add\">往箱子里扔数据包</string>\n    <string name=\"script_library_delete\">扔进虚空</string>\n    <string name=\"script_library_confirm_delete\">确定要把这个数据包扔进虚空吗？</string>\n    <string name=\"script_library_delete_success\">数据包已掉进虚空</string>\n    <string name=\"script_library_delete_failed\">扔不掉: %s</string>\n    <string name=\"script_library_output\">命令输出</string>\n    <string name=\"script_library_no_output\">无输出</string>\n    <string name=\"script_library_save_log\">保存日志</string>\n    <string name=\"script_library_log_saved\">日志已保存至 %s</string>\n    <string name=\"script_library_log_save_failed\">保存日志失败：%s</string>\n\n    <!-- Umount 服务 -->\n    <string name=\"settings_umount_service\">Umount Service</string>\n    <string name=\"settings_umount_service_summary\">配置重生时自动卸载的挂载点</string>\n    <string name=\"umount_config_title\">Umount 配置</string>\n    <string name=\"umount_config_enabled\">启用 Umount</string>\n    <string name=\"umount_config_enabled_summary\">重生时自动卸载指定挂载点</string>\n    <string name=\"umount_config_paths_label\">挂载点路径（每行一个）</string>\n    <string name=\"umount_config_paths_placeholder\">/system/vendor/app\\n/system/vendor/priv-app</string>\n    <string name=\"umount_config_paths_helper\">每行输入一个需要卸载的挂载点路径</string>\n    <string name=\"umount_config_save\">保存</string>\n    <string name=\"umount_config_save_confirm\">确认保存配置？</string>\n    <string name=\"umount_config_save_success\">配置保存成功</string>\n    <string name=\"umount_config_save_failed\">配置保存失败</string>\n    <string name=\"settings_predictive_back\">预测性返回手势</string>\n    <string name=\"settings_predictive_back_summary\">启用 Android 14+ 的预测性返回手势动画</string>\n\n    <string name=\"home_device_status_battery_charging\">充电中（附魔中）</string>\n    <string name=\"home_device_status_cpu_temp\">CPU温度（熔炉温度）</string>\n    <string name=\"home_device_status_memory_trend\">内存趋势（经验变化）</string>\n    <string name=\"home_storage_partitions\">存储分区（世界分区）</string>\n    <string name=\"home_device_status_cpu_freq\">CPU频率（红石频率）</string>\n    <string name=\"home_network_rx\">下载（收集掉落物）</string>\n    <string name=\"home_network_tx\">上传（投掷物品）</string>\n    <string name=\"settings_home_layout_stats\">StatsUI</string>\n    <string name=\"home_stats_more_options\">更多选项（更多合成配方）</string>\n    <string name=\"home_stats_kp_label\">KP</string>\n    <string name=\"home_stats_manager_label\">管理器（服务器OP）</string>\n    <string name=\"home_device_status_gpu_load\">GPU负载（渲染负载）</string>\n    <string name=\"home_stats_kernel_modules\">内核模块（核心模组）</string>\n    <string name=\"home_stats_apm_modules\">APM模块（APM模组）</string>\n    <string name=\"home_stats_superusers\">超级用户（管理员）</string>\n    <string name=\"settings_kernel_spoof\">内核伪装配置（核心伪装）</string>\n    <string name=\"settings_kernel_spoof_summary\">伪装内核版本和构建时间（修改核心版本和时间）</string>\n    <string name=\"settings_kernel_spoof_version\">内核版本（核心版本）</string>\n    <string name=\"settings_kernel_spoof_build_time\">内核构建时间（核心构建时间）</string>\n    <string name=\"settings_kernel_spoof_restore\">恢复（复原）</string>\n\n    <string name=\"kernel_spoof_enabled\">内核伪装已启用</string>\n    <string name=\"kernel_spoof_disabled_restored\">内核伪装已停用并还原</string>\n    <string name=\"kernel_spoof_failed\">内核伪装失败: %d</string>\n    <string name=\"kernel_spoof_applied\">内核伪装已应用</string>\n\n    <string name=\"settings_path_hide\">路径隐藏（方块隐身）</string>\n    <string name=\"settings_path_hide_summary\">在核心层面隐藏文件和目录（就像方块变透明）</string>\n    <string name=\"path_hide_paths_label\">隐藏路径（每行一个，像列清单）</string>\n\n    <string name=\"path_hide_paths_helper\">每行输入一个路径，匹配的路径会返回不存在（就像找不到方块）</string>\n    <string name=\"path_hide_save\">保存（存档）</string>\n    <string name=\"path_hide_enabled\">路径隐藏已启用（方块已隐身）</string>\n    <string name=\"path_hide_disabled\">路径隐藏已禁用（方块恢复可见）</string>\n    <string name=\"path_hide_applied\">路径隐藏配置已应用（隐身效果已生效）</string>\n    <string name=\"path_hide_failed\">路径隐藏操作失败: %d</string>\n    <string name=\"path_hide_uid_mode\">UID 指定模式（精准选择方块）</string>\n    <string name=\"path_hide_uid_mode_summary\">只对指定 UID 的应用隐藏路径（像选择特定方块）</string>\n    <string name=\"path_hide_uids_label\">目标 UID（每行一个，像列清单）</string>\n    <string name=\"path_hide_uids_helper\">每行输入一个应用 UID，只有这些应用的路径会被隐藏</string>\n    <string name=\"path_hide_uid_save\">保存 UID（存档）</string>\n    <string name=\"path_hide_uid_mode_enabled\">UID 指定模式已启用（精准选择已激活）</string>\n    <string name=\"path_hide_uid_mode_disabled\">UID 指定模式已禁用（精准选择已关闭）</string>\n    <string name=\"path_hide_filter_system\">过滤系统 UID（精准排除）</string>\n    <string name=\"path_hide_filter_system_summary\">同时对 root 和系统进程（UID &lt; 10000）隐藏路径</string>\n    <string name=\"path_hide_filter_system_enabled\">系统 UID 过滤已启用（精准排除已激活）</string>\n    <string name=\"path_hide_filter_system_disabled\">系统 UID 过滤已禁用（精准排除已关闭）</string>\n    <string name=\"path_hide_filter_system_warning_title\">警告</string>\n    <string name=\"path_hide_filter_system_warning_message\">启用后将同时对 root 和系统进程（UID &lt; 10000）隐藏路径。这可能导致部分系统功能异常（像苦力怕炸方块一样），请谨慎操作。</string>\n    <string name=\"path_hide_uid_applied\">UID 白名单已应用（选择列表已生效）</string>\n    <string name=\"path_hide_select_apps\">挑选应用</string>\n    <string name=\"path_hide_no_apps_selected\">冇选中任何应用</string>\n    <string name=\"path_hide_app_removed\">已经清理咗 %d 个已卸载应用的配置</string>\n    <string name=\"path_hide_search_apps\">搵应用…</string>\n    <string name=\"path_hide_show_system\">显示系统应用</string>\n    <string name=\"netisolate_title\">网络隔离（切断连接）</string>\n    <string name=\"netisolate_enable\">网线已拔</string>\n    <string name=\"netisolate_disable\">网线已插</string>\n    <string name=\"netisolate_enable_summary\">在核心层面阻止所选应用的网络访问（像拔掉网线一样）</string>\n    <string name=\"netisolate_uids_label\">已隔离应用</string>\n    <string name=\"netisolate_uids_hint\">输入要隔离的 UID（每行一个，像列清单）</string>\n    <string name=\"netisolate_no_uids\">没有应用被隔离</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-zh-rTW/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\" translatable=\"false\">FolkPatch</string>\n    <string name=\"app_name_fpatch\">FPatch</string>\n\n    <string name=\"desktop_app_name\">桌面應用程式名稱</string>\n\n    <string name=\"home\">首頁</string>\n\n    <string name=\"settings_apm_stay_on_page\">留在操作頁面</string>\n    <string name=\"settings_apm_stay_on_page_summary\">系統模組操作完成後不自動返回</string>\n\n    <string name=\"success\">成功</string>\n    <string name=\"failure\">失敗</string>\n\n    <string name=\"patch_warnning\">安裝過程存在風險，請確保你的資料已備份。</string>\n    <string name=\"patch\">修補</string>\n\n    <string name=\"kernel_patch\">核心修補</string>\n    <string name=\"android_patch\">系統修補</string>\n\n    <string name=\"settings_nav_layout_title\">底部導覽列設定</string>\n    <string name=\"settings_nav_layout_summary\">顯示或隱藏導覽列中的項目</string>\n    <string name=\"settings_nav_scheme\">導航方案設定</string>\n    <string name=\"settings_nav_mode\">導覽列模式</string>\n    <string name=\"settings_nav_mode_summary\">選擇導覽列的顯示方式</string>\n    <string name=\"settings_nav_mode_auto\">自動傳統導覽列</string>\n    <string name=\"settings_nav_mode_bottom\">始終底部導覽列</string>\n    <string name=\"settings_nav_mode_rail\">始終側邊導覽列</string>\n    <string name=\"settings_nav_mode_floating\">浮動底部導覽列</string>\n    <string name=\"settings_show_apm\">顯示系統模組</string>\n    <string name=\"settings_show_kpm\">顯示核心模組</string>\n    <string name=\"settings_show_superuser\">顯示超級使用者</string>\n    <string name=\"settings_floating_auto_hide\">自動隱藏懸浮底欄</string>\n    <string name=\"settings_floating_auto_hide_summary\">無操作3秒後自動隱藏懸浮底欄</string>\n    <string name=\"settings_floating_swipe_hide\">滑動隱藏懸浮底欄</string>\n    <string name=\"settings_floating_swipe_hide_summary\">向下滑動時隱藏底欄，向上滑動時顯示底欄</string>\n    <string name=\"settings_navbar_glass_effect\">毛玻璃效果</string>\n    <string name=\"settings_navbar_glass_effect_summary\">為懸浮導航列套用毛玻璃視覺效果</string>\n    <string name=\"settings_navbar_glass_blur_strength\">模糊強度</string>\n    <string name=\"settings_navbar_glass_transparency\">背景透明度</string>\n    <string name=\"settings_navbar_glass_highlight_strength\">高光強度</string>\n    <string name=\"settings_navbar_glass_specular\">鏡面反射</string>\n    <string name=\"settings_navbar_glass_specular_summary\">在導航列頂部啟用鏡面高光效果</string>\n    <string name=\"settings_navbar_glass_inner_glow\">內發光</string>\n    <string name=\"settings_navbar_glass_inner_glow_summary\">在導航列底部啟用微光效果</string>\n    <string name=\"settings_navbar_glass_border\">玻璃邊框</string>\n    <string name=\"settings_navbar_glass_border_summary\">在導航列周圍啟用微妙的邊框描邊</string>\n    <string name=\"settings_stats_top_layout\">頂部佈局</string>\n    <string name=\"settings_stats_top_layout_summary\">選擇頂部資訊卡片樣式</string>\n    <string name=\"settings_stats_top_layout_list\">列表樣式</string>\n    <string name=\"settings_stats_top_layout_grid\">網格樣式</string>\n    <string name=\"settings_block_kernelpatch_update\">屏蔽核心修補更新提示</string>\n    <string name=\"settings_block_kernelpatch_update_summary\">不顯示核心修補的更新通知</string>\n    <string name=\"settings_block_androidpatch_update\">屏蔽系統修補更新提示</string>\n    <string name=\"settings_block_androidpatch_update_summary\">不顯示系統修補(APD)的更新通知</string>\n    <string name=\"settings_disable_module_update_check\">停用模組更新檢查</string>\n    <string name=\"settings_disable_module_update_check_summary\">停用系統模組的自動更新檢查</string>\n\n    <string name=\"reboot\">重新啟動</string>\n    <string name=\"settings\">設定</string>\n    <string name=\"reboot_recovery\">重新啟動至 Recovery</string>\n    <string name=\"reboot_bootloader\">重新啟動至 Bootloader</string>\n    <string name=\"reboot_download\">重新啟動至 Download</string>\n    <string name=\"reboot_edl\">重新啟動至 EDL</string>\n    <string name=\"reboot_fastbootd\">重新啟動至 FastbootD</string>\n    <string name=\"about\">關於</string>\n    <string name=\"developer_and_maintainer\">開發者 | 維護者</string>\n    <string name=\"settings_app_language\">語言</string>\n    <string name=\"system_default\">系統預設</string>\n    <string name=\"settings_global_namespace_mode\">全域命名空間模式</string>\n    <string name=\"settings_global_namespace_mode_summary\">所有 Root 工作階段將會全域掛載命名空間</string>\n    <string name=\"settings_clear_super_key_dialog\">確定要繼續嗎？</string>\n\n    <string name=\"home_learn_apatch\">深入了解 FolkPatch</string>\n    <string name=\"home_click_to_learn_apatch\">學習有關 FolkPatch 的功能及使用方式</string>\n    <string name=\"settings_hide_apatch_card\">隱藏「了解 FolkPatch」卡片</string>\n    <string name=\"settings_hide_apatch_card_summary\">控制首頁是否顯示「深入了解 FolkPatch」卡片</string>\n\n    <string name=\"send_log\">傳送日誌</string>\n    <string name=\"save_log\">儲存日誌</string>\n    <string name=\"log_saved\">日誌已儲存</string>\n    <string name=\"safe_mode\">安全模式</string>\n    <string name=\"github\">GitHub</string>\n    <string name=\"telegram\">Telegram</string>\n    <string name=\"support_or_donate\">支持 / 捐贈</string>\n\n    <string name=\"super_key\">超級密鑰</string>\n    <string name=\"clear_super_key\">清除超級密鑰</string>\n    <string name=\"patch_set_superkey\">設定超級密鑰</string>\n    <string name=\"home_patch_set_key_desc\">執行 KernelPatch 所需的唯一憑證</string>\n    <string name=\"home_patch_next_step\">下一步</string>\n\n    <string name=\"home_not_installed\">尚未安裝</string>\n    <string name=\"home_install_unknown\">未安裝或未驗證</string>\n    <string name=\"home_install_unknown_summary\">點擊以安裝</string>\n    <string name=\"home_click_to_install\">點擊安裝</string>\n    <string name=\"home_working\">運作中</string>\n    <string name=\"home_kp_need_update\">有可用更新</string>\n    <string name=\"home_kp_cando_update\">更新</string>\n\n    <string name=\"home_installing\">安裝中</string>\n\n    <string name=\"kpatch_version\">版本：%s</string>\n    <string name=\"apatch_version\">版本：%s</string>\n\n    <string name=\"kpatch_version_update\" formatted=\"false\">版本：%s → %s</string>\n    <string name=\"kpatch_shadow_path_title\">核心補丁</string>\n    <string name=\"home_auth_key_title\">輸入超級密鑰</string>\n    <string name=\"home_auth_key_desc\">驗證成功後開始執行</string>\n    <string name=\"home_kpatch_info_title\">資訊</string>\n\n    <string name=\"home_ap_cando_install\">安裝</string>\n    <string name=\"home_ap_cando_uninstall\">解除安裝</string>\n    <string name=\"home_ap_cando_reboot\">重新啟動</string>\n\n    <string name=\"patch_title\">修補</string>\n    <string name=\"patch_config_title\">補丁</string>\n    <string name=\"patch_mode_bootimg_patch\">模式：修補</string>\n    <string name=\"patch_mode_patch_and_install\">模式：更新</string>\n    <string name=\"patch_mode_install_to_next_slot\">安裝至非使用中的插槽 (OTA 後)</string>\n    <string name=\"patch_mode_uninstall_patch\">模式：取消補丁</string>\n    <string name=\"patch_select_bootimg_btn\">選取 Boot 映像檔</string>\n    <string name=\"patch_embed_kpm_btn\">嵌入核心模組</string>\n    <string name=\"patch_start_patch_btn\">開始</string>\n    <string name=\"patch_start_unpatch_btn\">取消修補</string>\n    <string name=\"patch_item_error\">‼錯誤‼</string>\n    <string name=\"patch_item_bootimg\">Boot 映像檔</string>\n    <string name=\"patch_item_bootimg_slot\">插槽：</string>\n    <string name=\"patch_item_bootimg_dev\">裝置：</string>\n    <string name=\"patch_item_kernel\">核心</string>\n    <string name=\"patch_item_kpimg\">核心補丁</string>\n    <string name=\"patch_item_kpimg_version\">版本：</string>\n    <string name=\"patch_item_kpimg_comile_time\">時間：</string>\n    <string name=\"patch_item_kpimg_config\">設定：</string>\n    <string name=\"patch_item_new_extra_kpm\">嵌入新的</string>\n    <string name=\"patch_item_existed_extra_kpm\">已嵌入</string>\n    <string name=\"patch_item_extra_name\">名稱：</string>\n    <string name=\"patch_item_extra_version\">版本：</string>\n    <string name=\"patch_item_extra_author\">作者：</string>\n    <string name=\"patch_item_extra_kpm_license\">授權：</string>\n    <string name=\"patch_item_extra_kpm_desciption\">描述：</string>\n    <string name=\"patch_item_extra_args\">參數：</string>\n    <string name=\"patch_item_extra_event\">事件：</string>\n    <string name=\"patch_item_skey\">超級密鑰</string>\n    <string name=\"patch_custom_superkey\">自訂超級密鑰</string>\n    <string name=\"patch_custom_superkey_summary\">你仍可以設定超級密鑰來管理 Root 以及內核，管理器已由內建簽名授權</string>\n    <string name=\"patch_item_set_skey_label\">超級密鑰至少需要８個字元，並且同時包含數字和字母。</string>\n    <string name=\"patch_confirm_superkey\">確認超級密鑰</string>\n    <string name=\"patch_skey_mismatch\">兩次輸入的超級密鑰不一致</string>\n\n    <string name=\"home_kernel\">核心版本</string>\n    <string name=\"home_manager_version\">管理器版本</string>\n    <string name=\"home_fingerprint\">指紋</string>\n\n    <string name=\"home_selinux_status\">SELinux 狀態</string>\n    <string name=\"home_selinux_status_disabled\">已停用</string>\n    <string name=\"home_selinux_status_enforcing\">強制模式</string>\n    <string name=\"home_selinux_status_permissive\">寬鬆模式</string>\n    <string name=\"home_selinux_status_unknown\">未知</string>\n\n    <string name=\"settings_selinux_mode\">SELinux 模式</string>\n    <string name=\"settings_selinux_mode_summary\">選擇 SELinux 強制執行模式</string>\n    <string name=\"settings_selinux_mode_enforcing\">強制模式</string>\n    <string name=\"settings_selinux_mode_permissive\">寬鬆模式</string>\n    <string name=\"settings_selinux_current_mode\">目前: %s</string>\n    <string name=\"settings_selinux_mode_enforcing_summary\">SELinux 會強制執行所有存取規則</string>\n    <string name=\"settings_selinux_mode_permissive_summary\">SELinux 僅記錄違規行為，不會阻擋存取</string>\n\n    <string name=\"home_device_info\">裝置</string>\n    <string name=\"home_system_version\">系統版本</string>\n    <string name=\"home_kpatch_version\">KernelPatch 版本</string>\n    <string name=\"home_su_path\">可執行 SU 路徑</string>\n    <string name=\"home_apatch_version\">FolkPatch 版本</string>\n\n    <string name=\"kpm\">核心模組</string>\n    <string name=\"kpm_kp_not_installed\">KernelPatch 尚未安裝</string>\n    <string name=\"kpm_add_kpm\">新增 KPM</string>\n    <string name=\"kpm_load\">匯入</string>\n    <string name=\"kpm_install\">安裝</string>\n    <string name=\"kpm_embed\">嵌入</string>\n    <string name=\"kpm_load_toast_succ\">匯入成功</string>\n    <string name=\"kpm_load_toast_failed\">匯入失敗</string>\n    <string name=\"kpm_unload_confirm\">解除安裝 \\\"%s\\\" 模組？</string>\n    <string name=\"kpm_unload\">解除安裝</string>\n    <string name=\"kpm_control\">控制</string>\n    <string name=\"kpm_apm_empty\">尚未安裝模組</string>\n    <string name=\"kpm_version\">版本</string>\n    <string name=\"kpm_license\">授權</string>\n    <string name=\"kpm_author\">作者</string>\n    <string name=\"kpm_desc\">說明</string>\n    <string name=\"kpm_args\">參數</string>\n\n    <string name=\"su_title\">超級使用者</string>\n    <string name=\"su_selinux_via_hook\">透過 Hook 忽略請求的方式</string>\n    <string name=\"su_pkg_excluded_label\">忽略</string>\n    <string name=\"su_pkg_excluded_setting_title\">排除修改</string>\n    <string name=\"su_pkg_excluded_setting_summary\">啟用此選項將允許 FolkPatch 還原此應用程式被模組修改的任何檔案。</string>\n    <string name=\"su_pkg_root_setting_title\">超級使用者</string>\n    <string name=\"su_pkg_root_setting_summary\">啟用此選項為您的軟體啟動超級使用者，軟體能夠使用 SU 指令</string>\n    <string name=\"su_pkg_normal_setting_title\">常規模式</string>\n    <string name=\"su_pkg_normal_setting_summary\">不授予 root 權限，應用在沒有超級用戶權限下運行。</string>\n    <string name=\"su_batch_exclude_title\">批次排除</string>\n    <string name=\"su_batch_exclude_content\">為全部非 Root 權限應用程式排除注入，請選擇一個操作</string>\n    <string name=\"su_exclude_btn\">排除</string>\n    <string name=\"su_exclude_reverse_btn\">反排除</string>\n\n    <!-- 超級使用者應用程式操作對話框 -->\n    <string name=\"su_app_action_title\">應用程式操作</string>\n    <string name=\"su_app_action_content\">請選擇一個操作來對應用程式執行</string>\n    <string name=\"su_app_action_launch\">啟動應用程式</string>\n    <string name=\"su_app_action_force_stop\">強行停止</string>\n    <string name=\"su_app_action_launch_success\">正在啟動 %s</string>\n    <string name=\"su_app_action_force_stop_success\">已強行停止 %s</string>\n    <string name=\"su_app_action_failed\">操作失敗: %s</string>\n\n    <!-- SU Audit Log -->\n    <string name=\"su_audit_log_title\">授權日誌</string>\n    <string name=\"su_audit_log_empty\">暫無授權記錄</string>\n    <string name=\"su_audit_log_clear\">清除日誌</string>\n    <string name=\"su_audit_log_clear_confirm\">確認清除所有授權記錄？</string>\n    <string name=\"su_audit_action_grant\">已授權</string>\n    <string name=\"su_audit_action_revoke\">已撤銷</string>\n    <string name=\"su_audit_action_exclude\">已排除</string>\n    <string name=\"su_audit_tab_usage\">使用記錄</string>\n    <string name=\"su_audit_tab_operations\">操作記錄</string>\n\n    <string name=\"su_show_system_apps\">顯示系統應用程式</string>\n    <string name=\"su_hide_system_apps\">隱藏系統應用程式</string>\n    <string name=\"su_refresh\">重新整理</string>\n\n    <string name=\"apm\">系統模組</string>\n    <string name=\"apm_not_installed\">AndroidPatch 尚未安裝</string>\n    <string name=\"apm_failed_to_enable\">無法啟用 \\\"%s\\\" 模組</string>\n    <string name=\"apm_failed_to_disable\">無法停用 \\\"%s\\\" 模組</string>\n    <string name=\"apm_empty\">尚未安裝模組</string>\n    <string name=\"apm_remove\">解除安裝</string>\n    <string name=\"apm_install\">安裝</string>\n    <string name=\"apm_uninstall_confirm\">確定要解除安裝 \\\"%s\\\" 模組？</string>\n    <string name=\"apm_uninstall_success\">%s 已解除安裝</string>\n    <string name=\"apm_uninstall_failed\">無法解除安裝 %s</string>\n    <string name=\"apm_undo\">復原</string>\n    <string name=\"apm_undo_uninstall_success\">%s 已復原</string>\n    <string name=\"apm_undo_uninstall_failed\">復原失敗: %s</string>\n    <string name=\"apm_version\">版本</string>\n    <string name=\"apm_author\">作者</string>\n    <string name=\"apm_desc\">說明</string>\n    <string name=\"apm_overlay_fs_not_available\">裝置核心停用了 OverlayFS，你無法使用模組功能!</string>\n    <string name=\"apm_magisk_conflict\">與 Magisk 發生衝突，你無法使用系統模組功能!</string>\n    <string name=\"apm_mount_warning_title\">重要提示</string>\n    <string name=\"apm_mount_warning_message\">預設不會掛載模組，請使用內建掛載系統或安裝元模組</string>\n    <string name=\"apm_mount_warning_button\">好</string>\n    <string name=\"apm_reboot_to_apply\">重新啟動以應用變更</string>\n    <string name=\"apm_changelog\">「更新日誌」</string>\n    <string name=\"apm_update\">更新</string>\n    <string name=\"apm_downloading\">正在下載 \\\"%s\\\" 模組……</string>\n    <string name=\"apm_start_downloading\">開始下載 %s</string>\n    <string name=\"apm_new_version_available\">點擊以安裝新版本的 %s。</string>\n\n    <string name=\"hide_apatch_manager\">隱藏 APatch 管理器</string>\n    <string name=\"hide_apatch_manager_summary\">使用隨機套件名稱和自訂應用程式標籤安裝新的代理應用程式</string>\n    <string name=\"hide_apatch_dialog_new_manager_name\">新管理器名稱</string>\n    <string name=\"hide_apatch_dialog_summary\">它將用作啟動器中顯示的新應用程式標籤</string>\n    <string name=\"hide_apatch_manager_failure\">隱藏失敗，請回報錯誤！</string>\n\n    <string name=\"setting_reset_su_path\">重設 SU 路徑</string>\n    <string name=\"setting_reset_su_new_path\">新的完整路徑</string>\n    <string name=\"settings_folkx_engine_title\">FolkX 動畫引擎</string>\n    <string name=\"settings_folkx_engine_summary\">在頂層頁面切換時使用絲滑的物理彈簧動畫</string>\n    <string name=\"settings_folkx_animation_type\">動畫類型</string>\n    <string name=\"settings_folkx_animation_linear\">線性運動</string>\n    <string name=\"settings_folkx_animation_speed\">動畫速度</string>\n    <string name=\"settings_folkx_animation_spatial\">空間運動</string>\n    <string name=\"settings_folkx_animation_fade\">漸隱漸顯</string>\n    <string name=\"settings_folkx_animation_vertical\">垂直滑動</string>\n    <string name=\"settings_folkx_animation_diagonal\">對角滑動</string>\n\n    <string name=\"apm_webui_open\">WebUI</string>\n    <string name=\"apm_action\">操作</string>\n    <string name=\"module_shortcut_add\">捷徑</string>\n    <string name=\"module_shortcut_not_supported\">啟動器不支援桌面捷徑。</string>\n    <string name=\"module_shortcut_created\">已在桌面建立捷徑。</string>\n    <string name=\"module_shortcut_updated\">捷徑已更新。</string>\n    <string name=\"module_shortcut_permission_tip_xiaomi\">請在小米設定中為本應用程式啟用「建立桌面捷徑」權限。</string>\n    <string name=\"module_shortcut_permission_tip_oppo\">請在 OPPO 設定中為本應用程式啟用「桌面捷徑」權限。</string>\n    <string name=\"module_shortcut_permission_tip_default\">若建立捷徑失敗，請在系統設定中為本應用程式啟用桌面捷徑權限。</string>\n    <string name=\"module_shortcut_name\">捷徑名稱</string>\n    <string name=\"module_shortcut_icon\">捷徑圖示</string>\n    <string name=\"module_shortcut_type\">捷徑類型</string>\n    <string name=\"module_shortcut_type_webui\">WebUI</string>\n    <string name=\"module_shortcut_type_action\">Action</string>\n    <string name=\"module_shortcut_icon_default\">使用預設圖示</string>\n    <string name=\"module_shortcut_icon_select\">選取圖示</string>\n    <string name=\"enable_web_debugging\">啟用 WebView 偵錯</string>\n    <string name=\"enable_web_debugging_summary\">可用於偵錯 WebUI，請僅在需要時啟用。</string>\n    <string name=\"settings_apm_install_confirm\">安裝模組確認</string>\n    <string name=\"settings_apm_install_confirm_summary\">安裝模組前顯示確認對話框</string>\n    <string name=\"settings_enable_module_shortcut_add\">啟用捷徑新增按鈕</string>\n    <string name=\"settings_enable_module_shortcut_add_summary\">在系統模組頁面顯示 WebUI 捷徑新增按鈕</string>\n    <string name=\"settings_module_sort_optimization\">模組卡片排序優化</string>\n    <string name=\"settings_module_sort_optimization_summary\">前置系統模組中帶有「WebUI」和「操作」的模組</string>\n    <string name=\"settings_fold_system_module\">折疊系統模組</string>\n    <string name=\"settings_fold_system_module_summary\">點擊模組卡片展開/折疊操作按鈕</string>\n    <string name=\"settings_simple_list_bottom_bar\">簡約列表底欄</string>\n    <string name=\"settings_simple_list_bottom_bar_summary\">模組操作使用純圖示按鈕樣式，靈感來自APatch</string>\n    <string name=\"settings_spliced_card_group\">拼接卡片組</string>\n    <string name=\"settings_spliced_card_group_summary\">模組列表使用 M3E 連續拼接卡片樣式</string>\n    <string name=\"apm_install_confirm_title\">安裝模組</string>\n    <string name=\"apm_install_confirm_content\">確定要安裝 %s 嗎？</string>\n    <string name=\"apm_disable_all_title\">停用所有系統模組</string>\n    <string name=\"apm_disable_all_confirm\">確定停用全部系統模組嗎？此操作將停用所有已安裝的模組</string>\n    <string name=\"apm_enable_module_banner\">啟用模組橫幅</string>\n    <string name=\"apm_enable_module_banner_summary\">為支援的模組顯示橫幅圖片</string>\n    <string name=\"apm_enable_folk_banner\">自訂模組橫幅</string>\n    <string name=\"apm_enable_folk_banner_summary\">長按模組卡片自選橫幅圖片</string>\n    <string name=\"apm_folk_banner_title\">FolkBanner</string>\n    <string name=\"apm_folk_banner_select\">選取圖片</string>\n    <string name=\"apm_folk_banner_clear\">清除 FolkBanner</string>\n    <string name=\"apm_folk_banner_saved\">已為 %s 注入 FolkBanner。</string>\n    <string name=\"apm_folk_banner_cleared\">已為 %s 清除 FolkBanner。</string>\n    <string name=\"apm_folk_banner_failed\">為 %s 更新 FolkBanner 失敗。</string>\n    <string name=\"apm_banner_api_mode\">API模式</string>\n    <string name=\"apm_banner_api_mode_summary\">使用隨機圖API或本機目錄為模組提供橫幅</string>\n    <string name=\"apm_banner_api_source\">設定API來源</string>\n    <string name=\"apm_banner_api_source_hint\">輸入API網址或本機路徑</string>\n    <string name=\"apm_banner_api_source_saved\">API來源已儲存</string>\n    <string name=\"apm_banner_api_source_not_configured\">未設定</string>\n    <string name=\"apm_banner_api_url_configured\">API網址已設定</string>\n    <string name=\"apm_banner_local_dir_configured\">本機目錄已設定</string>\n    <string name=\"apm_banner_clear_cache\">清除快取</string>\n    <string name=\"apm_banner_cache_cleared\">橫幅快取已清除</string>\n    <string name=\"apm_banner_api_config_title\">設定API來源</string>\n    <string name=\"apm_banner_api_config_desc\">輸入隨機圖API網址或本機目錄路徑，每個模組將獲得唯一的橫幅圖片。</string>\n    <string name=\"apm_banner_api_examples_title\">範例：</string>\n    <string name=\"apm_banner_api_examples\">API: https://picsum.photos/800/400\\n本機: /sdcard/Pictures/Banners</string>\n    <!-- API 接口市場 -->\n    <string name=\"apm_api_marketplace_title\">API 接口市場</string>\n    <string name=\"apm_api_preview\">預覽</string>\n    <string name=\"apm_api_apply\">應用</string>\n    <string name=\"apm_api_preview_title\">API 預覽</string>\n    <string name=\"apm_api_preview_failed\">預覽載入失敗</string>\n    <string name=\"apm_api_url_label\">API 位址：</string>\n    <string name=\"apm_api_apply_success\">API 來源已成功應用</string>\n    <string name=\"apm_api_verifying\">驗證中…</string>\n    <string name=\"apm_api_retry\">重試</string>\n    <string name=\"apm_api_empty\">暫無 API 來源</string>\n    <string name=\"settings_banner_custom_opacity\">橫幅透明度</string>\n    <string name=\"settings_banner_custom_opacity_summary\">自訂橫幅透明度，不再跟隨桌布模式</string>\n    <string name=\"settings_banner_opacity\">橫幅不透明度</string>\n\n    <string name=\"settings_donot_store_superkey\">停用管理器儲存密鑰</string>\n    <string name=\"settings_donot_store_superkey_summary\">每次管理器啟動時驗證超級密鑰</string>\n    <string name=\"settings_show_more_module_info\">顯示模組資訊詳情</string>\n    <string name=\"settings_show_more_module_info_summary\">在模組列表中顯示模組 ID、大小等訊息</string>\n    <string name=\"mode_select_page_title\">安裝</string>\n    <string name=\"mode_select_page_patch_and_install\">直接安裝（通常選擇此項）</string>\n    <string name=\"mode_select_page_select_file\">選取要修補的「Boot 映像檔」</string>\n    <string name=\"mode_select_page_install_inactive_slot\">安裝至非使用中的插槽 (OTA 後)</string>\n    <string name=\"mode_select_page_install_inactive_slot_warning\">重新啟動後，您的裝置將**強制**於目前非使用中的插槽啟動！\n\n注意請僅在 OTA 後重啟設備之前使用。\n\n 繼續？</string>\n    <string name=\"mode_select_page_select_kpimg\">使用本地修補檔案 (KPimg)</string>\n    <string name=\"patch_custom_kpimg_label\">自訂 KPimg</string>\n    <string name=\"patch_custom_kpimg_file\">檔案: %s</string>\n    <string name=\"patch_select_kpimg_btn\">選擇 KPimg</string>\n\n    <string name=\"home_dialog_auth_fail_title\">驗證失敗</string>\n    <string name=\"home_dialog_auth_fail_content\">無法認證超級密鑰，導致 FolkPatch 激活失敗。\n以下是認證失敗的一些可能原因——請對號入座：\n\\n1. 您根本沒使用 KernelPatch 來修補 boot.img，或者忘了自己到底做了啥。\n\\n2. 修補完的 boot.img 還躺在電腦裡睡覺，根本沒刷進設備。\n\\n3. 超級密鑰輸錯了，或者夾雜了神秘符號，比如來自外星文的字符。\n\\n4. 您的設備可能並不兼容 FolkPatch 和 KernelPatch，強行折騰也是徒勞。\n\\n5. 您可能搞了一些神秘操作——比如用了某些排除包名的模組把 FolkPatch 給屏蔽了，結果把自己整出局了。\n\\n請先好好檢查一遍，再試一次。如果問題依舊，歡迎到官方倉庫的 issue 頁面提問。\n畢竟，有些問題可能純粹是您自己親手造成的！\n祝你好運啊！順帶一提，您點擊下面的文檔按鈕準沒錯！</string>\n\n    <string name=\"home_more_menu_feedback_or_suggestion\">回饋或建議</string>\n    <string name=\"home_more_menu_about\">關於</string>\n    <string name=\"home_more_menu_document\">文檔</string>\n\n    <string name=\"home_dialog_uninstall_title\">解除安裝</string>\n    <string name=\"home_dialog_uninstall_ap_only\">僅移除系統修補</string>\n    <string name=\"home_dialog_uninstall_ap_only_desc\">僅移除系統修補，保留管理器。</string>\n    <string name=\"home_dialog_uninstall_all\">完全解除安裝</string>\n    <string name=\"home_dialog_uninstall_all_desc\">移除系統修補並進入完整解除安裝流程。</string>\n    \n    <!-- Install Mode Select -->\n    <string name=\"kp_install_methods\">KernelPatch 安裝方式</string>\n    <string name=\"restore_boot_methods\">選擇 Boot 映像檔以還原</string>\n    <string name=\"restore_select_file\">選擇要還原的 Boot 映像檔</string>\n\n    <string name=\"kpm_control_dialog_title\">控制 KPM</string>\n    <string name=\"kpm_control_dialog_content\">輸入控制參數：</string>\n    <string name=\"kpm_control_paramters\">參數</string>\n    <string name=\"kpm_control_outMsg\">訊息</string>\n    <string name=\"kpm_control_ok\">操作成功！</string>\n    <string name=\"kpm_control_failed\">操作失敗！</string>\n\n    <string name=\"settings_night_mode_follow_sys\">跟隨系統深色模式</string>\n    <string name=\"settings_night_mode_follow_sys_summary\">依系統設定自動切換深色模式</string>\n    <string name=\"settings_night_theme_enabled\">啟用深色模式</string>\n\n    <string name=\"settings_use_system_color_theme\">使用系統色彩主題</string>\n    <string name=\"settings_use_system_color_theme_summary\">使用系統依目前桌布自動產生的色彩主題</string>\n    <string name=\"settings_custom_color_theme\">自訂色彩主題</string>\n\n    <string name=\"amber_theme\">琥珀色主題</string>\n    <string name=\"blue_theme\">藍色主題</string>\n    <string name=\"blue_grey_theme\">藍灰色主題</string>\n    <string name=\"brown_theme\">大地主題</string>\n    <string name=\"cyan_theme\">碧湖主題</string>\n    <string name=\"deep_orange_theme\">夕陽主題</string>\n    <string name=\"deep_purple_theme\">星夜主題</string>\n    <string name=\"green_theme\">森林主題</string>\n    <string name=\"indigo_theme\">深海主題</string>\n    <string name=\"light_blue_theme\">海洋主題</string>\n    <string name=\"light_green_theme\">春草主題</string>\n    <string name=\"lime_theme\">新葉主題</string>\n    <string name=\"orange_theme\">晨曦主題</string>\n    <string name=\"pink_theme\">桃夭主題</string>\n    <string name=\"purple_theme\">薰衣主題</string>\n    <string name=\"red_theme\">烈焰主題</string>\n    <string name=\"sakura_theme\">櫻花主題</string>\n    <string name=\"teal_theme\">翡翠主題</string>\n    <string name=\"yellow_theme\">金穗主題</string>\n    <string name=\"theme_color\">主題顏色</string>\n    <string name=\"theme_light\">淺色</string>\n    <string name=\"theme_dark\">深色</string>\n    <string name=\"theme_system\">系統</string>\n    <string name=\"loading_modules\">正在取得模組…</string>\n    <string name=\"loading_scripts\">正在尋找腳本…</string>\n    <string name=\"loading_themes\">正在載入主題…</string>\n    <string name=\"loading_apis\">正在載入 API 來源…</string>\n\n    <string name=\"about_app_desc\">基於 KernelPatch 的 Root 實現，無需重新編譯內核的同時，允許 Hook 內核函數。</string>\n    <string name=\"about_powered_by\">由 %1$s 提供支援</string>\n    <string name=\"about_telegram_group\">Telegram 群組</string>\n\n    <!-- Online Module Strings -->\n    <string name=\"online_module_title\">線上系統模組</string>\n    <string name=\"online_module_download_start\">開始下載：%s</string>\n    <string name=\"online_module_download_complete\">下載完成：%s</string>\n    <string name=\"online_module_download_notification\">正在下載 %s。請查看通知列取得下載進度，下載檔案位於 Download 資料夾中。</string>\n\n    <!-- Online KPM Strings -->\n    <string name=\"online_kpm_title\">線上核心模組</string>\n    <string name=\"online_kpm_download_start\">開始下載：%s</string>\n    <string name=\"online_kpm_download_complete\">下載完成：%s</string>\n    <string name=\"online_kpm_download_notification\">正在下載 %s。請查看通知列取得下載進度，下載檔案位於 Download 資料夾中。</string>\n\n    <!-- Online Script Strings -->\n    <string name=\"online_script_title\">線上腳本</string>\n    <string name=\"online_script_download_start\">開始下載：%s</string>\n    <string name=\"online_script_download_complete\">下載完成：%s</string>\n    <string name=\"online_script_download_notification\">正在下載 %s</string>\n\n    <!-- Crash Handle Strings -->\n    <string name=\"crash_handle_title\">崩潰報告</string>\n    <string name=\"crash_handle_copy\">複製</string>\n\n    <!-- About Screen Strings -->\n    <string name=\"about_app_version\">應用程式版本: %s</string>\n    <string name=\"about_github\">GitHub</string>\n    <string name=\"about_telegram_channel\">Telegram 頻道</string>\n\n    <string name=\"settings_app_dpi\">應用程式 DPI</string>\n    <string name=\"dpi_apply_settings\">套用</string>\n    <string name=\"dpi_confirm_title\">確認變更 DPI</string>\n    <string name=\"dpi_confirm_message\">將應用程式 DPI 從 %1$s 變更為 %2$s？</string>\n    <string name=\"settings_magic_mount\">Folk Mount API</string>\n    <string name=\"settings_magic_mount_summary\">啟用內建的模組掛載系統</string>\n    <string name=\"settings_new_app_profile_mode\">新軟體預設模式</string>\n    <string name=\"settings_new_app_profile_normal\">一般</string>\n    <string name=\"settings_new_app_profile_root\">超級使用者</string>\n    <string name=\"settings_new_app_profile_exclude\">排除</string>\n    <string name=\"settings_hide_service\">FolkPatch Hide</string>\n    <string name=\"settings_hide_service_summary\">一定程度上隱藏Bootloader解鎖狀態</string>\n    <string name=\"settings_app_title\">應用程式標題</string>\n    <string name=\"app_title_custom\">自訂</string>\n    <string name=\"settings_custom_app_title\">設定應用程式名稱</string>\n    <string name=\"custom_app_title_dialog_title\">自訂應用程式名稱</string>\n    <string name=\"custom_app_title_dialog_hint\">請輸入應用程式名稱</string>\n    <string name=\"custom_app_title_dialog_confirm\">確定</string>\n    <string name=\"custom_app_title_dialog_empty\">名稱不能為空</string>\n    <string name=\"cancel\">取消</string>\n    <string name=\"settings_custom_background\">自訂背景</string>\n    <string name=\"settings_custom_background_enabled\">已啟用自訂背景</string>\n    <string name=\"settings_custom_background_opacity\">卡片透明度</string>\n    <string name=\"settings_custom_background_blur\">背景模糊效果</string>\n    <string name=\"settings_custom_background_dim\">背景暗度</string>\n    <string name=\"settings_alt_icon\">備用圖標</string>\n    <string name=\"alt_icon_summary\">使用備選啟動器圖標</string>\n    <string name=\"settings_select_background_image\">選擇背景圖片</string>\n    <string name=\"settings_custom_background_summary\">設定自訂背景圖片</string>\n    <string name=\"settings_custom_background_saved\">背景儲存成功</string>\n    <string name=\"settings_custom_background_error\">背景儲存失敗</string>\n    <string name=\"settings_background_selected\">背景已選擇</string>\n    <string name=\"settings_background_image_cleared\">背景圖片已清除</string>\n    <string name=\"settings_clear_background\">清除背景</string>\n    <string name=\"settings_clear_background_confirm\">確定要清除背景圖片嗎？</string>\n    <string name=\"settings_multi_background_mode\">啟用多背景模式</string>\n    <string name=\"settings_multi_background_mode_summary\">為不同頁面設定不同的背景圖片</string>\n    <string name=\"settings_select_home_background\">首頁背景</string>\n    <string name=\"settings_select_kernel_background\">核心模組背景</string>\n    <string name=\"settings_select_superuser_background\">超級使用者背景</string>\n    <string name=\"settings_select_system_module_background\">系統模組背景</string>\n    <string name=\"settings_select_settings_background\">設定頁面背景</string>\n\n    <string name=\"settings_advanced_title_style\">進階標題樣式</string>\n    <string name=\"settings_advanced_title_style_summary\">使用自訂圖片替換頂部標題</string>\n    <string name=\"settings_advanced_title_style_enabled\">進階標題樣式已啟用</string>\n    <string name=\"settings_select_title_image\">選擇標題圖片</string>\n    <string name=\"settings_title_image_selected\">已選擇標題圖片</string>\n    <string name=\"settings_title_image_saved\">標題圖片儲存成功</string>\n    <string name=\"settings_title_image_error\">儲存標題圖片失敗</string>\n    <string name=\"settings_clear_title_image\">清除標題圖片</string>\n    <string name=\"settings_clear_title_image_confirm\">確定要清除標題圖片嗎？</string>\n    <string name=\"settings_title_image_cleared\">標題圖片已清除</string>\n    <string name=\"settings_title_image_day_opacity\">白天模式透明度</string>\n    <string name=\"settings_title_image_night_opacity\">夜間模式透明度</string>\n    <string name=\"settings_title_image_dim\">背景暗度</string>\n    <string name=\"settings_title_image_offset_x\">水平位置</string>\n\n    <string name=\"settings_launcher_icon\">桌面圖示</string>\n    <string name=\"su_exclude_all_title\">批次排除</string>\n    <string name=\"su_exclude_all_confirm\">確定要將所有未授權 Root 的應用程式設為排除嗎？</string>\n    <!-- KPM AutoLoad Strings -->\n    <string name=\"kpm_autoload_title\">KPM 自動載入</string>\n    <string name=\"kpm_autoload_enabled\">啟用自動載入</string>\n    <string name=\"kpm_autoload_enabled_summary\">開機時自動載入 KPM 模組</string>\n    <string name=\"kpm_autoload_json_config\">JSON 設定</string>\n    <string name=\"kpm_autoload_json_label\">JSON 設定</string>\n    <string name=\"kpm_autoload_json_placeholder\">{\n  \"enabled\": true,\n  \"kpmPaths\": [\n    \"/path/to/module1.kpm\",\n    \"/path/to/module2.kpm\"\n  ]\n}</string>\n    <string name=\"kpm_autoload_json_error\">無效的 JSON 格式</string>\n    <string name=\"kpm_autoload_json_helper\">輸入有效的 JSON，包含 KPM 檔案路徑陣列</string>\n    <string name=\"kpm_autoload_save\">儲存設定</string>\n    <string name=\"settings_grid_working_card_hide_check\">隱藏狀態圖示</string>\n    <string name=\"settings_grid_working_card_hide_check_summary\">隱藏工作卡片上的勾選或警告圖示</string>\n    <string name=\"settings_grid_working_card_hide_text\">隱藏狀態文字</string>\n    <string name=\"settings_grid_working_card_hide_text_summary\">隱藏工作卡片上的「工作中」或「未安裝」文字</string>\n    <string name=\"settings_grid_working_card_hide_mode\">隱藏工作模式</string>\n    <string name=\"settings_grid_working_card_hide_mode_summary\">隱藏工作卡片上的「Full」或「Half」文字</string>\n    <string name=\"settings_list_card_hide_status_badge\">使用經典表情</string>\n    <string name=\"settings_list_card_hide_status_badge_summary\">首頁的狀態徽章替換為經典表情</string>\n    <string name=\"settings_custom_badge_text\">自選角標文字</string>\n    <string name=\"settings_custom_badge_text_summary\">修改角標的文字顯示，僅供娛樂</string>\n    <string name=\"settings_custom_background_dual_dim\">啟用日夜雙切模式</string>\n    <string name=\"settings_custom_background_dual_dim_desc\">根據亮色和暗色主題自動調整背景暗度</string>\n    <string name=\"settings_custom_background_day_dim\">白天模式暗度</string>\n    <string name=\"settings_custom_background_night_dim\">夜晚模式暗度</string>\n    <string name=\"settings_grid_working_card_dual_opacity\">啟用日夜雙切透明度</string>\n    <string name=\"settings_grid_working_card_dual_opacity_desc\">根據亮色和暗色主題自動調整卡片透明度</string>\n    <string name=\"settings_grid_working_card_day_opacity\">白天模式卡片透明度</string>\n    <string name=\"settings_grid_working_card_night_opacity\">夜晚模式卡片透明度</string>\n    <string name=\"kpm_autoload_save_confirm\">儲存自動載入設定？</string>\n    <string name=\"kpm_autoload_save_success\">設定儲存成功</string>\n    <string name=\"kpm_autoload_save_failed\">設定儲存失敗</string>\n    <string name=\"kpm_autoload_visual_mode\">可視化模式</string>\n    <string name=\"kpm_autoload_json_mode\">JSON 模式</string>\n    <string name=\"kpm_autoload_add_kpm\">新增 KPM</string>\n    <string name=\"kpm_autoload_remove_kpm\">移除</string>\n    <string name=\"kpm_autoload_edit_kpm\">編輯</string>\n    <string name=\"kpm_autoload_edit_dialog_title\">編輯 KPM</string>    <string name=\"kpm_autoload_event_label\">事件:</string>    <string name=\"kpm_autoload_args_label\">參數:</string>    <string name=\"kpm_autoload_event_service\">service</string>    <string name=\"kpm_autoload_event_post_fs_data\">post-fs-data</string>\n    <string name=\"kpm_autoload_kpm_list_title\">KPM 模組列表</string>\n    <string name=\"kpm_autoload_no_kpm_added\">未新增 KPM 模組</string>\n    <string name=\"kpm_autoload_kpm_path\">KPM 路徑</string>\n    <string name=\"kpm_autoload_file_not_found\">檔案未找到</string>\n    <string name=\"kpm_autoload_select_kpm_file\">選擇 KPM 檔案</string>\n    <string name=\"kpm_autoload_first_time_title\">關於 KPM 自動載入</string>\n    <string name=\"kpm_autoload_first_time_message\">此功能可在開機時，自動以「臨時方式」載入設定中的所有 KPM 模組。相較於直接將模組嵌入至核心，這種方式更方便且安全。\n\n例如某些用於防止分割區被修改的 KPM 模組，僅能以臨時載入方式使用，若直接嵌入至核心，可能會導致裝置無法開機。若您不希望修改或破壞 Boot 分割區，也可透過此功能快速載入所需模組。\n\n請確保應用程式已完全結束後重新啟動，系統才會執行載入指令。一般情況下，裝置開機時也會自動載入模組。管理器在啟動時短暫黑畫面屬於正常現象，此功能採用純背景載入機制，請於進入後手動下拉重新整理，以確認模組是否已成功載入。</string>\n    <string name=\"kpm_autoload_first_time_confirm\">好</string>\n    <string name=\"kpm_autoload_do_not_show_again\">不再顯示</string>\n\n    <string name=\"kpm_page_first_time_title\">警告</string>\n    <string name=\"kpm_page_first_time_message\">核心模組會直接修改 Boot 分割區，與系統模組不同，核心模組不具備完善的救援機制，一旦發生問題，僅能透過 Fastboot 模式進行修復。\n\n建議先以臨時載入方式測試模組的穩定性，確認運作正常後，再將模組嵌入至 Boot 分割區。\n\n若該模組僅支援臨時載入，可改用 KPM 自動載入功能。\n\n若您不熟悉核心模組的運作原理，請勿使用此功能。</string>\n\n    <!-- APM Bulk Install Strings -->\n    <string name=\"apm_bulk_install_title\">系統模組批次安裝</string>\n    <string name=\"apm_bulk_install_list_title\">模組列表</string>\n    <string name=\"apm_bulk_install_add\">添加模組</string>\n    <string name=\"apm_bulk_install_empty\">未添加模組</string>\n    <string name=\"apm_bulk_install_action\">批次安裝</string>\n    <string name=\"apm_bulk_install_log_title\">批次安裝日誌</string>\n    <string name=\"apm_bulk_install_log_start\">開始批次安裝...</string>\n    <string name=\"apm_bulk_install_log_installing\">正在安裝 %1$s</string>\n    <string name=\"apm_batch_install_full_process\">完整批次安裝流程</string>\n    <string name=\"apm_batch_install_full_process_summary\">使用完整流程進行系統模組批次安裝</string>\n    <string name=\"next_module\">下一個模組</string>\n    <string name=\"apm_bulk_install_log_installed\">模組 %s 安裝完成。</string>\n    <string name=\"apm_bulk_install_log_done\">所有操作已完成。</string>\n    <string name=\"apm_bulk_install_first_use_text\">此功能允許一次性安裝多個模組，屬於快速安裝，適合安裝過程中未涉及按鍵操作的模組。若模組安裝過程需點按音量鍵，請於設定中啟用「完整批次安裝流程」。</string>\n    <string name=\"apm_bulk_install_remove\">移除</string>\n    <string name=\"apm_first_use_title\">歡迎使用系統模組</string>\n    <string name=\"apm_first_use_text\">歡迎使用系統模組。本功能支援 Magisk 模組。您可以點擊右下角的按鈕安裝模組，或使用頂部的安裝器進行模組批次安裝。此外，FolkPatch 也提供一鍵備份所有模組的功能，但請注意，此方式不一定適用於所有模組，建議自行備份關鍵模組。</string>\n    <!-- Module Backup/Restore Strings -->\n    <string name=\"apm_backup_title\">備份模組</string>\n    <string name=\"apm_restore_title\">還原模組</string>\n    <string name=\"apm_backup_success\">備份成功</string>\n    <string name=\"apm_restore_success\">還原成功</string>\n    <string name=\"apm_backup_failed\">備份失敗</string>\n    <string name=\"apm_restore_failed\">還原失敗</string>\n    <string name=\"apm_backup_failed_msg\">備份失敗：%s</string>\n    <string name=\"apm_restore_failed_msg\">還原失敗：%s</string>\n    <string name=\"apm_copy_list_title\">複製列表</string>\n    <string name=\"apm_copy_list_success\">模組列表已複製</string>\n\n    <!-- Font Settings -->\n    <string name=\"settings_custom_font\">自訂字體</string>\n    <string name=\"settings_select_font_file\">選擇字體檔案</string>\n    <string name=\"settings_custom_font_enabled\">已啟用自訂字體</string>\n    <string name=\"settings_custom_font_summary\">使用自訂 TTF 字體</string>\n    <string name=\"settings_font_selected\">已選擇自訂字體</string>\n    <string name=\"settings_custom_font_error\">儲存字體檔案失敗</string>\n    <string name=\"settings_custom_font_saved\">自訂字體儲存成功</string>\n    <string name=\"settings_clear_font\">恢復預設字體</string>\n    <string name=\"settings_clear_font_confirm\">確定要恢復預設字體嗎？</string>\n    <string name=\"settings_font_cleared\">已恢復預設字體</string>\n    <string name=\"settings_font_select_hint\">請選擇一個 TTF 字體檔案</string>\n    \n    <!-- Auto Backup Strings -->\n    <string name=\"settings_auto_backup_module\">自動備份系統模組</string>\n    <string name=\"settings_auto_backup_module_summary\">安裝模組時自動備份檔案到私有目錄</string>\n    <string name=\"settings_open_backup_dir\">開啟備份目錄</string>\n    <string name=\"backup_dir_empty\">備份目錄為空</string>\n    <string name=\"backup_dir_open_failed\">開啟備份目錄失敗</string>\n    <string name=\"auto_backup_failed\">自動備份失敗: %s</string>\n    <string name=\"auto_backup_success\">自動備份成功: %s</string>\n    <string name=\"settings_auto_backup_boot\">自動備份Boot</string>\n    <string name=\"settings_auto_backup_boot_summary\">自動將 Boot 分割區備份到本機儲存 (Downloads/FolkPatch/BootBackups)</string>\n\n    <!-- Auto-added missing strings -->\n    <string name=\"home_hide_kpatch_version_summary\">在資訊卡中隱藏 「KernelPatch 版本」</string>\n    <string name=\"home_hide_su_path_summary\">在資訊卡中隱藏「可執行 SU 路徑」</string>\n    <string name=\"home_hide_zygisk_summary\">在資訊卡中隱藏「Zygisk 實現」</string>\n    <string name=\"home_hide_mount_summary\">在資訊卡中隱藏「掛載實現」</string>\n    <string name=\"home_hide_fingerprint_summary\">在資訊卡中隱藏「指紋」</string>\n    <string name=\"home_hide_fingerprint\">隱藏「指紋」</string>\n    <string name=\"about_source_code\"><![CDATA[<p>查看原始碼於 %1$s<p/>加入我們的 %2$s 頻道<p/>加入我們的 %3$s 群組]]></string>\n    <string name=\"home_hide_kpatch_version\">隱藏「KernelPatch 版本」</string>\n    <string name=\"home_hide_su_path\">隱藏「可執行 SU 路徑」</string>\n    <string name=\"home_hide_zygisk\">隱藏「Zygisk 實現」</string>\n    <string name=\"home_hide_mount\">隱藏「掛載實現」</string>\n\n    <!-- Home Layout Strings -->\n    <string name=\"settings_home_layout_style\">首頁佈局樣式</string>\n    <string name=\"settings_home_layout_default\">ListUI</string>\n    <string name=\"settings_home_layout_grid\">GridUI</string>\n    <string name=\"settings_home_layout_focus\">FocusUI</string>\n    <string name=\"settings_home_layout_sign\">SignUI</string>\n    <string name=\"settings_home_layout_circle\">CircleUI</string>\n    <string name=\"settings_home_layout_dashboard_pro\">DashboardUI</string>\n    <string name=\"unofficial_version_title\">非官方版本</string>\n    <string name=\"unofficial_version_message\">你使用的 FolkPatch 並非官方正版，請下載正版 App 使用！</string>\n    <string name=\"go_to_github\">前往 Github</string>\n    <string name=\"settings_save_theme\">儲存主題</string>\n    <string name=\"settings_import_theme\">匯入主題</string>\n    <string name=\"settings_theme_saved\">主題已儲存</string>\n    <string name=\"settings_theme_imported\">主題已匯入</string>\n    <string name=\"settings_theme_save_failed\">儲存主題失敗</string>\n    <string name=\"settings_theme_import_failed\">匯入主題失敗</string>\n    <string name=\"settings_reset_theme\">恢復主題</string>\n    <string name=\"settings_reset_theme_confirm\">確定要將所有主題設置恢復為默認值嗎？這將清除所有自定義背景、字體、音樂和音效。</string>\n    <string name=\"settings_theme_reset\">主題已恢復默認</string>\n    <string name=\"settings_theme_reset_failed\">恢復主題失敗</string>\n\n    <!-- Theme Strings -->\n    <string name=\"theme_export_title\">匯出主題</string>\n    <string name=\"theme_import_title\">匯入主題</string>\n    <string name=\"theme_name\">主題名稱</string>\n    <string name=\"theme_type\">主題類型</string>\n    <string name=\"theme_type_phone\">手機</string>\n    <string name=\"theme_type_tablet\">平板</string>\n    <string name=\"theme_version\">版本</string>\n    <string name=\"theme_author\">作者</string>\n    <string name=\"theme_description\">描述</string>\n    <string name=\"theme_export_action\">匯出</string>\n    <string name=\"theme_import_action\">匯入</string>\n    <string name=\"theme_import_confirm\">確定要匯入此主題嗎？</string>\n    <string name=\"theme_info\">主題資訊</string>\n\n    <!-- Grid Working Card Background -->\n    <string name=\"settings_grid_working_card_background\">工作卡片背景</string>\n    <string name=\"settings_grid_working_card_background_enabled\">自定義卡片背景已啟用</string>\n    <string name=\"settings_grid_working_card_background_summary\">為工作卡片設定自定義背景圖片</string>\n    <string name=\"settings_grid_working_card_background_selected\">背景圖片已選擇</string>\n    <string name=\"settings_clear_grid_working_card_background\">清除卡片背景圖片</string>\n    <string name=\"settings_clear_grid_working_card_background_confirm\">確認清除卡片背景圖片？</string>\n    <string name=\"settings_grid_working_card_background_saved\">卡片背景圖片儲存成功</string>\n    <string name=\"settings_grid_working_card_background_error\">卡片背景圖片儲存失敗</string>\n    <string name=\"settings_grid_working_card_background_cleared\">卡片背景圖片已清除</string>\n\n    <!-- Biometric Strings -->\n    <string name=\"action_biometric\">生物識別認證</string>\n    <string name=\"msg_biometric\">請驗證您的生物識別身分</string>\n    <string name=\"settings_biometric_login\">生物識別認證</string>\n    <string name=\"settings_biometric_login_summary\">開啟應用程式時要求生物識別認證</string>\n    <string name=\"settings_strong_biometric\">強力生物識別</string>\n    <string name=\"settings_strong_biometric_summary\">安裝、解除安裝或停用模組時需要驗證</string>\n\n    <!-- Theme Store Strings -->\n    <string name=\"theme_store_title\">主題商店</string>\n    <string name=\"theme_source_official\">官方</string>\n    <string name=\"theme_source_third_party\">第三方</string>\n    <string name=\"theme_source_local\">本地</string>\n    <string name=\"theme_store_author\">作者: %s</string>\n    <string name=\"theme_store_version\">版本: %s</string>\n    <string name=\"theme_source\">來源</string>\n    <string name=\"theme_store_download_failed\">下載失敗</string>\n    <string name=\"theme_store_download\">下載</string>\n    <string name=\"theme_store_search_hint\">搜尋主題...</string>\n\n    <!-- Update Check Strings -->\n    <string name=\"settings_auto_update_check\">自動檢查更新</string>\n    <string name=\"settings_auto_update_check_summary\">應用程式啟動時自動檢查更新</string>\n    <string name=\"settings_check_update\">檢查更新</string>\n    <string name=\"update_available_title\">有可用更新</string>\n    <string name=\"update_available_message\">檢測到您的版本過低，是否要下載新版本？</string>\n    <string name=\"update_action\">更新</string>\n    <string name=\"update_close\">關閉</string>\n    <string name=\"update_latest\">您已是最新版本</string>\n    <string name=\"update_error\">檢查更新時出錯</string>\n    <!--折疊選項-->\n    <string name=\"settings_category_general\">一般</string>\n    <string name=\"superuser\">超級使用者</string>\n    <string name=\"module\">系統模組</string>\n    <string name=\"settings_category_appearance\">外觀</string>\n    <string name=\"settings_appearance_font\">字體設定</string>\n    <string name=\"settings_appearance_font_summary\">自訂字體配置</string>\n    <string name=\"settings_appearance_theme\">主題設定</string>\n    <string name=\"settings_appearance_theme_summary\">主題商店、儲存、匯入和重設</string>\n    <string name=\"settings_appearance_banner\">橫幅設定</string>\n    <string name=\"settings_appearance_banner_summary\">模組橫幅配置</string>\n    <string name=\"settings_appearance_layout\">版面設定</string>\n    <string name=\"settings_appearance_layout_summary\">首頁版面、導覽和卡片自訂</string>\n    <string name=\"settings_appearance_background\">背景設定</string>\n    <string name=\"settings_appearance_background_summary\">自訂背景、影片和多背景</string>\n    <string name=\"settings_appearance_night_mode\">夜間模式設定</string>\n    <string name=\"settings_appearance_night_mode_summary\">深色主題和顏色配置</string>\n    <string name=\"settings_amoled_theme\">AMOLED 純黑主題</string>\n    <string name=\"settings_amoled_theme_desc\">為深色模式使用純黑背景</string>\n    <string name=\"settings_switch_icon\">啟用按鈕指示器</string>\n    <string name=\"settings_switch_icon_desc\">為開關按鈕添加狀態指示圖標</string>\n    <string name=\"settings_category_behavior\">行為</string>\n    <string name=\"settings_category_function\">功能</string>\n    <string name=\"settings_use_legacy_su_page\">單頁超級用戶授權</string>\n    <string name=\"settings_use_legacy_su_page_summary\">超級用戶頁面改用單頁授權設計</string>\n    <string name=\"settings_category_module\">模組</string>\n    <string name=\"settings_category_security\">安全</string>\n    \n    <!-- 背景音樂字元串 -->\n    <string name=\"settings_background_music\">背景音樂</string>\n    <string name=\"settings_background_music_summary\">應用在前台運行時播放背景音樂</string>\n    <string name=\"settings_background_music_playing\">正在播放: %s</string>\n    <string name=\"settings_background_music_enabled\">背景音樂已啟用</string>\n    <string name=\"settings_select_music_file\">選擇音樂檔案</string>\n    <string name=\"settings_music_selected\">音樂已選擇</string>\n    <string name=\"settings_clear_music\">清除音樂</string>\n    <string name=\"settings_clear_music_confirm\">確定要清除背景音樂嗎？</string>\n    <string name=\"settings_music_auto_play\">自動播放</string>\n    <string name=\"settings_music_auto_play_summary\">應用開啟時自動播放音樂</string>\n    <string name=\"settings_music_looping\">循環播放</string>\n    <string name=\"settings_music_looping_summary\">循環播放當前歌曲</string>\n    <string name=\"settings_music_volume\">音量</string>\n    <string name=\"settings_music_saved\">音樂檔案已儲存</string>\n    <string name=\"settings_music_save_error\">音樂檔案儲存失敗</string>\n    <string name=\"settings_music_cleared\">音樂已清除</string>\n    <string name=\"settings_category_multimedia\">多媒體</string>\n    <string name=\"settings_category_general_summary\">語言、更新、SELinux、系統調整</string>\n    <string name=\"settings_category_appearance_summary\">主題、顏色、佈局、背景、字型</string>\n    <string name=\"settings_category_behavior_summary\">Web除錯、安裝行為、首頁顯示</string>\n    <string name=\"settings_category_security_summary\">生物辨識、超級密鑰管理</string>\n    <string name=\"settings_category_backup_summary\">本地備份、雲端備份、WebDAV</string>\n    <string name=\"settings_category_module_summary\">模組資訊、排序、批次安裝</string>\n    <string name=\"settings_category_function_summary\">FolkPatch隱藏、Umount服務</string>\n    <string name=\"settings_category_multimedia_summary\">背景音樂、音效、振動</string>\n    <string name=\"settings_music_playback_control\">播放控制</string>\n    <!-- Theme Store Filter -->\n    <string name=\"theme_store_filter_title\">篩選主題</string>\n    <string name=\"theme_store_filter_author\">作者</string>\n    <string name=\"theme_store_filter_author_hint\">輸入作者名稱</string>\n    <string name=\"theme_store_filter_source\">來源</string>\n    <string name=\"theme_store_filter_source_all\">全部</string>\n    <string name=\"theme_store_filter_type\">裝置類型</string>\n    <string name=\"theme_store_filter_apply\">應用</string>\n    <string name=\"theme_store_filter_reset\">重置</string>\n\n    <!-- Video Background Settings -->\n    <string name=\"settings_video_background\">影片背景</string>\n    <string name=\"settings_video_background_summary\">使用影片作為背景</string>\n    <string name=\"settings_select_video\">選擇影片</string>\n    <string name=\"settings_video_selected\">已選擇影片</string>\n    <string name=\"settings_video_background_enabled\">影片背景已啟用</string>\n    <string name=\"settings_clear_video_background\">清除影片牆紙</string>\n    <string name=\"settings_clear_video_background_confirm\">確定要清除影片牆紙嗎？</string>\n    <string name=\"settings_video_volume\">影片音量</string>\n    <!-- File Picker -->\n    <string name=\"file_picker_permission_required\">需要權限</string>\n    <string name=\"file_picker_permission_desc\">為了瀏覽檔案，請授予應用「所有檔案存取權」。</string>\n    <string name=\"file_picker_grant_permission\">授予權限</string>\n    <string name=\"file_picker_cancel\">取消</string>\n    <string name=\"file_picker_internal_storage\">內部儲存</string>\n    <string name=\"file_picker_no_files\">無檔案</string>\n\n    <!-- HomeV3 Strings -->\n    <string name=\"home_device_status_title\">裝置狀態</string>\n    <string name=\"home_device_status_battery_temp\">電池溫度</string>\n    <string name=\"home_device_status_cpu_load\">CPU 負載</string>\n    <string name=\"home_device_status_battery_level\">電池電量</string>\n    <string name=\"home_storage_title\">儲存資訊</string>\n    <string name=\"home_storage_internal\">內部儲存</string>\n    <string name=\"home_storage_ram\">記憶體</string>\n    <string name=\"home_storage_zram\">ZRAM</string>\n    <string name=\"home_storage_swap\">交換檔案</string>\n    <string name=\"home_info_kernel\">核心</string>\n    <string name=\"home_info_superkey\">超級密鑰</string>\n    <string name=\"home_info_auth_auth\">已驗證</string>\n    <string name=\"home_info_auth_na\">不可用</string>\n    <string name=\"home_info_device_slot\">裝置插槽</string>\n    <string name=\"home_info_device_model\">裝置型號</string>\n    <string name=\"home_info_running_mode\">運行模式</string>\n    <string name=\"home_info_mode_full\">完整</string>\n    <string name=\"home_info_mode_half\">部分功能</string>\n    <string name=\"home_version\">版本: %s</string>\n    <string name=\"home_zygisk_implement\">Zygisk 實現</string>\n    <string name=\"home_mount_implement\">掛載實現</string>\n\n    <string name=\"settings_app_list_loading_scheme\">應用程式列表載入方案</string>\n    <string name=\"settings_app_list_loading_scheme_summary\">選擇如何載入應用程式列表</string>\n    <string name=\"app_list_loading_scheme_root_service\">RootService (預設)</string>\n    <string name=\"app_list_loading_scheme_package_manager\">PackageManager (系統 API)</string>\n    <string name=\"app_list_loading_scheme_dialog_title\">載入方案</string>\n    <string name=\"su_backup_list\">備份列表</string>\n    <string name=\"su_restore_list\">還原列表</string>\n    <string name=\"backup_success\">備份成功</string>\n    <string name=\"restore_success\">還原成功</string>\n    <!-- Badge Count Settings -->\n    <string name=\"enable_badge_count\">計數標記顯示設定</string>\n    <string name=\"enable_badge_count_summary\">設定導覽列項目的計數標記顯示</string>\n    <string name=\"badge_superuser\">顯示超級使用者計數標記</string>\n    <string name=\"badge_apm\">顯示系統模組計數標記</string>\n    <string name=\"badge_kernel\">顯示核心模組計數標記</string>\n\n    <string name=\"settings_sound_effect\">點擊音效</string>\n    <string name=\"settings_sound_effect_summary\">點擊時播放音效</string>\n    <string name=\"settings_sound_effect_enabled\">已啟用</string>\n    <string name=\"settings_sound_effect_playing\">目前選擇：%s</string>\n    <string name=\"settings_sound_effect_source\">音效來源</string>\n    <string name=\"settings_sound_effect_source_local\">本地檔案</string>\n    <string name=\"settings_sound_effect_source_preset\">預設</string>\n    <string name=\"settings_sound_effect_preset_title\">預設音效</string>\n    <string name=\"settings_select_sound_effect\">選擇音效檔案</string>\n    <string name=\"settings_sound_effect_selected\">已選擇音效檔案</string>\n    <string name=\"settings_sound_effect_scope\">生效範圍</string>\n    <string name=\"settings_sound_effect_scope_global\">全域（任何位置）</string>\n    <string name=\"settings_sound_effect_scope_bottom_bar\">僅導航列</string>\n    <string name=\"settings_clear_sound_effect\">清除音效</string>\n    <string name=\"settings_clear_sound_effect_confirm\">確定要清除音效嗎？</string>\n    <string name=\"settings_sound_effect_cleared\">音效已清除</string>\n\n    <string name=\"settings_startup_sound\">啟動音效</string>\n    <string name=\"settings_startup_sound_summary\">應用啟動時播放音效</string>\n    <string name=\"settings_startup_sound_enabled\">已啟用啟動音效</string>\n    <string name=\"settings_startup_sound_playing\">正在播放: %s</string>\n    <string name=\"settings_select_startup_sound\">選擇啟動音效</string>\n    <string name=\"settings_startup_sound_selected\">已選擇啟動音效</string>\n    <string name=\"settings_clear_startup_sound\">清除啟動音效</string>\n    <string name=\"settings_clear_startup_sound_confirm\">確定要清除啟動音效嗎？</string>\n    <string name=\"settings_startup_sound_cleared\">啟動音效已清除</string>\n\n    <string name=\"settings_vibration\">震動觸感</string>\n    <string name=\"settings_vibration_summary\">在觸摸事件時震動</string>\n    <string name=\"settings_vibration_enabled\">啟用震動</string>\n    <string name=\"settings_vibration_intensity\">震動強度</string>\n    <string name=\"settings_vibration_scope\">震動範圍</string>\n    <string name=\"settings_vibration_scope_global\">全域(任意位置)</string>\n    <string name=\"settings_vibration_scope_bottom_bar\">僅底部欄</string>\n\n    <!-- Backup Settings -->\n    <string name=\"settings_category_backup\">備份</string>\n    <string name=\"settings_enable_cloud_backup\">啟用雲端備份</string>\n    <string name=\"settings_enable_cloud_backup_summary\">自動備份已安裝的模組到雲端服務</string>\n    <string name=\"settings_configure_webdav\">設定 WebDAV 服務</string>\n    <string name=\"webdav_config_title\">WebDAV 設定</string>\n    <string name=\"webdav_url\">WebDAV 網址</string>\n    <string name=\"webdav_username\">使用者名稱</string>\n    <string name=\"webdav_password\">密碼</string>\n    <string name=\"webdav_test_success\">測試成功</string>\n    <string name=\"webdav_test_failed\">測試失敗: %s</string>\n    <string name=\"webdav_uploading\">正在上傳至 WebDAV...</string>\n    <string name=\"webdav_backup_success\">WebDAV 備份成功</string>\n    <string name=\"webdav_backup_failed\">WebDAV 備份失敗: %s</string>\n    <string name=\"save\">儲存</string>\n    <string name=\"test\">測試</string>\n    <string name=\"webdav_path_label\">路徑 (例如 /Backup)</string>\n    <string name=\"webdav_view_logs\">查看記錄</string>\n    <string name=\"webdav_backup_logs_title\">備份記錄</string>\n    <string name=\"webdav_no_logs\">暫無記錄</string>\n    <string name=\"webdav_clear_logs\">清除</string>\n    <string name=\"settings_enable_local_backup\">啟用本機備份</string>\n    <string name=\"settings_enable_local_backup_summary\">備份模組到本機儲存空間 (Downloads/FolkPatch/ModuleBackups)</string>\n    <string name=\"close\">關閉</string>\n    <string name=\"settings_backup_password\">備份密碼</string>\n    <string name=\"settings_backup_password_hint\">輸入備份密碼</string>\n\n    <string name=\"patch_output_written_to\"> 修補成功，檔案輸出到 </string>\n    <string name=\"patch_write_failed\"> 修補失敗，發生了致命錯誤</string>\n\n    <!-- Script Library Strings -->\n    <string name=\"script_library\">腳本庫</string>\n    <string name=\"script_library_title\">腳本庫</string>\n    <string name=\"script_library_empty\">暫無腳本，點擊新增按鈕新增一個</string>\n    <string name=\"script_library_add\">新增腳本</string>\n    <string name=\"script_library_add_title\">新增腳本</string>\n    <string name=\"script_library_select_file\">選擇腳本檔案</string>\n    <string name=\"script_library_alias\">別名</string>\n    <string name=\"script_library_alias_hint\">輸入腳本別名（可選）</string>\n    <string name=\"script_library_run\">執行</string>\n    <string name=\"script_library_delete\">刪除</string>\n    <string name=\"script_library_path\">路徑：%s</string>\n    <string name=\"script_library_confirm_delete\">確認刪除腳本？</string>\n    <string name=\"script_library_delete_success\">腳本刪除成功</string>\n    <string name=\"script_library_delete_failed\">刪除腳本失敗</string>\n    <string name=\"script_library_add_success\">腳本新增成功</string>\n    <string name=\"script_library_add_failed\">新增腳本失敗：%s</string>\n    <string name=\"script_library_load_failed\">載入腳本庫失敗</string>\n    <string name=\"script_library_output\">執行輸出</string>\n    <string name=\"script_library_no_output\">無輸出</string>\n    <string name=\"script_library_save_log\">儲存日誌</string>\n    <string name=\"script_library_log_saved\">日誌已儲存至 %s</string>\n    <string name=\"script_library_log_save_failed\">儲存日誌失敗：%s</string>\n\n    <string name=\"search_modules\">搜尋模組...</string>\n    <string name=\"search_scripts\">搜尋腳本...</string>\n\n    <!-- Umount Service -->\n    <string name=\"settings_umount_service\">Umount Service</string>\n    <string name=\"settings_umount_service_summary\">配置開機自動卸載的掛載點</string>\n    <string name=\"umount_config_title\">Umount 配置</string>\n    <string name=\"umount_config_enabled\">啟用 Umount</string>\n    <string name=\"umount_config_enabled_summary\">開機自動卸載指定掛載點</string>\n    <string name=\"umount_config_paths_label\">掛載點路徑（每行一個）</string>\n    <string name=\"umount_config_paths_placeholder\">/system/vendor/app\\n/system/vendor/priv-app</string>\n    <string name=\"umount_config_paths_helper\">每行輸入一個需要卸載的掛載點路徑</string>\n    <string name=\"umount_config_save\">儲存</string>\n    <string name=\"umount_config_save_confirm\">確認儲存配置？</string>\n    <string name=\"umount_config_save_success\">配置儲存成功</string>\n    <string name=\"umount_config_save_failed\">配置儲存失敗</string>\n\n    <!-- 我的主題頁面 -->\n    <string name=\"my_themes_title\">我的主題</string>\n    <string name=\"my_themes_empty\">暫無主題</string>\n    <string name=\"my_themes_empty_action\">逛逛主題商店</string>\n    <string name=\"my_themes_apply\">應用主題</string>\n    <string name=\"my_themes_delete\">刪除主題</string>\n    <string name=\"my_themes_delete_confirm\">確定要刪除此主題嗎？</string>\n    <string name=\"my_themes_deleted\">主題已刪除</string>\n    <string name=\"my_themes_applied\">主題已應用</string>\n    <string name=\"my_themes_apply_failed\">應用主題失敗</string>\n    <string name=\"my_themes_details\">主題詳情</string>\n\n    <!-- 下載對話框 -->\n    <string name=\"theme_download_title\">正在下載主題</string>\n    <string name=\"theme_download_completed\">下載完成</string>\n    <string name=\"theme_download_failed\">下載失敗</string>\n    <string name=\"theme_download_progress\">進度</string>\n    <string name=\"theme_download_file\">主題檔案</string>\n    <string name=\"theme_download_image\">預覽圖片</string>\n    <string name=\"theme_download_cancel\">取消</string>\n    <string name=\"theme_download_pause\">暫停</string>\n    <string name=\"theme_download_resume\">繼續</string>\n    <string name=\"theme_download_retry\">重試</string>\n    <string name=\"theme_download_apply\">應用主題</string>\n    <string name=\"theme_download_go_to_my_themes\">我的主題</string>\n    <string name=\"theme_download_retrying\">正在重試 (%d/3)...</string>\n    <string name=\"theme_download_preparing\">準備下載...</string>\n    <string name=\"theme_download_downloading_file\">正在下載主題檔案...</string>\n    <string name=\"theme_download_downloading_image\">正在下載預覽圖片...</string>\n    <string name=\"theme_download_finalizing\">正在完成...</string>\n    <string name=\"settings_predictive_back\">預測性返回手勢</string>\n    <string name=\"settings_predictive_back_summary\">啟用 Android 14+ 的預測性返回手勢動畫</string>\n\n    <string name=\"home_device_status_battery_charging\">充電中</string>\n    <string name=\"home_device_status_cpu_temp\">CPU 溫度</string>\n    <string name=\"home_device_status_memory_trend\">記憶體趨勢</string>\n    <string name=\"home_storage_partitions\">儲存分區</string>\n    <string name=\"home_device_status_cpu_freq\">CPU 頻率</string>\n    <string name=\"home_network_rx\">下載</string>\n    <string name=\"home_network_tx\">上傳</string>\n    <string name=\"settings_home_layout_stats\">StatsUI</string>\n    <string name=\"home_stats_more_options\">更多選項</string>\n    <string name=\"home_stats_kp_label\">KP</string>\n    <string name=\"home_stats_manager_label\">管理器</string>\n    <string name=\"home_device_status_gpu_load\">GPU 負載</string>\n    <string name=\"home_stats_kernel_modules\">內核模組</string>\n    <string name=\"home_stats_apm_modules\">系統模組</string>\n    <string name=\"home_stats_superusers\">超級使用者</string>\n    <string name=\"settings_kernel_spoof\">內核偽裝配置</string>\n    <string name=\"settings_kernel_spoof_summary\">偽裝內核版本和構建時間</string>\n    <string name=\"settings_kernel_spoof_version\">內核版本</string>\n    <string name=\"settings_kernel_spoof_build_time\">內核構建時間</string>\n    <string name=\"settings_kernel_spoof_restore\">恢復</string>\n\n    <string name=\"kernel_spoof_enabled\">內核偽裝已啟用</string>\n    <string name=\"kernel_spoof_disabled_restored\">內核偽裝已停用並還原</string>\n    <string name=\"kernel_spoof_failed\">內核偽裝失敗: %d</string>\n    <string name=\"kernel_spoof_applied\">內核偽裝已應用</string>\n\n    <string name=\"settings_path_hide\">路徑隱藏</string>\n    <string name=\"settings_path_hide_summary\">在核心層面向應用程式隱藏檔案和目錄</string>\n    <string name=\"path_hide_paths_label\">隱藏路徑（每行一個）</string>\n\n    <string name=\"path_hide_paths_helper\">每行輸入一個路徑。符合的路徑將回傳 ENOENT。</string>\n    <string name=\"path_hide_save\">儲存</string>\n    <string name=\"path_hide_enabled\">路徑隱藏已啟用</string>\n    <string name=\"path_hide_disabled\">路徑隱藏已停用</string>\n    <string name=\"path_hide_applied\">路徑隱藏設定已套用</string>\n    <string name=\"path_hide_failed\">路徑隱藏操作失敗：%d</string>\n    <string name=\"path_hide_uid_mode\">UID 執行模式</string>\n    <string name=\"path_hide_uid_mode_summary\">僅對指定應用程式 UID 隱藏路徑</string>\n    <string name=\"path_hide_uids_label\">目標 UID（每行一個）</string>\n    <string name=\"path_hide_uids_helper\">輸入應用程式 UID，僅這些應用程式的路徑會被隱藏。</string>\n    <string name=\"path_hide_uid_save\">儲存 UID</string>\n    <string name=\"path_hide_uid_mode_enabled\">UID 執行模式已啟用</string>\n    <string name=\"path_hide_uid_mode_disabled\">UID 執行模式已停用</string>\n    <string name=\"path_hide_filter_system\">過濾系統 UID</string>\n    <string name=\"path_hide_filter_system_summary\">同時對 root 和系統進程（UID &lt; 10000）隱藏路徑</string>\n    <string name=\"path_hide_filter_system_enabled\">系統 UID 過濾已啟用</string>\n    <string name=\"path_hide_filter_system_disabled\">系統 UID 過濾已停用</string>\n    <string name=\"path_hide_filter_system_warning_title\">警告</string>\n    <string name=\"path_hide_filter_system_warning_message\">啟用後將同時對 root 和系統進程（UID &lt; 10000）隱藏路徑。這可能導致部分系統功能異常，請謹慎操作。</string>\n    <string name=\"path_hide_uid_applied\">UID 白名單已套用</string>\n    <string name=\"path_hide_select_apps\">選擇應用</string>\n    <string name=\"path_hide_no_apps_selected\">未選擇應用</string>\n    <string name=\"path_hide_app_removed\">已移除 %d 個已解除安裝應用的設定</string>\n    <string name=\"path_hide_search_apps\">搜尋應用…</string>\n    <string name=\"path_hide_show_system\">顯示系統應用</string>\n    <string name=\"netisolate_title\">網路隔離</string>\n    <string name=\"netisolate_enable\">網路隔離已啟用</string>\n    <string name=\"netisolate_disable\">網路隔離已停用</string>\n    <string name=\"netisolate_enable_summary\">在核心層面阻止所選應用的網路存取</string>\n    <string name=\"netisolate_uids_label\">已隔離應用</string>\n    <string name=\"netisolate_uids_hint\">輸入要隔離的 UID（每行一個）</string>\n    <string name=\"netisolate_no_uids\">暫無隔離應用</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-zh-rWC/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\" translatable=\"false\">FolkPatch</string>\n    <string name=\"app_name_fpatch\">FPatch</string>\n\n    <string name=\"desktop_app_name\">仙府应用名称</string>\n\n    <string name=\"home\">仙府主殿</string>\n    <string name=\"success\">登仙功成</string>\n    <string name=\"failure\">渡劫败陨</string>\n\n    <string name=\"patch_warnning\">炼符补道有天劫之险，必先将本命数据存入乾坤戒！</string>\n    <string name=\"patch\">炼符补道</string>\n\n    <string name=\"kernel_patch\">道核符箓</string>\n    <string name=\"android_patch\">天道符箓</string>\n\n    <string name=\"settings_nav_layout_title\">天机阁导航阵图设定</string>\n    <string name=\"settings_nav_layout_summary\">隐去或现出天机阁导航阵图之部分组件</string>\n    <string name=\"settings_nav_scheme\">天机阁导航阵法方案</string>\n    <string name=\"settings_nav_mode\">天机阁导航阵法模式</string>\n    <string name=\"settings_nav_mode_summary\">选择天机阁导航阵法之现出方式</string>\n    <string name=\"settings_nav_mode_auto\">天机自动传统</string>\n    <string name=\"settings_nav_mode_bottom\">永恒现于天机阁底部阵法</string>\n    <string name=\"settings_nav_mode_rail\">永恒现于天机阁侧方阵法</string>\n    <string name=\"settings_nav_mode_floating\">悬浮底部阵法</string>\n    <string name=\"settings_show_apm\">现天道仙符</string>\n    <string name=\"settings_show_kpm\">现道核仙符</string>\n    <string name=\"settings_show_superuser\">现登仙至尊</string>\n    <string name=\"settings_floating_auto_hide\">仙法底栏自动隐遁</string>\n    <string name=\"settings_floating_auto_hide_summary\">3秒无仙法操作后底栏自动隐遁</string>\n    <string name=\"settings_floating_swipe_hide\">仙法底栏滑动隐遁</string>\n    <string name=\"settings_floating_swipe_hide_summary\">向下划动隐遁底栏，向上划动现出底栏</string>\n    <string name=\"settings_navbar_glass_effect\">仙法琉璃效果</string>\n    <string name=\"settings_navbar_glass_effect_summary\">为悬浮仙法导航栏施加仙法琉璃视觉效果</string>\n    <string name=\"settings_navbar_glass_blur_strength\">琉璃模糊强度</string>\n    <string name=\"settings_navbar_glass_transparency\">琉璃背景透明度</string>\n    <string name=\"settings_navbar_glass_highlight_strength\">琉璃高光强度</string>\n    <string name=\"settings_navbar_glass_specular\">琉璃镜面反射</string>\n    <string name=\"settings_navbar_glass_specular_summary\">在仙法导航栏顶部启用琉璃镜面高光效果</string>\n    <string name=\"settings_navbar_glass_inner_glow\">琉璃内发光</string>\n    <string name=\"settings_navbar_glass_inner_glow_summary\">在仙法导航栏底部启用琉璃微光效果</string>\n    <string name=\"settings_navbar_glass_border\">琉璃边框</string>\n    <string name=\"settings_navbar_glass_border_summary\">在仙法导航栏周围启用琉璃边框描边</string>\n    <string name=\"settings_stats_top_layout\">顶部仙法阵</string>\n    <string name=\"settings_stats_top_layout_summary\">选择顶部信息仙卡样式</string>\n    <string name=\"settings_stats_top_layout_list\">列表仙卡</string>\n    <string name=\"settings_stats_top_layout_grid\">网格仙卡</string>\n    <string name=\"settings_block_kernelpatch_update\">屏蔽道核符箓更新神识</string>\n    <string name=\"settings_block_kernelpatch_update_summary\">不再以此俗事扰乱道心</string>\n    <string name=\"settings_block_androidpatch_update\">屏蔽护法灵符更新神识</string>\n    <string name=\"settings_block_androidpatch_update_summary\">不再以此等杂务扰乱道心</string>\n    <string name=\"settings_disable_module_update_check\">禁诸仙符更新之查</string>\n    <string name=\"settings_disable_module_update_check_summary\">禁诸天道仙符自动更新之查</string>\n\n    <string name=\"reboot\">重铸仙躯</string>\n    <string name=\"settings\">天机阁</string>\n    <string name=\"reboot_recovery\">重铸入回魂境</string>\n    <string name=\"reboot_bootloader\">重铸入引仙台</string>\n    <string name=\"reboot_download\">重铸入纳宝阁</string>\n    <string name=\"reboot_edl\">重铸入封灵域</string>\n    <string name=\"reboot_fastbootd\">重铸入太虚境</string>\n    <string name=\"about\">仙门叙略</string>\n    <string name=\"developer_and_maintainer\">道门尊长 | 护道者</string>\n    <string name=\"settings_app_language\">仙音道语</string>\n    <string name=\"system_default\">循天道常序</string>\n    <string name=\"settings_global_namespace_mode\">全域仙域结界</string>\n    <string name=\"settings_global_namespace_mode_summary\">凡登仙法会，皆启全域洞天挂载结界</string>\n    <string name=\"settings_clear_super_key_dialog\">道友确欲破除此秘钥乎？</string>\n\n    <string name=\"home_learn_apatch\">参悟FolkPatch仙法</string>\n    <string name=\"home_click_to_learn_apatch\">探FolkPatch仙法之玄妙，明渡劫登仙之术</string>\n    <string name=\"settings_hide_apatch_card\">隐去「参悟FolkPatch仙法」仙笺</string>\n    <string name=\"settings_hide_apatch_card_summary\">藏起仙府主殿之「参悟FolkPatch仙法」仙笺</string>\n\n    <string name=\"about_source_code\"><![CDATA[<p>于%1$s观仙法源典<p/>入吾%2$s仙音频道<p/>赴吾%3$s仙友寮阁]]></string>\n    <string name=\"send_log\">呈渡劫日志</string>\n    <string name=\"save_log\">藏渡劫日志</string>\n    <string name=\"log_saved\">渡劫日志已存入乾坤戒</string>\n    <string name=\"safe_mode\">避劫之境</string>\n    <string name=\"github\">GitHub</string>\n    <string name=\"telegram\">Telegram</string>\n    <string name=\"support_or_donate\">道力支持 / 仙石捐赠</string>\n\n    <string name=\"super_key\">登仙秘钥</string>\n    <string name=\"clear_super_key\">碎毁登仙秘钥</string>\n    <string name=\"patch_set_superkey\">凝炼登仙秘钥</string>\n    <string name=\"home_patch_set_key_desc\">KernelPatch仙法之唯一登仙凭证</string>\n    <string name=\"home_patch_next_step\">下一道劫</string>\n\n    <string name=\"home_not_installed\">未入仙门</string>\n    <string name=\"home_install_unknown\">未入仙门或未验仙籍</string>\n    <string name=\"home_install_unknown_summary\">点触引仙入道</string>\n    <string name=\"home_click_to_install\">点触引仙入道</string>\n    <string name=\"home_working\">闭关修炼中</string>\n    <string name=\"home_kp_need_update\">有仙法新篇可用</string>\n    <string name=\"home_kp_cando_update\">渡劫升仙</string>\n\n    <string name=\"home_installing\">引仙入道中</string>\n\n    <string name=\"kpatch_version\">仙法篇次：%s</string>\n    <string name=\"apatch_version\">仙法篇次：%s</string>\n\n    <string name=\"kpatch_version_update\" formatted=\"false\">仙法篇次：%s → %s</string>\n    <string name=\"kpatch_shadow_path_title\">kpatch</string>\n    <string name=\"home_auth_key_title\">输入登仙秘钥</string>\n    <string name=\"home_auth_key_desc\">验明仙籍，方可执掌仙法</string>\n    <string name=\"home_kpatch_info_title\">仙门事宜</string>\n\n    <string name=\"home_ap_cando_install\">引仙入道</string>\n    <string name=\"home_ap_cando_uninstall\">归为凡常</string>\n    <string name=\"home_ap_cando_reboot\">重铸仙躯</string>\n\n    <string name=\"patch_title\">炼符补道</string>\n\n    <string name=\"patch_config_title\">仙符</string>\n    <string name=\"patch_mode_bootimg_patch\">渡劫之式：炼符补道</string>\n    <string name=\"patch_mode_patch_and_install\">渡劫之式：炼符且引仙入道</string>\n    <string name=\"patch_mode_install_to_next_slot\">渡劫之式：入未启仙槽（天道易筋后）</string>\n    <string name=\"patch_mode_uninstall_patch\">渡劫之式：废去KPatch仙法</string>\n    <string name=\"patch_select_bootimg_btn\">择仙躯镜像</string>\n    <string name=\"patch_embed_kpm_btn\">嵌仙符KPM</string>\n    <string name=\"patch_start_patch_btn\">启渡劫</string>\n    <string name=\"patch_start_unpatch_btn\">归凡复道</string>\n    <string name=\"patch_item_error\">！！仙法舛误！！</string>\n    <string name=\"patch_item_bootimg\">仙躯bootimg</string>\n    <string name=\"patch_item_bootimg_slot\">仙槽：</string>\n    <string name=\"patch_item_bootimg_dev\">仙器：</string>\n    <string name=\"patch_item_kernel\">道核</string>\n    <string name=\"patch_item_kpimg\">仙符kpimg</string>\n    <string name=\"patch_item_kpimg_version\">仙法篇次：</string>\n    <string name=\"patch_item_kpimg_comile_time\">炼符辰刻：</string>\n    <string name=\"patch_item_kpimg_config\">天机典式：</string>\n    <string name=\"patch_item_new_extra_kpm\">嵌新仙符</string>\n    <string name=\"patch_item_existed_extra_kpm\">已存仙符</string>\n    <string name=\"patch_item_extra_name\">符名：</string>\n    <string name=\"patch_item_extra_version\">符篆篇次：</string>\n    <string name=\"patch_item_extra_author\">符祖：</string>\n    <string name=\"patch_item_extra_kpm_license\">仙门允准：</string>\n    <string name=\"patch_item_extra_kpm_desciption\">符篆玄义：</string>\n    <string name=\"patch_item_extra_args\">渡劫数参：</string>\n    <string name=\"patch_item_extra_event\">应劫机宜：</string>\n    <string name=\"patch_item_skey\">登仙秘钥</string>\n    <string name=\"patch_custom_superkey\">自拟登仙秘钥</string>\n    <string name=\"patch_custom_superkey_summary\">道友仍可设秘钥以御内核，此器已获仙印认可</string>\n    <string name=\"patch_item_set_skey_label\">登仙秘钥须凝炼八至六十三道符文，含天数字文，毋杂邪魔异符。</string>\n    <string name=\"patch_confirm_superkey\">再次凝炼登仙秘钥</string>\n    <string name=\"patch_skey_mismatch\">两次登仙秘钥不合</string>\n\n    <string name=\"home_kernel\">道核篇次</string>\n    <string name=\"home_manager_version\">仙府辖器篇次</string>\n    <string name=\"home_fingerprint\">仙门印玺</string>\n\n    <string name=\"home_selinux_status\">SELinux禁守令</string>\n    <string name=\"home_selinux_status_disabled\">仙禁已破</string>\n    <string name=\"home_selinux_status_enforcing\">闭关</string>\n    <string name=\"home_selinux_status_permissive\">仙禁宽宥之境</string>\n    <string name=\"home_selinux_status_unknown\">仙禁未辨</string>\n\n    <string name=\"settings_selinux_mode\">禁守令式</string>\n    <string name=\"settings_selinux_mode_summary\">择选禁守令之模式</string>\n    <string name=\"settings_selinux_mode_enforcing\">闭关 (森严)</string>\n    <string name=\"settings_selinux_mode_permissive\">宽宥 (宽松)</string>\n    <string name=\"settings_selinux_current_mode\">当下: %s</string>\n    <string name=\"settings_selinux_mode_enforcing_summary\">禁守令严苛执行，绝无通融</string>\n    <string name=\"settings_selinux_mode_permissive_summary\">禁守令仅记录违规，不予惩戒</string>\n\n    <string name=\"home_device_info\">仙器</string>\n    <string name=\"home_system_version\">天道篇次</string>\n    <string name=\"home_kpatch_version\">道核符箓篇次</string>\n    <string name=\"home_su_path\">登仙令符</string>\n    <string name=\"home_apatch_version\">FolkPatch仙法篇次</string>\n\n    <string name=\"kpm\">道核仙符</string>\n    <string name=\"kpm_kp_not_installed\">未凝炼道核符箓</string>\n    <string name=\"kpm_add_kpm\">纳新仙符KPM</string>\n    <string name=\"kpm_load\">引符入道</string>\n    <string name=\"kpm_install\">凝符入体</string>\n    <string name=\"kpm_embed\">嵌符入道核</string>\n    <string name=\"kpm_load_toast_succ\">引符入道功成</string>\n    <string name=\"kpm_load_toast_failed\">引符入道败陨</string>\n    <string name=\"kpm_unload_confirm\">欲废去%s仙符？</string>\n    <string name=\"kpm_unload\">废符出体</string>\n    <string name=\"kpm_control\">驭符辖制</string>\n    <string name=\"kpm_apm_empty\">未引仙符入道</string>\n    <string name=\"kpm_version\">符篆篇次</string>\n    <string name=\"kpm_license\">仙门允准</string>\n    <string name=\"kpm_author\">符祖</string>\n    <string name=\"kpm_desc\">符篆玄义</string>\n    <string name=\"kpm_args\">渡劫数参</string>\n\n    <string name=\"su_title\">登仙至尊</string>\n    <string name=\"su_selinux_via_hook\">假仙法钩连破仙禁</string>\n    <string name=\"su_pkg_excluded_label\">摒于仙域之外</string>\n    <string name=\"su_batch_exclude_title\">批量摒除仙籍</string>\n    <string name=\"su_batch_exclude_content\">为诸无登仙权之凡器摒除仙符注入，道友宜择一渡劫之措</string>\n    <string name=\"su_exclude_btn\">摒除仙籍</string>\n    <string name=\"su_exclude_reverse_btn\">反摒仙籍</string>\n\n    <!-- 超级用户应用操作弹窗 -->\n    <string name=\"su_app_action_title\">法器仙术</string>\n    <string name=\"su_app_action_content\">请择一仙术来对法器施之</string>\n    <string name=\"su_app_action_launch\">唤出法器</string>\n    <string name=\"su_app_action_force_stop\">封禁法器</string>\n    <string name=\"su_app_action_launch_success\">正在唤出法器 %s</string>\n    <string name=\"su_app_action_force_stop_success\">已封禁法器 %s</string>\n    <string name=\"su_app_action_failed\">渡劫败陨: %s</string>\n\n    <!-- SU Audit Log -->\n    <string name=\"su_audit_log_title\">授权日志</string>\n    <string name=\"su_audit_log_empty\">暂无授权记录</string>\n    <string name=\"su_audit_log_clear\">清除日志</string>\n    <string name=\"su_audit_log_clear_confirm\">确认清除所有授权记录？</string>\n    <string name=\"su_audit_action_grant\">已授权</string>\n    <string name=\"su_audit_action_revoke\">已撤销</string>\n    <string name=\"su_audit_action_exclude\">已排除</string>\n    <string name=\"su_audit_tab_usage\">使用记录</string>\n    <string name=\"su_audit_tab_operations\">操作记录</string>\n\n    <string name=\"su_pkg_excluded_setting_title\">改仙籍摒除之令</string>\n    <string name=\"su_pkg_excluded_setting_summary\">启此仙令，许FolkPatch仙法复此凡器仙符所改之诸天道文卷。</string>\n    <string name=\"su_pkg_root_setting_title\">登仙至尊</string>\n    <string name=\"su_pkg_root_setting_summary\">启此仙令为尔凡器启登仙至尊，凡器能登仙之咒</string>\n    <string name=\"su_pkg_normal_setting_title\">凡尘模式</string>\n    <string name=\"su_pkg_normal_setting_summary\">凡尘之境，无仙法加持，不授登仙至尊之权。</string>\n    <string name=\"su_show_system_apps\">显天道凡器</string>\n    <string name=\"su_hide_system_apps\">隐天道凡器</string>\n    <string name=\"su_refresh\">刷新仙域</string>\n\n    <string name=\"apm\">天道仙符</string>\n    <string name=\"apm_not_installed\">未凝炼天道符箓</string>\n    <string name=\"apm_failed_to_enable\">启仙符败陨：%s</string>\n    <string name=\"apm_failed_to_disable\">禁仙符败陨：%s</string>\n    <string name=\"apm_empty\">未凝炼仙符入体</string>\n    <string name=\"apm_remove\">祛符出体</string>\n    <string name=\"apm_undo\">撤销</string>\n    <string name=\"apm_install\">凝符入体</string>\n    <string name=\"apm_uninstall_confirm\">欲废去%s天道仙符？</string>\n    <string name=\"apm_uninstall_success\">%s仙符已废</string>\n    <string name=\"apm_uninstall_failed\">废符败陨：%s</string>\n    <string name=\"apm_undo_uninstall_success\">%s已恢复契约</string>\n    <string name=\"apm_undo_uninstall_failed\">恢复契约失败: %s</string>\n    <string name=\"apm_version\">符篆篇次</string>\n    <string name=\"apm_author\">符祖</string>\n    <string name=\"apm_desc\">符篆玄义</string>\n    <string name=\"apm_overlay_fs_not_available\">OverlayFS仙域为道核所禁，天道仙符不可用！</string>\n    <string name=\"apm_magisk_conflict\">以与Magisk魔符相戾，天道仙符不可用！</string>\n    <string name=\"apm_mount_warning_title\">重要提示</string>\n    <string name=\"apm_mount_warning_message\">默认不挂载仙符，请使用内置挂载系统或者元仙符</string>\n    <string name=\"apm_mount_warning_button\">我知道了</string>\n    <string name=\"apm_reboot_to_apply\">重铸仙躯乃显符效</string>\n    <string name=\"apm_changelog\">仙法易新录</string>\n    <string name=\"apm_update\">渡劫升符</string>\n    <string name=\"apm_downloading\">纳新仙符中：%s</string>\n    <string name=\"apm_start_downloading\">启纳仙符：%s</string>\n    <string name=\"apm_new_version_available\">新符篇%s已现世，点触渡劫升符。</string>\n\n    <string name=\"hide_apatch_manager\">隐APatch仙府辖器</string>\n    <string name=\"hide_apatch_manager_summary\">凝炼一代用仙器，易仙府印记，更凡器名讳，顺天随机而定</string>\n    <string name=\"hide_apatch_dialog_new_manager_name\">新仙府辖器名讳</string>\n    <string name=\"hide_apatch_dialog_summary\">此名讳将显于引仙台，为新凡器之称</string>\n    <string name=\"hide_apatch_manager_failure\">隐匿仙器败陨。宜报此仙法舛误！</string>\n\n    <string name=\"setting_reset_su_path\">重铸登仙令符之路</string>\n    <string name=\"setting_reset_su_new_path\">新登仙令符全径</string>\n\n    <string name=\"settings_folkx_engine_title\">FolkX仙法动画引擎</string>\n    <string name=\"settings_folkx_engine_summary\">在天阶仙页转换时使用仙域灵动的物理符箓动画</string>\n    <string name=\"settings_folkx_animation_type\">仙法动画类型</string>\n    <string name=\"settings_folkx_animation_speed\">仙法动画速度</string>\n    <string name=\"settings_folkx_animation_linear\">仙域线性运动</string>\n    <string name=\"settings_folkx_animation_spatial\">仙域空间运动</string>\n    <string name=\"settings_folkx_animation_fade\">仙光渐隐渐显</string>\n    <string name=\"settings_folkx_animation_vertical\">仙光垂直滑动</string>\n    <string name=\"settings_folkx_animation_diagonal\">仙光对角滑动</string>\n\n    <string name=\"apm_webui_open\">启仙法玄窗</string>\n    <string name=\"apm_action\">渡劫措置</string>\n    <string name=\"module_shortcut_add\">遁仙符</string>\n    <string name=\"module_shortcut_not_supported\">仙门唤阵之台不承仙术界之遁仙符。</string>\n    <string name=\"module_shortcut_created\">已于仙术界造遁仙符之宜。</string>\n    <string name=\"module_shortcut_updated\">遁仙符已易之宜。</string>\n    <string name=\"module_shortcut_permission_tip_xiaomi\">请于小米仙门事宜之台，为本仙门者启\"造仙术界之遁仙符\"之宜。</string>\n    <string name=\"module_shortcut_permission_tip_oppo\">请于OPPO仙门事宜之台，为本仙门者启\"仙术界之遁仙符\"之宜。</string>\n    <string name=\"module_shortcut_permission_tip_default\">若造遁仙符之宜败，请于仙门事宜之台，为本仙门者启仙术界之遁仙符之宜。</string>\n    <string name=\"module_shortcut_name\">遁仙符名</string>\n    <string name=\"module_shortcut_icon\">遁仙符像</string>\n    <string name=\"module_shortcut_type\">遁仙符类</string>\n    <string name=\"module_shortcut_type_webui\">WebUI</string>\n    <string name=\"module_shortcut_type_action\">Action</string>\n    <string name=\"module_shortcut_icon_default\">循旧像</string>\n    <string name=\"module_shortcut_icon_select\">择新像</string>\n    <string name=\"enable_web_debugging\">启WebView勘符之术</string>\n    <string name=\"enable_web_debugging_summary\">可勘WebUI仙符之误。唯渡劫需时启之。</string>\n    <string name=\"settings_apm_install_confirm\">凝符入体之仙籍确证</string>\n    <string name=\"settings_apm_install_confirm_summary\">凝符入体前，显仙籍确证之玄窗</string>\n    <string name=\"settings_apm_stay_on_page\">留于渡劫措置台</string>\n    <string name=\"settings_apm_stay_on_page_summary\">天道仙符渡劫措置毕，不自动返仙府</string>\n    <string name=\"settings_enable_module_shortcut_add\">启遁仙符添加仙钮</string>\n    <string name=\"settings_enable_module_shortcut_add_summary\">于天界仙符台，显 WebUI 遁仙符添加之仙钮</string>\n    <string name=\"apm_install_confirm_title\">凝符入体</string>\n    <string name=\"apm_install_confirm_content\">道友确欲凝炼%s仙符入体乎？</string>\n    <string name=\"settings_show_more_module_info\">显仙符详录</string>\n    <string name=\"settings_show_more_module_info_summary\">仙符录中显仙符号与大小</string>\n    <string name=\"settings_module_sort_optimization\">仙符排序优化</string>\n    <string name=\"settings_module_sort_optimization_summary\">使天界仙符中具WebUI与符法者排于前列</string>\n    <string name=\"settings_fold_system_module\">折叠天界仙符</string>\n    <string name=\"settings_fold_system_module_summary\">点击仙符画卷展开/折叠符法按钮</string>\n        <string name=\"settings_simple_list_bottom_bar\">简约仙符底栏</string>\n        <string name=\"settings_simple_list_bottom_bar_summary\">仙符符法使用纯图标按钮样式，灵感来自APatch</string>\n    <string name=\"settings_spliced_card_group\">拼接天界仙符</string>\n    <string name=\"settings_spliced_card_group_summary\">仙符符法使用连续拼接卡片样式</string>\n    <string name=\"apm_disable_all_title\">禁诸仙符</string>\n    <string name=\"apm_disable_all_confirm\">确欲尽禁诸仙符乎？此术将废诸已施之仙符</string>\n    <string name=\"apm_enable_module_banner\">启仙阵横幅</string>\n    <string name=\"apm_enable_module_banner_summary\">为所容之仙阵显横幅仙图</string>\n    <string name=\"apm_enable_folk_banner\">自定仙阵横幅</string>\n    <string name=\"apm_enable_folk_banner_summary\">长按仙阵卡片自选横幅仙图</string>\n    <string name=\"apm_folk_banner_title\">FolkBanner</string>\n    <string name=\"apm_folk_banner_select\">择仙图</string>\n    <string name=\"apm_folk_banner_clear\">消解 FolkBanner</string>\n    <string name=\"apm_folk_banner_saved\">已为 %s 注入 FolkBanner 仙气。</string>\n    <string name=\"apm_folk_banner_cleared\">%s 的 FolkBanner 仙气已消解。</string>\n    <string name=\"apm_folk_banner_failed\">为 %s 注入 FolkBanner 仙气失败。</string>\n    <string name=\"apm_banner_api_mode\">仙图法门</string>\n    <string name=\"apm_banner_api_mode_summary\">借用天界仙图之法或凡间图库为仙阵提供仙图</string>\n    <string name=\"apm_banner_api_source\">布置仙图法门</string>\n    <string name=\"apm_banner_api_source_hint\">输入天界仙图法门或凡间图库路径</string>\n    <string name=\"apm_banner_api_source_saved\">仙图法门已布置</string>\n    <string name=\"apm_banner_api_source_not_configured\">尚未布置</string>\n    <string name=\"apm_banner_api_url_configured\">天界仙图法门已布置</string>\n    <string name=\"apm_banner_local_dir_configured\">凡间图库已布置</string>\n    <string name=\"apm_banner_clear_cache\">消解仙图印记</string>\n    <string name=\"apm_banner_cache_cleared\">仙图印记已消解</string>\n    <string name=\"apm_banner_api_config_title\">布置仙图法门</string>\n    <string name=\"apm_banner_api_config_desc\">输入天界仙图法门或凡间图库路径，每座仙阵将得独一无二的仙图。</string>\n    <string name=\"apm_banner_api_examples_title\">法门示例：</string>\n    <string name=\"apm_banner_api_examples\">天界仙图: https://picsum.photos/800/400\\n凡间图库: /sdcard/Pictures/Banners</string>\n    <!-- API 仙界集市 -->\n    <string name=\"apm_api_marketplace_title\">API 仙界集市</string>\n    <string name=\"apm_api_preview\">仙法预览</string>\n    <string name=\"apm_api_apply\">施展仙法</string>\n    <string name=\"apm_api_preview_title\">API 仙法预览</string>\n    <string name=\"apm_api_preview_failed\">仙法预览加载失败</string>\n    <string name=\"apm_api_url_label\">API 仙法门路：</string>\n    <string name=\"apm_api_apply_success\">API 仙法施展成功</string>\n    <string name=\"apm_api_verifying\">仙法验证中…</string>\n    <string name=\"apm_api_retry\">重新施展</string>\n    <string name=\"apm_api_empty\">暂无 API 仙法门</string>\n    <string name=\"settings_banner_custom_opacity\">仙阵横幅透度</string>\n    <string name=\"settings_banner_custom_opacity_summary\">自定仙阵横幅透度，不随凡界背景而变</string>\n    <string name=\"settings_banner_opacity\">仙阵横幅不透法</string>\n\n    <string name=\"settings_donot_store_superkey\">不藏登仙秘钥于凡界</string>\n    <string name=\"settings_donot_store_superkey_summary\">每启仙府辖器，皆验登仙秘钥</string>\n\n    <string name=\"mode_select_page_title\">引仙入道</string>\n    <string name=\"mode_select_page_patch_and_install\">炼符且引仙入道</string>\n    <string name=\"mode_select_page_select_file\">择待炼之仙躯镜像</string>\n    <string name=\"mode_select_page_install_inactive_slot\">引仙入未启仙槽（天道易筋后）</string>\n    <string name=\"mode_select_page_install_inactive_slot_warning\">仙器重铸后，必启于当前未启之仙槽！\\n唯天道易筋既毕，可用此渡劫之选。\\n道友欲继之乎？</string>\n    <string name=\"mode_select_page_select_kpimg\">择本地仙法核心 (KPimg)</string>\n    <string name=\"patch_custom_kpimg_label\">自定义 KPimg</string>\n    <string name=\"patch_custom_kpimg_file\">文件: %s</string>\n    <string name=\"patch_select_kpimg_btn\">选择 KPimg</string>\n\n    <string name=\"home_dialog_auth_fail_title\">仙籍验败</string>\n    <string name=\"home_dialog_auth_fail_content\">未验登仙秘钥，致 FolkPatch 仙法未成。\n仙籍验败之由，约有数端——道友可自省之：\n\n\\n一、尔根本未以 KernelPatch 仙法炼符补仙躯 boot.img，或忘却自身所为之事。\n\\n二、炼符后之仙躯 boot.img 尚卧于凡间电脑之中，根本未引仙入体。\n\\n三、登仙秘钥符文舛误，或杂邪魔异符，如来自外域之文字。\n\\n四、尔之仙器可能不兼容 FolkPatch 与 KernelPatch 仙法，强行渡劫亦是徒劳。\n\\n五、尔或行神秘操作——如用排除仙名之仙法屏蔽 FolkPatch，终致自绝于仙途。\n\\n六、尔或安装与当世仙官兼容有碍之内核仙法，致使仙力冲突！\n\\n宜先勘之重试，若仍有天劫，可于仙门官库 issue 仙台问之。\n毕竟，有些劫数可能纯粹是尔亲手种下之因！\n祝道友渡劫顺利！顺带一提，点击下方道藏按钮准没错！</string>\n\n    <string name=\"home_more_menu_feedback_or_suggestion\">呈渡劫见谛或献升仙策</string>\n    <string name=\"home_more_menu_about\">仙门叙略</string>\n    <string name=\"home_more_menu_document\">道藏</string>\n\n    <string name=\"home_dialog_uninstall_title\">废去仙籍</string>\n    <string name=\"home_dialog_uninstall_ap_only\">唯废符箓</string>\n    <string name=\"home_dialog_uninstall_ap_only_desc\">唯废系统符箓，留仙官</string>\n    <string name=\"home_dialog_uninstall_all\">尽废仙法</string>\n    <string name=\"home_dialog_uninstall_all_desc\">废去系统符箓，始废仙流程</string>\n\n    <string name=\"kpm_control_dialog_title\">驭道核仙符</string>\n    <string name=\"kpm_control_dialog_content\">请输驭符之渡劫数参：</string>\n    <string name=\"kpm_control_paramters\">渡劫数参</string>\n    <string name=\"kpm_control_outMsg\">驭符显化</string>\n    <string name=\"kpm_control_ok\">驭符功成！</string>\n    <string name=\"kpm_control_failed\">驭符败陨！</string>\n    \n    <string name=\"settings_night_mode_follow_sys\">幽冥之境，循天道</string>\n    <string name=\"settings_night_mode_follow_sys_summary\">依天道之设，自动易幽冥之境</string>\n    <string name=\"settings_night_theme_enabled\">幽冥之境</string>\n    \n    <string name=\"settings_use_system_color_theme\">天道色章</string>\n    <string name=\"settings_use_system_color_theme_summary\">用天道因仙壁而生之色章</string>\n    <string name=\"settings_custom_color_theme\">仙府色章</string>\n\n    <string name=\"amber_theme\">琥珀仙光</string>\n    <string name=\"blue_theme\">青冥仙光</string>\n    <string name=\"blue_grey_theme\">青灰仙光</string>\n    <string name=\"brown_theme\">褐尘仙光</string>\n    <string name=\"cyan_theme\">苍云仙光</string>\n    <string name=\"deep_orange_theme\">深橙仙焰</string>\n    <string name=\"deep_purple_theme\">深紫仙雾</string>\n    <string name=\"green_theme\">青木仙光</string>\n    <string name=\"indigo_theme\">靛蓝仙霭</string>\n    <string name=\"light_blue_theme\">浅青仙光</string>\n    <string name=\"light_green_theme\">浅绿仙光</string>\n    <string name=\"lime_theme\">柠黄仙光</string>\n    <string name=\"orange_theme\">橙阳仙焰</string>\n    <string name=\"pink_theme\">粉霞仙光</string>\n    <string name=\"purple_theme\">紫霄仙雾</string>\n    <string name=\"red_theme\">赤火仙焰</string>\n    <string name=\"sakura_theme\">樱粉仙霞</string>\n    <string name=\"teal_theme\">苍绿仙光</string>\n    <string name=\"yellow_theme\">金阳仙光</string>\n    <string name=\"theme_color\">主题颜色</string>\n    <string name=\"theme_light\">浅色</string>\n    <string name=\"theme_dark\">深色</string>\n    <string name=\"theme_system\">系统</string>\n    <string name=\"loading_modules\">正在获取模块…</string>\n    <string name=\"loading_scripts\">正在查找脚本…</string>\n    <string name=\"loading_themes\">正在加载主题…</string>\n    <string name=\"loading_apis\">正在加载 API 仙法门…</string>\n\n    <string name=\"about_app_desc\">基于 KernelPatch 仙法之 Root 实现，毋需重编道核的同时，允许钩道核仙函。</string>\n    <string name=\"about_powered_by\">赖%s仙门以辅</string>\n    <string name=\"about_telegram_group\">Telegram仙友寮阁</string>\n    \n    <!-- Online Module Strings -->\n    <string name=\"online_module_title\">云上天道仙符</string>\n    <string name=\"online_module_download_start\">启纳仙符：%s</string>\n    <string name=\"online_module_download_complete\">纳符功成：%s</string>\n    <string name=\"online_module_download_notification\">纳%s仙符中。可察仙音栏知渡劫进度，功成则视纳宝之匣。</string>\n\n    <!-- Online KPM Strings -->\n    <string name=\"online_kpm_title\">云上天道核符</string>\n    <string name=\"online_kpm_download_start\">启纳仙符：%s</string>\n    <string name=\"online_kpm_download_complete\">纳符功成：%s</string>\n    <string name=\"online_kpm_download_notification\">纳%s仙符中。可察仙音栏知渡劫进度，功成则视纳宝之匣。</string>\n\n    <!-- Online Script Strings -->\n    <string name=\"online_script_title\">云上天道仙阵</string>\n    <string name=\"online_script_download_start\">启纳仙阵：%s</string>\n    <string name=\"online_script_download_complete\">纳符功成：%s</string>\n    <string name=\"online_script_download_notification\">纳%s仙阵中</string>\n\n    <!-- Crash Handle Strings -->\n    <string name=\"crash_handle_title\">渡劫崩毁呈报</string>\n    <string name=\"crash_handle_copy\">誊录渡劫残卷</string>\n\n    <!-- About Screen Strings -->\n    <string name=\"about_app_version\">仙法篇次: %s</string>\n    <string name=\"about_github\">GitHub仙府</string>\n    <string name=\"about_telegram_channel\">Telegram仙音频道</string>\n\n    <string name=\"app_title_superroot\">SuperRoot</string>\n\n    <string name=\"settings_app_dpi\">仙器DPI</string>\n    <string name=\"dpi_apply_settings\">确认</string>\n    <string name=\"dpi_confirm_title\">确认更改DPI</string>\n    <string name=\"dpi_confirm_message\">将仙器DPI从%1$s更改为%2$s？</string>\n    <string name=\"settings_magic_mount\">Folk Mount API</string>\n    <string name=\"settings_magic_mount_summary\">启内建仙符挂载系统</string>\n    <string name=\"settings_new_app_profile_mode\">新仙器默认道法</string>\n    <string name=\"settings_new_app_profile_normal\">凡体</string>\n    <string name=\"settings_new_app_profile_root\">超凡</string>\n    <string name=\"settings_new_app_profile_exclude\">驱逐</string>\n    <string name=\"settings_hide_service\">FolkPatch Hide</string>\n    <string name=\"settings_hide_service_summary\">施展障眼法隐藏Bootloader解锁状态</string>\n    <string name=\"settings_app_title\">仙器名讳</string>\n    <string name=\"app_title_custom\">自赐名讳</string>\n    <string name=\"settings_custom_app_title\">设置仙器名讳</string>\n    <string name=\"custom_app_title_dialog_title\">自赐仙器名讳</string>\n    <string name=\"custom_app_title_dialog_hint\">请赐仙器名讳</string>\n    <string name=\"custom_app_title_dialog_confirm\">赐名</string>\n    <string name=\"custom_app_title_dialog_empty\">名讳不可为空</string>\n    <string name=\"cancel\">罢了</string>\n    <string name=\"settings_custom_background\">自定仙府背景</string>\n    <string name=\"settings_custom_background_enabled\">已启自定仙府背景</string>\n    <string name=\"settings_custom_background_opacity\">仙笺透度</string>\n    <string name=\"settings_custom_background_blur\">仙府背景模糊</string>\n    <string name=\"settings_custom_background_dim\">仙府暗度</string>\n    <string name=\"settings_select_background_image\">择仙府背景图</string>\n    <string name=\"settings_custom_background_summary\">设自定仙府背景图</string>\n    <string name=\"settings_custom_background_saved\">仙府背景已存</string>\n    <string name=\"settings_alt_icon\">備用圖標</string>\n    <string name=\"alt_icon_summary\">用備選啟動器圖標</string>\n    <string name=\"settings_custom_background_error\">存仙府背景败陨</string>\n    <string name=\"settings_background_selected\">已择仙府背景</string>\n    <string name=\"settings_background_image_cleared\">仙府背景图已涤</string>\n    <string name=\"settings_clear_background\">涤仙府背景</string>\n    <string name=\"settings_clear_background_confirm\">确欲涤仙府背景图乎？</string>\n    <string name=\"settings_multi_background_mode\">多背景模式</string>\n    <string name=\"settings_multi_background_mode_summary\">为不同页面设置不同的仙府背景图片</string>\n    <string name=\"settings_select_home_background\">首页仙府背景</string>\n    <string name=\"settings_select_kernel_background\">内核模块仙府背景</string>\n    <string name=\"settings_select_superuser_background\">超级用户仙府背景</string>\n    <string name=\"settings_select_system_module_background\">系统模块仙府背景</string>\n    <string name=\"settings_select_settings_background\">设置页面仙府背景</string>\n\n    <string name=\"settings_advanced_title_style\">进阶题款样式</string>\n    <string name=\"settings_advanced_title_style_summary\">以自绘图样置换顶栏题款</string>\n    <string name=\"settings_advanced_title_style_enabled\">进阶题款样式已启</string>\n    <string name=\"settings_select_title_image\">择题款图样</string>\n    <string name=\"settings_title_image_selected\">已选题款图样</string>\n    <string name=\"settings_title_image_saved\">题款图样存讫</string>\n    <string name=\"settings_title_image_error\">存题款图样败</string>\n    <string name=\"settings_clear_title_image\">除题款图样</string>\n    <string name=\"settings_clear_title_image_confirm\">确欲除题款图样乎？</string>\n    <string name=\"settings_title_image_cleared\">题款图样已除</string>\n    <string name=\"settings_title_image_day_opacity\">白昼模式透明度</string>\n    <string name=\"settings_title_image_night_opacity\">黑夜模式透明度</string>\n    <string name=\"settings_title_image_dim\">背景昏暗度</string>\n    <string name=\"settings_title_image_offset_x\">横平位置</string>\n\n    <string name=\"settings_launcher_icon\">引仙台标识</string>\n    \n    <string name=\"su_exclude_all_title\">批量摒除仙籍</string>\n    <string name=\"su_exclude_all_confirm\">确欲尽摒诸未授登仙权之凡器乎？</string>\n\n    <!-- 隐藏设定字符串 -->\n    <string name=\"home_hide_su_path\">隐登仙令符之路</string>\n    <string name=\"home_hide_kpatch_version\">隐道核符箓篇次</string>\n    <string name=\"home_hide_fingerprint\">隐仙门指印之宜</string>\n    <string name=\"home_hide_zygisk\">隐 Zygisk 仙人指路之宜</string>\n    <string name=\"home_hide_mount\">隐符箓悬挂之宜</string>\n    <string name=\"home_hide_su_path_summary\">于仙门事宜仙笺，隐登仙令符之路</string>\n    <string name=\"home_hide_kpatch_version_summary\">于仙门事宜仙笺，隐道核符箓篇次之宜</string>\n    <string name=\"home_hide_zygisk_summary\">于仙门事宜仙笺，隐 Zygisk 仙人指路之宜</string>\n    <string name=\"home_hide_mount_summary\">于仙门事宜仙笺，隐符箓悬挂之宜</string>\n    <string name=\"home_hide_fingerprint_summary\">于仙门事宜仙笺，隐仙门指印之宜</string>\n    <string name=\"settings_grid_working_card_hide_check\">隐仙符标记</string>\n    <string name=\"settings_grid_working_card_hide_check_summary\">隐仙术卡片之符印或警示</string>\n    <string name=\"settings_grid_working_card_hide_text\">隐仙术卡片之文字</string>\n    <string name=\"settings_grid_working_card_hide_text_summary\">隐仙术卡片上\"闭关修炼中中\"或\"未入仙门\"文字</string>\n    <string name=\"settings_grid_working_card_hide_mode\">隐仙术卡片之道法</string>\n    <string name=\"settings_grid_working_card_hide_mode_summary\">隐仙术卡片上\"Full\"或\"Half\"文字</string>\n    <string name=\"settings_list_card_hide_status_badge\">使用经典表情</string>\n    <string name=\"settings_list_card_hide_status_badge_summary\">首页之仙域印记即刻变身经典表情</string>\n    <string name=\"settings_custom_badge_text\">自选仙域印记文字</string>\n    <string name=\"settings_custom_badge_text_summary\">修改仙域印记之文字显示，仅供道友娱乐</string>\n    <string name=\"settings_custom_background_dual_dim\">启昼夜双修仙法</string>\n    <string name=\"settings_custom_background_dual_dim_desc\">根据亮色和暗色主题自动调整背景暗度</string>\n    <string name=\"settings_custom_background_day_dim\">白昼仙法暗度</string>\n    <string name=\"settings_custom_background_night_dim\">夜晚仙法暗度</string>\n    <string name=\"settings_grid_working_card_dual_opacity\">启昼夜双透仙法</string>\n    <string name=\"settings_grid_working_card_dual_opacity_desc\">根据亮色和暗色主题自动调整卡片透明度</string>\n    <string name=\"settings_grid_working_card_day_opacity\">白昼卡片透法</string>\n    <string name=\"settings_grid_working_card_night_opacity\">夜晚卡片透法</string>\n    <!-- KPM AutoLoad Strings -->\n    <string name=\"kpm_autoload_title\">道核仙符自动引符</string>\n    <string name=\"kpm_autoload_enabled\">启自动引符</string>\n    <string name=\"kpm_autoload_enabled_summary\">重铸仙躯时，自动引道核仙符入道</string>\n    <string name=\"kpm_autoload_json_config\">JSON天机典式</string>\n    <string name=\"kpm_autoload_json_label\">JSON天机典式</string>\n    <string name=\"kpm_autoload_json_placeholder\">{\n  \"enabled\": true,\n  \"kpmPaths\": [\n    \"/path/to/module1.kpm\",\n    \"/path/to/module2.kpm\"\n  ]\n}</string>\n    <string name=\"kpm_autoload_json_error\">JSON天机典式非正</string>\n    <string name=\"kpm_autoload_json_helper\">输正之JSON天机典式，含道核仙符文卷路之数组</string>\n    <string name=\"kpm_autoload_save\">存天机典式</string>\n    <string name=\"kpm_autoload_save_confirm\">存自动引符之天机典式？</string>\n    <string name=\"kpm_autoload_save_success\">天机典式已存</string>\n    <string name=\"kpm_autoload_save_failed\">存天机典式败陨</string>\n    <string name=\"kpm_autoload_visual_mode\">可视渡劫之式</string>\n    <string name=\"kpm_autoload_json_mode\">JSON天机之式</string>\n    <string name=\"kpm_autoload_add_kpm\">纳新道核仙符</string>\n    <string name=\"kpm_autoload_remove_kpm\">祛符出典</string>\n    <string name=\"kpm_autoload_edit_kpm\">编辑</string>\n    <string name=\"kpm_autoload_edit_dialog_title\">编辑 KPM</string>    <string name=\"kpm_autoload_event_label\">事件:</string>    <string name=\"kpm_autoload_args_label\">参数:</string>    <string name=\"kpm_autoload_event_service\">service</string>    <string name=\"kpm_autoload_event_post_fs_data\">post-fs-data</string>\n    <string name=\"kpm_autoload_kpm_list_title\">道核仙符列</string>\n    <string name=\"kpm_autoload_no_kpm_added\">未纳道核仙符</string>\n    <string name=\"kpm_autoload_kpm_path\">道核仙符之路</string>\n    <string name=\"kpm_autoload_file_not_found\">仙符文卷未觅</string>\n    <string name=\"kpm_autoload_select_kpm_file\">择道核仙符文卷</string>\n    <string name=\"kpm_autoload_first_time_title\">论道核仙符自动引符</string>\n    <string name=\"kpm_autoload_first_time_message\">此仙能可令重铸仙躯时自动临时引天机典式文卷之诸道核仙符，此法较之直嵌道核，更便渡劫也。\n\n譬如，防仙域分区被改之道核仙符，唯可临时引符用之，嵌之则仙器不启。或不欲损BOOT仙域，可藉此天机典式速引符入道。\n\n必使凡器尽退再入，方行渡劫之令。大抵重铸入仙府，亦能引符。仙府辖器黑屏片时，乃渡劫常态也，用纯后台引符之法，宜手动上滑刷新仙域，察其是否引符入道！</string>\n    <string name=\"kpm_autoload_first_time_confirm\">贫道已知</string>\n    <string name=\"kpm_autoload_do_not_show_again\">毋复显渡劫警言</string>\n\n    <!-- APM Bulk Install Strings -->\n    <string name=\"apm_bulk_install_title\">天道仙符批量引符</string>\n    <string name=\"apm_bulk_install_list_title\">仙符列</string>\n    <string name=\"apm_bulk_install_add\">纳仙符</string>\n    <string name=\"apm_bulk_install_empty\">未纳仙符</string>\n    <string name=\"apm_bulk_install_action\">批量引符</string>\n    <string name=\"apm_bulk_install_log_title\">批量引符录</string>\n    <string name=\"apm_bulk_install_log_start\">始批量引符渡劫...</string>\n    <string name=\"apm_bulk_install_log_installing\">正在引符 %1$s</string>\n    <string name=\"apm_batch_install_full_process\">完整批量引符渡劫</string>\n    <string name=\"apm_batch_install_full_process_summary\">使用完整渡劫流程进行天道仙符批量引符</string>\n    <string name=\"next_module\">下一道仙符</string>\n    <string name=\"apm_bulk_install_log_installed\">仙符 %s 引符毕。</string>\n    <string name=\"apm_bulk_install_log_done\">诸渡劫事了。</string>\n    <string name=\"apm_bulk_install_first_use_text\">此仙能可令汝一次性引多枚仙符，此乃速引之法，宜用引符时不涉音量仙键之仙符。若涉音量仙键之操作，请去设置启用完整流程引符之法。</string>\n    <string name=\"apm_bulk_install_remove\">除</string>\n    <string name=\"apm_first_use_title\">欢迎使用天道仙符</string>\n    <string name=\"apm_first_use_text\">欢迎使用天道仙符，此地所用乃Magisk仙阵兼容之仙符，右下之钮可引仙符，亦可引顶部之仙器批量引符，又有一键备份所有仙符之能，然此法未必适所有仙符，汝当多加备份</string>\n\n    <string name=\"kpm_page_first_time_title\">渡劫警言</string>\n    <string name=\"kpm_page_first_time_message\">道核仙符者，直改仙躯Boot之法也，不若天道仙符有救砖之策，有天劫则唯入Fastboot仙台修之。宜先引符入道，无天劫再嵌于仙躯Boot。若仙符唯可引符用之，可试自动道核仙符引符之能。若未谙道核仙符，毋用此渡劫之能！</string>\n    <!-- Module Backup/Restore Strings -->\n    <string name=\"apm_backup_title\">拓印灵纹</string>\n    <string name=\"apm_restore_title\">重塑灵纹</string>\n    <string name=\"apm_backup_success\">拓印圆满</string>\n    <string name=\"apm_restore_success\">重塑圆满</string>\n    <string name=\"apm_backup_failed\">拓印受阻</string>\n    <string name=\"apm_restore_failed\">重塑受阻</string>\n    <string name=\"apm_backup_failed_msg\">拓印受阻：%s</string>\n    <string name=\"apm_restore_failed_msg\">重塑受阻：%s</string>\n    <string name=\"apm_copy_list_title\">抄录卷宗</string>\n    <string name=\"apm_copy_list_success\">名录已拓印</string>\n\n    <!-- Font Settings -->\n    <string name=\"settings_custom_font\">仙府字体</string>\n    <string name=\"settings_select_font_file\">择仙府字体</string>\n    <string name=\"settings_custom_font_enabled\">已启仙府字体</string>\n    <string name=\"settings_custom_font_summary\">用自定仙府字体(TTF)</string>\n    <string name=\"settings_font_selected\">已择仙府字体</string>\n    <string name=\"settings_custom_font_error\">存仙府字体败陨</string>\n    <string name=\"settings_custom_font_saved\">仙府字体已存</string>\n    <string name=\"settings_clear_font\">复归原初字体</string>\n    <string name=\"settings_clear_font_confirm\">确欲复归原初字体乎？</string>\n    <string name=\"settings_font_cleared\">已复归原初字体</string>\n    <string name=\"settings_font_select_hint\">请择一 TTF 仙府字体</string>\n    \n    <!-- Auto Backup Strings -->\n    <string name=\"settings_auto_backup_module\">自动拓印天道仙符</string>\n    <string name=\"settings_auto_backup_module_summary\">凝符入体时自动拓印仙符至乾坤戒</string>\n    <string name=\"settings_open_backup_dir\">观乾坤戒</string>\n    <string name=\"backup_dir_empty\">乾坤戒空空如也</string>\n    <string name=\"backup_dir_open_failed\">观乾坤戒受阻</string>\n    <string name=\"auto_backup_failed\">自动拓印受阻: %s</string>\n    <string name=\"auto_backup_success\">自动拓印圆满: %s</string>\n    <string name=\"settings_auto_backup_boot\">自动拓印Boot神印</string>\n    <string name=\"settings_auto_backup_boot_summary\">自动将Boot神印拓印至乾坤戒 (Downloads/FolkPatch/BootBackups)</string>\n\n    <!-- Auto-added missing strings -->\n\n    <!-- Home Layout Strings -->\n    <string name=\"settings_home_layout_style\">仙府主殿布局</string>\n    <string name=\"settings_home_layout_default\">ListUI</string>\n    <string name=\"settings_home_layout_grid\">GridUI</string>\n    <string name=\"settings_home_layout_focus\">FocusUI</string>\n    <string name=\"settings_home_layout_sign\">SignUI</string>\n    <string name=\"settings_home_layout_circle\">CircleUI</string>\n    <string name=\"settings_home_layout_dashboard_pro\">DashboardUI</string>\n\n    <!-- Unofficial Version Strings -->\n    <string name=\"unofficial_version_title\">非官制版</string>\n    <string name=\"unofficial_version_message\">君所用之FolkPatch非官制所出，请下载官制正版应用！</string>\n    <string name=\"go_to_github\">往Github处</string>\n    <string name=\"settings_save_theme\">藏仙府色章</string>\n    <string name=\"settings_import_theme\">引仙府色章</string>\n    <string name=\"settings_theme_saved\">仙府色章已藏</string>\n    <string name=\"settings_theme_imported\">仙府色章已引</string>\n    <string name=\"settings_theme_save_failed\">藏仙府色章败陨</string>\n    <string name=\"settings_theme_import_failed\">引仙府色章败陨</string>\n    <string name=\"settings_reset_theme\">复归仙府本色</string>\n    <string name=\"settings_reset_theme_confirm\">道友确欲复归仙府本色乎？此将尽除诸般私设背景、字样、仙乐与音效。</string>\n    <string name=\"settings_theme_reset\">仙府本色已复</string>\n    <string name=\"settings_theme_reset_failed\">复归仙府本色败陨</string>\n\n    <!-- Theme Strings -->\n    <string name=\"theme_export_title\">拓印仙府色章</string>\n    <string name=\"theme_import_title\">引入仙府色章</string>\n    <string name=\"theme_name\">色章名讳</string>\n    <string name=\"theme_type\">色章品阶</string>\n    <string name=\"theme_type_phone\">传音玉简</string>\n    <string name=\"theme_type_tablet\">留影宝鉴</string>\n    <string name=\"theme_version\">色章篇次</string>\n    <string name=\"theme_author\">炼制者</string>\n    <string name=\"theme_description\">色章玄义</string>\n    <string name=\"theme_export_action\">拓印</string>\n    <string name=\"theme_import_action\">引入</string>\n    <string name=\"theme_import_confirm\">道友确欲引入此仙府色章乎？</string>\n    <string name=\"theme_info\">色章天机</string>\n    <string name=\"settings_grid_working_card_background\">本命法宝卡片法相</string>\n    <string name=\"settings_grid_working_card_background_enabled\">卡片法相已激活</string>\n    <string name=\"settings_grid_working_card_background_summary\">为本命卡片凝练自定义法相</string>\n    <string name=\"settings_grid_working_card_background_selected\">法相已选定</string>\n    <string name=\"settings_clear_grid_working_card_background\">散去卡片法相</string>\n    <string name=\"settings_clear_grid_working_card_background_confirm\">道友确欲散去此卡片法相乎？</string>\n    <string name=\"settings_grid_working_card_background_saved\">卡片法相凝练成功</string>\n    <string name=\"settings_grid_working_card_background_error\">卡片法相凝练失败</string>\n    <string name=\"settings_grid_working_card_background_cleared\">卡片法相已散去</string>\n\n    <!-- Biometric Strings-->\n    <string name=\"action_biometric\">仙纹核验</string>\n    <string name=\"msg_biometric\">请核验本命仙纹</string>\n    <string name=\"settings_biometric_login\">仙纹核验</string>\n    <string name=\"settings_biometric_login_summary\">启府需验本命仙纹</string>\n    <string name=\"settings_strong_biometric\">强力本命仙纹</string>\n    <string name=\"settings_strong_biometric_summary\">启用、禁用或封印仙符时需要验证</string>\n\n    <!-- Theme Store Strings -->\n    <string name=\"theme_store_title\">仙府色章阁</string>\n    <string name=\"theme_store_search_hint\">搜寻色章...</string>\n    <string name=\"theme_source_official\">仙府制</string>\n    <string name=\"theme_source_third_party\">散修炼制</string>\n    <string name=\"theme_source_local\">本地</string>\n    <string name=\"theme_store_author\">炼制者：%s</string>\n    <string name=\"theme_store_version\">色章篇次：%s</string>\n    <string name=\"theme_source\">出处</string>\n    <string name=\"theme_store_download_failed\">纳符败陨</string>\n<string name=\"theme_store_download\">纳符</string>\n\n    <!-- Update Check Strings -->\n    <string name=\"settings_auto_update_check\">自动检查仙法更新</string>\n    <string name=\"settings_auto_update_check_summary\">仙府启动时自动检查新仙法符箓</string>\n    <string name=\"settings_check_update\">检查仙法更新</string>\n    <string name=\"update_available_title\">发现新仙法</string>\n    <string name=\"update_available_message\">检测到道友的仙法篇次过低，是否要下载新篇次的仙法？</string>\n    <string name=\"update_action\">渡劫升仙</string>\n    <string name=\"update_close\">关闭</string>\n    <string name=\"update_latest\">道友已掌握最新仙法</string>\n    <string name=\"update_error\">检查仙法更新时出错</string>\n    <!--折叠选项-->\n    <string name=\"settings_category_general\">凡尘设置</string>\n    <string name=\"superuser\">登仙至尊</string>\n    <string name=\"module\">天道仙符</string>\n    <string name=\"settings_use_legacy_su_page\">单页登仙至尊授权</string>\n    <string name=\"settings_use_legacy_su_page_summary\">登仙至尊页面改用单页符箓设计</string>\n    <string name=\"settings_category_appearance\">道韵外观</string>\n    <string name=\"settings_appearance_font\">道韵字体</string>\n    <string name=\"settings_appearance_font_summary\">自定义字体配置</string>\n    <string name=\"settings_appearance_theme\">洞府主题</string>\n    <string name=\"settings_appearance_theme_summary\">主题商店、保存、导入和重置</string>\n    <string name=\"settings_appearance_banner\">仙门横幅</string>\n    <string name=\"settings_appearance_banner_summary\">模块横幅配置</string>\n    <string name=\"settings_appearance_layout\">仙府布局</string>\n    <string name=\"settings_appearance_layout_summary\">首页布局、导航和卡片自定义</string>\n    <string name=\"settings_appearance_background\">洞府背景</string>\n    <string name=\"settings_appearance_background_summary\">自定义背景、视频和多背景</string>\n    <string name=\"settings_appearance_night_mode\">夜间闭关模式</string>\n    <string name=\"settings_appearance_night_mode_summary\">深色主题和颜色配置</string>\n    <string name=\"settings_amoled_theme\">AMOLED 纯黑主题</string>\n    <string name=\"settings_amoled_theme_desc\">为深色模式使用纯黑背景</string>\n    <string name=\"settings_switch_icon\">启用按钮指示器</string>\n    <string name=\"settings_switch_icon_desc\">为开关按钮添加状态指示图标</string>\n    <string name=\"settings_category_behavior\">功法运转</string>\n    <string name=\"settings_category_function\">悟道功能</string>\n    <string name=\"settings_category_module\">悟道模块</string>\n    <string name=\"settings_category_security\">护道结界</string>\n    \n    <!-- 背景音乐字符串（修仙风格版） -->\n    <string name=\"settings_background_music\">仙音缭绕</string>\n    <string name=\"settings_background_music_summary\">仙门驻留时，伴仙音袅袅</string>\n    <string name=\"settings_background_music_playing\">仙音奏响: %s</string>\n    <string name=\"settings_background_music_enabled\">仙音已启</string>\n    <string name=\"settings_select_music_file\">择取仙音玉简</string>\n    <string name=\"settings_music_selected\">仙音玉简已选定</string>\n    <string name=\"settings_clear_music\">涤除仙音</string>\n    <string name=\"settings_clear_music_confirm\">是否确定涤除当前仙音缭绕？</string>\n    <string name=\"settings_music_auto_play\">仙音自鸣</string>\n    <string name=\"settings_music_auto_play_summary\">踏入仙门时，仙音自鸣相伴</string>\n    <string name=\"settings_music_looping\">仙音轮回</string>\n    <string name=\"settings_music_looping_summary\">仙音缭绕不绝于耳</string>\n    <string name=\"settings_music_volume\">仙音响度</string>\n    <string name=\"settings_music_saved\">仙音玉简已封存</string>\n    <string name=\"settings_music_save_error\">仙音玉简封存失败</string>\n    <string name=\"settings_music_cleared\">仙音已涤除</string>\n    <string name=\"settings_category_multimedia\">仙音幻阁</string>\n    <string name=\"settings_category_general_summary\">凡尘语韵、天道更新、仙界禁令、乾坤调整</string>\n    <string name=\"settings_category_appearance_summary\">道韵主题、灵光色彩、仙阵布局、虚幻背景、符文字体</string>\n    <string name=\"settings_category_behavior_summary\">万维除错、阵法植入、仙府显影</string>\n    <string name=\"settings_category_security_summary\">仙灵印记、万法密钥管理</string>\n    <string name=\"settings_category_backup_summary\">本地拓印、云端仙录、WebDAV传功</string>\n    <string name=\"settings_category_module_summary\">悟道信息、仙序排列、批量灌顶</string>\n    <string name=\"settings_category_function_summary\">FolkPatch隐匿、卸载仙侍</string>\n    <string name=\"settings_category_multimedia_summary\">背景仙音、法术音效、灵力振动</string>\n    <string name=\"settings_music_playback_control\">仙音法阵</string>\n    <!-- Theme Store Filter -->\n    <string name=\"theme_store_filter_title\">筛选仙法</string>\n    <string name=\"theme_store_filter_author\">仙师</string>\n    <string name=\"theme_store_filter_author_hint\">输入仙师名号</string>\n    <string name=\"theme_store_filter_source\">仙源</string>\n    <string name=\"theme_store_filter_source_all\">万法</string>\n    <string name=\"theme_store_filter_type\">仙器类型</string>\n    <string name=\"theme_store_filter_apply\">施法</string>\n    <string name=\"theme_store_filter_reset\">重炼</string>\n\n    <!-- Video Background Settings -->\n    <string name=\"settings_video_background\">影像仙幕</string>\n    <string name=\"settings_video_background_summary\">以影像为仙幕</string>\n    <string name=\"settings_select_video\">择影像</string>\n    <string name=\"settings_video_selected\">影像已择</string>\n    <string name=\"settings_video_background_enabled\">影像仙幕已启</string>\n    <string name=\"settings_clear_video_background\">涤除影像仙幕</string>\n    <string name=\"settings_clear_video_background_confirm\">确要涤除影像仙幕？</string>\n    <string name=\"settings_video_volume\">影像音量</string>\n    <!-- File Picker -->\n    <string name=\"file_picker_permission_required\">需道友赐法</string>\n    <string name=\"file_picker_permission_desc\">为查阅仙门典籍，请道友授予\\\"乾坤万物访问\\\"仙法。</string>\n    <string name=\"file_picker_grant_permission\">授予仙法</string>\n    <string name=\"file_picker_cancel\">作罢</string>\n    <string name=\"file_picker_internal_storage\">乾坤宝阁</string>\n    <string name=\"file_picker_no_files\">宝阁空空如也</string>\n    <!-- HomeV3 Strings -->\n    <string name=\"home_device_status_title\">仙府状态</string>\n    <string name=\"home_device_status_battery_temp\">灵气温</string>\n    <string name=\"home_device_status_cpu_load\">道力负载</string>\n    <string name=\"home_device_status_battery_level\">灵力值</string>\n    <string name=\"home_storage_title\">仙府宝库</string>\n    <string name=\"home_storage_internal\">乾坤宝阁</string>\n    <string name=\"home_storage_ram\">灵念内存</string>\n    <string name=\"home_storage_zram\">灵力压缩</string>\n    <string name=\"home_storage_swap\">灵力交换</string>\n    <string name=\"home_info_kernel\">道核</string>\n    <string name=\"home_info_superkey\">登仙秘钥</string>\n    <string name=\"home_info_auth_auth\">已验证</string>\n    <string name=\"home_info_auth_na\">不可用</string>\n    <string name=\"home_info_device_slot\">灵根槽位</string>\n    <string name=\"home_info_device_model\">仙府形态</string>\n    <string name=\"home_info_running_mode\">修炼模式</string>\n    <string name=\"home_info_mode_full\">圆满</string>\n    <string name=\"home_info_mode_half\">残缺</string>\n    <string name=\"home_version\">仙位: %s</string>\n    <string name=\"home_zygisk_implement\">Zygisk 仙人指路</string>\n    <string name=\"home_mount_implement\">符箓悬挂</string>\n\n    <!-- Install Mode Select -->\n    <string name=\"kp_install_methods\">仙道符箓修补/安装</string>\n    <string name=\"restore_boot_methods\">选择一仙法镜像恢复至仙籍分区</string>\n    <string name=\"restore_select_file\">选择一仙籍引导文件以还原</string>\n    \n    <string name=\"settings_app_list_loading_scheme\">仙器召唤方案</string>\n    <string name=\"settings_app_list_loading_scheme_summary\">选择如何召唤仙器列表</string>\n    <string name=\"app_list_loading_scheme_root_service\">仙阵召唤 (默认)</string>\n    <string name=\"app_list_loading_scheme_package_manager\">天道召唤 (天道 API)</string>\n    <string name=\"app_list_loading_scheme_dialog_title\">召唤方案</string>\n    <string name=\"su_backup_list\">备份至尊密钥</string>\n    <string name=\"su_restore_list\">还原至尊密钥</string>\n    <string name=\"backup_success\">备份成功</string>\n    <string name=\"restore_success\">还原成功</string>\n    <!-- Badge Count Settings -->\n    <string name=\"enable_badge_count\">灵印显法设定</string>\n    <string name=\"enable_badge_count_summary\">配置天机阁灵印之显现</string>\n    <string name=\"badge_superuser\">显登仙至尊之数</string>\n    <string name=\"badge_apm\">显天道仙符之数</string>\n    <string name=\"badge_kernel\">显道核仙符之数</string>\n    <string name=\"settings_sound_effect\">仙音</string>\n    <string name=\"settings_sound_effect_summary\">触动法阵时奏响仙音</string>\n    <string name=\"settings_sound_effect_enabled\">已启阵</string>\n    <string name=\"settings_sound_effect_playing\">当前仙音：%s</string>\n    <string name=\"settings_sound_effect_source\">仙音之源</string>\n    <string name=\"settings_sound_effect_source_local\">凡间法宝</string>\n    <string name=\"settings_sound_effect_source_preset\">仙界预设</string>\n    <string name=\"settings_sound_effect_preset_title\">仙音宝库</string>\n    <string name=\"settings_select_sound_effect\">择仙音法宝</string>\n    <string name=\"settings_sound_effect_selected\">已择仙音法宝</string>\n    <string name=\"settings_sound_effect_scope\">仙音结界范围</string>\n    <string name=\"settings_sound_effect_scope_global\">全域 (三界皆闻)</string>\n    <string name=\"settings_sound_effect_scope_bottom_bar\">仅基座法阵</string>\n    <string name=\"settings_clear_sound_effect\">散去仙音</string>\n    <string name=\"settings_clear_sound_effect_confirm\">道友确欲散去此仙音乎？</string>\n    <string name=\"settings_sound_effect_cleared\">仙音已散</string>\n\n    <string name=\"settings_startup_sound\">开境仙音</string>\n    <string name=\"settings_startup_sound_summary\">开启仙界时奏响开境仙音</string>\n    <string name=\"settings_startup_sound_enabled\">开境仙音已启</string>\n    <string name=\"settings_startup_sound_playing\">正在奏响：%s</string>\n    <string name=\"settings_select_startup_sound\">选择开境仙音</string>\n    <string name=\"settings_startup_sound_selected\">已择开境仙音</string>\n    <string name=\"settings_clear_startup_sound\">散去开境仙音</string>\n    <string name=\"settings_clear_startup_sound_confirm\">道友确欲散去开境仙音乎？</string>\n    <string name=\"settings_startup_sound_cleared\">开境仙音已散</string>\n\n    <!-- Backup Settings -->\n    <string name=\"settings_category_backup\">乾坤备份</string>\n    <string name=\"settings_enable_cloud_backup\">开启云端乾坤</string>\n    <string name=\"settings_enable_cloud_backup_summary\">自动将炼化的符箓备份至云端仙界</string>\n    <string name=\"settings_configure_webdav\">配置须弥芥子</string>\n    <string name=\"webdav_config_title\">须弥芥子阵法</string>\n    <string name=\"webdav_url\">仙界阵眼</string>\n    <string name=\"webdav_username\">道号</string>\n    <string name=\"webdav_password\">灵识印记</string>\n    <string name=\"webdav_test_success\">阵法连通成功</string>\n    <string name=\"webdav_test_failed\">阵法连通失败: %s</string>\n    <string name=\"webdav_uploading\">正在飞升至云端...</string>\n    <string name=\"webdav_backup_success\">云端乾坤备份成功</string>\n    <string name=\"webdav_backup_failed\">云端乾坤备份失败: %s</string>\n    <string name=\"save\">封印</string>\n    <string name=\"test\">试阵</string>\n    <string name=\"webdav_path_label\">仙途路径 (如 /Backup)</string>\n    <string name=\"webdav_view_logs\">观天道录</string>\n    <string name=\"webdav_backup_logs_title\">乾坤录</string>\n    <string name=\"webdav_no_logs\">暂无天道录</string>\n    <string name=\"webdav_clear_logs\">抹除</string>\n    <string name=\"settings_enable_local_backup\">开启洞天备份</string>\n    <string name=\"settings_enable_local_backup_summary\">将符箓备份至本地洞天 (Downloads/FolkPatch/ModuleBackups)</string>\n    <string name=\"close\">闭关</string>\n    <string name=\"settings_backup_password\">禁制口诀</string>\n    <string name=\"settings_backup_password_hint\">输入禁制口诀</string>\n\n    <string name=\"patch_output_written_to\"> 炼器大成，法宝输出至 </string>\n    <string name=\"patch_write_failed\"> 炼器失败，天劫降临</string>\n\n    <!-- Script Library Strings -->\n    <string name=\"script_library\">仙符箓卷宗</string>\n    <string name=\"script_library_title\">仙符箓卷宗</string>\n    <string name=\"script_library_empty\">暂无仙符，点击纳符按钮纳符入卷</string>\n    <string name=\"script_library_add\">纳符入卷</string>\n    <string name=\"script_library_add_title\">纳符入卷</string>\n    <string name=\"script_library_select_file\">择仙符文卷</string>\n    <string name=\"script_library_alias\">符号</string>\n    <string name=\"script_library_alias_hint\">输仙符符号（可选）</string>\n    <string name=\"script_library_run\">施符</string>\n    <string name=\"script_library_delete\">废符</string>\n    <string name=\"script_library_path\">仙途：%s</string>\n    <string name=\"script_library_confirm_delete\">确欲废去此仙符乎？</string>\n    <string name=\"script_library_delete_success\">仙符已废</string>\n    <string name=\"script_library_delete_failed\">废符败陨</string>\n    <string name=\"script_library_add_success\">仙符已入卷</string>\n    <string name=\"script_library_add_failed\">纳符败陨：%s</string>\n    <string name=\"script_library_load_failed\">开卷失敗</string>\n    <string name=\"script_library_output\">施符显化</string>\n    <string name=\"script_library_no_output\">无显化</string>\n    <string name=\"script_library_save_log\">藏施符录</string>\n    <string name=\"script_library_log_saved\">施符录已藏至 %s</string>\n    <string name=\"script_library_log_save_failed\">藏施符录败陨：%s</string>\n\n    <string name=\"settings_vibration\">天地律动</string>\n    <string name=\"settings_vibration_summary\">感知天地之颤</string>\n    <string name=\"settings_vibration_enabled\">开启律动</string>\n    <string name=\"settings_vibration_intensity\">律动强度</string>\n    <string name=\"settings_vibration_scope\">律动范围</string>\n    <string name=\"settings_vibration_scope_global\">普天之下</string>\n    <string name=\"settings_vibration_scope_bottom_bar\">仅限基台</string>\n    <string name=\"search_modules\">搜索仙符...</string>\n    <string name=\"search_scripts\">搜索丹方...</string>\n\n    <!-- Umount Service -->\n    <string name=\"settings_umount_service\">Umount Service</string>\n    <string name=\"settings_umount_service_summary\">配置开炉自动撤除的灵力连接点</string>\n    <string name=\"umount_config_title\">Umount 丹方</string>\n    <string name=\"umount_config_enabled\">启用 Umount</string>\n    <string name=\"umount_config_enabled_summary\">开炉自动撤除指定的灵力连接点</string>\n    <string name=\"umount_config_paths_label\">灵力连接点路径（每行一个）</string>\n    <string name=\"umount_config_paths_placeholder\">/system/vendor/app\\n/system/vendor/priv-app</string>\n    <string name=\"umount_config_paths_helper\">每行输入一个需要撤除的灵力连接点路径</string>\n    <string name=\"umount_config_save\">炼丹</string>\n    <string name=\"umount_config_save_confirm\">确认炼制并保存丹方？</string>\n    <string name=\"umount_config_save_success\">丹成，丹方保存成功</string>\n    <string name=\"umount_config_save_failed\">炉火不旺，丹方保存失败</string>\n\n    <!-- 我的仙府页面 -->\n    <string name=\"my_themes_title\">我的仙府</string>\n    <string name=\"my_themes_empty\">仙府空空如也</string>\n    <string name=\"my_themes_empty_action\">前往仙市逛逛</string>\n    <string name=\"my_themes_apply\">施展主题</string>\n    <string name=\"my_themes_delete\">舍弃主题</string>\n    <string name=\"my_themes_delete_confirm\">确定要舍弃此主题仙卷吗？</string>\n    <string name=\"my_themes_deleted\">主题已舍弃</string>\n    <string name=\"my_themes_applied\">主题已施展</string>\n    <string name=\"my_themes_apply_failed\">施展主题失败</string>\n    <string name=\"my_themes_details\">主题仙卷详情</string>\n\n    <!-- 下载对话 -->\n    <string name=\"theme_download_title\">正在纳符主题</string>\n    <string name=\"theme_download_completed\">纳符圆满</string>\n    <string name=\"theme_download_failed\">纳符受阻</string>\n    <string name=\"theme_download_progress\">仙途进度</string>\n    <string name=\"theme_download_file\">主题仙卷</string>\n    <string name=\"theme_download_image\">仙图预览</string>\n    <string name=\"theme_download_cancel\">终止纳符</string>\n    <string name=\"theme_download_pause\">暂停纳符</string>\n    <string name=\"theme_download_resume\">继续纳符</string>\n    <string name=\"theme_download_retry\">重新纳符</string>\n    <string name=\"theme_download_apply\">施展主题</string>\n    <string name=\"theme_download_go_to_my_themes\">我的仙府</string>\n    <string name=\"theme_download_retrying\">重新纳符中 (%d/3)...</string>\n    <string name=\"theme_download_preparing\">准备纳符...</string>\n    <string name=\"theme_download_downloading_file\">正在纳取主题仙卷...</string>\n    <string name=\"theme_download_downloading_image\">正在纳取仙图预览...</string>\n    <string name=\"theme_download_finalizing\">凝丹中...</string>\n    <string name=\"settings_predictive_back\">仙预返回手势</string>\n    <string name=\"settings_predictive_back_summary\">启用仙界 Android 14+ 的预知返回仙法动画</string>\n\n    <string name=\"home_device_status_battery_charging\">灵力充盈中</string>\n    <string name=\"home_device_status_cpu_temp\">道力温度</string>\n    <string name=\"home_device_status_memory_trend\">灵念趋势</string>\n    <string name=\"home_storage_partitions\">乾坤分区</string>\n    <string name=\"home_device_status_cpu_freq\">道力频率</string>\n    <string name=\"home_network_rx\">纳取</string>\n    <string name=\"home_network_tx\">传送</string>\n    <string name=\"settings_home_layout_stats\">StatsUI</string>\n    <string name=\"home_stats_more_options\">更多仙法</string>\n    <string name=\"home_stats_kp_label\">KP</string>\n    <string name=\"home_stats_manager_label\">仙管</string>\n    <string name=\"home_device_status_gpu_load\">法宝负载</string>\n    <string name=\"home_stats_kernel_modules\">内核仙卷</string>\n    <string name=\"home_stats_apm_modules\">APM 仙卷</string>\n    <string name=\"home_stats_superusers\">至尊权限</string>\n    <string name=\"settings_kernel_spoof\">内核伪装术</string>\n    <string name=\"settings_kernel_spoof_summary\">伪装内核版本和修炼时辰</string>\n    <string name=\"settings_kernel_spoof_version\">内核仙卷版本</string>\n    <string name=\"settings_kernel_spoof_build_time\">内核修炼时辰</string>\n    <string name=\"settings_kernel_spoof_restore\">恢复原状</string>\n\n    <string name=\"kernel_spoof_enabled\">内核伪装术已施展</string>\n    <string name=\"kernel_spoof_disabled_restored\">内核伪装术已解除并恢复原状</string>\n    <string name=\"kernel_spoof_failed\">内核伪装术施展失败: %d</string>\n    <string name=\"kernel_spoof_applied\">内核伪装术已生效</string>\n\n    <string name=\"settings_path_hide\">路径隐遁术</string>\n    <string name=\"settings_path_hide_summary\">于道核层面施展隐遁之术，令凡俗应用不得窥见文件与目录</string>\n    <string name=\"path_hide_paths_label\">隐遁路径（每行一道）</string>\n    <string name=\"path_hide_paths_helper\">每行书写一个路径，匹配之路径将如入虚无，查无可寻</string>\n    <string name=\"path_hide_save\">封印保存</string>\n    <string name=\"path_hide_enabled\">路径隐遁术已施展</string>\n    <string name=\"path_hide_disabled\">路径隐遁术已解除</string>\n    <string name=\"path_hide_applied\">路径隐遁术已布阵生效</string>\n    <string name=\"path_hide_failed\">路径隐遁术施展失败: %d</string>\n    <string name=\"path_hide_uid_mode\">UID 锁定术</string>\n    <string name=\"path_hide_uid_mode_summary\">仅对锁定之 UID 施以隐遁之术</string>\n    <string name=\"path_hide_uids_label\">锁定 UID（每行一道）</string>\n    <string name=\"path_hide_uids_helper\">书写应用 UID，唯锁定之目标方受隐遁术庇护</string>\n    <string name=\"path_hide_uid_save\">封印 UID</string>\n    <string name=\"path_hide_uid_mode_enabled\">UID 锁定术已施展</string>\n    <string name=\"path_hide_uid_mode_disabled\">UID 锁定术已解除</string>\n    <string name=\"path_hide_filter_system\">屏蔽系统 UID</string>\n    <string name=\"path_hide_filter_system_summary\">同时对 root 和系统进程（UID &lt; 10000）隐匿路径</string>\n    <string name=\"path_hide_filter_system_enabled\">系统 UID 屏蔽术已施展</string>\n    <string name=\"path_hide_filter_system_disabled\">系统 UID 屏蔽术已解除</string>\n    <string name=\"path_hide_filter_system_warning_title\">道法警示</string>\n    <string name=\"path_hide_filter_system_warning_message\">施展后将同时对 root 和系统进程（UID &lt; 10000）隐匿路径。此术可能引致部分仙法功能异常，道友请三思而行。</string>\n    <string name=\"path_hide_uid_applied\">UID 锁定名录已布阵生效</string>\n    <string name=\"path_hide_select_apps\">拣选应用</string>\n    <string name=\"path_hide_no_apps_selected\">未拣到任何应用</string>\n    <string name=\"path_hide_app_removed\">已清走 %d 个已卸载应用的残局</string>\n    <string name=\"path_hide_search_apps\">捞应用…</string>\n    <string name=\"path_hide_show_system\">露系统应用</string>\n    <string name=\"netisolate_title\">网络封印术</string>\n    <string name=\"netisolate_enable\">网络封印术已施展</string>\n    <string name=\"netisolate_disable\">网络封印术已解除</string>\n    <string name=\"netisolate_enable_summary\">于道核层面封印所选应用之网络连接</string>\n    <string name=\"netisolate_uids_label\">已封印应用</string>\n    <string name=\"netisolate_uids_hint\">输入要封印的 UID（每行一道）</string>\n    <string name=\"netisolate_no_uids\">暂无被封印应用</string>\n</resources>\n"
  },
  {
    "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\n<paths>\n    <cache-path name=\"cache\" path=\".\"/>\n    <root-path name=\"root\" path=\".\" />\n    <files-path name=\"backgrounds\" path=\"backgrounds/\" />\n    <external-files-path name=\"apatch_files\" path=\"Apatch\" />\n    <external-files-path name=\"module_backups\" path=\"ModuleBackups\" />\n    <external-path name=\"folkpatch_backup\" path=\"Download/FolkPatch/ModuleBackups\" />\n</paths>"
  },
  {
    "path": "app/src/main/res/xml/network_security_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<network-security-config>\n    <base-config cleartextTrafficPermitted=\"true\" />\n    <domain-config cleartextTrafficPermitted=\"true\">\n        <domain includeSubdomains=\"true\">127.0.0.1</domain>\n        <domain includeSubdomains=\"true\">0.0.0.0</domain>\n        <domain includeSubdomains=\"true\">::1</domain>\n    </domain-config>\n</network-security-config>\n"
  },
  {
    "path": "auth.properties.template",
    "content": "# API Token Configuration\n# 请填入服务端Token\napi.token=YOUR_SERVER_TOKEN_HERE\n# 请填入应用签名Hash (SHA-256)\napp.signature.hash=YOUR_APP_SIGNATURE_HASH_HERE"
  },
  {
    "path": "build.gradle.kts",
    "content": "plugins {\n    alias(libs.plugins.agp.app) apply false\n    alias(libs.plugins.kotlin) apply false\n    alias(libs.plugins.kotlin.compose.compiler) apply false\n}\n\nproject.ext.set(\"kernelPatchVersion\", \"0.13.1\")\n\nval androidMinSdkVersion by extra(26)\nval androidTargetSdkVersion by extra(36)\nval androidCompileSdkVersion by extra(36)\nval androidBuildToolsVersion by extra(\"36.1.0\")\nval androidCompileNdkVersion by extra(\"29.0.14206865\")\nval managerVersionCode by extra(getVersionCode())\nval managerVersionName by extra(getVersionName())\nval branchName by extra(getbranch())\nfun Project.exec(command: String, default: String): String {\n    return try {\n        providers.exec {\n            commandLine(command.split(\" \"))\n            isIgnoreExitValue = true\n        }.standardOutput.asText.get().trim().takeIf { it.isNotEmpty() } ?: default\n    } catch (e: Exception) {\n        default\n    }\n}\n\nfun getGitCommitCount(): Int {\n    return exec(\"git rev-list --count HEAD\", \"0\").toInt()\n}\n\nfun getGitDescribe(): String {\n    return exec(\"git rev-parse --verify --short HEAD\", \"unknown\")\n}\n\nfun getVersionCode(): Int {\n    return 114238\n}\n\nfun getbranch(): String {\n    return exec(\"git rev-parse --abbrev-ref HEAD\", \"unknown\")\n}\n\nfun getVersionName(): String {\n    return \"4.2\"\n}\n\ntasks.register(\"printVersion\") {\n    doLast {\n        println(\"Version code: $managerVersionCode\")\n        println(\"Version name: $managerVersionName\")\n    }\n}\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/full_description.txt",
    "content": "The patching of Android kernel and Android system.\n<ul>\n<li>A Root implementation based on KernelPatch for Android devices.</li>\n<li>APM: Support for modules similar to Magisk.</li>\n<li>KPM: Support for modules that allow you to inject any code into the kernel (Requires kernel function <code>inline-hook</code> and <code>syscall-table-hook</code> enabled).</li>\n<li>APatch relies on KernelPatch.</li>\n<li>The APatch UI and the APModule source code have been derived and modified from KernelSU.</li>\n</ul>\n<ul>\n<li>Only supports the ARM64 architecture.</li>\n<li>Only supports Android kernel versions 3.18 - 6.1</li>\n</ul>\nSupport for Samsung devices with security protection: Planned\nKernel configs:\n<ul>\n<li>\n<code>CONFIG_KALLSYMS=y</code> and <code>CONFIG_KALLSYMS_ALL=y</code>\n</li>\n<li>\n<code>CONFIG_KALLSYMS=y</code> and <code>CONFIG_KALLSYMS_ALL=n</code>: Initial support\n</li>\n</ul>\nThe <strong>SuperKey</strong> has higher privileges than root access.\nWeak or compromised keys can lead to unauthorized control of your device.\nIt is critical to use robust keys and safeguard them from exposure to maintain the security of your device.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/short_description.txt",
    "content": "The patching of Android kernel and Android system\n"
  },
  {
    "path": "fastlane/metadata/android/pl-PL/full_description.txt",
    "content": "Patchowanie jądra Androida oraz systemu Android.\n<ul>\n<li>Implementacja root oparta na KernelPatch dla urządzeń z Androidem.</li>\n<li>APM: Obsługa modułów podobnych do Magisk.</li>\n<li>KPM: Obsługa modułów umożliwiających wstrzykiwanie dowolnego kodu do jądra (wymaga włączonych funkcji jądra <code>inline-hook</code> oraz <code>syscall-table-hook</code>).</li>\n<li>APatch opiera się na KernelPatch.</li>\n<li>Interfejs APatch oraz kod źródłowy APModule zostały oparte i zmodyfikowane na bazie KernelSU.</li>\n</ul>\n<ul>\n<li>Obsługuje wyłącznie architekturę ARM64.</li>\n<li>Obsługuje wyłącznie wersje jądra Androida 3.18 - 6.1.</li>\n</ul>\nWsparcie dla urządzeń Samsung z zabezpieczeniami: Planowane\nKonfiguracje jądra:\n<ul>\n<li>\n<code>CONFIG_KALLSYMS=y</code> oraz <code>CONFIG_KALLSYMS_ALL=y</code>\n</li>\n<li>\n<code>CONFIG_KALLSYMS=y</code> oraz <code>CONFIG_KALLSYMS_ALL=n</code>: Wstępne wsparcie\n</li>\n</ul>\n<strong>SuperKey</strong> posiada wyższe uprawnienia niż dostęp root.\nSłabe lub przejęte klucze mogą prowadzić do nieautoryzowanej kontroli nad urządzeniem.\nKrytyczne jest stosowanie silnych kluczy oraz ich odpowiednie zabezpieczenie, aby utrzymać bezpieczeństwo urządzenia.\n"
  },
  {
    "path": "fastlane/metadata/android/pl-PL/short_description.txt",
    "content": "Łatanie jądra Androida i systemu Android\n"
  },
  {
    "path": "fpd/Cargo.toml",
    "content": "[package]\nname = \"fpd\"\nversion = \"1.0.0\"\nedition = \"2024\"\ndescription = \"Android system property patching and mount-point unmounting utility\"\nrepository = \"https://github.com/epicxafang/FolkPatch\"\nlicense = \"MIT\"\n\n[dependencies]\nlibc = \"0.2\"\n\n[profile.release]\nopt-level = \"z\"\nlto = true\nstrip = true\npanic = \"abort\"\ncodegen-units = 1\n"
  },
  {
    "path": "fpd/src/main.rs",
    "content": "mod prop_patch;\nmod umount;\n\nconst VERSION: &str = env!(\"CARGO_PKG_VERSION\");\nconst HELP_URL: &str = \"https://fp.mysqil.com/\";\n\nfn print_version() {\n    println!(\"v{VERSION}\");\n}\n\nfn print_help() {\n    println!(\"Please read the FolkPatch documentation at {HELP_URL} for help.\");\n}\n\nfn usage() -> ! {\n    eprintln!(\"Usage: fpd [-version] [-hide] [-help] [-umount]\");\n    std::process::exit(1);\n}\n\nfn main() {\n    let args: Vec<String> = std::env::args().collect();\n\n    if args.len() == 1 {\n        let exe = &args[0];\n        let name = std::path::Path::new(exe)\n            .file_name()\n            .and_then(|n| n.to_str())\n            .unwrap_or(\"\");\n        match name {\n            \"hide\" => return prop_patch::run(),\n            \"umount\" => std::process::exit(umount::run() as i32),\n            _ => {}\n        }\n    }\n\n    let mut flags = args.iter().skip(1);\n    let flag = flags.next();\n    if flags.next().is_some() {\n        usage();\n    }\n\n    match flag.map(|s| s.as_str()) {\n        None => print_help(),\n        Some(\"-version\") => print_version(),\n        Some(\"-hide\") => prop_patch::run(),\n        Some(\"-help\") => print_help(),\n        Some(\"-umount\") => std::process::exit(umount::run() as i32),\n        Some(_) => usage(),\n    }\n}\n"
  },
  {
    "path": "fpd/src/prop_patch.rs",
    "content": "use std::process::Stdio;\n\nconst RESETPROP: &str = \"/data/adb/ap/bin/resetprop\";\n\nstruct PropItem {\n    key: &'static str,\n    value: &'static str,\n}\n\nconst PATCH_LIST: &[PropItem] = &[\n    PropItem {\n        key: \"ro.boot.vbmeta.device_state\",\n        value: \"locked\",\n    },\n    PropItem {\n        key: \"ro.boot.verifiedbootstate\",\n        value: \"green\",\n    },\n    PropItem {\n        key: \"ro.boot.flash.locked\",\n        value: \"1\",\n    },\n    PropItem {\n        key: \"ro.boot.veritymode\",\n        value: \"enforcing\",\n    },\n    PropItem {\n        key: \"vendor.boot.vbmeta.device_state\",\n        value: \"locked\",\n    },\n    PropItem {\n        key: \"vendor.boot.verifiedbootstate\",\n        value: \"green\",\n    },\n    PropItem {\n        key: \"ro.boot.vbmeta.invalidate_on_error\",\n        value: \"yes\",\n    },\n    PropItem {\n        key: \"ro.boot.vbmeta.avb_version\",\n        value: \"1.0\",\n    },\n    PropItem {\n        key: \"ro.boot.vbmeta.hash_alg\",\n        value: \"sha256\",\n    },\n    PropItem {\n        key: \"ro.boot.vbmeta.size\",\n        value: \"4096\",\n    },\n    PropItem {\n        key: \"ro.boot.warranty_bit\",\n        value: \"0\",\n    },\n    PropItem {\n        key: \"ro.warranty_bit\",\n        value: \"0\",\n    },\n    PropItem {\n        key: \"ro.vendor.boot.warranty_bit\",\n        value: \"0\",\n    },\n    PropItem {\n        key: \"ro.vendor.warranty_bit\",\n        value: \"0\",\n    },\n    PropItem {\n        key: \"sys.oem_unlock_allowed\",\n        value: \"0\",\n    },\n    PropItem {\n        key: \"ro.build.type\",\n        value: \"user\",\n    },\n    PropItem {\n        key: \"ro.build.tags\",\n        value: \"release-keys\",\n    },\n    PropItem {\n        key: \"ro.secureboot.lockstate\",\n        value: \"locked\",\n    },\n    PropItem {\n        key: \"ro.debuggable\",\n        value: \"0\",\n    },\n    PropItem {\n        key: \"ro.force.debuggable\",\n        value: \"0\",\n    },\n    PropItem {\n        key: \"ro.secure\",\n        value: \"1\",\n    },\n    PropItem {\n        key: \"ro.adb.secure\",\n        value: \"1\",\n    },\n    PropItem {\n        key: \"ro.boot.realmebootstate\",\n        value: \"green\",\n    },\n    PropItem {\n        key: \"ro.boot.realme.lockstate\",\n        value: \"1\",\n    },\n    PropItem {\n        key: \"persist.logd.size\",\n        value: \"\",\n    },\n    PropItem {\n        key: \"persist.logd.size.crash\",\n        value: \"\",\n    },\n    PropItem {\n        key: \"persist.logd.size.system\",\n        value: \"\",\n    },\n    PropItem {\n        key: \"persist.logd.size.main\",\n        value: \"\",\n    },\n];\n\nconst BOOT_KEYS: &[&str] = &[\"ro.bootmode\", \"ro.boot.bootmode\", \"vendor.boot.bootmode\"];\n\nfn set_prop(key: &str, value: &str) {\n    let _ = std::process::Command::new(RESETPROP)\n        .args([\"-n\", key, value])\n        .stderr(Stdio::null())\n        .status();\n}\n\nfn get_prop(key: &str) -> Option<String> {\n    let output = std::process::Command::new(RESETPROP)\n        .arg(key)\n        .stderr(Stdio::null())\n        .output()\n        .ok()?;\n\n    let lossy = String::from_utf8_lossy(&output.stdout);\n    let trimmed = lossy.trim();\n    if trimmed.is_empty() {\n        None\n    } else {\n        Some(trimmed.to_owned())\n    }\n}\n\nfn patch_boot_keys() {\n    for &key in BOOT_KEYS {\n        if let Some(val) = get_prop(key) {\n            if val.contains(\"recovery\") {\n                set_prop(key, \"unknown\");\n            }\n        }\n    }\n}\n\npub fn run() {\n    for item in PATCH_LIST {\n        if get_prop(item.key).is_some() {\n            set_prop(item.key, item.value);\n        }\n    }\n    patch_boot_keys();\n}\n"
  },
  {
    "path": "fpd/src/umount.rs",
    "content": "use std::ffi::CString;\nuse std::fs;\nuse std::path::PathBuf;\n\nconst MNT_DETACH: libc::c_int = 2;\n\nfn umount_lazy(path: &str) -> bool {\n    let c_path = match CString::new(path) {\n        Ok(s) => s,\n        Err(_) => return false,\n    };\n    let ret = unsafe { libc::umount2(c_path.as_ptr(), MNT_DETACH) };\n    if ret != 0 {\n        let errno = unsafe { *libc::__errno() };\n        eprintln!(\"unmount fail: {path} (errno={errno})\");\n        return false;\n    }\n    true\n}\n\nfn config_path() -> PathBuf {\n    std::env::current_exe()\n        .expect(\"failed to determine executable path\")\n        .parent()\n        .expect(\"executable has no parent directory\")\n        .parent()\n        .expect(\"executable parent has no grandparent directory\")\n        .join(\"UmountPATH\")\n}\n\npub fn run() -> u32 {\n    let config = config_path();\n    let content = match fs::read_to_string(&config) {\n        Ok(c) => c,\n        Err(e) => {\n            eprintln!(\"cannot read {}: {e}\", config.display());\n            return 1;\n        }\n    };\n\n    let mut failures = 0u32;\n    for line in content.lines() {\n        let path = line.trim();\n        if path.is_empty() {\n            continue;\n        }\n        if umount_lazy(path) {\n            eprintln!(\"unmount ok: {path}\");\n        } else {\n            eprintln!(\"unmount fail: {path}\");\n            failures += 1;\n        }\n    }\n    failures\n}\n"
  },
  {
    "path": "gradle/libs.versions.toml",
    "content": "[versions]\nagp = \"8.13.1\"\nkotlin = \"2.2.21\"\nksp = \"2.3.1\"\ncompose-bom = \"2025.11.00\"\nlifecycle = \"2.9.4\"\ncompose-destination = \"2.3.0\"\nlibsu = \"6.0.0\"\nsheets-compose-dialogs = \"1.3.0\"\nbiometric = \"1.2.0-alpha05\"\ngson = \"2.10.1\"\nvico = \"2.0.0-alpha.13\"\nliquid = \"1.1.1\"\n\n[plugins]\nagp-app = { id = \"com.android.application\", version.ref = \"agp\" }\nagp-lib = { id = \"com.android.library\", version.ref = \"agp\" }\nkotlin = { id = \"org.jetbrains.kotlin.android\", version.ref = \"kotlin\" }\nksp = { id = \"com.google.devtools.ksp\", version.ref = \"ksp\" }\nlsplugin-apksign = { id = \"org.lsposed.lsplugin.apksign\", version = \"1.4\" }\nlsplugin-resopt = { id = \"org.lsposed.lsplugin.resopt\", version = \"1.6\" }\nlsplugin-cmaker = { id = \"org.lsposed.lsplugin.cmaker\", version = \"1.2\" }\nkotlin-compose-compiler = { id = \"org.jetbrains.kotlin.plugin.compose\", version.ref = \"kotlin\" }\n\n[libraries]\nandroidx-appcompat = { group = \"androidx.appcompat\", name = \"appcompat\", version = \"1.7.1\" }\nandroidx-activity-compose = { group = \"androidx.activity\", name = \"activity-compose\", version = \"1.11.0\" }\nandroidx-core-splashscreen = { group = \"androidx.core\", name = \"core-splashscreen\", version = \"1.2.0\" }\nandroidx-webkit = { group = \"androidx.webkit\", name = \"webkit\", version = \"1.14.0\" }\nandroidx-biometric = { group = \"androidx.biometric\", name = \"biometric\", version.ref = \"biometric\" }\n\nandroidx-compose-bom = { group = \"androidx.compose\", name = \"compose-bom\", version.ref = \"compose-bom\" }\nandroidx-compose-material-icons-extended = { group = \"androidx.compose.material\", name = \"material-icons-extended\" }\nandroidx-compose-material = { group = \"androidx.compose.material\", name = \"material\" }\nandroidx-compose-material3 = { group = \"androidx.compose.material3\", name = \"material3\" }\nandroidx-compose-ui = { group = \"androidx.compose.ui\", name = \"ui\" }\nandroidx-compose-ui-test-manifest = { group = \"androidx.compose.ui\", name = \"ui-test-manifest\" }\nandroidx-compose-ui-tooling = { group = \"androidx.compose.ui\", name = \"ui-tooling\" }\nandroidx-compose-ui-tooling-preview = { group = \"androidx.compose.ui\", name = \"ui-tooling-preview\" }\nandroidx-compose-runtime-livedata = { group = \"androidx.compose.runtime\", name = \"runtime-livedata\" }\n\nandroidx-lifecycle-runtime-ktx = { group = \"androidx.lifecycle\", name = \"lifecycle-runtime-ktx\", version.ref = \"lifecycle\" }\nandroidx-lifecycle-runtime-compose = { group = \"androidx.lifecycle\", name = \"lifecycle-runtime-compose\", version.ref = \"lifecycle\" }\nandroidx-lifecycle-viewmodel-compose = { group = \"androidx.lifecycle\", name = \"lifecycle-viewmodel-compose\", version.ref = \"lifecycle\" }\n\ncom-github-topjohnwu-libsu-core = { group = \"com.github.topjohnwu.libsu\", name = \"core\", version.ref = \"libsu\" }\ncom-github-topjohnwu-libsu-service = { group = \"com.github.topjohnwu.libsu\", name = \"service\", version.ref = \"libsu\" }\ncom-github-topjohnwu-libsu-nio = { group = \"com.github.topjohnwu.libsu\", name = \"nio\", version.ref = \"libsu\" }\ncom-github-topjohnwu-libsu-io = { group = \"com.github.topjohnwu.libsu\", name = \"io\", version.ref = \"libsu\" }\n\ndev-rikka-rikkax-parcelablelist = { module = \"dev.rikka.rikkax.parcelablelist:parcelablelist\", version = \"2.0.1\" }\n\nio-coil-kt-coil-compose = { group = \"io.coil-kt\", name = \"coil-compose\", version = \"2.7.0\" }\nio-coil-kt-coil-gif = { group = \"io.coil-kt\", name = \"coil-gif\", version = \"2.7.0\" }\n\nkotlinx-coroutines-core = { module = \"org.jetbrains.kotlinx:kotlinx-coroutines-core\", version = \"1.10.2\" }\n\nme-zhanghai-android-appiconloader-coil = { group = \"me.zhanghai.android.appiconloader\", name = \"appiconloader-coil\", version = \"1.5.0\" }\n\ncompose-destinations-core = { group = \"io.github.raamcosta.compose-destinations\", name = \"core\", version.ref = \"compose-destination\" }\ncompose-destinations-ksp = { group = \"io.github.raamcosta.compose-destinations\", name = \"ksp\", version.ref = \"compose-destination\" }\n\nsheet-compose-dialogs-core = { group = \"com.maxkeppeler.sheets-compose-dialogs\", name = \"core\", version.ref = \"sheets-compose-dialogs\" }\nsheet-compose-dialogs-list = { group = \"com.maxkeppeler.sheets-compose-dialogs\", name = \"list\", version.ref = \"sheets-compose-dialogs\" }\nsheet-compose-dialogs-input = { group = \"com.maxkeppeler.sheets-compose-dialogs\", name = \"input\", version.ref = \"sheets-compose-dialogs\" }\n\nmarkdown = { group = \"io.noties.markwon\", name = \"core\", version = \"4.6.2\" }\n\nini4j = { group = \"org.ini4j\", name = \"ini4j\", version = \"0.5.4\" }\n\ngoogle-code-gson = { group = \"com.google.code.gson\", name = \"gson\", version.ref = \"gson\" }\n\nvico-compose = { group = \"com.patrykandpatrick.vico\", name = \"compose-m3\", version.ref = \"vico\" }\n\ncxx = { module = \"org.lsposed.libcxx:libcxx\", version = \"28.1.13356709\" }\n\nliquid = { group = \"io.github.fletchmckee.liquid\", name = \"liquid-android\", version.ref = \"liquid\" }\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.2.0-bin.zip\nnetworkTimeout=11142\nvalidateDistributionUrl=true\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=-Xmx4096m -Dfile.encoding=UTF-8 -Dhttps.protocols=TLSv1.2,TLSv1.3 -Djdk.tls.client.protocols=TLSv1.2,TLSv1.3 -Djavax.net.ssl.trustStoreType=JKS\n\n# Load keystore.properties file\nkeystorePropertiesFile=file(\"../keystore.properties\")\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. For more details, visit\n# https://developer.android.com/r/tools/gradle-multi-project-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.experimental.enableNewResourceShrinker.preciseShrinking=true\n# android.enableAppCompileTimeRClass=true # Commented out as it's experimental\nkapt.include.compile.classpath=false\nksp.useKSP2=true\n"
  },
  {
    "path": "gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015 the original 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# SPDX-License-Identifier: Apache-2.0\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd -P \"${APP_HOME:-./}\" > /dev/null && printf '%s\\n' \"$PWD\" ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\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    if ! command -v java >/dev/null 2>&1\n    then\n        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.\"\n    fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\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# Collect all arguments for the java command:\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n#     and any embedded shellness will be escaped.\n#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n#     treated as '${Hostname}' itself on the command line.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -jar \"$APP_HOME/gradle/wrapper/gradle-wrapper.jar\" \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n@rem SPDX-License-Identifier: Apache-2.0\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -jar \"%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\" %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "keystore.properties.template",
    "content": "# 密钥库配置模板\n# 请将此文件重命名为 keystore.properties 并填入您的实际密钥信息\n# 确保 keystore.properties 文件不会被提交到版本控制\n\nKEYSTORE_FILE=../app/your-keystore.jks\nKEYSTORE_PASSWORD=your-keystore-password\nKEY_ALIAS=your-key-alias\nKEY_PASSWORD=your-key-password\n\n"
  },
  {
    "path": "local.properties.template",
    "content": "## This file must *NOT* be checked into Version Control Systems,\n# as it contains information specific to your local configuration.\n#\n# Location of the SDK. This is only used by Gradle.\n# For customization when using a Version Control System, please read the\n# header note.\n#\n# ── Windows (native build) ──\n# sdk.dir=C:\\\\Users\\\\<YourName>\\\\AppData\\\\Local\\\\Android\\\\Sdk\n#\n# ── WSL (recommended) ──\n# Run: bash scripts/setup-wsl.sh\n# This will auto-generate local.properties with correct WSL paths.\n#\nsdk.dir=/home/<YourWSLUser>/Android/Sdk\ndebug.fake_root=false\n"
  },
  {
    "path": "scripts/Build-Debug.sh",
    "content": "#!/usr/bin/env bash\n# =============================================================================\n# FolkPatch Debug Build Script (WSL)\n# =============================================================================\n# Usage: bash scripts/Build-Debug.sh\n#\n# Mirrors Build-Debug.bat functionality for WSL environment.\n# Builds: cargo clean (apd) → Gradle assembleDebug\n# =============================================================================\n\nset -euo pipefail\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nPROJECT_DIR=\"$(dirname \"$SCRIPT_DIR\")\"\n\n# ── Colors ───────────────────────────────────────────────────────────────────\nGREEN='\\033[0;32m'\nRED='\\033[0;31m'\nNC='\\033[0m'\n\nstep() { echo -e \"${GREEN}[$1/4]${NC} $2\"; }\ndie()  { echo -e \"${RED}[ERROR]${NC} $*\"; exit 1; }\n\ncd \"$PROJECT_DIR\"\n\n# Load Rust env\n[[ -f \"${HOME}/.cargo/env\" ]] && source \"${HOME}/.cargo/env\"\n\n# ── Step 1: Clean apd (Rust) ────────────────────────────────────────────────\nstep 1 \"Cleaning apd directory...\"\ncd apd\ncargo clean || die \"cargo clean failed!\"\ncd ..\n\n# ── Step 2: Build Debug APK ────────────────────────────────────────────────\nstep 2 \"Building debug APK with Gradle...\"\n./gradlew assembleDebug || die \"Gradle assembleDebug failed!\"\n\necho \"\"\necho -e \"${GREEN}✅ Debug build complete!${NC}\"\necho \"  Output: app/build/outputs/apk/debug/*.apk\"\n"
  },
  {
    "path": "scripts/Build-Release.sh",
    "content": "#!/usr/bin/env bash\n# =============================================================================\n# FolkPatch Release Build Script (WSL)\n# =============================================================================\n# Usage: bash scripts/Build-Release.sh\n#\n# Mirrors Build-Release.bat functionality for WSL environment.\n# Builds: cargo clean (apd) → Gradle assembleRelease\n# =============================================================================\n\nset -euo pipefail\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nPROJECT_DIR=\"$(dirname \"$SCRIPT_DIR\")\"\n\n# ── Colors ───────────────────────────────────────────────────────────────────\nGREEN='\\033[0;32m'\nRED='\\033[0;31m'\nNC='\\033[0m'\n\nstep() { echo -e \"${GREEN}[$1/4]${NC} $2\"; }\ndie()  { echo -e \"${RED}[ERROR]${NC} $*\"; exit 1; }\n\ncd \"$PROJECT_DIR\"\n\n# Load Rust env\n[[ -f \"${HOME}/.cargo/env\" ]] && source \"${HOME}/.cargo/env\"\n\n# ── Step 1: Clean apd (Rust) ────────────────────────────────────────────────\nstep 1 \"Cleaning apd directory...\"\ncd apd\ncargo clean || die \"cargo clean failed!\"\ncd ..\n\n# ── Step 2: Build Release APK ──────────────────────────────────────────────\nstep 2 \"Building release APK with Gradle...\"\n./gradlew assembleRelease || die \"Gradle assembleRelease failed!\"\n\necho \"\"\necho -e \"${GREEN}✅ Release build complete!${NC}\"\necho \"  Output: app/build/outputs/apk/release/*.apk\"\n"
  },
  {
    "path": "scripts/init-wsl.sh",
    "content": "#!/usr/bin/env bash\n# =============================================================================\n# FolkPatch WSL Quick Start - Clone + Setup + Build\n# =============================================================================\n# Usage:\n#   bash <(curl -sL https://raw.githubusercontent.com/LyraVoid/FolkPatch/main/scripts/init-wsl.sh)\n#\n# Or download first:\n#   curl -sLO https://raw.githubusercontent.com/LyraVoid/FolkPatch/main/scripts/init-wsl.sh\n#   bash init-wsl.sh\n# =============================================================================\n\nset -euo pipefail\n\nGREEN='\\033[0;32m'\nBLUE='\\033[0;34m'\nYELLOW='\\033[1;33m'\nNC='\\033[0m'\n\ninfo()  { echo -e \"${BLUE}[INFO]${NC}  $*\"; }\nok()    { echo -e \"${GREEN}[OK]${NC}    $*\"; }\nwarn()  { echo -e \"${YELLOW}[WARN]${NC}  $*\"; }\n\n# ── Config ────────────────────────────────────────────────────────────────────\nREPO_URL=\"${FOLKPATCH_REPO:-https://github.com/LyraVoid/FolkPatch.git}\"\nTARGET_DIR=\"${FOLKPATCH_DIR:-$HOME/FolkPatch}\"\n\necho \"\"\necho \"==========================================\"\necho -e \"${GREEN}FolkPatch WSL Quick Start${NC}\"\necho \"==========================================\"\necho \"\"\n\n# ── Step 1: Clone ────────────────────────────────────────────────────────────\nif [[ -d \"${TARGET_DIR}\" ]]; then\n    warn \"Directory ${TARGET_DIR} already exists, skipping clone.\"\nelse\n    info \"[1/3] Cloning FolkPatch to ${TARGET_DIR}...\"\n    git clone --recursive \"${REPO_URL}\" \"${TARGET_DIR}\"\n    ok \"Repository cloned.\"\nfi\n\ncd \"${TARGET_DIR}\"\n\n# ── Step 2: Setup environment ───────────────────────────────────────────────\ninfo \"[2/3] Running setup-wsl.sh (installing JDK/SDK/NDK/Rust/CMake)...\"\nbash scripts/setup-wsl.sh\n\n# ── Step 3: Verify build ────────────────────────────────────────────────────\ninfo \"[3/3] Verifying build environment...\"\nsource \"${HOME}/.cargo/env\" 2>/dev/null || true\n\necho \"\"\necho \"==========================================\"\necho -e \"${GREEN}All Done! 🎉${NC}\"\necho \"==========================================\"\necho \"\"\necho \"Project location : ${TARGET_DIR}\"\necho \"Build command    : bash scripts/Build-Debug.sh\"\necho \"VS Code          : code ${TARGET_DIR}\"\necho \"\"\necho \"Tips:\"\necho \"  - Use 'Ctrl+Shift+B' in VS Code for quick build\"\necho \"  - Rust builds (apd/fpd) benefit most from WSL native filesystem\"\necho \"  - First build will be slow (downloading dependencies), subsequent builds use ccache\"\necho \"\"\n"
  },
  {
    "path": "scripts/setup-wsl.sh",
    "content": "#!/usr/bin/env bash\n# =============================================================================\n# FolkPatch WSL Build Environment Setup Script\n# =============================================================================\n# Prerequisites: Ubuntu 24.04 (or compatible) on WSL2\n# Usage: bash scripts/setup-wsl.sh\n#\n# This script installs everything needed to build FolkPatch from WSL:\n#   - JDK 21\n#   - Android SDK (cmdline-tools, build-tools, platform)\n#   - Android NDK r29\n#   - Rust + cargo-ndk\n#   - CMake 3.28+\n#   - ccache\n# =============================================================================\n\nset -euo pipefail\n\n# ── Colors ───────────────────────────────────────────────────────────────────\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'\nBLUE='\\033[0;34m'\nNC='\\033[0m'\n\ninfo()    { echo -e \"${BLUE}[INFO]${NC}  $*\"; }\nok()      { echo -e \"${GREEN}[OK]${NC}    $*\"; }\nwarn()    { echo -e \"${YELLOW}[WARN]${NC}  $*\"; }\nerr()     { echo -e \"${RED}[ERROR]${NC} $*\"; }\n\n# ── Resolve project root ─────────────────────────────────────────────────────\n_SCRIPT_LOCATION=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\n_PROJECT_CANDIDATE=\"$_SCRIPT_LOCATION\"\n\nwhile [[ \"$_PROJECT_CANDIDATE\" != \"/\" ]]; do\n    if [[ -f \"${_PROJECT_CANDIDATE}/build.gradle.kts\" ]]; then\n        PROJECT_DIR=\"$_PROJECT_CANDIDATE\"\n        break\n    fi\n    _PROJECT_CANDIDATE=\"$(dirname \"$_PROJECT_CANDIDATE\")\"\ndone\n\nif [[ -z \"${PROJECT_DIR:-}\" ]] || [[ ! -f \"${PROJECT_DIR}/build.gradle.kts\" ]]; then\n    if [[ -f \"$(pwd)/build.gradle.kts\" ]]; then\n        PROJECT_DIR=\"$(pwd)\"\n    else\n        err \"Cannot find build.gradle.kts.\"\n        echo \"  Script location: $_SCRIPT_LOCATION\"\n        echo \"  Current dir   : $(pwd)\"\n        echo \"\"\n        echo \"  Run from project root:\"\n        echo \"    cd ~/FolkPatch && bash scripts/setup-wsl.sh\"\n        exit 1\n    fi\nfi\n\nunset _SCRIPT_LOCATION _PROJECT_CANDIDATE\n\n# ── Paths ────────────────────────────────────────────────────────────────────\nANDROID_HOME=\"${HOME}/Android/Sdk\"\nNDK_VERSION=\"29.0.14206865\"\nBUILD_TOOLS_VERSION=\"36.1.0\"\nCOMPILE_SDK_VERSION=\"36\"\n\n# ── Pre-checks ──────────────────────────────────────────────────────────────\ninfo \"FolkPatch WSL Environment Setup\"\ninfo \"===============================\"\ninfo \"Project dir : ${PROJECT_DIR}\"\ninfo \"Android Home: ${ANDROID_HOME}\"\necho \"\"\n\n# ── Step 1: System packages ─────────────────────────────────────────────────\ninfo \"[1/7] Installing system dependencies...\"\necho \"  (apt-get update + install cmake/ninja/ccache/build-essential/...)\"\n\nsudo apt-get update\nsudo apt-get install -y \\\n    wget unzip git curl \\\n    cmake ninja-build \\\n    ccache \\\n    build-essential \\\n    pkg-config \\\n    python3\n\nok \"System packages installed\"\n\n# ── Step 2: JDK 21 ──────────────────────────────────────────────────────────\ninfo \"[2/7] Installing JDK 21...\"\n\nif ! java -version 2>&1 | grep -q \"21\\.\"; then\n    echo \"  Installing openjdk-21-jdk (this may take a minute)...\"\n    sudo apt-get install -y openjdk-21-jdk\nfi\n\nJAVA_HOME=\"$(dirname $(dirname $(readlink -f $(which java))))\"\nok \"JDK 21 at ${JAVA_HOME}\"\n\n# ── Step 3: Android SDK Command-line Tools ──────────────────────────────────\ninfo \"[3/7] Installing Android SDK command-line tools...\"\n\nmkdir -p \"${ANDROID_HOME}/cmdline-tools\"\n_SAVED_PWD=\"$(pwd)\"\ncd \"${ANDROID_HOME}/cmdline-tools\"\n\nif [[ ! -d \"latest\" ]]; then\n    CMDLINE_URL=\"https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip\"\n    info \"  Downloading cmdline-tools (~150MB)...\"\n    wget --progress=bar:force:noscroll \"${CMDLINE_URL}\" -O cmdline-tools.zip\n    echo \"  Extracting...\"\n    unzip -qo cmdline-tools.zip\n    mv cmdline-tools latest\n    rm -f cmdline-tools.zip\nelse\n    info \"  cmdline-tools already exists, skipping.\"\nfi\n\nexport ANDROID_HOME=\"${ANDROID_HOME}\"\nexport PATH=\"${ANDROID_HOME}/cmdline-tools/latest/bin:${PATH}\"\n\n# Accept licenses\ninfo \"  Accepting SDK licenses...\"\nyes | sdkmanager --licenses 2>/dev/null || true\n\n# Install required components\ninfo \"  Installing platform android-${COMPILE_SDK_VERSION} + build-tools ${BUILD_TOOLS_VERSION}...\"\nsdkmanager \\\n    \"platforms;android-${COMPILE_SDK_VERSION}\" \\\n    \"build-tools;${BUILD_TOOLS_VERSION}\"\n\ncd \"$_SAVED_PWD\"\nunset _SAVED_PWD\n\nok \"Android SDK installed (platform ${COMPILE_SDK_VERSION}, build-tools ${BUILD_TOOLS_VERSION})\"\n\n# ── Step 4: Android NDK r29 ────────────────────────────────────────────────\ninfo \"[4/7] Installing Android NDK ${NDK_VERSION}...\"\n\nNDK_HOME=\"${ANDROID_HOME}/ndk/${NDK_VERSION}\"\n\nif [[ ! -d \"${NDK_HOME}\" ]]; then\n    # Try sdkmanager first (usually faster, uses partial downloads)\n    if sdkmanager \"ndk;${NDK_VERSION}\" 2>/dev/null; then\n        ok \"NDK installed via sdkmanager\"\n    else\n        warn \"  sdkmanager failed, falling back to direct download (~2GB)...\"\n        mkdir -p \"${ANDROID_HOME}/ndk\"\n        NDK_URL=\"https://dl.google.com/android/repository/android-ndk-${NDK_VERSION}-linux.zip\"\n        wget --progress=bar:force:noscroll \"${NDK_URL}\" -O ndk.zip\n        echo \"  Extracting NDK (this takes a while)...\"\n        unzip -qo ndk.zip -d \"${ANDROID_HOME}/ndk/\"\n        rm -f ndk.zip\n        if [[ -d \"${ANDROID_HOME}/ndk/android-ndk-${NDK_VERSION}\" ]] && [[ ! -d \"${NDK_HOME}\" ]]; then\n            mv \"${ANDROID_HOME}/ndk/android-ndk-${NDK_VERSION}\" \"${NDK_HOME}\"\n        fi\n    fi\nelse\n    info \"  NDK already exists at ${NDK_HOME}, skipping.\"\nfi\n\nexport ANDROID_NDK_HOME=\"${NDK_HOME}\"\nok \"Android NDK ${NDK_VERSION} at ${NDK_HOME}\"\n\n# ── Step 5: Rust + cargo-ndk ───────────────────────────────────────────────\ninfo \"[5/7] Installing Rust toolchain + cargo-ndk...\"\n\nif ! command -v rustc &> /dev/null; then\n    echo \"  Installing rustup...\"\n    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y\nfi\n\nsource \"${HOME}/.cargo/env\"\n\necho \"  Installing cargo-ndk (cross-compilation helper)...\"\ncargo install cargo-ndk 2>/dev/null || true\n\necho \"  Adding Android target: aarch64-linux-android...\"\nrustup target add aarch64-linux-android 2>/dev/null || true\n\nok \"Rust $(rustc --version) + cargo-ndk ready\"\n\n# ── Step 6: Verify CMake & ccache ──────────────────────────────────────────\ninfo \"[6/7] Verifying CMake and ccache...\"\n\nCMAKE_VERSION=$(cmake --version 2>/dev/null | head -1 | awk '{print $3}')\nCCACHE_VERSION=$(ccache --version 2>/dev/null | head -1)\n\nok \"CMake ${CMAKE_VERSION:-not found}\"\nok \"ccache ${CCACHE_VERSION:-not found}\"\n\n# ── Step 7: Write environment & local.properties ────────────────────────────\ninfo \"[7/7] Writing environment configuration...\"\n\nBASHRC=\"${HOME}/.bashrc\"\n\ngrep -q '# FolkPatch Android SDK' \"${BASHRC}\" 2>/dev/null || cat >> \"${BASHRC}\" << 'EOF'\n\n# FolkPatch Android SDK\nexport ANDROID_HOME=\"$HOME/Android/Sdk\"\nexport ANDROID_SDK_ROOT=\"$HOME/Android/Sdk\"\nexport PATH=\"$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$PATH\"\nEOF\n\ngrep -q '# FolkPatch Java' \"${BASHRC}\" 2>/dev/null || cat >> \"${BASHRC}\" << 'EOF'\n\n# FolkPatch Java\nexport JAVA_HOME=\"$(dirname $(dirname $(readlink -f $(which java) 2>/dev/null || echo /usr/lib/jvm/java-21-openjdk-amd64/bin/java)))\"\nEOF\n\ngrep -q '# FolkPatch Rust' \"${BASHRC}\" 2>/dev/null || cat >> \"${BASHRC}\" << 'EOF'\n\n# FolkPatch Rust\n. \"$HOME/.cargo/env\"\nEOF\n\ncat > \"${PROJECT_DIR}/local.properties\" << EOF\n# Auto-generated by setup-wsl.sh\nsdk.dir=${ANDROID_HOME}\ndebug.fake_root=false\nEOF\n\nok \"Environment written to ~/.bashrc\"\nok \"local.properties created at ${PROJECT_DIR}/local.properties\"\n\n# ── Summary ─────────────────────────────────────────────────────────────────\necho \"\"\necho \"==========================================\"\necho -e \"${GREEN}FolkPatch WSL Setup Complete!${NC}\"\necho \"==========================================\"\necho \"\"\necho \"Installed components:\"\necho \"  JDK          : $(java -version 2>&1 | head -1)\"\necho \"  Android SDK  : ${ANDROID_HOME}\"\necho \"  NDK          : ${NDK_VERSION} at ${NDK_HOME:-${ANDROID_HOME}/ndk/${NDK_VERSION}}\"\necho \"  Rust         : $(rustc --version 2>/dev/null || echo 'not found')\"\necho \"  cargo-ndk    : $(cargo ndk --version 2>/dev/null || echo 'not found')\"\necho \"  CMake        : $(cmake --version 2>/dev/null | head -1 || echo 'not found')\"\necho \"  ccache       : $(ccache --version 2>/dev/null | head -1 || echo 'not found')\"\necho \"\"\necho \"Next steps:\"\necho \"  1. Close and reopen terminal (or: source ~/.bashrc)\"\necho \"  2. Open VS Code: code '${PROJECT_DIR}'\"\necho \"  3. Build debug:  bash scripts/Build-Debug.sh\"\necho \"  4. Build release: bash scripts/Build-Release.sh\"\necho \"\"\n"
  },
  {
    "path": "scripts/update_binary.sh",
    "content": "#!/bin/sh\n\nTMPDIR=/dev/tmp\nrm -rf $TMPDIR\nmkdir -p $TMPDIR 2>/dev/null\n\nexport BBBIN=$TMPDIR/busybox\nunzip -o \"$3\" \"busybox\" -d $TMPDIR >&2\n\nfor arch in  \"arm64-v8a\" ; do\n  unzip -o \"$3\" \"lib/$arch/libbusybox.so\" -d $TMPDIR >&2\n  libpath=\"$TMPDIR/lib/$arch/libbusybox.so\"\n  chmod 755 $libpath\n  if [ -x $libpath ] && $libpath >/dev/null 2>&1; then\n    mv -f $libpath $BBBIN\n    break\n  fi\ndone\n$BBBIN rm -rf $TMPDIR/lib\n\nexport INSTALLER=$TMPDIR/install\n$BBBIN mkdir -p $INSTALLER\n$BBBIN unzip -o \"$3\" \"assets/*\" \"META-INF/com/google/*\" \"lib/*\" \"META-INF/com/google/*\" -x \"lib/*/libbusybox.so\" -d $INSTALLER >&2\nexport ASH_STANDALONE=1\nif echo \"$3\" | $BBBIN grep -q \"uninstall\"; then\n  exec $BBBIN sh \"$INSTALLER/assets/UninstallAP.sh\" \"$@\"\nelif echo \"$3\" | $BBBIN grep -q \"uninstaller\"; then\n  exec $BBBIN sh \"$INSTALLER/assets/UninstallAP.sh\" \"$@\"\nelse\n  exec $BBBIN sh \"$INSTALLER/assets/InstallAP.sh\" \"$@\"\nfi"
  },
  {
    "path": "scripts/update_script.sh",
    "content": "######################\n# APatch Empty script\n# Check update-binary\n######################\n\n"
  },
  {
    "path": "settings.gradle.kts",
    "content": "@file:Suppress(\"UnstableApiUsage\")\n\npluginManagement {\n    repositories {\n        gradlePluginPortal()\n        google()\n        mavenCentral()\n    }\n}\n\nplugins {\n    id(\"org.gradle.toolchains.foojay-resolver-convention\") version \"1.0.0\"\n}\n\ndependencyResolutionManagement {\n    repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS\n    repositories {\n        google()\n        mavenCentral()\n        maven(\"https://jitpack.io\")\n    }\n}\n\nrootProject.name = \"APatch\"\ninclude(\":app\")\n"
  }
]