[
  {
    "path": ".github/FUNDING.yml",
    "content": "github: trinadhthatakula\npatreon: trinadh\nko_fi: trinadh\nbuy_me_a_coffee: trinadh\ncustom: [ \"https://www.paypal.me/trinadhthatakula\" ]\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"gradle\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n    target-branch: \"dev\"\n    groups:\n      maven:\n        patterns:\n          - \"*\"\n"
  },
  {
    "path": ".github/workflows/copilot-setup-steps.yml",
    "content": "name: \"Copilot Setup Steps\"\n\non:\n  workflow_dispatch:\n  push:\n    branches: [ \"master\" ]\n    paths:\n      - .github/workflows/copilot-setup-steps.yml\n\njobs:\n  copilot-setup-steps:\n    runs-on: ubuntu-latest\n\n    permissions:\n      contents: read\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Set up JDK 21\n        uses: actions/setup-java@v4\n        with:\n          distribution: 'zulu'\n          java-version: '21'\n          cache: 'gradle'\n\n      # Optional: Verify installation and warm up the Gradle daemon\n      - name: Verify Java Version\n        run: java -version && ./gradlew --version"
  },
  {
    "path": ".github/workflows/dev-check.yml",
    "content": "name: Dev Build & Test\n\non:\n  push:\n    branches: [ \"master\" ]\n  workflow_dispatch:\n\njobs:\n  build-release-check:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write # Required for creating GitHub Releases\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Setup JDK 21\n        uses: actions/setup-java@v4\n        with:\n          distribution: 'zulu'\n          java-version: '21'\n          cache: 'gradle'\n\n      # Added Ruby for Fastlane in Dev\n      - name: Setup Ruby\n        uses: ruby/setup-ruby@v1\n        with:\n          ruby-version: '3.0'\n\n      - name: Install Fastlane\n        run: gem install fastlane\n\n      # --- 1. SECRETS INJECTION ---\n      - name: Decode Keystore\n        env:\n          ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}\n        run: echo \"$ANDROID_KEYSTORE_BASE64\" | base64 --decode > app/release.jks\n\n      # Added Play Store JSON for Dev (needed for Internal Track upload)\n      - name: Decode Google Play Service Account\n        env:\n          PLAY_STORE_JSON_KEY: ${{ secrets.PLAY_STORE_JSON_KEY }}\n        run: echo \"$PLAY_STORE_JSON_KEY\" > app/google-play-api.json\n\n      - name: Grant execute permission for gradlew\n        run: chmod +x gradlew\n\n      # --- 2. BUILD & DEPLOY (FASTLANE) ---\n      # Replaced manual Gradle command with Fastlane lane\n      - name: Run Fastlane (Distribute Dev)\n        env:\n          JSON_KEY_FILE: ${{ github.workspace }}/app/google-play-api.json\n          SUPPLY_JSON_KEY: ${{ github.workspace }}/app/google-play-api.json\n          KEY_ALIAS: ${{ secrets.KEY_ALIAS }}\n          KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}\n          KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}\n          KEYSTORE_FILE_PATH: ${{ github.workspace }}/app/release.jks\n        run: fastlane android distribute_dev\n\n      # --- 3. TELEGRAM ---\n      - name: Send APK to Telegram\n        if: success()\n        env:\n          TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}\n          TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}\n        run: |\n          # Fastlane 'copyStoreReleaseApk' task puts it here\n          APK_PATH=$(find app/build/distribution/store -name \"*.apk\" | head -n 1)\n          \n          if [ ! -f \"$APK_PATH\" ]; then\n            echo \"Error: APK not found in app/build/distribution/store/\"\n            exit 1\n          fi\n\n          echo \"Sending $APK_PATH to Telegram...\"\n          \n          CAPTION=\"🚀 *Thor (Valhalla) Dev Build*\n          \n          branch: ${{ github.ref_name }}\n          author: ${{ github.actor }}\n          track: Internal Testing\n          status: Uploaded to Play Store & GitHub\"\n\n          curl -s -F \"chat_id=$TELEGRAM_CHAT_ID\" \\\n               -F \"document=@$APK_PATH\" \\\n               -F \"caption=$CAPTION\" \\\n               -F \"parse_mode=Markdown\" \\\n               \"https://api.telegram.org/bot$TELEGRAM_TOKEN/sendDocument\" > /dev/null\n\n      # --- 4. GITHUB RELEASE (PRE-RELEASE) ---\n      - name: Read Version Info\n        id: get_version\n        run: |\n          VERSION_NAME=$(cat version_name.txt)\n          echo \"version_name=$VERSION_NAME\" >> \"$GITHUB_OUTPUT\"\n\n      - name: Create GitHub Pre-Release\n        uses: softprops/action-gh-release@v2\n        with:\n          # Unique tag for dev builds: v1.0.0-dev-45\n          tag_name: v${{ steps.get_version.outputs.version_name }}-dev-${{ github.run_number }}\n          name: Dev Build v${{ steps.get_version.outputs.version_name }} (${{ github.run_number }})\n          files: |\n            app/build/distribution/foss/foss-release.apk\n            app/build/distribution/store/store-release.apk\n          draft: false\n          prerelease: true\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      # --- 5. CLEANUP ---\n      - name: Cleanup sensitive files\n        if: always()\n        run: |\n          rm -f app/release.jks\n          rm -f app/google-play-api.json"
  },
  {
    "path": ".github/workflows/manual-build.yml",
    "content": "name: Manual Build (No Upload)\n\non:\n  workflow_dispatch:\n\njobs:\n  build-only:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Setup JDK 21\n        uses: actions/setup-java@v4\n        with:\n          distribution: 'zulu'\n          java-version: '21'\n          cache: 'gradle'\n\n      - name: Setup Ruby\n        uses: ruby/setup-ruby@v1\n        with:\n          ruby-version: '3.0'\n\n      - name: Install Fastlane\n        run: gem install fastlane\n\n      # --- SECRET DECODING (Required for Play Store Version Check & Signing) ---\n      - name: Decode Keystore\n        env:\n          ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}\n        run: echo \"$ANDROID_KEYSTORE_BASE64\" | base64 --decode > app/release.jks\n\n      - name: Decode Google Play Service Account\n        env:\n          PLAY_STORE_JSON_KEY: ${{ secrets.PLAY_STORE_JSON_KEY }}\n        run: echo \"$PLAY_STORE_JSON_KEY\" > app/google-play-api.json\n\n      - name: Grant execute permission for gradlew\n        run: chmod +x gradlew\n\n      # --- FASTLANE ---\n      - name: Run Fastlane (Build Candidates)\n        env:\n          JSON_KEY_FILE: ${{ github.workspace }}/app/google-play-api.json\n          SUPPLY_JSON_KEY: ${{ github.workspace }}/app/google-play-api.json\n          KEY_ALIAS: ${{ secrets.KEY_ALIAS }}\n          KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}\n          KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}\n          KEYSTORE_FILE_PATH: ${{ github.workspace }}/app/release.jks\n        run: fastlane android build_release_candidates\n\n      # --- ARTIFACT UPLOAD ---\n      - name: Read Version Info\n        id: version_info\n        run: |\n          echo \"VERSION_NAME=$(cat version_name.txt)\" >> $GITHUB_ENV\n          echo \"VERSION_CODE=$(cat version_code.txt)\" >> $GITHUB_ENV\n\n      - name: Upload APKs to GitHub Actions\n        uses: actions/upload-artifact@v4\n        with:\n          name: Thor-v${{ env.VERSION_NAME }}-Build-${{ env.VERSION_CODE }}\n          path: |\n            app/build/distribution/foss/foss-release.apk\n            app/build/distribution/store/store-release.apk\n\n      # --- SECURITY CLEANUP ---\n      - name: Cleanup sensitive files\n        if: always()\n        run: |\n          rm -f app/release.jks\n          rm -f app/google-play-api.json"
  },
  {
    "path": ".github/workflows/production-deploy.yml",
    "content": "name: 2. Production Build & Distribute\n\non:\n  push:\n    branches: [ \"production\" ]\n  workflow_dispatch:\n\njobs:\n  release-distribute:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Setup JDK 21\n        uses: actions/setup-java@v4\n        with:\n          distribution: 'zulu'\n          java-version: '21'\n          cache: 'gradle'\n\n      - name: Setup Ruby\n        uses: ruby/setup-ruby@v1\n        with:\n          ruby-version: '3.0'\n\n      - name: Install Fastlane\n        run: gem install fastlane\n\n      # --- SECRET DECODING ---\n      - name: Decode Keystore\n        env:\n          ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}\n        run: echo \"$ANDROID_KEYSTORE_BASE64\" | base64 --decode > app/release.jks\n\n      - name: Decode Google Play Service Account\n        env:\n          PLAY_STORE_JSON_KEY: ${{ secrets.PLAY_STORE_JSON_KEY }}\n        run: echo \"$PLAY_STORE_JSON_KEY\" > app/google-play-api.json\n\n      - name: Grant execute permission for gradlew\n        run: chmod +x gradlew\n\n      # --- FASTLANE (CLOSED TESTING) ---\n      - name: Run Fastlane (Distribute Prod)\n        env:\n          JSON_KEY_FILE: ${{ github.workspace }}/app/google-play-api.json\n          SUPPLY_JSON_KEY: ${{ github.workspace }}/app/google-play-api.json\n          KEY_ALIAS: ${{ secrets.KEY_ALIAS }}\n          KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}\n          KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}\n          KEYSTORE_FILE_PATH: ${{ github.workspace }}/app/release.jks\n        # Updated to use the production lane (Closed Track)\n        run: fastlane android distribute_production\n\n      # --- SYNC & RELEASE ---\n      - name: Read Version Name\n        id: get_app_version\n        run: |\n          VERSION_NAME=$(cat version_name.txt)\n          echo \"version_name=$VERSION_NAME\" >> \"$GITHUB_OUTPUT\"\n\n      - name: Create GitHub Release\n        uses: softprops/action-gh-release@v2\n        with:\n          tag_name: v${{ steps.get_app_version.outputs.version_name }}\n          name: Release v${{ steps.get_app_version.outputs.version_name }}\n          files: |\n            app/build/distribution/foss/foss-release.apk\n            app/build/distribution/store/store-release.apk\n            app/build/outputs/bundle/storeRelease/*.aab\n          draft: false\n          prerelease: false\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      # --- SECURITY CLEANUP ---\n      - name: Cleanup sensitive files\n        if: always()\n        run: |\n          rm -f app/release.jks\n          rm -f app/google-play-api.json"
  },
  {
    "path": ".github/workflows/release-manager.yml",
    "content": "name: 1. Release Manager (Bump Version)\n\non:\n  workflow_dispatch:\n    inputs:\n      bump_type:\n        description: 'Version Bump Type (affects Version Name)'\n        required: true\n        default: 'patch'\n        type: choice\n        options:\n          - patch\n          - minor\n          - major\n          - none (code only)\n\njobs:\n  bump-and-push:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n        with:\n          # Use a PAT if you want this push to trigger the 'production-deploy' workflow automatically.\n          # If you use the default token, you must trigger production-deploy manually.\n          token: ${{ secrets.PAT_TOKEN || secrets.GITHUB_TOKEN }}\n          ref: production\n\n      - name: Setup Java (for property parsing if needed, or simple bash)\n        uses: actions/setup-java@v4\n        with:\n          distribution: 'zulu'\n          java-version: '21'\n\n      - name: Bump Version in gradle.properties\n        run: |\n          PROPS_FILE=\"gradle.properties\"\n          \n          # 1. Read Current Values\n          CURRENT_CODE=$(grep \"versionCode\" $PROPS_FILE | cut -d'=' -f2)\n          CURRENT_NAME=$(grep \"versionName\" $PROPS_FILE | cut -d'=' -f2)\n          \n          echo \"Current Version: $CURRENT_NAME ($CURRENT_CODE)\"\n          \n          # 2. Increment Version Code (Always +1 for release)\n          NEW_CODE=$((CURRENT_CODE + 1))\n          \n          # 3. Increment Version Name\n          # Split version name X.Y.Z\n          IFS='.' read -r -a parts <<< \"$CURRENT_NAME\"\n          major=${parts[0]}\n          minor=${parts[1]}\n          patch=${parts[2]}\n          \n          case \"${{ inputs.bump_type }}\" in\n            major) major=$((major + 1)); minor=0; patch=0 ;;\n            minor) minor=$((minor + 1)); patch=0 ;;\n            patch) patch=$((patch + 1)) ;;\n            *) echo \"Skipping name bump\";;\n          esac\n          \n          NEW_NAME=\"$major.$minor.$patch\"\n          \n          echo \"New Version: $NEW_NAME ($NEW_CODE)\"\n          \n          # 4. Write back to file (Linux sed)\n          sed -i \"s/versionCode=.*/versionCode=$NEW_CODE/\" $PROPS_FILE\n          sed -i \"s/versionName=.*/versionName=$NEW_NAME/\" $PROPS_FILE\n          \n          # 5. Output for next steps\n          echo \"NEW_NAME=$NEW_NAME\" >> $GITHUB_ENV\n          echo \"NEW_CODE=$NEW_CODE\" >> $GITHUB_ENV\n\n      - name: Commit and Push\n        run: |\n          git config --global user.name \"GitHub Actions CI\"\n          git config --global user.email \"actions@github.com\"\n          \n          git add gradle.properties\n          git commit -m \"chore(release): bump version to v${{ env.NEW_NAME }} ($NEW_CODE) [skip ci]\"\n          \n          # Tagging is optional here, usually better done after successful deploy\n          # git tag -a \"v${{ env.NEW_NAME }}\" -m \"Release v${{ env.NEW_NAME }}\"\n          \n          git push origin production"
  },
  {
    "path": ".github/workflows/telegram-release.yml",
    "content": "name: Telegram Release\n\non:\n  workflow_dispatch:\n    inputs:\n      version_code:\n        description: 'Specific Version Code (Leave empty to use latest Store version)'\n        required: false\n        type: string\n      increment:\n        description: 'Increment Version? (Only used if Version Code is empty)'\n        required: true\n        type: boolean\n        default: false\n\njobs:\n  build-and-announce:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read # Required to check for releases\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Setup JDK 21\n        uses: actions/setup-java@v4\n        with:\n          distribution: 'zulu'\n          java-version: '21'\n          cache: 'gradle'\n\n      - name: Setup Ruby\n        uses: ruby/setup-ruby@v1\n        with:\n          ruby-version: '3.0'\n\n      - name: Install Fastlane\n        run: gem install fastlane\n\n      # --- SECRET DECODING ---\n      - name: Decode Keystore\n        env:\n          ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}\n        run: echo \"$ANDROID_KEYSTORE_BASE64\" | base64 --decode > app/release.jks\n\n      - name: Decode Google Play Service Account\n        env:\n          PLAY_STORE_JSON_KEY: ${{ secrets.PLAY_STORE_JSON_KEY }}\n        run: echo \"$PLAY_STORE_JSON_KEY\" > app/google-play-api.json\n\n      - name: Grant execute permission for gradlew\n        run: chmod +x gradlew\n\n      # --- BUILD ARTIFACTS ---\n      # Updated to pass inputs to Fastlane\n      - name: Run Fastlane (Build Candidates)\n        env:\n          JSON_KEY_FILE: ${{ github.workspace }}/app/google-play-api.json\n          SUPPLY_JSON_KEY: ${{ github.workspace }}/app/google-play-api.json\n          KEY_ALIAS: ${{ secrets.KEY_ALIAS }}\n          KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}\n          KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}\n          KEYSTORE_FILE_PATH: ${{ github.workspace }}/app/release.jks\n          # Map inputs to Env Vars (cleaner than passing directly in run command string)\n          INPUT_VERSION_CODE: ${{ inputs.version_code }}\n          INPUT_INCREMENT: ${{ inputs.increment }}\n        run: |\n          # Pass arguments to the lane using key:value syntax\n          # If INPUT_VERSION_CODE is empty, Fastlane receives nil/empty string and logic handles it\n          \n          fastlane android build_release_candidates \\\n            version_code:\"$INPUT_VERSION_CODE\" \\\n            increment:\"$INPUT_INCREMENT\"\n\n      # --- PREPARE ANNOUNCEMENT ---\n      - name: Read Version Info\n        id: version_info\n        run: |\n          echo \"VERSION_NAME=$(cat version_name.txt)\" >> $GITHUB_ENV\n          echo \"VERSION_CODE=$(cat version_code.txt)\" >> $GITHUB_ENV\n\n      - name: Check for GitHub Release\n        id: check_release\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          TAG=\"v${{ env.VERSION_NAME }}\"\n          echo \"Checking for release tag: $TAG\"\n          \n          # Check if release exists using GitHub CLI\n          if gh release view \"$TAG\" > /dev/null 2>&1; then\n            echo \"Release found!\"\n            echo \"release_url=https://github.com/${{ github.repository }}/releases/tag/$TAG\" >> $GITHUB_OUTPUT\n            echo \"has_release=true\" >> $GITHUB_OUTPUT\n          else\n            echo \"No release found for $TAG\"\n            echo \"has_release=false\" >> $GITHUB_OUTPUT\n          fi\n\n      - name: Publish to Telegram Channel\n        env:\n          TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}\n          TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_RELEASE_CHAT_ID }}\n          RELEASE_URL: ${{ steps.check_release.outputs.release_url }}\n          HAS_RELEASE: ${{ steps.check_release.outputs.has_release }}\n        run: |\n          STORE_APK=\"app/build/distribution/store/store-release.apk\"\n          FOSS_APK=\"app/build/distribution/foss/foss-release.apk\"\n          \n          # 1. Construct the Caption\n          CAPTION=\"⚡️ *Thor v${{ env.VERSION_NAME }} Released!*\n          \n          Build: ${{ env.VERSION_CODE }}\n          Branch: ${{ github.ref_name }}\"\n          \n          if [ \"$HAS_RELEASE\" = \"true\" ]; then\n            CAPTION=\"$CAPTION\n          \n          🔗 [View on GitHub]($RELEASE_URL)\"\n          fi\n          \n          # 2. Send Store APK\n          echo \"Sending Store APK...\"\n          curl -s -F \"chat_id=$TELEGRAM_CHAT_ID\" \\\n               -F \"document=@$STORE_APK\" \\\n               -F \"caption=$CAPTION\" \\\n               -F \"parse_mode=Markdown\" \\\n               \"https://api.telegram.org/bot$TELEGRAM_TOKEN/sendDocument\" > /dev/null\n\n          # 3. Send FOSS APK (Reply to previous? No, just send as second message for now)\n          echo \"Sending FOSS APK...\"\n          curl -s -F \"chat_id=$TELEGRAM_CHAT_ID\" \\\n               -F \"document=@$FOSS_APK\" \\\n               -F \"caption=🔓 *FOSS Version (No Google Services)*\" \\\n               -F \"parse_mode=Markdown\" \\\n               \"https://api.telegram.org/bot$TELEGRAM_TOKEN/sendDocument\" > /dev/null\n\n      # --- SECURITY CLEANUP ---\n      - name: Cleanup sensitive files\n        if: always()\n        run: |\n          rm -f app/release.jks\n          rm -f app/google-play-api.json"
  },
  {
    "path": ".gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n/.idea/caches\n/.idea/libraries\n/.idea/modules.xml\n/.idea/workspace.xml\n/.idea/navEditor.xml\n/.idea/assetWizardSettings.xml\n/.idea/deploymentTargetDropDown.xml\n/.idea/deploymentTargetSelector.xml\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n.cxx\nlocal.properties\n/.idea/studiobot.xml\n/.idea/appInsightsSettings.xml\n/.agents\n/*/build\n/graphify-out"
  },
  {
    "path": ".idea/.gitignore",
    "content": "# Default ignored files\n/shelf/\n/workspace.xml\n"
  },
  {
    "path": ".idea/AndroidProjectSystem.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"AndroidProjectSystem\">\n    <option name=\"providerId\" value=\"com.android.tools.idea.GradleProjectSystem\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/codeStyles/Project.xml",
    "content": "<component name=\"ProjectCodeStyleConfiguration\">\n  <code_scheme name=\"Project\" version=\"173\">\n    <JavaCodeStyleSettings>\n      <option name=\"IMPORT_LAYOUT_TABLE\">\n        <value>\n          <package name=\"\" withSubpackages=\"true\" static=\"false\" module=\"true\" />\n          <package name=\"android\" withSubpackages=\"true\" static=\"true\" />\n          <package name=\"androidx\" withSubpackages=\"true\" static=\"true\" />\n          <package name=\"com\" withSubpackages=\"true\" static=\"true\" />\n          <package name=\"junit\" withSubpackages=\"true\" static=\"true\" />\n          <package name=\"net\" withSubpackages=\"true\" static=\"true\" />\n          <package name=\"org\" withSubpackages=\"true\" static=\"true\" />\n          <package name=\"java\" withSubpackages=\"true\" static=\"true\" />\n          <package name=\"javax\" withSubpackages=\"true\" static=\"true\" />\n          <package name=\"\" withSubpackages=\"true\" static=\"true\" />\n          <emptyLine />\n          <package name=\"android\" withSubpackages=\"true\" static=\"false\" />\n          <emptyLine />\n          <package name=\"androidx\" withSubpackages=\"true\" static=\"false\" />\n          <emptyLine />\n          <package name=\"com\" withSubpackages=\"true\" static=\"false\" />\n          <emptyLine />\n          <package name=\"junit\" withSubpackages=\"true\" static=\"false\" />\n          <emptyLine />\n          <package name=\"net\" withSubpackages=\"true\" static=\"false\" />\n          <emptyLine />\n          <package name=\"org\" withSubpackages=\"true\" static=\"false\" />\n          <emptyLine />\n          <package name=\"java\" withSubpackages=\"true\" static=\"false\" />\n          <emptyLine />\n          <package name=\"javax\" withSubpackages=\"true\" static=\"false\" />\n          <emptyLine />\n          <package name=\"\" withSubpackages=\"true\" static=\"false\" />\n          <emptyLine />\n        </value>\n      </option>\n    </JavaCodeStyleSettings>\n    <JetCodeStyleSettings>\n      <option name=\"CODE_STYLE_DEFAULTS\" value=\"KOTLIN_OFFICIAL\" />\n    </JetCodeStyleSettings>\n    <codeStyleSettings language=\"XML\">\n      <option name=\"FORCE_REARRANGE_MODE\" value=\"1\" />\n      <indentOptions>\n        <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\" />\n      </indentOptions>\n      <arrangement>\n        <rules>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>xmlns:android</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>^$</XML_NAMESPACE>\n                </AND>\n              </match>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>xmlns:.*</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>^$</XML_NAMESPACE>\n                </AND>\n              </match>\n              <order>BY_NAME</order>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>.*:id</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n                </AND>\n              </match>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>.*:name</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n                </AND>\n              </match>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>name</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>^$</XML_NAMESPACE>\n                </AND>\n              </match>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>style</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>^$</XML_NAMESPACE>\n                </AND>\n              </match>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>.*</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>^$</XML_NAMESPACE>\n                </AND>\n              </match>\n              <order>BY_NAME</order>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>.*</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n                </AND>\n              </match>\n              <order>ANDROID_ATTRIBUTE_ORDER</order>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>.*</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>.*</XML_NAMESPACE>\n                </AND>\n              </match>\n              <order>BY_NAME</order>\n            </rule>\n          </section>\n        </rules>\n      </arrangement>\n    </codeStyleSettings>\n    <codeStyleSettings language=\"kotlin\">\n      <option name=\"CODE_STYLE_DEFAULTS\" value=\"KOTLIN_OFFICIAL\" />\n    </codeStyleSettings>\n  </code_scheme>\n</component>"
  },
  {
    "path": ".idea/codeStyles/codeStyleConfig.xml",
    "content": "<component name=\"ProjectCodeStyleConfiguration\">\n  <state>\n    <option name=\"USE_PER_PROJECT_SETTINGS\" value=\"true\" />\n  </state>\n</component>"
  },
  {
    "path": ".idea/compiler.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"CompilerConfiguration\">\n    <bytecodeTargetLevel target=\"21\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/copilot.data.migration.agent.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"AgentMigrationStateService\">\n    <option name=\"migrationStatus\" value=\"COMPLETED\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/copilot.data.migration.ask.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"AskMigrationStateService\">\n    <option name=\"migrationStatus\" value=\"COMPLETED\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/copilot.data.migration.ask2agent.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"Ask2AgentMigrationStateService\">\n    <option name=\"migrationStatus\" value=\"COMPLETED\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/copilot.data.migration.edit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"EditMigrationStateService\">\n    <option name=\"migrationStatus\" value=\"COMPLETED\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/deviceManager.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"DeviceTable\">\n    <option name=\"columnSorters\">\n      <list>\n        <ColumnSorterState>\n          <option name=\"column\" value=\"Name\" />\n          <option name=\"order\" value=\"ASCENDING\" />\n        </ColumnSorterState>\n      </list>\n    </option>\n  </component>\n</project>"
  },
  {
    "path": ".idea/dictionaries/project.xml",
    "content": "<component name=\"ProjectDictionaryState\">\n  <dictionary name=\"project\">\n    <words>\n      <w>Appfile</w>\n      <w>apkm</w>\n      <w>chown</w>\n      <w>dcim</w>\n      <w>fastlane</w>\n      <w>fira</w>\n      <w>hmmss</w>\n      <w>libsu</w>\n      <w>readlines</w>\n      <w>shellout</w>\n      <w>shizuku</w>\n      <w>xapk</w>\n      <w>zulu</w>\n    </words>\n  </dictionary>\n</component>"
  },
  {
    "path": ".idea/google-java-format.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"GoogleJavaFormatSettings\">\n    <option name=\"enabled\" value=\"false\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/gradle.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"GradleMigrationSettings\" migrationVersion=\"1\" />\n  <component name=\"GradleSettings\">\n    <option name=\"linkedExternalProjectsSettings\">\n      <GradleProjectSettings>\n        <option name=\"testRunner\" value=\"CHOOSE_PER_TEST\" />\n        <option name=\"externalProjectPath\" value=\"$PROJECT_DIR$\" />\n        <option name=\"modules\">\n          <set>\n            <option value=\"$PROJECT_DIR$\" />\n            <option value=\"$PROJECT_DIR$/app\" />\n            <option value=\"$PROJECT_DIR$/bypass\" />\n            <option value=\"$PROJECT_DIR$/suCore\" />\n            <option value=\"$PROJECT_DIR$/vm-runtime\" />\n          </set>\n        </option>\n        <option name=\"resolveExternalAnnotations\" value=\"true\" />\n      </GradleProjectSettings>\n    </option>\n    <option name=\"parallelModelFetch\" value=\"true\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/inspectionProfiles/Project_Default.xml",
    "content": "<component name=\"InspectionProjectProfileManager\">\n  <profile version=\"1.0\">\n    <option name=\"myName\" value=\"Project Default\" />\n    <inspection_tool class=\"AssignedValueIsNeverRead\" enabled=\"false\" level=\"WARNING\" enabled_by_default=\"false\" />\n    <inspection_tool class=\"ComposePreviewDimensionRespectsLimit\" enabled=\"true\" level=\"WARNING\" enabled_by_default=\"true\">\n      <option name=\"composableFile\" value=\"true\" />\n      <option name=\"previewFile\" value=\"true\" />\n    </inspection_tool>\n    <inspection_tool class=\"ComposePreviewMustBeTopLevelFunction\" enabled=\"true\" level=\"ERROR\" enabled_by_default=\"true\">\n      <option name=\"composableFile\" value=\"true\" />\n      <option name=\"previewFile\" value=\"true\" />\n    </inspection_tool>\n    <inspection_tool class=\"ComposePreviewNeedsComposableAnnotation\" enabled=\"true\" level=\"ERROR\" enabled_by_default=\"true\">\n      <option name=\"composableFile\" value=\"true\" />\n      <option name=\"previewFile\" value=\"true\" />\n    </inspection_tool>\n    <inspection_tool class=\"ComposePreviewNotSupportedInUnitTestFiles\" enabled=\"true\" level=\"ERROR\" enabled_by_default=\"true\">\n      <option name=\"composableFile\" value=\"true\" />\n      <option name=\"previewFile\" value=\"true\" />\n    </inspection_tool>\n    <inspection_tool class=\"GlancePreviewDimensionRespectsLimit\" enabled=\"true\" level=\"WARNING\" enabled_by_default=\"true\">\n      <option name=\"composableFile\" value=\"true\" />\n    </inspection_tool>\n    <inspection_tool class=\"GlancePreviewMustBeTopLevelFunction\" enabled=\"true\" level=\"ERROR\" enabled_by_default=\"true\">\n      <option name=\"composableFile\" value=\"true\" />\n    </inspection_tool>\n    <inspection_tool class=\"GlancePreviewNeedsComposableAnnotation\" enabled=\"true\" level=\"ERROR\" enabled_by_default=\"true\">\n      <option name=\"composableFile\" value=\"true\" />\n    </inspection_tool>\n    <inspection_tool class=\"GlancePreviewNotSupportedInUnitTestFiles\" enabled=\"true\" level=\"ERROR\" enabled_by_default=\"true\">\n      <option name=\"composableFile\" value=\"true\" />\n    </inspection_tool>\n    <inspection_tool class=\"PreviewAnnotationInFunctionWithParameters\" enabled=\"true\" level=\"ERROR\" enabled_by_default=\"true\">\n      <option name=\"composableFile\" value=\"true\" />\n      <option name=\"previewFile\" value=\"true\" />\n    </inspection_tool>\n    <inspection_tool class=\"PreviewApiLevelMustBeValid\" enabled=\"true\" level=\"ERROR\" enabled_by_default=\"true\">\n      <option name=\"composableFile\" value=\"true\" />\n      <option name=\"previewFile\" value=\"true\" />\n    </inspection_tool>\n    <inspection_tool class=\"PreviewDeviceShouldUseNewSpec\" enabled=\"true\" level=\"WEAK WARNING\" enabled_by_default=\"true\">\n      <option name=\"composableFile\" value=\"true\" />\n      <option name=\"previewFile\" value=\"true\" />\n    </inspection_tool>\n    <inspection_tool class=\"PreviewFontScaleMustBeGreaterThanZero\" enabled=\"true\" level=\"ERROR\" enabled_by_default=\"true\">\n      <option name=\"composableFile\" value=\"true\" />\n      <option name=\"previewFile\" value=\"true\" />\n    </inspection_tool>\n    <inspection_tool class=\"PreviewMultipleParameterProviders\" enabled=\"true\" level=\"ERROR\" enabled_by_default=\"true\">\n      <option name=\"composableFile\" value=\"true\" />\n      <option name=\"previewFile\" value=\"true\" />\n    </inspection_tool>\n    <inspection_tool class=\"PreviewParameterProviderOnFirstParameter\" enabled=\"true\" level=\"ERROR\" enabled_by_default=\"true\">\n      <option name=\"composableFile\" value=\"true\" />\n      <option name=\"previewFile\" value=\"true\" />\n    </inspection_tool>\n    <inspection_tool class=\"PreviewPickerAnnotation\" enabled=\"true\" level=\"ERROR\" enabled_by_default=\"true\">\n      <option name=\"composableFile\" value=\"true\" />\n      <option name=\"previewFile\" value=\"true\" />\n    </inspection_tool>\n  </profile>\n</component>"
  },
  {
    "path": ".idea/kotlinc.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"Kotlin2JsCompilerArguments\">\n    <option name=\"moduleKind\" value=\"plain\" />\n  </component>\n  <component name=\"Kotlin2JvmCompilerArguments\">\n    <option name=\"jvmTarget\" value=\"21\" />\n  </component>\n  <component name=\"KotlinJpsPluginSettings\">\n    <option name=\"externalSystemId\" value=\"Gradle\" />\n    <option name=\"version\" value=\"2.3.20\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/markdown.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"MarkdownSettings\">\n    <option name=\"previewPanelProviderInfo\">\n      <ProviderInfo name=\"Compose (experimental)\" className=\"com.intellij.markdown.compose.preview.ComposePanelProvider\" />\n    </option>\n  </component>\n</project>"
  },
  {
    "path": ".idea/migrations.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectMigrations\">\n    <option name=\"MigrateToGradleLocalJavaHome\">\n      <set>\n        <option value=\"$PROJECT_DIR$\" />\n      </set>\n    </option>\n  </component>\n</project>"
  },
  {
    "path": ".idea/misc.xml",
    "content": "<project version=\"4\">\n  <component name=\"ExternalStorageConfigurationManager\" enabled=\"true\" />\n  <component name=\"ProjectRootManager\" version=\"2\" languageLevel=\"JDK_21\" default=\"true\" project-jdk-name=\"jbr-21\" project-jdk-type=\"JavaSDK\">\n    <output url=\"file://$PROJECT_DIR$/build/classes\" />\n  </component>\n  <component name=\"ProjectType\">\n    <option name=\"id\" value=\"Android\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/runConfigurations/Generate_Baseline_Profile_for_app.xml",
    "content": "<component name=\"ProjectRunConfigurationManager\">\n  <configuration default=\"false\" name=\"Generate Baseline Profile for app\" type=\"AndroidBaselineProfileRunConfigurationType\" factoryName=\"Android Baseline Profile Configuration Factory\">\n    <module name=\"Thor.app\" />\n    <option name=\"generateAllVariants\" value=\"false\" />\n    <option name=\"CLEAR_LOGCAT\" value=\"false\" />\n    <option name=\"SHOW_LOGCAT_AUTOMATICALLY\" value=\"false\" />\n    <option name=\"TARGET_SELECTION_MODE\" value=\"DEVICE_AND_SNAPSHOT_COMBO_BOX\" />\n    <option name=\"SELECTED_CLOUD_MATRIX_CONFIGURATION_ID\" value=\"-1\" />\n    <option name=\"SELECTED_CLOUD_MATRIX_PROJECT_ID\" value=\"\" />\n    <option name=\"DEBUGGER_TYPE\" value=\"Auto\" />\n    <Auto>\n      <option name=\"USE_JAVA_AWARE_DEBUGGER\" value=\"false\" />\n      <option name=\"SHOW_STATIC_VARS\" value=\"true\" />\n      <option name=\"WORKING_DIR\" value=\"\" />\n      <option name=\"TARGET_LOGGING_CHANNELS\" value=\"lldb process:gdb-remote packets\" />\n      <option name=\"SHOW_OPTIMIZED_WARNING\" value=\"true\" />\n      <option name=\"ATTACH_ON_WAIT_FOR_DEBUGGER\" value=\"false\" />\n      <option name=\"DEBUG_SANDBOX_SDK\" value=\"false\" />\n    </Auto>\n    <Hybrid>\n      <option name=\"USE_JAVA_AWARE_DEBUGGER\" value=\"false\" />\n      <option name=\"SHOW_STATIC_VARS\" value=\"true\" />\n      <option name=\"WORKING_DIR\" value=\"\" />\n      <option name=\"TARGET_LOGGING_CHANNELS\" value=\"lldb process:gdb-remote packets\" />\n      <option name=\"SHOW_OPTIMIZED_WARNING\" value=\"true\" />\n      <option name=\"ATTACH_ON_WAIT_FOR_DEBUGGER\" value=\"false\" />\n      <option name=\"DEBUG_SANDBOX_SDK\" value=\"false\" />\n    </Hybrid>\n    <Java>\n      <option name=\"ATTACH_ON_WAIT_FOR_DEBUGGER\" value=\"false\" />\n      <option name=\"DEBUG_SANDBOX_SDK\" value=\"false\" />\n    </Java>\n    <Native>\n      <option name=\"USE_JAVA_AWARE_DEBUGGER\" value=\"false\" />\n      <option name=\"SHOW_STATIC_VARS\" value=\"true\" />\n      <option name=\"WORKING_DIR\" value=\"\" />\n      <option name=\"TARGET_LOGGING_CHANNELS\" value=\"lldb process:gdb-remote packets\" />\n      <option name=\"SHOW_OPTIMIZED_WARNING\" value=\"true\" />\n      <option name=\"ATTACH_ON_WAIT_FOR_DEBUGGER\" value=\"false\" />\n      <option name=\"DEBUG_SANDBOX_SDK\" value=\"false\" />\n    </Native>\n    <Profilers>\n      <option name=\"ADVANCED_PROFILING_ENABLED\" value=\"false\" />\n      <option name=\"STARTUP_PROFILING_ENABLED\" value=\"false\" />\n      <option name=\"STARTUP_CPU_PROFILING_ENABLED\" value=\"false\" />\n      <option name=\"STARTUP_CPU_PROFILING_CONFIGURATION_NAME\" value=\"Java/Kotlin Method Sample (legacy)\" />\n      <option name=\"STARTUP_NATIVE_MEMORY_PROFILING_ENABLED\" value=\"false\" />\n      <option name=\"NATIVE_MEMORY_SAMPLE_RATE_BYTES\" value=\"2048\" />\n    </Profilers>\n    <method v=\"2\" />\n  </configuration>\n</component>"
  },
  {
    "path": ".idea/runConfigurations.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"RunConfigurationProducerService\">\n    <option name=\"ignoredProducers\">\n      <set>\n        <option value=\"com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer\" />\n        <option value=\"com.intellij.execution.junit.AllInPackageConfigurationProducer\" />\n        <option value=\"com.intellij.execution.junit.PatternConfigurationProducer\" />\n        <option value=\"com.intellij.execution.junit.TestInClassConfigurationProducer\" />\n        <option value=\"com.intellij.execution.junit.UniqueIdConfigurationProducer\" />\n        <option value=\"com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer\" />\n        <option value=\"org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer\" />\n        <option value=\"org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer\" />\n      </set>\n    </option>\n  </component>\n</project>"
  },
  {
    "path": ".idea/vcs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"VcsDirectoryMappings\">\n    <mapping directory=\"\" vcs=\"Git\" />\n  </component>\n</project>"
  },
  {
    "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": "PROJECT_CONTEXT.md",
    "content": "# Thor App Manager - Project Context\n\nThor is a modern, lightweight, and privacy-focused Android App Manager. It is designed to be 100%\noffline, free, and open-source (FOSS), providing advanced app management capabilities through\nShizuku, Dhizuku, and Root access.\n\n## 🏗 Architecture\n\nThe project follows **Clean Architecture** principles combined with **MVVM (Model-View-ViewModel)**\nfor the presentation layer.\n\n### Modules:\n\n- **`app/`**: The core application module.\n    - **Presentation**: Built with **Jetpack Compose**. ViewModels manage state using `StateFlow`\n      and Koin for dependency injection.\n    - **Domain**: Pure Kotlin layer containing business logic, Use Cases, and repository interfaces.\n      Platform-agnostic where possible.\n    - **Data**: Implementation of repositories, interacting with Android's `PackageManager`,\n      `Shizuku`/`Dhizuku` APIs, and `DataStore` for persistence.\n    - **DI**: Dependency Injection using **Koin**, organized into `commonModule`, `installerModule`,\n      `preferenceModule`, `coreModule`, and `roomModule`.\n- **`suCore/`**: A specialized module for root shell management. It's a Kotlin-refactored version of\n  the `libsu` core module by `topjohnwu`, optimized for modern Kotlin idioms and memory safety.\n- **`bypass/`**: A core utility module for bypassing Android's hidden API restrictions using\n  `VMRuntime` exemptions and enhanced reflection.\n- **`vm-runtime/`**: Compile-only **Java** stubs required for the `bypass` module to interface with\n  internal Android classes like `VMRuntime`. Intentionally a pure Java library (not Kotlin) to\n  ensure correct class shadowing behaviour at compile time.\n\n## 🛠 Tech Stack\n\n- **Language**: Kotlin (all modules except `vm-runtime`, which is pure Java for stub compatibility)\n- **UI Framework**: Jetpack Compose with `MaterialExpressiveTheme` + `MotionScheme.expressive()`.\n  Static \"Asgardian\" color scheme by default; optional Material You dynamic color on Android 12+.\n  Navigation uses a custom `ThorNavigationBar` with spring animations + `HorizontalPager` for\n  swipe-between-screens.\n- **Dependency Injection**: Koin\n- **Asynchronous Programming**: Kotlin Coroutines & Flow\n- **Image Loading**: Coil 3\n- **Animation**: Lottie + Compose `AnimatedVisibility`/`AnimatedContent`\n- **Persistence**:\n    - **Jetpack Room**: High-performance caching of `AppInfo` metadata, invalidated via\n      `lastUpdateTime`.\n    - **Jetpack DataStore**: User preferences including theme, AMOLED mode, biometric lock, and\n      preferred privilege mode.\n- **Security**: Android Biometrics via `BiometricPrompt` API directly (no `androidx.biometric`\n  dependency). `HomeActivity` extends `ComponentActivity`.\n- **Elevated Privileges**:\n    - **Root (su)**: Via `suCore` module (Kotlin-refactored fork of `libsu`).\n    - **Shizuku**: Shell-command-first (`am`, `pm`, `appops`) with reflection fallback via\n      `:bypass`.\n    - **Dhizuku**: Device Owner API with reflection fallback via `:bypass`.\n    - **Work Mode (`PrivilegeMode`)**: User-selectable privilege engine (ROOT / SHIZUKU / DHIZUKU)\n      with automatic fallback strategy (Root → Shizuku → Dhizuku).\n    - **Internal Bypass (`:bypass`)**: Custom Kotlin implementation using `VMRuntime` exemptions and\n      reflection, backed by Java stubs in `:vm-runtime`.\n- **Build System**: Gradle Kotlin DSL with Version Catalog (`libs.versions.toml`). Exact tool and\n  library versions defined in `libs.versions.toml`; do not hardcode them in docs.\n- **Distribution**: Two product flavors: `store` (Play Store compliant) and `foss` (fully\n  libre/open).\n\n## ✨ Key Features\n\n- **App Management**: Install, uninstall, freeze (disable/enable), suspend/unsuspend, and\n  background-restrict apps. Tracks `isSuspended` and `isDebuggable` flags directly on `AppInfo`.\n- **Work Mode**: User-selectable privilege engine (`PrivilegeMode`: ROOT, SHIZUKU, DHIZUKU) stored\n  in `UserPreferences`. Falls back automatically if the preferred mode is unavailable.\n- **Batch Operations**: Batch freeze/unfreeze, reinstall, uninstall, kill, suspend/unsuspend, and\n  clear data — all logged in real time through the terminal logger dialog.\n- **App Suspension**: Uses `IPackageManager` reflection to suspend apps, showing a custom \"Thor\"\n  -branded system dialog. Supports Android 10 through 13+ with version-specific fallbacks.\n- **Background Restriction**: Restricts an app's background activity via `setAppRestricted`.\n- **Fix Store (Reinstall with Google)**: Reassigns installer to Play Store. Available in all\n  privilege modes (Root, Shizuku, Dhizuku).\n- **Clear Data / Clear Cache**: Available in all privilege modes; `clearAppData` uses `pm clear`\n  with multi-user support.\n- **Advanced Insights**: Installer source (resolved from package labels, not hardcoded), split APK\n  indicators, version codes, SDK targets, `isSuspended`, `isDebuggable`.\n- **System App Support**: Uninstall or freeze system apps (requires any privilege mode).\n- **Security**: Biometric/device-credential lock for app access. Per-session authentication state.\n- **App Metadata Caching**: Room DB cache for `AppInfo`, invalidated via `lastUpdateTime`.\n- **Preferences** (`UserPreferences`): theme, AMOLED, dynamic color, biometric lock, sort/filter\n  state, privilege mode, and language — all persisted via DataStore.\n- **Customization**: Dark/Light/System + AMOLED themes. \"Asgardian\" static color scheme is the\n  default; Material You dynamic color opt-in. Preferred privilege mode persisted across sessions.\n- **Search**: Live search by app name or package name in App List and Freezer screens.\n- **Multi-language**: Supports English, Spanish, French, Arabic, and Chinese. Runtime locale\n  switching via `LocaleManager` (`util/LocaleManager.kt`); language preference stored in\n  `UserPreferences.language` (null = system default).\n- **Privacy**: Fully offline, no ads, no trackers, FOSS (GPL-3.0).\n\n## ⚠️ Limitations\n\n- **Privilege Dependency**: Advanced features (freeze, suspend, system app removal) require at least\n  one of Root, Shizuku, or Dhizuku. Work Mode selection with automatic fallback mitigates partial\n  availability.\n- **Suspension Compatibility**: `setAppSuspended` uses reflection against internal APIs; behaviour\n  may vary across Android 10–14+ due to API signature changes.\n- **Offline Only**: No cloud backup or remote synchronization (by design, for privacy).\n- **Android Constraints**: Subject to evolving Android security restrictions (hidden API policy,\n  target SDK requirements).\n- **Feature Gap**: App data backup is not yet implemented.\n\n## 🚀 Opportunities\n\n- **Data Backup**: Implementing local app data backup and restoration.\n- **Package Editing**: Direct editing of `packages.xml` for advanced users.\n- **Batch Install**: Installing multiple APKs in one operation.\n- **Automation**: Scheduled freezing/unfreezing or automated cleanup tasks.\n- **Installer Integration**: Expanding support for third-party installers (e.g., F-Droid, Aurora\n  Store).\n\n## 🛡 Threats\n\n- **Play Store Policies**: As an \"App Manager\" with elevated privileges, it faces strict scrutiny\n  from Google Play.\n- **Android OS Changes**: Future Android updates might further restrict Shizuku or root-level access\n  methods.\n- **Competition**: Several established open-source app managers exist; maintaining a niche in \"\n  lightweight & offline\" is key.\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <img src=\"app/src/main/thor_drawn-playstore.png\" alt=\"Thor Logo\" height=\"192dp\">\n</p>\n\n<h1 align=\"center\">Thor App Manager</h1>\n\n<p align=\"center\">\n  <a href=\"https://play.google.com/store/apps/details?id=com.valhalla.thor\" target=\"_blank\">\n    <img src=\"https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png\" alt=\"Get it on Google Play\" height=\"60\">\n  </a>\n  &nbsp;\n  <a href=\"https://apt.izzysoft.de/fdroid/index/apk/com.valhalla.thor\" target=\"_blank\">\n    <img src=\"https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png\" alt=\"Get it on IzzyOnDroid\" height=\"60\">\n  </a>\n  &nbsp;\n  <a href=\"https://www.indusappstore.com/apps/productivity/thor/com.valhalla.thor/?page=details&id=com.valhalla.thor\" target=\"_blank\">\n    <img src=\".github/assets/indus-badge.png\" alt=\"Download on Indus Appstore\" height=\"60\">\n  </a>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://t.me/thorAppDev\">Telegram Channel</a>\n</p>\n\n\n---\n\n* Kotlin + Material 3 Design\n* Jetpack Compose\n* Room DB App Caching\n* Custom Hidden API Bypass\n* PlayStore Download Size (around 2.0 MB)\n* Smallest APK size (less than 4 MB)\n* FOSS - GPL-3.0\n* Fully Offline\n* No Ads/Trackers\n\n## Working Features\n\n- High-performance app list loading with Room DB metadata caching\n- Fingerprint Lock\n- Themes (dark, light, system) + AMOLED + Asgardian static theme\n- App Installer (install with root, shizuku or normal)\n- Root Support\n- Shizuku Support\n- Dhizuku Support\n- Fully reproducible, copyleft libre software (GPLv3.0)\n- Material 3 with optional dynamic colors (Material You)\n- Work Mode selection — manually choose between Root, Shizuku, or Dhizuku as the active privilege\n  engine\n- Displays App List while sorting them based on Installation source\n- Search in App List and Freezer\n- Multi-language support (English, Spanish, French, Arabic, Chinese) with in-app language switcher\n- Launch App Activities\n- Install/Uninstall/Freeze/Unfreeze Apk files\n- Suspend/Unsuspend apps (shows custom Thor-branded system dialog)\n- Background Restriction (restrict app background activity)\n- Reinstall APKs/Fix Store installer record (available in all privilege modes)\n- Share App Apk file\n- Batch Reinstall/Uninstall/Freeze/Unfreeze/Kill/Suspend/Clear Data\n- Split App Indicator\n- AppState Indicator (frozen / suspended / hidden)\n- Uninstall System Apps\n- Freeze/UnFreeze System apps\n- Sorting & filters\n- Clear Data/Cache (available in all privilege modes)\n\n## Upcoming Features\n\n- BackUp App Data\n- Editing Packages.xml\n- Batch Install\n- Many more\n\n## 💖 Support Development\n\nThor is a labor of love, built to be **100% offline, ad-free, and tracker-free**. If this tool has\nmade your Android management easier, consider supporting its continued development. Your\ncontributions help keep the project alive and free for everyone.\n\n| Platform            | Link                                                        |\n|---------------------|-------------------------------------------------------------|\n| **Patreon**         | [Support on Patreon](https://www.patreon.com/trinadh)       |\n| **Buy Me a Coffee** | [Buy me a coffee](https://www.buymeacoffee.com/trinadh)     |\n| **PayPal**          | [Donate via PayPal](https://www.paypal.me/trinadhthatakula) |\n\n## Credits\n\n- Portions of this app use code from [`libsu`](https://github.com/topjohnwu/libsu)\n  by [topjohnwu](https://github.com/topjohnwu/), adapted and integrated as the [\n  `suCore`](https://github.com/trinadhthatakula/Thor/tree/master/suCore) module.\n- Replaced [`AndroidHiddenApiBypass`](https://github.com/LSPosed/AndroidHiddenApiBypass) with an\n  internal Kotlin implementation in the [\n  `bypass`](https://github.com/trinadhthatakula/Thor/tree/master/bypass) module, backed by Java\n  stubs in the [`vm-runtime`](https://github.com/trinadhthatakula/Thor/tree/master/vm-runtime)\n  module for maximum compatibility when shadowing system classes.\n\n### Modifications to libsu\n\n- Fully converted the original Java-based `libsu` code to Kotlin for `suCore`\n- Refer SuCore [README](https://github.com/trinadhthatakula/Thor/blob/master/suCore/README.md) for\n  more details\n\n## License\n\nThis project is licensed under the GNU General Public License v3.0 (GPL-3.0).\n\n- `libsu` is licensed under the Apache License 2.0. All modifications and usage in this project\n  comply with the Apache-2.0 requirements.\n- This project as a whole is distributed under the GNU General Public License v3.0 (GPL-3.0).\n- See the [LICENSE](LICENSE) file for full license text.\n"
  },
  {
    "path": "app/.gitignore",
    "content": "/build"
  },
  {
    "path": "app/baselineprofile/.gitignore",
    "content": "/build"
  },
  {
    "path": "app/baselineprofile/build.gradle.kts",
    "content": "plugins {\n    alias(libs.plugins.android.test)\n    alias(libs.plugins.baselineprofile)\n}\n\nandroid {\n    namespace = \"com.valhalla.thor.baselineprofile\"\n    compileSdk {\n        version = release(36)\n    }\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_11\n        targetCompatibility = JavaVersion.VERSION_11\n    }\n\n    defaultConfig {\n        minSdk = 28\n        targetSdk = 36\n\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n\n    targetProjectPath = \":app\"\n\n    flavorDimensions += listOf(\"distribution\")\n    productFlavors {\n        create(\"store\") { dimension = \"distribution\" }\n        create(\"foss\") { dimension = \"distribution\" }\n    }\n\n}\n\n// This is the configuration block for the Baseline Profile plugin.\n// You can specify to run the generators on a managed devices or connected devices.\nbaselineProfile {\n    useConnectedDevices = true\n}\n\ndependencies {\n    implementation(libs.androidx.junit)\n    implementation(libs.androidx.espresso.core)\n    implementation(libs.androidx.uiautomator)\n    implementation(libs.androidx.benchmark.macro.junit4)\n}\n\nandroidComponents {\n    onVariants { v ->\n        val artifactsLoader = v.artifacts.getBuiltArtifactsLoader()\n        v.instrumentationRunnerArguments.put(\n            \"targetAppId\",\n            v.testedApks.map { artifactsLoader.load(it)?.applicationId }\n        )\n    }\n}"
  },
  {
    "path": "app/baselineprofile/src/main/AndroidManifest.xml",
    "content": "<manifest />"
  },
  {
    "path": "app/baselineprofile/src/main/java/com/valhalla/thor/baselineprofile/BaselineProfileGenerator.kt",
    "content": "package com.valhalla.thor.baselineprofile\n\nimport androidx.benchmark.macro.junit4.BaselineProfileRule\nimport androidx.test.ext.junit.runners.AndroidJUnit4\nimport androidx.test.filters.LargeTest\nimport androidx.test.platform.app.InstrumentationRegistry\nimport org.junit.Rule\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\n/**\n * This test class generates a basic startup baseline profile for the target package.\n *\n * We recommend you start with this but add important user flows to the profile to improve their performance.\n * Refer to the [baseline profile documentation](https://d.android.com/topic/performance/baselineprofiles)\n * for more information.\n *\n * You can run the generator with the \"Generate Baseline Profile\" run configuration in Android Studio or\n * the equivalent `generateBaselineProfile` gradle task:\n * ```\n * ./gradlew :app:generateReleaseBaselineProfile\n * ```\n * The run configuration runs the Gradle task and applies filtering to run only the generators.\n *\n * Check [documentation](https://d.android.com/topic/performance/benchmarking/macrobenchmark-instrumentation-args)\n * for more information about available instrumentation arguments.\n *\n * After you run the generator, you can verify the improvements running the [StartupBenchmarks] benchmark.\n *\n * When using this class to generate a baseline profile, only API 33+ or rooted API 28+ are supported.\n *\n * The minimum required version of androidx.benchmark to generate a baseline profile is 1.2.0.\n **/\n@RunWith(AndroidJUnit4::class)\n@LargeTest\nclass BaselineProfileGenerator {\n\n    @get:Rule\n    val rule = BaselineProfileRule()\n\n    @Test\n    fun generate() {\n        // The application id for the running build variant is read from the instrumentation arguments.\n        rule.collect(\n            packageName = InstrumentationRegistry.getArguments().getString(\"targetAppId\")\n                ?: throw Exception(\"targetAppId not passed as instrumentation runner arg\"),\n\n            // See: https://d.android.com/topic/performance/baselineprofiles/dex-layout-optimizations\n            includeInStartupProfile = true\n        ) {\n            // This block defines the app's critical user journey. Here we are interested in\n            // optimizing for app startup. But you can also navigate and scroll through your most important UI.\n\n            // Start default activity for your app\n            pressHome()\n            startActivityAndWait()\n\n            // TODO Write more interactions to optimize advanced journeys of your app.\n            // For example:\n            // 1. Wait until the content is asynchronously loaded\n            // 2. Scroll the feed content\n            // 3. Navigate to detail screen\n\n            // Check UiAutomator documentation for more information how to interact with the app.\n            // https://d.android.com/training/testing/other-components/ui-automator\n        }\n    }\n}"
  },
  {
    "path": "app/baselineprofile/src/main/java/com/valhalla/thor/baselineprofile/StartupBenchmarks.kt",
    "content": "package com.valhalla.thor.baselineprofile\n\nimport androidx.benchmark.macro.BaselineProfileMode\nimport androidx.benchmark.macro.CompilationMode\nimport androidx.benchmark.macro.StartupMode\nimport androidx.benchmark.macro.StartupTimingMetric\nimport androidx.benchmark.macro.junit4.MacrobenchmarkRule\nimport androidx.test.ext.junit.runners.AndroidJUnit4\nimport androidx.test.filters.LargeTest\nimport androidx.test.platform.app.InstrumentationRegistry\nimport org.junit.Rule\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\n/**\n * This test class benchmarks the speed of app startup.\n * Run this benchmark to verify how effective a Baseline Profile is.\n * It does this by comparing [CompilationMode.None], which represents the app with no Baseline\n * Profiles optimizations, and [CompilationMode.Partial], which uses Baseline Profiles.\n *\n * Run this benchmark to see startup measurements and captured system traces for verifying\n * the effectiveness of your Baseline Profiles. You can run it directly from Android\n * Studio as an instrumentation test, or run all benchmarks for a variant, for example benchmarkRelease,\n * with this Gradle task:\n * ```\n * ./gradlew :app:baselineprofile:connectedBenchmarkReleaseAndroidTest\n * ```\n *\n * You should run the benchmarks on a physical device, not an Android emulator, because the\n * emulator doesn't represent real world performance and shares system resources with its host.\n *\n * For more information, see the [Macrobenchmark documentation](https://d.android.com/macrobenchmark#create-macrobenchmark)\n * and the [instrumentation arguments documentation](https://d.android.com/topic/performance/benchmarking/macrobenchmark-instrumentation-args).\n **/\n@RunWith(AndroidJUnit4::class)\n@LargeTest\nclass StartupBenchmarks {\n\n    @get:Rule\n    val rule = MacrobenchmarkRule()\n\n    @Test\n    fun startupCompilationNone() =\n        benchmark(CompilationMode.None())\n\n    @Test\n    fun startupCompilationBaselineProfiles() =\n        benchmark(CompilationMode.Partial(BaselineProfileMode.Require))\n\n    private fun benchmark(compilationMode: CompilationMode) {\n        // The application id for the running build variant is read from the instrumentation arguments.\n        rule.measureRepeated(\n            packageName = InstrumentationRegistry.getArguments().getString(\"targetAppId\")\n                ?: throw Exception(\"targetAppId not passed as instrumentation runner arg\"),\n            metrics = listOf(StartupTimingMetric()),\n            compilationMode = compilationMode,\n            startupMode = StartupMode.COLD,\n            iterations = 10,\n            setupBlock = {\n                pressHome()\n            },\n            measureBlock = {\n                startActivityAndWait()\n\n                // TODO Add interactions to wait for when your app is fully drawn.\n                // The app is fully drawn when Activity.reportFullyDrawn is called.\n                // For Jetpack Compose, you can use ReportDrawn, ReportDrawnWhen and ReportDrawnAfter\n                // from the AndroidX Activity library.\n\n                // Check the UiAutomator documentation for more information on how to\n                // interact with the app.\n                // https://d.android.com/training/testing/other-components/ui-automator\n            }\n        )\n    }\n}"
  },
  {
    "path": "app/build.gradle.kts",
    "content": "import com.android.build.api.artifact.SingleArtifact\nimport org.jetbrains.kotlin.gradle.dsl.JvmTarget\nimport java.io.FileInputStream\nimport java.util.Properties\n\nplugins {\n    alias(libs.plugins.android.application)\n    alias(libs.plugins.kotlin.compose)\n    alias(libs.plugins.kotlinSerialization)\n    alias(libs.plugins.room)\n    alias(libs.plugins.ksp)\n}\n\nkotlin {\n    compilerOptions {\n        jvmTarget.set(JvmTarget.JVM_21)\n        freeCompilerArgs.add(\"-Xexplicit-backing-fields\")\n        optIn.add(\"kotlin.RequiresOptIn\")\n        optIn.add(\"kotlin.time.ExperimentalTime\")\n        optIn.add(\"org.koin.core.annotation.KoinExperimentalAPI\")\n        optIn.add(\"androidx.compose.material3.ExperimentalMaterial3ExpressiveApi\")\n        optIn.add(\"androidx.compose.material3.ExperimentalMaterial3Api\")\n    }\n}\n\nroom {\n    schemaDirectory(\"$projectDir/schemas\")\n}\n\nval keystorePropertiesFile: File = rootProject.file(\"jks.properties\")\nval keystoreProperties = Properties()\nif (keystorePropertiesFile.exists()) {\n    keystoreProperties.load(FileInputStream(keystorePropertiesFile))\n}\n\n// --- VERSIONING HELPERS (Private & Modernized) ---\n\n// 1. Resolve Code: Checks property 'versionCode' first, falls back to 'initialVersionCode'\nprivate fun resolveVersionCode(): Int {\n    val initial = providers.gradleProperty(\"initialVersionCode\")\n        .orNull\n        ?.toIntOrNull()\n        ?: throw GradleException(\"Required 'initialVersionCode' missing in gradle.properties\")\n\n    val override = providers.gradleProperty(\"versionCode\")\n        .orNull\n        ?.toIntOrNull()\n\n    return override ?: initial\n}\n\n// 2. Calculate Name: The math logic (1712 -> 1.71.2)\nprivate fun calculateVersionName(code: Int): String {\n    val major = code / 1000\n    val minor = (code % 1000) / 10\n    val patch = code % 10\n    return \"$major.$minor.$patch\"\n}\n\n// 3. Resolve Name: Checks property 'versionName' first, falls back to math\nprivate fun resolveVersionName(code: Int): String {\n    return providers.gradleProperty(\"versionName\").orNull\n        ?: calculateVersionName(code)\n}\n\nandroid {\n    namespace = \"com.valhalla.thor\"\n    compileSdk = 37\n\n    defaultConfig {\n        applicationId = \"com.valhalla.thor\"\n        minSdk = 28\n        targetSdk = 37\n\n        // Calculate versions using the private helpers\n        val code = resolveVersionCode()\n        versionCode = code\n        versionName = resolveVersionName(code)\n\n        vectorDrawables.useSupportLibrary = true\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n        ndk {\n            debugSymbolLevel = \"SYMBOL_TABLE\"\n        }\n    }\n\n    signingConfigs {\n        create(\"release\") {\n            if (keystorePropertiesFile.exists()) {\n                keyAlias = keystoreProperties[\"keyAlias\"] as String\n                keyPassword = keystoreProperties[\"keyPassword\"] as String\n                storeFile = file(keystoreProperties[\"storeFile\"] as String)\n                storePassword = keystoreProperties[\"storePassword\"] as String\n            } else if (System.getenv(\"KEY_ALIAS\") != null) {\n                // CI/CD Build (GitHub Actions)\n                keyAlias = System.getenv(\"KEY_ALIAS\")\n                keyPassword = System.getenv(\"KEY_PASSWORD\")\n                storePassword = System.getenv(\"KEYSTORE_PASSWORD\")\n                storeFile = file(System.getenv(\"KEYSTORE_FILE_PATH\") ?: \"release.jks\")\n            } else {\n                logger.warn(\"⚠️ keystore.properties not found or environment variables not set. Release build will not be signed properly.\")\n            }\n        }\n    }\n\n    dependenciesInfo {\n        includeInApk = false\n        includeInBundle = true\n    }\n\n    buildTypes {\n        release {\n            isMinifyEnabled = true\n            isShrinkResources = true\n            proguardFiles(\n                getDefaultProguardFile(\"proguard-android-optimize.txt\"),\n                \"proguard-rules.pro\"\n            )\n            signingConfig = signingConfigs.getByName(\"release\")\n        }\n        debug {\n            isMinifyEnabled = false\n            applicationIdSuffix = \".debug\"\n        }\n    }\n\n    flavorDimensions += \"distribution\"\n\n    productFlavors {\n        create(\"store\") {\n            dimension = \"distribution\"\n        }\n\n        create(\"foss\") {\n            dimension = \"distribution\"\n            versionNameSuffix = \"-foss\"\n            proguardFile(\"proguard-rules-foss.pro\")\n        }\n    }\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_21\n        targetCompatibility = JavaVersion.VERSION_21\n    }\n\n    buildFeatures {\n        buildConfig = true\n        compose = true\n        aidl = true\n    }\n\n    packaging {\n        resources {\n            excludes += \"/specs/**\"\n            excludes += \"**/*.dll\"\n            excludes += \"**/*.dylib\"\n            excludes += \"**/x64/**\"\n            excludes += \"**/x86_64/*.dll\"\n            excludes += \"**/META-INF/*.{kotlin_module,dot}\"\n            excludes += \"META-INF/services/javax.annotation.processing.Processor\"\n            excludes += \"META-INF/DEPENDENCIES\"\n            excludes += \"META-INF/LICENSE*\"\n            excludes += \"META-INF/NOTICE*\"\n        }\n    }\n}\n\nandroidComponents {\n    // 1. Existing FOSS Copy Task\n    onVariants(selector().withFlavor(\"distribution\", \"foss\")) { variant ->\n        if (variant.buildType == \"release\") {\n            val apkDir = variant.artifacts.get(SingleArtifact.APK)\n            tasks.register<Copy>(\"copyFossReleaseApk\") {\n                dependsOn(\"assembleFossRelease\")\n                from(apkDir) { include(\"*.apk\") }\n                into(layout.buildDirectory.dir(\"distribution/foss\"))\n                rename(\".*\\\\.apk\", \"foss-release.apk\")\n            }\n        }\n    }\n\n    // 2. Store Copy Task\n    onVariants(selector().withFlavor(\"distribution\", \"store\")) { variant ->\n        if (variant.buildType == \"release\") {\n            val apkDir = variant.artifacts.get(SingleArtifact.APK)\n            tasks.register<Copy>(\"copyStoreReleaseApk\") {\n                dependsOn(\"assembleStoreRelease\")\n                from(apkDir) { include(\"*.apk\") }\n                into(layout.buildDirectory.dir(\"distribution/store\"))\n                rename(\".*\\\\.apk\", \"store-release.apk\")\n            }\n        }\n    }\n}\n\ndependencies {\n    implementation(project(\":suCore\"))\n    implementation(project(\":bypass\"))\n    implementation(libs.androidx.datastore.preferences)\n    implementation(libs.androidx.splashscreen)\n    implementation(libs.androidx.core.ktx)\n    implementation(libs.androidx.activity.compose)\n    implementation(libs.androidx.biometric)\n    implementation(platform(libs.androidx.compose.bom))\n    implementation(libs.androidx.ui)\n    implementation(libs.androidx.ui.graphics)\n    implementation(libs.androidx.ui.tooling.preview)\n    implementation(libs.androidx.material3)\n    testImplementation(libs.junit)\n    androidTestImplementation(libs.androidx.junit)\n    androidTestImplementation(libs.androidx.espresso.core)\n    androidTestImplementation(platform(libs.androidx.compose.bom))\n    androidTestImplementation(libs.androidx.ui.test.junit4)\n    debugImplementation(libs.androidx.ui.tooling)\n    debugImplementation(libs.androidx.ui.test.manifest)\n    implementation(libs.androidx.lifecycle.runtime.ktx)\n    implementation(libs.androidx.lifecycle.runtime.compose)\n    implementation(libs.androidx.lifecycle.viewmodel.compose)\n    implementation(libs.accompanist.drawablepainter)\n    implementation(libs.kotlinx.serialization.json)\n    implementation(libs.lottie.compose)\n    implementation(libs.shizuku.api)\n    implementation(libs.shizuku.provider)\n    implementation(libs.dhizuku.api)\n    implementation(libs.bundles.coil)\n    implementation(libs.bundles.koin)\n\n    implementation(libs.room.runtime)\n    implementation(libs.room.ktx)\n    ksp(libs.room.compiler)\n}\n\n// These rely on the private functions above, which is allowed in the same file scope\nval currentVersionCode = resolveVersionCode()\nval currentVersionName = resolveVersionName(currentVersionCode)\n\ntasks.register(\"printVersionName\") {\n    val vName = currentVersionName\n    doLast {\n        println(vName)\n    }\n}"
  },
  {
    "path": "app/proguard-rules-foss.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n\n# Ignore missing service definitions that are not relevant for Android runtime\n-dontwarn javax.annotation.processing.Processor\n-dontwarn javax.annotation.Nullable\n\n-dontobfuscate\n\n-keepattributes SourceFile,LineNumberTable\n\n#-keep class com.valhalla.thor.** { *; }"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n# Ignore missing service definitions that are not relevant for Android runtime\n-dontwarn javax.annotation.processing.Processor\n-dontwarn javax.annotation.Nullable\n-dontwarn dalvik.system.VMRuntime\n"
  },
  {
    "path": "app/schemas/com.valhalla.thor.data.source.local.room.AppDatabase/1.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 1,\n    \"identityHash\": \"0250ce576c64723c12faf13e9d8ccb18\",\n    \"entities\": [\n      {\n        \"tableName\": \"apps\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`packageName` TEXT NOT NULL, `appName` TEXT, `versionName` TEXT, `versionCode` INTEGER NOT NULL, `minSdk` INTEGER NOT NULL, `targetSdk` INTEGER NOT NULL, `isSystem` INTEGER NOT NULL, `installerPackageName` TEXT, `publicSourceDir` TEXT, `splitPublicSourceDirs` TEXT NOT NULL, `enabled` INTEGER NOT NULL, `dataDir` TEXT, `nativeLibraryDir` TEXT, `deviceProtectedDataDir` TEXT, `sharedLibraryFiles` TEXT, `obbFilePath` TEXT, `sourceDir` TEXT, `sharedDataDir` TEXT NOT NULL, `lastUpdateTime` INTEGER NOT NULL, `firstInstallTime` INTEGER NOT NULL, `isDebuggable` INTEGER NOT NULL, PRIMARY KEY(`packageName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"packageName\",\n            \"columnName\": \"packageName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"appName\",\n            \"columnName\": \"appName\",\n            \"affinity\": \"TEXT\"\n          },\n          {\n            \"fieldPath\": \"versionName\",\n            \"columnName\": \"versionName\",\n            \"affinity\": \"TEXT\"\n          },\n          {\n            \"fieldPath\": \"versionCode\",\n            \"columnName\": \"versionCode\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"minSdk\",\n            \"columnName\": \"minSdk\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"targetSdk\",\n            \"columnName\": \"targetSdk\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isSystem\",\n            \"columnName\": \"isSystem\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"installerPackageName\",\n            \"columnName\": \"installerPackageName\",\n            \"affinity\": \"TEXT\"\n          },\n          {\n            \"fieldPath\": \"publicSourceDir\",\n            \"columnName\": \"publicSourceDir\",\n            \"affinity\": \"TEXT\"\n          },\n          {\n            \"fieldPath\": \"splitPublicSourceDirs\",\n            \"columnName\": \"splitPublicSourceDirs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"dataDir\",\n            \"columnName\": \"dataDir\",\n            \"affinity\": \"TEXT\"\n          },\n          {\n            \"fieldPath\": \"nativeLibraryDir\",\n            \"columnName\": \"nativeLibraryDir\",\n            \"affinity\": \"TEXT\"\n          },\n          {\n            \"fieldPath\": \"deviceProtectedDataDir\",\n            \"columnName\": \"deviceProtectedDataDir\",\n            \"affinity\": \"TEXT\"\n          },\n          {\n            \"fieldPath\": \"sharedLibraryFiles\",\n            \"columnName\": \"sharedLibraryFiles\",\n            \"affinity\": \"TEXT\"\n          },\n          {\n            \"fieldPath\": \"obbFilePath\",\n            \"columnName\": \"obbFilePath\",\n            \"affinity\": \"TEXT\"\n          },\n          {\n            \"fieldPath\": \"sourceDir\",\n            \"columnName\": \"sourceDir\",\n            \"affinity\": \"TEXT\"\n          },\n          {\n            \"fieldPath\": \"sharedDataDir\",\n            \"columnName\": \"sharedDataDir\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"firstInstallTime\",\n            \"columnName\": \"firstInstallTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isDebuggable\",\n            \"columnName\": \"isDebuggable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"packageName\"\n          ]\n        }\n      }\n    ],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0250ce576c64723c12faf13e9d8ccb18')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/com.valhalla.thor.data.source.local.room.AppDatabase/2.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 2,\n    \"identityHash\": \"37ee5d25d4bab9e2695c36e7dc21a849\",\n    \"entities\": [\n      {\n        \"tableName\": \"apps\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`packageName` TEXT NOT NULL, `appName` TEXT, `versionName` TEXT, `versionCode` INTEGER NOT NULL, `minSdk` INTEGER NOT NULL, `targetSdk` INTEGER NOT NULL, `isSystem` INTEGER NOT NULL, `installerPackageName` TEXT, `publicSourceDir` TEXT, `splitPublicSourceDirs` TEXT NOT NULL, `enabled` INTEGER NOT NULL, `dataDir` TEXT, `nativeLibraryDir` TEXT, `deviceProtectedDataDir` TEXT, `sharedLibraryFiles` TEXT, `obbFilePath` TEXT, `sourceDir` TEXT, `sharedDataDir` TEXT NOT NULL, `lastUpdateTime` INTEGER NOT NULL, `firstInstallTime` INTEGER NOT NULL, `isDebuggable` INTEGER NOT NULL, `isSuspended` INTEGER NOT NULL, PRIMARY KEY(`packageName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"packageName\",\n            \"columnName\": \"packageName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"appName\",\n            \"columnName\": \"appName\",\n            \"affinity\": \"TEXT\"\n          },\n          {\n            \"fieldPath\": \"versionName\",\n            \"columnName\": \"versionName\",\n            \"affinity\": \"TEXT\"\n          },\n          {\n            \"fieldPath\": \"versionCode\",\n            \"columnName\": \"versionCode\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"minSdk\",\n            \"columnName\": \"minSdk\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"targetSdk\",\n            \"columnName\": \"targetSdk\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isSystem\",\n            \"columnName\": \"isSystem\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"installerPackageName\",\n            \"columnName\": \"installerPackageName\",\n            \"affinity\": \"TEXT\"\n          },\n          {\n            \"fieldPath\": \"publicSourceDir\",\n            \"columnName\": \"publicSourceDir\",\n            \"affinity\": \"TEXT\"\n          },\n          {\n            \"fieldPath\": \"splitPublicSourceDirs\",\n            \"columnName\": \"splitPublicSourceDirs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"dataDir\",\n            \"columnName\": \"dataDir\",\n            \"affinity\": \"TEXT\"\n          },\n          {\n            \"fieldPath\": \"nativeLibraryDir\",\n            \"columnName\": \"nativeLibraryDir\",\n            \"affinity\": \"TEXT\"\n          },\n          {\n            \"fieldPath\": \"deviceProtectedDataDir\",\n            \"columnName\": \"deviceProtectedDataDir\",\n            \"affinity\": \"TEXT\"\n          },\n          {\n            \"fieldPath\": \"sharedLibraryFiles\",\n            \"columnName\": \"sharedLibraryFiles\",\n            \"affinity\": \"TEXT\"\n          },\n          {\n            \"fieldPath\": \"obbFilePath\",\n            \"columnName\": \"obbFilePath\",\n            \"affinity\": \"TEXT\"\n          },\n          {\n            \"fieldPath\": \"sourceDir\",\n            \"columnName\": \"sourceDir\",\n            \"affinity\": \"TEXT\"\n          },\n          {\n            \"fieldPath\": \"sharedDataDir\",\n            \"columnName\": \"sharedDataDir\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"firstInstallTime\",\n            \"columnName\": \"firstInstallTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isDebuggable\",\n            \"columnName\": \"isDebuggable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isSuspended\",\n            \"columnName\": \"isSuspended\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"packageName\"\n          ]\n        }\n      }\n    ],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '37ee5d25d4bab9e2695c36e7dc21a849')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/src/androidTest/java/com/valhalla/thor/ExampleInstrumentedTest.kt",
    "content": "package com.valhalla.thor\n\nimport androidx.test.ext.junit.runners.AndroidJUnit4\nimport androidx.test.platform.app.InstrumentationRegistry\nimport org.junit.Assert.assertEquals\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\n/**\n * Instrumented test, which will execute on an Android device.\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\n@RunWith(AndroidJUnit4::class)\nclass ExampleInstrumentedTest {\n    @Test\n    fun useAppContext() {\n        // Context of the app under test.\n        val appContext = InstrumentationRegistry.getInstrumentation().targetContext\n        assertEquals(\"com.valhalla.thor\", appContext.packageName)\n    }\n}"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <uses-sdk tools:overrideLibrary=\"rikka.shizuku.api\" />\n\n    <uses-permission\n        android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\"\n        tools:ignore=\"RequestInstallPackagesPolicy\" />\n    <!-- Needed if targeting Android 11+ for certain file types, but we use strict Stream access -->\n\n    <uses-permission\n        android:name=\"android.permission.QUERY_ALL_PACKAGES\"\n        tools:ignore=\"PackageVisibilityPolicy,QueryAllPackagesPermission\" />\n\n    <permission\n        android:name=\"android.permission.QUERY_ALL_PACKAGES\"\n        tools:ignore=\"ReservedSystemPermission\" />\n\n    <uses-permission android:name=\"android.permission.REQUEST_DELETE_PACKAGES\" />\n\n    <uses-permission android:name=\"shizuku.permission.API_V23\" />\n    <uses-permission android:name=\"com.rosan.dhizuku.permission.API\" />\n\n\n    <application\n        android:name=\".ThorApplication\"\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/thor_drawn\"\n        android:label=\"@string/app_name\"\n        android:localeConfig=\"@xml/locales_config\"\n        android:roundIcon=\"@mipmap/thor_drawn_round\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/Theme.Thor.SplashScreen\"\n        tools:targetApi=\"33\">\n\n        <activity\n            android:name=\".HomeActivity\"\n            android:exported=\"true\"\n            android:theme=\"@style/Theme.Thor.SplashScreen\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n\n        <activity\n            android:name=\".presentation.installer.PortableInstallerActivity\"\n            android:exported=\"true\"\n            android:theme=\"@android:style/Theme.Translucent.NoTitleBar\">\n\n            <intent-filter>\n                <action android:name=\"android.intent.action.VIEW\" />\n\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <category android:name=\"android.intent.category.BROWSABLE\" />\n\n                <data android:scheme=\"content\" />\n                <data android:scheme=\"file\" />\n                <data android:mimeType=\"application/vnd.android.package-archive\" />\n            </intent-filter>\n\n            <intent-filter>\n                <action android:name=\"android.intent.action.VIEW\" />\n\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <category android:name=\"android.intent.category.BROWSABLE\" />\n\n                <data android:scheme=\"content\" />\n                <data android:scheme=\"file\" />\n                <data android:mimeType=\"application/octet-stream\" />\n                <data android:mimeType=\"application/zip\" />\n                <data android:mimeType=\"application/x-zip-compressed\" />\n                <data android:mimeType=\"application/x-apks\" />\n                <data android:mimeType=\"application/x-xapk\" />\n                <data android:mimeType=\"application/x-apkm\" />\n            </intent-filter>\n\n            <intent-filter>\n                <action android:name=\"android.intent.action.VIEW\" />\n\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <category android:name=\"android.intent.category.BROWSABLE\" />\n\n                <data android:scheme=\"content\" />\n                <data android:scheme=\"file\" />\n                <data android:host=\"*\" />\n                <data android:mimeType=\"*/*\" />\n\n                <data android:pathPattern=\".*\\\\.apk\" />\n                <data android:pathPattern=\".*\\\\.APK\" />\n                <data android:pathPattern=\".*\\\\.xapk\" />\n                <data android:pathPattern=\".*\\\\.XAPK\" />\n                <data android:pathPattern=\".*\\\\.apks\" />\n                <data android:pathPattern=\".*\\\\.APKS\" />\n                <data android:pathPattern=\".*\\\\.apkm\" />\n                <data android:pathPattern=\".*\\\\.APKM\" />\n\n            </intent-filter>\n        </activity>\n\n\n        <receiver\n            android:name=\".data.receivers.InstallReceiver\"\n            android:exported=\"false\">\n            <intent-filter>\n                <action android:name=\"com.valhalla.thor.INSTALL_STATUS\" />\n            </intent-filter>\n        </receiver>\n\n        <provider\n            android:name=\"androidx.core.content.FileProvider\"\n            android:authorities=\"${applicationId}.provider\"\n            android:exported=\"false\"\n            android:grantUriPermissions=\"true\">\n            <meta-data\n                android:name=\"android.support.FILE_PROVIDER_PATHS\"\n                android:resource=\"@xml/provider_paths\" />\n        </provider>\n\n        <provider\n            android:name=\"rikka.shizuku.ShizukuProvider\"\n            android:authorities=\"${applicationId}.shizuku\"\n            android:enabled=\"true\"\n            android:exported=\"true\"\n            android:multiprocess=\"false\"\n            android:permission=\"android.permission.INTERACT_ACROSS_USERS_FULL\" />\n\n        <provider\n            android:name=\"androidx.startup.InitializationProvider\"\n            android:authorities=\"${applicationId}.androidx-startup\"\n            android:exported=\"false\"\n            tools:node=\"merge\" />\n\n    </application>\n\n</manifest>"
  },
  {
    "path": "app/src/main/assets/adi-registration.properties",
    "content": "CIQKVO6RQ32OMAAAAAAAAAAAAA"
  },
  {
    "path": "app/src/main/java/android/content/pm/IPackageInstaller.java",
    "content": "package android.content.pm;\n\nimport android.os.Binder;\nimport android.os.IBinder;\nimport android.os.IInterface;\n\n/**\n * Taken from <a href=\"https://github.com/RikkaApps/Shizuku-API/blob/master/demo-hidden-api-stub/src/main/java/android/content/pm/IPackageInstaller.java\">Shizuku API Demo</a>\n *\n * @author RikkaW\n */\npublic interface IPackageInstaller extends IInterface {\n\n    abstract class Stub extends Binder implements IPackageInstaller {\n\n        public static IPackageInstaller asInterface(IBinder binder) {\n            throw new UnsupportedOperationException();\n        }\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/android/content/pm/IPackageManager.java",
    "content": "package android.content.pm;\n\nimport android.os.Binder;\nimport android.os.IBinder;\nimport android.os.IInterface;\nimport android.os.RemoteException;\n\n\n/**\n * Taken from <a href=\"https://github.com/RikkaApps/Shizuku-API/blob/master/demo-hidden-api-stub/src/main/java/android/content/pm/IPackageManager.java\">Shizuku API Demo</a>\n *\n * @author RikkaW\n */\npublic interface IPackageManager extends IInterface {\n\n    IPackageInstaller getPackageInstaller()\n            throws RemoteException;\n\n    abstract class Stub extends Binder implements IPackageManager {\n\n        public static IPackageManager asInterface(IBinder obj) {\n            throw new UnsupportedOperationException();\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/HomeActivity.kt",
    "content": "package com.valhalla.thor\n\nimport android.os.Bundle\nimport androidx.activity.ComponentActivity\nimport androidx.activity.compose.setContent\nimport androidx.activity.enableEdgeToEdge\nimport androidx.compose.foundation.isSystemInDarkTheme\nimport androidx.compose.runtime.getValue\nimport androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen\nimport androidx.lifecycle.compose.collectAsStateWithLifecycle\nimport androidx.lifecycle.lifecycleScope\nimport com.valhalla.thor.domain.model.ThemeMode\nimport com.valhalla.thor.domain.repository.SystemRepository\nimport com.valhalla.thor.presentation.common.ShizukuPermissionHandler\nimport com.valhalla.thor.presentation.home.HomeViewModel\nimport com.valhalla.thor.presentation.main.MainScreen\nimport com.valhalla.thor.presentation.security.AuthState\nimport com.valhalla.thor.presentation.security.BiometricScreen\nimport com.valhalla.thor.presentation.security.SecurityViewModel\nimport com.valhalla.thor.presentation.settings.SettingsViewModel\nimport com.valhalla.thor.presentation.theme.ThorTheme\nimport com.valhalla.thor.util.Logger\nimport kotlinx.coroutines.launch\nimport org.koin.android.ext.android.inject\nimport org.koin.androidx.viewmodel.ext.android.viewModel\n\nclass HomeActivity : ComponentActivity() {\n\n    private val systemRepository: SystemRepository by inject()\n    private val homeViewModel: HomeViewModel by viewModel()\n    private val securityViewModel: SecurityViewModel by viewModel()\n    private val settingsViewModel: SettingsViewModel by viewModel()\n\n    private val requestCode = 1001\n    private var hasRequestedShizuku = false\n\n    private val shizukuHandler = ShizukuPermissionHandler(\n        onPermissionGranted = {\n            Logger.d(\"HomeActivity\", \"Shizuku Ready\")\n            homeViewModel.loadDashboardData()\n        },\n        onPermissionDenied = {\n            Logger.d(\"HomeActivity\", \"Shizuku Denied\")\n        },\n        onBinderDead = {\n            Logger.w(\"HomeActivity\", \"Shizuku Binder Died\")\n        }\n    )\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        installSplashScreen()\n        enableEdgeToEdge()\n        shizukuHandler.register()\n\n        setContent {\n            val prefs by settingsViewModel.preferences.collectAsStateWithLifecycle()\n\n            val systemDark = isSystemInDarkTheme()\n            val darkTheme = when (prefs.themeMode) {\n                ThemeMode.LIGHT -> false\n                ThemeMode.DARK -> true\n                ThemeMode.SYSTEM -> systemDark\n            }\n\n            ThorTheme(\n                darkTheme = darkTheme,\n                dynamicColor = prefs.useDynamicColor,\n                amoledMode = prefs.useAmoled,\n            ) {\n                val authState by securityViewModel.authState.collectAsStateWithLifecycle()\n\n                when (authState) {\n                    AuthState.NotRequired,\n                    AuthState.Unlocked -> {\n                        MainScreen(\n                            homeViewModel = homeViewModel,\n                            onExit = { finish() }\n                        )\n                    }\n\n                    AuthState.Locked,\n                    is AuthState.Error -> {\n                        BiometricScreen(\n                            isError = authState is AuthState.Error,\n                            errorMessage = (authState as? AuthState.Error)?.message ?: \"\",\n                            onAuthenticated = { securityViewModel.onAuthenticated() },\n                            onError = { message ->\n                                Logger.e(\"HomeActivity\", \"Biometric error: $message\")\n                                securityViewModel.onAuthError(message)\n                            },\n                            onRetry = { securityViewModel.onRetry() },\n                            onExit = { finish() }\n                        )\n                    }\n                }\n            }\n        }\n    }\n\n    override fun onResume() {\n        super.onResume()\n        lifecycleScope.launch {\n            if (!systemRepository.isRootAvailable() && !hasRequestedShizuku) {\n                hasRequestedShizuku = true\n                shizukuHandler.checkAndRequestPermission(requestCode)\n            }\n        }\n    }\n\n    override fun onDestroy() {\n        shizukuHandler.unregister()\n        super.onDestroy()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/ThorApplication.kt",
    "content": "package com.valhalla.thor\n\nimport android.app.Application\nimport com.rosan.dhizuku.api.Dhizuku\nimport com.valhalla.bypass.Bypass\nimport com.valhalla.thor.core.ThorShellConfig\nimport com.valhalla.thor.di.commonModule\nimport com.valhalla.thor.di.coreModule\nimport com.valhalla.thor.di.installerModule\nimport com.valhalla.thor.di.preferenceModule\nimport com.valhalla.thor.di.presentationModule\nimport com.valhalla.thor.di.roomModule\nimport com.valhalla.thor.domain.repository.PreferenceRepository\nimport com.valhalla.thor.util.LocaleManager\nimport com.valhalla.thor.util.Logger\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.MainScope\nimport kotlinx.coroutines.flow.first\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport org.koin.android.ext.android.inject\nimport org.koin.android.ext.koin.androidContext\nimport org.koin.android.ext.koin.androidLogger\nimport org.koin.androix.startup.KoinStartup\nimport org.koin.dsl.koinConfiguration\n\nclass ThorApplication : Application(), KoinStartup {\n\n    private val preferenceRepository: PreferenceRepository by inject()\n    private val localeManager: LocaleManager by inject()\n\n    override fun onKoinStartup() = koinConfiguration {\n        androidContext(this@ThorApplication)\n        androidLogger(Logger.koinLogLevel)\n        modules(\n            coreModule,\n            installerModule,\n            preferenceModule,\n            commonModule,\n            presentationModule,\n            roomModule\n        )\n    }\n\n    override fun onCreate() {\n        super.onCreate()\n\n        // Initialize Bypass with custom logging\n        Bypass.setLogger { message, throwable ->\n            Logger.e(\"Bypass\", message, throwable)\n        }\n        Bypass.prepareThor()\n        ThorShellConfig.init()\n\n        try {\n            Dhizuku.init(this)\n        } catch (e: Exception) {\n            Logger.e(\"ThorApp\", \"Dhizuku init failed\", e)\n        }\n\n        // Apply saved language on startup\n        MainScope().launch {\n            val prefs = preferenceRepository.userPreferences.first()\n            withContext(Dispatchers.Main) {\n                localeManager.applyLocale(prefs.language)\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/core/ThorShellConfig.kt",
    "content": "package com.valhalla.thor.core\n\nimport com.valhalla.superuser.Shell\nimport com.valhalla.thor.BuildConfig\nimport com.valhalla.thor.core.ThorShellConfig.init\n\n/**\n * Centralized configuration for the Root Shell.\n * Call [init] in your Application.onCreate().\n */\nobject ThorShellConfig {\n\n    fun init() {\n        // Set logging based on build type\n        Shell.enableVerboseLogging = BuildConfig.DEBUG\n\n        // Configure the default builder.\n        // FLAG_MOUNT_MASTER: Essential for global namespace operations (mounting, etc.)\n        Shell.setDefaultBuilder(\n            Shell.Builder.create()\n                .setFlags(Shell.FLAG_MOUNT_MASTER)\n            // If you have specific initializers, add them here\n            // .setInitializers(MyInitializer::class.java)\n        )\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/data/Constants.kt",
    "content": "package com.valhalla.thor.data\n\nconst val ACTION_INSTALL_STATUS = \"com.valhalla.thor.INSTALL_STATUS\""
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/data/gateway/DhizukuSystemGateway.kt",
    "content": "package com.valhalla.thor.data.gateway\n\nimport com.valhalla.thor.data.source.local.dhizuku.DhizukuHelper\nimport com.valhalla.thor.data.source.local.dhizuku.DhizukuReflector\nimport com.valhalla.thor.domain.gateway.SystemGateway\n\nclass DhizukuSystemGateway(\n    private val reflector: DhizukuReflector\n) : SystemGateway {\n\n    override suspend fun isRootAvailable() = false\n\n    override fun isShizukuAvailable(): Boolean = false\n\n    override fun isDhizukuAvailable(): Boolean {\n        return DhizukuHelper.isDhizukuAvailable()\n    }\n\n    override suspend fun forceStopApp(packageName: String): Result<Unit> {\n        return if (reflector.forceStop(packageName)) Result.success(Unit)\n        else Result.failure(Exception(\"Dhizuku: Force stop failed. Shell command and reflection both denied.\"))\n    }\n\n    override suspend fun clearCache(packageName: String): Result<Unit> {\n        return if (reflector.clearCache(packageName)) Result.success(Unit)\n        else Result.failure(Exception(\"Dhizuku: Clear cache failed. System reflection and shell rm -rf both failed.\"))\n    }\n\n    override suspend fun clearAppData(packageName: String): Result<Unit> {\n        return if (reflector.clearData(packageName)) Result.success(Unit)\n        else Result.failure(Exception(\"Dhizuku: Clear data failed. Shell pm clear and reflection both failed.\"))\n    }\n\n    override suspend fun setAppDisabled(packageName: String, isDisabled: Boolean): Result<Unit> {\n        return if (reflector.setAppEnabled(packageName, !isDisabled)) Result.success(Unit)\n        else Result.failure(Exception(\"Dhizuku: Set enabled state failed. Shell and reflection both failed.\"))\n    }\n\n    override suspend fun rebootDevice(reason: String): Result<Unit> {\n        return Result.failure(Exception(\"Dhizuku: Reboot not supported directly. Use Root mode instead.\"))\n    }\n\n    override suspend fun uninstallApp(packageName: String): Result<Unit> {\n        return if (reflector.uninstallApp(packageName)) {\n            Result.success(Unit)\n        } else {\n            Result.failure(Exception(\"Dhizuku: Uninstall failed.\"))\n        }\n    }\n\n    override suspend fun installApp(apkPath: String, canDowngrade: Boolean): Result<Unit> {\n        val result = DhizukuHelper.execute(\n            \"pm install -r -g${if (canDowngrade) \" -d\" else \"\"} ${\n                com.valhalla.superuser.ShellUtils.escapedString(apkPath)\n            }\"\n        )\n        return if (result.first == 0) {\n            Result.success(Unit)\n        } else {\n            Result.failure(Exception(\"Dhizuku: Install failed: ${result.second}\"))\n        }\n    }\n\n    override suspend fun getAppCacheSize(packageName: String): Long {\n        return 0L\n    }\n\n    override suspend fun reinstallAppWithGoogle(packageName: String): Result<Unit> {\n        if (packageName == com.valhalla.thor.BuildConfig.APPLICATION_ID)\n            return Result.failure(Exception(\"Cannot reinstall Thor\"))\n\n        return try {\n            // 1. Get the APK path(s)\n            val pathResult = DhizukuHelper.execute(\"pm path $packageName\")\n            val paths = pathResult.second?.lines()\n                ?.filter { it.isNotBlank() }\n                ?.map { it.removePrefix(\"package:\").trim() } ?: emptyList()\n\n            if (paths.isEmpty()) {\n                return Result.failure(Exception(\"Dhizuku: Could not find APK path for $packageName\"))\n            }\n\n            val combinedPath = paths.joinToString(\" \") { \"\\\"$it\\\"\" }\n\n            // 2. Get Current User ID\n            val userResult = DhizukuHelper.execute(\"am get-current-user\")\n            val currentUser = userResult.second?.trim()\n                ?: return Result.failure(Exception(\"Dhizuku: Could not determine current user\"))\n\n            // 3. Execute the reinstallation command\n            val command =\n                \"pm install -r -d -i \\\"com.android.vending\\\" --user $currentUser --install-reason 0 $combinedPath\"\n            val result = DhizukuHelper.execute(command)\n            if (result.first == 0) Result.success(Unit)\n            else Result.failure(Exception(\"Dhizuku: Reinstall failed: ${result.second}\"))\n        } catch (e: Exception) {\n            Result.failure(e)\n        }\n    }\n\n    override suspend fun setAppSuspended(packageName: String, isSuspended: Boolean): Result<Unit> {\n        return if (reflector.setAppSuspended(packageName, isSuspended)) Result.success(Unit)\n        else Result.failure(Exception(\"Dhizuku: Set suspended state failed.\"))\n    }\n\n    override suspend fun setAppRestricted(\n        packageName: String,\n        isRestricted: Boolean\n    ): Result<Unit> {\n        return if (reflector.setAppRestricted(packageName, isRestricted)) Result.success(Unit)\n        else Result.failure(Exception(\"Dhizuku: Set restricted state failed.\"))\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/data/gateway/RootSystemGateway.kt",
    "content": "package com.valhalla.thor.data.gateway\n\nimport android.content.Context\nimport com.valhalla.superuser.ktx.ShellRepository\nimport com.valhalla.thor.BuildConfig\nimport com.valhalla.thor.domain.gateway.SystemGateway\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\n\n/**\n * Modern implementation of SystemGateway using the reactive ShellRepository.\n * No more static blocking calls.\n */\nclass RootSystemGateway(\n    private val context: Context,\n    private val shellRepository: ShellRepository\n) : SystemGateway {\n\n    // A root check is strictly asynchronous. Blocking the thread for this is unacceptable.\n    override suspend fun isRootAvailable(): Boolean {\n        return shellRepository.isRootGranted()\n    }\n\n    override fun isShizukuAvailable(): Boolean = false\n    override fun isDhizukuAvailable(): Boolean = false\n\n    override suspend fun forceStopApp(packageName: String): Result<Unit> {\n        return runCommand(\"am force-stop $packageName\")\n    }\n\n    override suspend fun clearCache(packageName: String): Result<Unit> {\n        val command = \"rm -rf /data/data/$packageName/cache /sdcard/Android/data/$packageName/cache\"\n        return runCommand(command)\n    }\n\n    override suspend fun clearAppData(packageName: String): Result<Unit> {\n        return runCommand(\"pm clear $packageName\")\n    }\n\n    override suspend fun setAppDisabled(packageName: String, isDisabled: Boolean): Result<Unit> {\n        val state = if (isDisabled) \"disable\" else \"enable\"\n        return runCommand(\"pm $state $packageName\")\n    }\n\n    override suspend fun setAppSuspended(packageName: String, isSuspended: Boolean): Result<Unit> {\n        // Try one-shot root task first to show proper branding\n        if (isSuspended && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {\n            val taskResult = runRootTask(\"suspend\", packageName, isSuspended.toString())\n            if (taskResult.isSuccess) return Result.success(Unit)\n        }\n\n        val state = if (isSuspended) \"suspend\" else \"unsuspend\"\n        return runCommand(\"pm $state $packageName\")\n    }\n\n    override suspend fun setAppRestricted(\n        packageName: String,\n        isRestricted: Boolean\n    ): Result<Unit> {\n        val state = if (isRestricted) \"ignore\" else \"allow\"\n        return runCommand(\"appops set $packageName RUN_ANY_IN_BACKGROUND $state\")\n    }\n\n    override suspend fun rebootDevice(reason: String): Result<Unit> {\n        // executeResult returns success if ANY of the commands succeed in the chain logic\n        return runCommand(\"svc power reboot $reason || reboot $reason\")\n    }\n\n    override suspend fun uninstallApp(packageName: String): Result<Unit> {\n        return runCommand(\"pm uninstall --user 0 $packageName\")\n    }\n\n    override suspend fun installApp(apkPath: String, canDowngrade: Boolean): Result<Unit> {\n        val command = \"pm install -r -g${if (canDowngrade) \" -d\" else \"\"} ${\n            com.valhalla.superuser.ShellUtils.escapedString(apkPath)\n        }\"\n        return runCommand(command)\n    }\n\n    override suspend fun getAppCacheSize(packageName: String): Long {\n        return try {\n            val result = shellRepository.runCommand(\"du -s /data/data/$packageName/cache\")\n            val outputLine = result.getOrNull()?.firstOrNull() ?: return 0L\n\n            // Output format is usually \"12345   /path/to/file\"\n            // We parse this in Kotlin, not using brittle 'awk' or 'cut'\n            val sizeInBlocks =\n                outputLine.substringBefore('\\t').substringBefore(' ').toLongOrNull() ?: 0L\n\n            // du usually returns 1k blocks\n            sizeInBlocks * 1024\n        } catch (_: Exception) {\n            0L\n        }\n    }\n\n    /**\n     * Modernized Reinstall Logic.\n     * Replaces the 'sed' and 'tr' pipes with proper Kotlin string manipulation.\n     */\n    override suspend fun reinstallAppWithGoogle(packageName: String): Result<Unit> {\n        if (packageName == BuildConfig.APPLICATION_ID)\n            return Result.failure(Exception(\"Cannot reinstall Thor\"))\n\n        return withContext(Dispatchers.IO) {\n            try {\n                // 1. Get the APK path(s)\n                val paths = getAppPaths(packageName)\n                if (paths.isEmpty()) {\n                    return@withContext Result.failure(Exception(\"Could not find APK path for $packageName\"))\n                }\n\n                val combinedPath = paths.joinToString(\" \") { \"\\\"$it\\\"\" }\n\n                // 2. Get Current User ID\n                val userResult = shellRepository.runCommand(\"am get-current-user\")\n                val currentUser = userResult.getOrNull()?.firstOrNull()?.trim()\n                    ?: return@withContext Result.failure(Exception(\"Could not determine current user\"))\n\n                // 3. Execute the reinstallation command\n                val command =\n                    \"pm install -r -d -i \\\"com.android.vending\\\" --user $currentUser --install-reason 0 $combinedPath\"\n                runCommand(command)\n            } catch (e: Exception) {\n                Result.failure(e)\n            }\n        }\n    }\n\n    /**\n     * Copies a file using Root privileges.\n     */\n    suspend fun copyFile(source: String, destination: String) {\n        val command = \"cp \\\"$source\\\" \\\"$destination\\\"\"\n        val result = runCommand(command)\n\n        if (result.isFailure) {\n            throw Exception(\"Root copy failed: $command\")\n        }\n    }\n\n    /**\n     * Retrieves all APK paths (Base + Splits) for a package.\n     */\n    suspend fun getAppPaths(packageName: String): List<String> {\n        val result = shellRepository.runCommand(\"pm path \\\"$packageName\\\"\")\n        val lines = result.getOrNull() ?: emptyList()\n\n        return lines\n            .filter { it.isNotBlank() }\n            .map { it.removePrefix(\"package:\").trim() }\n    }\n\n    /**\n     * Executes a Root command in a separate process using app_process.\n     */\n    private suspend fun runRootTask(action: String, vararg args: String): Result<Unit> {\n        val apkPath = context.packageCodePath\n        val className = \"com.valhalla.thor.data.source.local.root.RootMain\"\n        val cmd = \"export CLASSPATH=$apkPath && app_process /system/bin $className $action ${args.joinToString(\" \")}\"\n        return runCommand(cmd)\n    }\n\n    /**\n     * Helper to bridge ShellRepository's Result<List<String>> to Result<Unit>\n     */\n    private suspend fun runCommand(cmd: String): Result<Unit> {\n        val result = shellRepository.runCommand(cmd)\n        return if (result.isSuccess) {\n            Result.success(Unit)\n        } else {\n            // Forward the exception from the repository or create a new one\n            Result.failure(result.exceptionOrNull() ?: Exception(\"Shell command failed: $cmd\"))\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/data/gateway/ShizukuSystemGateway.kt",
    "content": "package com.valhalla.thor.data.gateway\n\nimport android.content.pm.PackageManager\nimport com.valhalla.thor.BuildConfig\nimport com.valhalla.thor.data.source.local.shizuku.ShizukuReflector\nimport com.valhalla.thor.domain.gateway.SystemGateway\nimport rikka.shizuku.Shizuku\nimport com.valhalla.thor.data.source.local.shizuku.Shizuku as ShizukuHelper\n\nclass ShizukuSystemGateway(\n    private val reflector: ShizukuReflector\n) : SystemGateway {\n\n    override suspend fun isRootAvailable() = false\n\n    override fun isShizukuAvailable(): Boolean {\n        return try {\n            Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED && Shizuku.pingBinder()\n        } catch (_: Exception) {\n            false\n        }\n    }\n\n    override fun isDhizukuAvailable(): Boolean = false\n\n    override suspend fun forceStopApp(packageName: String): Result<Unit> {\n        return runAction { reflector.forceStop(packageName) }\n    }\n\n    override suspend fun clearCache(packageName: String): Result<Unit> {\n        return runAction { reflector.clearCache(packageName) }\n    }\n\n    override suspend fun clearAppData(packageName: String): Result<Unit> {\n        return runAction { reflector.clearData(packageName) }\n    }\n\n    override suspend fun setAppDisabled(packageName: String, isDisabled: Boolean): Result<Unit> {\n        return runAction { reflector.setAppEnabled(packageName, !isDisabled) }\n    }\n\n    override suspend fun setAppSuspended(packageName: String, isSuspended: Boolean): Result<Unit> {\n        return runAction { reflector.setAppSuspended(packageName, isSuspended) }\n    }\n\n    override suspend fun setAppRestricted(\n        packageName: String,\n        isRestricted: Boolean\n    ): Result<Unit> {\n        return runAction { reflector.setAppRestricted(packageName, isRestricted) }\n    }\n\n    override suspend fun rebootDevice(reason: String): Result<Unit> {\n        return Result.failure(Exception(\"Reboot requires Root. Shizuku cannot perform this action.\"))\n    }\n\n    override suspend fun uninstallApp(packageName: String): Result<Unit> {\n        return if (reflector.uninstallApp(packageName)) {\n            Result.success(Unit)\n        } else {\n            Result.failure(Exception(\"Uninstall failed\"))\n        }\n    }\n\n    override suspend fun installApp(apkPath: String, canDowngrade: Boolean): Result<Unit> {\n        return if (reflector.installPackage(apkPath, canDowngrade)) {\n            Result.success(Unit)\n        } else {\n            Result.failure(Exception(\"Shizuku install failed. Ensure the file path is readable by Shell/ADB.\"))\n        }\n    }\n\n    override suspend fun getAppCacheSize(packageName: String): Long {\n        return 0L // Requires specialized logic\n    }\n\n    override suspend fun reinstallAppWithGoogle(packageName: String): Result<Unit> {\n        if (packageName == BuildConfig.APPLICATION_ID)\n            return Result.failure(Exception(\"Cannot reinstall Thor\"))\n\n        return try {\n            // 1. Get the APK path(s)\n            val pathResult = ShizukuHelper.execute(\"pm path $packageName\")\n            val paths = pathResult.second?.lines()\n                ?.filter { it.isNotBlank() }\n                ?.map { it.removePrefix(\"package:\").trim() } ?: emptyList()\n\n            if (paths.isEmpty()) {\n                return Result.failure(Exception(\"Could not find APK path for $packageName\"))\n            }\n\n            val combinedPath = paths.joinToString(\" \") { \"\\\"$it\\\"\" }\n\n            // 2. Get Current User ID\n            val userResult = ShizukuHelper.execute(\"am get-current-user\")\n            val currentUser = userResult.second?.trim()\n                ?: return Result.failure(Exception(\"Could not determine current user\"))\n\n            // 3. Execute the reinstallation command\n            val command =\n                \"pm install -r -d -i \\\"com.android.vending\\\" --user $currentUser --install-reason 0 $combinedPath\"\n            val result = ShizukuHelper.execute(command)\n            if (result.first == 0) Result.success(Unit)\n            else Result.failure(Exception(\"Shizuku reinstall failed: ${result.second}\"))\n        } catch (e: Exception) {\n            Result.failure(e)\n        }\n    }\n\n    /**\n     * Standardizes error handling for reflection and shell actions.\n     */\n    private inline fun runAction(action: () -> Boolean): Result<Unit> {\n        if (!isShizukuAvailable()) {\n            return Result.failure(Exception(\"Shizuku is not available or permission denied.\"))\n        }\n        return try {\n            if (action()) Result.success(Unit)\n            else Result.failure(Exception(\"Action failed. This may happen if reflection is blocked or shell lacks permissions.\"))\n        } catch (e: Exception) {\n            if (BuildConfig.DEBUG)\n                e.printStackTrace()\n            Result.failure(e)\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/data/receivers/InstallReceiver.kt",
    "content": "package com.valhalla.thor.data.receivers\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.pm.PackageInstaller\nimport android.os.Build\nimport com.valhalla.thor.data.ACTION_INSTALL_STATUS\nimport com.valhalla.thor.domain.InstallState\nimport com.valhalla.thor.domain.InstallerEventBus\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport org.koin.core.component.KoinComponent\nimport org.koin.core.component.inject\n\n/**\n * Receives the async installation status result from the Android System.\n * This receiver is not exported for security; it is triggered via a targeted PendingIntent.\n */\nclass InstallReceiver : BroadcastReceiver(), KoinComponent {\n\n    private val eventBus: InstallerEventBus by inject()\n\n    override fun onReceive(context: Context, intent: Intent) {\n        if (intent.action != ACTION_INSTALL_STATUS) return\n\n        val pendingResult = goAsync()\n        val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)\n\n        CoroutineScope(Dispatchers.IO).launch {\n            try {\n                when (status) {\n                    PackageInstaller.STATUS_SUCCESS -> {\n                        eventBus.emit(InstallState.Success)\n                    }\n\n                    PackageInstaller.STATUS_PENDING_USER_ACTION -> {\n                        val confirmIntent: Intent? =\n                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n                                intent.getParcelableExtra(\n                                    Intent.EXTRA_INTENT,\n                                    Intent::class.java\n                                )\n                            } else {\n                                @Suppress(\"DEPRECATION\")\n                                intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)\n                            }\n                        if (confirmIntent != null) {\n                            eventBus.emit(InstallState.UserConfirmationRequired(confirmIntent))\n                        }\n                    }\n\n                    else -> {\n                        val msg = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)\n                            ?: \"Unknown Error\"\n                        eventBus.emit(InstallState.Error(\"Install Failed ($status): $msg\"))\n                    }\n                }\n            } finally {\n                pendingResult.finish()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/data/repository/AppAnalyzerImpl.kt",
    "content": "package com.valhalla.thor.data.repository\n\nimport android.content.Context\nimport android.content.pm.PackageManager\nimport android.graphics.Bitmap\nimport android.graphics.Canvas\nimport android.graphics.drawable.BitmapDrawable\nimport android.graphics.drawable.Drawable\nimport android.net.Uri\nimport android.os.Build\nimport androidx.core.graphics.createBitmap\nimport com.valhalla.thor.domain.model.AppMetadata\nimport com.valhalla.thor.domain.repository.AppAnalyzer\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport java.io.File\nimport java.io.FileOutputStream\nimport java.util.zip.ZipInputStream\n\nclass AppAnalyzerImpl(private val context: Context) : AppAnalyzer {\n\n    override suspend fun analyze(uri: Uri): Result<AppMetadata> = withContext(Dispatchers.IO) {\n        val tempFile = File(context.cacheDir, \"analysis_${System.currentTimeMillis()}.apk\")\n\n        try {\n            val contentResolver = context.contentResolver\n\n            // Phase 1: Try to extract a nested APK (for XAPK/APKS)\n            var isNestedBundle = false\n\n            try {\n                contentResolver.openInputStream(uri)?.use { inputStream ->\n                    ZipInputStream(inputStream).use { zipStream ->\n                        var entry = zipStream.nextEntry\n                        while (entry != null) {\n                            val name = entry.name\n                            if (name.endsWith(\".apk\", ignoreCase = true)) {\n                                FileOutputStream(tempFile).use { fos ->\n                                    zipStream.copyTo(fos)\n                                }\n                                isNestedBundle = true\n                                break\n                            }\n                            zipStream.closeEntry()\n                            entry = zipStream.nextEntry\n                        }\n                    }\n                }\n            } catch (_: Exception) {\n                isNestedBundle = false\n            }\n\n            // Phase 2: Fallback (Standard APK)\n            if (!isNestedBundle) {\n                contentResolver.openInputStream(uri)?.use { input ->\n                    FileOutputStream(tempFile).use { output ->\n                        input.copyTo(output)\n                    }\n                }\n            }\n\n            // Phase 3: Parsing\n            val pm = context.packageManager\n            val flags = PackageManager.GET_META_DATA or PackageManager.GET_PERMISSIONS\n\n            val archiveInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n                pm.getPackageArchiveInfo(\n                    tempFile.absolutePath,\n                    PackageManager.PackageInfoFlags.of(flags.toLong())\n                )\n            } else {\n                @Suppress(\"DEPRECATION\")\n                pm.getPackageArchiveInfo(tempFile.absolutePath, flags)\n            }\n\n            if (archiveInfo == null) {\n                return@withContext Result.failure(Exception(\"Failed to parse APK manifest. The file might be corrupted or encrypted.\"))\n            }\n\n            // Necessary to load resources properly from an external file\n            archiveInfo.applicationInfo?.sourceDir = tempFile.absolutePath\n            archiveInfo.applicationInfo?.publicSourceDir = tempFile.absolutePath\n\n            val label = archiveInfo.applicationInfo?.loadLabel(pm).toString()\n            val drawable = archiveInfo.applicationInfo?.loadIcon(pm)\n            val version = archiveInfo.versionName ?: \"Unknown\"\n            val versionCode = archiveInfo.longVersionCode\n            val pkgName = archiveInfo.packageName\n            val permissions = archiveInfo.requestedPermissions?.toList() ?: emptyList()\n\n            Result.success(\n                AppMetadata(\n                    label = label,\n                    packageName = pkgName,\n                    version = version,\n                    versionCode = versionCode,\n                    icon = drawable?.toBitmap(),\n                    permissions = permissions\n                )\n            )\n\n        } catch (e: Exception) {\n            Result.failure(e)\n        } finally {\n            if (tempFile.exists()) {\n                tempFile.delete()\n            }\n        }\n    }\n\n    private fun Drawable.toBitmap(): Bitmap {\n        if (this is BitmapDrawable) return this.bitmap\n\n        val bitmap = createBitmap(intrinsicWidth.coerceAtLeast(1), intrinsicHeight.coerceAtLeast(1))\n        val canvas = Canvas(bitmap)\n        setBounds(0, 0, canvas.width, canvas.height)\n        draw(canvas)\n        return bitmap\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/data/repository/AppRepositoryImpl.kt",
    "content": "package com.valhalla.thor.data.repository\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.content.pm.PackageManager\nimport android.os.Build\nimport com.valhalla.thor.BuildConfig\nimport com.valhalla.thor.data.source.local.room.AppDao\nimport com.valhalla.thor.data.source.local.room.AppEntity\nimport com.valhalla.thor.domain.model.AppInfo\nimport com.valhalla.thor.domain.repository.AppRepository\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.channels.Channel\nimport kotlinx.coroutines.channels.awaitClose\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.callbackFlow\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\n\nclass AppRepositoryImpl(\n    private val context: Context,\n    private val appDao: AppDao\n) : AppRepository {\n\n    private val pm = context.packageManager\n\n    /**\n     * RUTHLESS OPTIMIZATION V2:\n     * We debounce the TRIGGER to prevent heavy package scanning during batch operations.\n     */\n    override fun getAllApps(): Flow<List<AppInfo>> = callbackFlow {\n        val producer = this\n\n        // A conflated channel acts as a signal buffer.\n        // If 50 broadcasts come in, we only keep the latest \"refresh needed\" flag.\n        val triggerChannel = Channel<Unit>(Channel.CONFLATED)\n\n        // The Worker: Consumes triggers, waits for quiet, then fetches ONCE.\n        val worker = launch(Dispatchers.IO) {\n            // Initial load from cache and baseline for comparison\n            val cachedMap = try {\n                val entities = appDao.getAllApps()\n                if (entities.isNotEmpty()) {\n                    producer.send(entities.map { it.toDomain() })\n                }\n                entities.associateBy { it.packageName }.toMutableMap()\n            } catch (e: Exception) {\n                if (BuildConfig.DEBUG) e.printStackTrace()\n                mutableMapOf<String, AppEntity>()\n            }\n\n            var lastLocale = context.resources.configuration.locales[0].toString()\n\n            // Signal the worker to refresh\n            triggerChannel.send(Unit)\n\n            for (signal in triggerChannel) {\n                // Drain any extra signals that arrived while we were waiting\n                while (triggerChannel.tryReceive().isSuccess) {\n                    // Do nothing, just consume them so we don't loop immediately again\n                }\n\n                // Now Perform the Heavy Fetch ONE time\n                try {\n                    val currentLocale = context.resources.configuration.locales[0].toString()\n                    val forceRefresh = currentLocale != lastLocale\n                    if (forceRefresh) {\n                        lastLocale = currentLocale\n                    }\n\n                    val flags = PackageManager.MATCH_UNINSTALLED_PACKAGES.toLong()\n                    val installedPackages =\n                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n                            pm.getInstalledPackages(PackageManager.PackageInfoFlags.of(flags))\n                        } else {\n                            pm.getInstalledPackages(PackageManager.MATCH_UNINSTALLED_PACKAGES)\n                        }\n\n                    val currentList = ArrayList<AppInfo>(installedPackages.size)\n                    val toUpdate = mutableListOf<AppEntity>()\n\n                    for (packInfo in installedPackages) {\n                        val appInfo = packInfo.applicationInfo ?: continue\n                        val packageName = packInfo.packageName\n\n                        val cachedEntry = cachedMap[packageName]\n                        val isSuspended =\n                            (appInfo.flags and android.content.pm.ApplicationInfo.FLAG_SUSPENDED) != 0\n\n                        if (!forceRefresh &&\n                            cachedEntry != null &&\n                            cachedEntry.lastUpdateTime == packInfo.lastUpdateTime &&\n                            cachedEntry.enabled == appInfo.enabled &&\n                            cachedEntry.isSuspended == isSuspended\n                        ) {\n                            currentList.add(cachedEntry.toDomain())\n                        } else {\n                            val mapped =\n                                AppInfo.mapToAppInfo(packInfo, appInfo, pm, isLightweight = true)\n                            currentList.add(mapped)\n                            val entity = AppEntity.fromDomain(mapped)\n                            toUpdate.add(entity)\n                            cachedMap[packageName] = entity\n                        }\n                    }\n\n                    // Handle uninstalled apps: Cleanup cache\n                    val currentPackageNames = installedPackages.map { it.packageName }.toSet()\n                    val toDelete = cachedMap.keys.filter { it !in currentPackageNames }\n\n                    if (toUpdate.isNotEmpty() || toDelete.isNotEmpty()) {\n                        appDao.syncCache(toUpdate, toDelete)\n                        toDelete.forEach { cachedMap.remove(it) }\n                    }\n\n                    // Emit a single complete snapshot of all installed apps\n                    producer.send(currentList.toList())\n\n                } catch (e: Exception) {\n                    if (BuildConfig.DEBUG) e.printStackTrace()\n                }\n            }\n        }\n\n        // Receiver for Package-specific changes (requires \"package\" data scheme)\n        val packageReceiver = object : BroadcastReceiver() {\n            override fun onReceive(context: Context, intent: Intent) {\n                triggerChannel.trySend(Unit)\n            }\n        }\n\n        val packageFilter = IntentFilter().apply {\n            addAction(Intent.ACTION_PACKAGE_ADDED)\n            addAction(Intent.ACTION_PACKAGE_REMOVED)\n            addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED)\n            addAction(Intent.ACTION_PACKAGE_REPLACED)\n            addAction(Intent.ACTION_PACKAGE_CHANGED)\n            addDataScheme(\"package\")\n        }\n\n        // Receiver for General Package changes (No data scheme)\n        val generalReceiver = object : BroadcastReceiver() {\n            override fun onReceive(context: Context, intent: Intent) {\n                triggerChannel.trySend(Unit)\n            }\n        }\n\n        val generalFilter = IntentFilter().apply {\n            addAction(Intent.ACTION_PACKAGES_SUSPENDED)\n            addAction(Intent.ACTION_PACKAGES_UNSUSPENDED)\n        }\n\n        context.registerReceiver(packageReceiver, packageFilter)\n        context.registerReceiver(generalReceiver, generalFilter)\n\n        awaitClose {\n            context.unregisterReceiver(packageReceiver)\n            context.unregisterReceiver(generalReceiver)\n            worker.cancel()\n        }\n    }.flowOn(Dispatchers.IO)\n\n    override suspend fun getAppDetails(packageName: String): AppInfo? =\n        withContext(Dispatchers.IO) {\n            try {\n                val flags = (PackageManager.MATCH_UNINSTALLED_PACKAGES).toLong()\n                val packInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n                    pm.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(flags))\n                } else {\n                    pm.getPackageInfo(packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES)\n                }\n                val appInfo = packInfo.applicationInfo ?: return@withContext null\n\n                AppInfo.mapToAppInfo(packInfo, appInfo, pm, isLightweight = false)\n            } catch (e: Exception) {\n                if (BuildConfig.DEBUG)\n                    e.printStackTrace()\n                null\n            }\n        }\n\n    override suspend fun getApkDetails(apkPath: String): AppInfo? = withContext(Dispatchers.IO) {\n        val flags = PackageManager.GET_PERMISSIONS\n        val packInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n            pm.getPackageArchiveInfo(apkPath, PackageManager.PackageInfoFlags.of(flags.toLong()))\n        } else {\n            @Suppress(\"DEPRECATION\")\n            pm.getPackageArchiveInfo(apkPath, flags)\n        } ?: return@withContext null\n\n        val appInfo = packInfo.applicationInfo?.apply {\n            sourceDir = apkPath\n            publicSourceDir = apkPath\n        } ?: return@withContext null\n\n        AppInfo.mapToAppInfo(packInfo, appInfo, pm, isLightweight = false).apply {\n            this.appName = pm.getApplicationLabel(appInfo).toString()\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/data/repository/InstallerRepositoryImpl.kt",
    "content": "package com.valhalla.thor.data.repository\n\nimport android.annotation.SuppressLint\nimport android.app.PendingIntent\nimport android.content.Context\nimport android.content.Intent\nimport android.content.pm.PackageInstaller\nimport android.database.Cursor\nimport android.net.Uri\nimport android.os.Build\nimport android.provider.OpenableColumns\nimport com.valhalla.bypass.Bypass\nimport com.valhalla.thor.data.ACTION_INSTALL_STATUS\nimport com.valhalla.thor.data.gateway.RootSystemGateway\nimport com.valhalla.thor.data.receivers.InstallReceiver\nimport com.valhalla.thor.data.source.local.shizuku.ShizukuPackageInstallerUtils\nimport com.valhalla.thor.data.source.local.shizuku.ShizukuReflector\nimport com.valhalla.thor.domain.InstallState\nimport com.valhalla.thor.domain.InstallerEventBus\nimport com.valhalla.thor.domain.repository.InstallMode\nimport com.valhalla.thor.domain.repository.InstallerRepository\nimport com.valhalla.thor.util.Logger\nimport kotlinx.coroutines.CancellationException\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport java.io.File\nimport java.io.FileOutputStream\nimport java.io.InputStream\nimport java.util.zip.ZipInputStream\n\nclass InstallerRepositoryImpl(\n    private val context: Context,\n    private val eventBus: InstallerEventBus,\n    private val rootGateway: RootSystemGateway,\n    private val shizukuReflector: ShizukuReflector\n) : InstallerRepository {\n\n    private val defaultInstaller = context.packageManager.packageInstaller\n\n    override suspend fun installPackage(uri: Uri, mode: InstallMode, canDowngrade: Boolean) =\n        withContext(Dispatchers.IO) {\n            try {\n                when (mode) {\n                    InstallMode.ROOT -> {\n                        installWithRoot(uri, canDowngrade)\n                    }\n\n                    InstallMode.SHIZUKU -> {\n                        val privilegedInstaller = try {\n                            getShizukuPackageInstaller()\n                        } catch (e: Throwable) {\n                            if (e is CancellationException) throw e\n                            Logger.e(\n                                \"InstallerRepo\",\n                                \"Failed to get Shizuku installer, will use normal installer: ${e.message}\"\n                            )\n                            null\n                        }\n\n                        if (privilegedInstaller != null) {\n                            try {\n                                // Try privileged path but suppress error emission so we can fall back silently\n                                performPackageInstallerInstall(\n                                    uri,\n                                    privilegedInstaller,\n                                    canDowngrade,\n                                    emitErrors = false\n                                )\n                            } catch (e: Throwable) {\n                                if (e is CancellationException) throw e\n                                Logger.e(\n                                    \"InstallerRepo\",\n                                    \"Shizuku privileged install failed, falling back to normal: ${e.message}\"\n                                )\n                                performPackageInstallerInstall(\n                                    uri,\n                                    defaultInstaller,\n                                    canDowngrade,\n                                    emitErrors = true\n                                )\n                            }\n                        } else {\n                            // No privileged installer available, use normal installer and allow errors\n                            performPackageInstallerInstall(\n                                uri,\n                                defaultInstaller,\n                                canDowngrade,\n                                emitErrors = true\n                            )\n                        }\n                    }\n\n                    InstallMode.DHIZUKU -> {\n                        val privilegedInstaller = try {\n                            getDhizukuPackageInstaller()\n                        } catch (e: Throwable) {\n                            if (e is CancellationException) throw e\n                            Logger.e(\n                                \"InstallerRepo\",\n                                \"Failed to get Dhizuku installer, will use normal installer: ${e.message}\"\n                            )\n                            null\n                        }\n\n                        if (privilegedInstaller != null) {\n                            try {\n                                // Try privileged path but suppress error emission so we can fall back silently\n                                performPackageInstallerInstall(\n                                    uri,\n                                    privilegedInstaller,\n                                    canDowngrade,\n                                    emitErrors = false\n                                )\n                            } catch (e: Throwable) {\n                                if (e is CancellationException) throw e\n                                Logger.e(\n                                    \"InstallerRepo\",\n                                    \"Dhizuku privileged install failed, falling back to normal: ${e.message}\"\n                                )\n                                performPackageInstallerInstall(\n                                    uri,\n                                    defaultInstaller,\n                                    canDowngrade,\n                                    emitErrors = true\n                                )\n                            }\n                        } else {\n                            // No privileged installer available, use normal installer and allow errors\n                            performPackageInstallerInstall(\n                                uri,\n                                defaultInstaller,\n                                canDowngrade,\n                                emitErrors = true\n                            )\n                        }\n                    }\n\n                    InstallMode.NORMAL -> {\n                        performPackageInstallerInstall(\n                            uri,\n                            defaultInstaller,\n                            canDowngrade,\n                            emitErrors = true\n                        )\n                    }\n\n                    InstallMode.EXTERNAL -> {\n                        installWithExternal(uri)\n                    }\n                }\n            } catch (e: Exception) {\n                if (e is CancellationException) throw e\n                eventBus.emit(InstallState.Error(e.message ?: \"Unknown error during installation\"))\n            }\n        }\n\n    // Create a PackageInstaller using Dhizuku's binder wrapper but make the installer package\n    // be this app's package name so created sessions belong to the app UID (avoids UID mismatch).\n    private fun getDhizukuPackageInstaller(): PackageInstaller {\n        // Prefer using the existing Shizuku helper which returns a privileged IPackageInstaller.\n        // This avoids calling IPackageManager.getPackageInstaller() directly (which may not exist\n        // on some ROMs / API versions and caused NoSuchMethodError).\n        try {\n            val iPackageInstaller = ShizukuPackageInstallerUtils.getPrivilegedPackageInstaller()\n            val root = try {\n                rikka.shizuku.Shizuku.getUid() == 0\n            } catch (_: Exception) {\n                false\n            }\n            val userId = if (root) android.os.Process.myUserHandle().hashCode() else 0\n            val installerPackageName = context.packageName\n\n            return ShizukuPackageInstallerUtils.createPackageInstaller(\n                iPackageInstaller,\n                installerPackageName,\n                userId\n            )\n        } catch (e: Throwable) {\n            if (e is CancellationException) throw e\n            // Bubble up so caller falls back to normal installer; log for debugging.\n            Logger.e(\"InstallerRepo\", \"getDhizukuPackageInstaller failed: ${e.message}\")\n            throw e\n        }\n    }\n\n    // Create a PackageInstaller using Shizuku's privileged installer helper (like ShizukuReflector)\n    // and make the installer package be this app's package name so sessions belong to app UID.\n    private fun getShizukuPackageInstaller(): PackageInstaller {\n        // Reuse ShizukuPackageInstallerUtils to get a privileged IPackageInstaller safely across API levels\n        val iPackageInstaller = ShizukuPackageInstallerUtils.getPrivilegedPackageInstaller()\n\n        val shizukuUid = try {\n            rikka.shizuku.Shizuku.getUid()\n        } catch (_: Exception) {\n            -1\n        }\n        val isRoot = shizukuUid == 0\n        val isShell = shizukuUid == 2000\n\n        // If Shizuku is running as root, set userId to current user; otherwise use 0\n        val userId = if (isRoot) android.os.Process.myUserHandle().hashCode() else 0\n\n        // For ADB-based Shizuku (shell), using \"com.android.shell\" often works better\n        // than the app's own package name to avoid permission/UID mismatch issues.\n        val installerPackageName = if (isShell) \"com.android.shell\" else context.packageName\n\n        return ShizukuPackageInstallerUtils.createPackageInstaller(\n            iPackageInstaller,\n            installerPackageName,\n            userId\n        )\n    }\n\n    private suspend fun installWithExternal(uri: Uri) {\n        withContext(Dispatchers.Main) {\n            try {\n                val intent = Intent(Intent.ACTION_VIEW).apply {\n                    setDataAndType(uri, \"application/vnd.android.package-archive\")\n                    addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)\n                    addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)\n                }\n\n                val chooser = Intent.createChooser(intent, \"Install with...\")\n                chooser.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)\n                context.startActivity(chooser)\n\n                // We consider this a success in terms of handing off the job\n                eventBus.emit(InstallState.Success)\n            } catch (e: Exception) {\n                if (e is CancellationException) throw e\n                eventBus.emit(InstallState.Error(\"Could not open external installer: ${e.message}\"))\n            }\n        }\n    }\n\n    private suspend fun installWithRoot(uri: Uri, canDowngrade: Boolean) {\n        eventBus.emit(InstallState.Installing(0f))\n\n        val tempFile = File(context.cacheDir, \"install_temp_${System.currentTimeMillis()}.apk\")\n\n        try {\n            // Copy uri to temp file\n            context.contentResolver.openInputStream(uri)?.use { input ->\n                FileOutputStream(tempFile).use { output ->\n                    input.copyTo(output)\n                }\n            } ?: run {\n                eventBus.emit(InstallState.Error(\"Failed to read input file\"))\n                return\n            }\n\n            eventBus.emit(InstallState.Installing(0.5f))\n\n            // Execute root install\n            val result = rootGateway.installApp(tempFile.absolutePath, canDowngrade)\n\n            if (result.isSuccess) {\n                eventBus.emit(InstallState.Installing(1.0f))\n                eventBus.emit(InstallState.Success)\n            } else {\n                eventBus.emit(\n                    InstallState.Error(\n                        result.exceptionOrNull()?.message ?: \"Root install failed\"\n                    )\n                )\n            }\n\n        } catch (e: Exception) {\n            if (e is CancellationException) throw e\n            eventBus.emit(InstallState.Error(\"Root install error: ${e.message}\"))\n        } finally {\n            if (tempFile.exists()) {\n                tempFile.delete()\n            }\n        }\n    }\n\n    @SuppressLint(\"RequestInstallPackagesPolicy\")\n    private suspend fun performPackageInstallerInstall(\n        uri: Uri,\n        packageInstaller: PackageInstaller,\n        canDowngrade: Boolean,\n        emitErrors: Boolean = true\n    ) {\n        val totalBytes = getFileSize(uri)\n        var bytesProcessed = 0L\n        var lastProgressEmitted = 0\n        var filesWritten = false\n\n        eventBus.emit(InstallState.Parsing)\n\n        val params = PackageInstaller.SessionParams(\n            PackageInstaller.SessionParams.MODE_FULL_INSTALL\n        )\n\n        if (canDowngrade) {\n            try {\n                // Use reflection via Bypass as it might be unresolved in some SDK configurations\n                Bypass.invoke<Any?>(params::class.java, params, \"setRequestDowngrade\", true)\n            } catch (e: Exception) {\n                if (e is CancellationException) throw e\n                Logger.e(\"InstallerRepo\", \"Failed to setRequestDowngrade\", e)\n                if (emitErrors) {\n                    eventBus.emit(InstallState.Error(\"Failed to request downgrade: ${e.message}\"))\n                    return\n                } else throw Exception(\"Failed to request downgrade: ${e.message}\")\n            }\n        }\n\n        val sessionId = try {\n            packageInstaller.createSession(params)\n        } catch (e: Exception) {\n            if (e is CancellationException) throw e\n            if (emitErrors) {\n                eventBus.emit(InstallState.Error(\"Failed to create session: ${e.message}\"))\n                return\n            } else throw e\n        }\n\n        val session = try {\n            packageInstaller.openSession(sessionId)\n        } catch (e: Exception) {\n            if (e is CancellationException) throw e\n            if (emitErrors) {\n                eventBus.emit(InstallState.Error(\"Failed to open session: ${e.message}\"))\n                return\n            } else throw e\n        }\n\n        // Helper to track progress across different streams\n        fun getTrackedStream(baseStream: InputStream): InputStream {\n            return object : InputStream() {\n                override fun read(): Int {\n                    val b = baseStream.read()\n                    if (b != -1) updateProgress(1)\n                    return b\n                }\n\n                override fun read(b: ByteArray, off: Int, len: Int): Int {\n                    val read = baseStream.read(b, off, len)\n                    if (read != -1) updateProgress(read.toLong())\n                    return read\n                }\n\n                override fun close() {\n                    baseStream.close()\n                }\n\n                private fun updateProgress(readBytes: Long) {\n                    bytesProcessed += readBytes\n                    if (totalBytes > 0) {\n                        val currentProgress =\n                            ((bytesProcessed.toDouble() / totalBytes) * 100).toInt()\n                        if (currentProgress > lastProgressEmitted) {\n                            lastProgressEmitted = currentProgress\n                            CoroutineScope(Dispatchers.IO).launch {\n                                eventBus.emit(InstallState.Installing(bytesProcessed.toFloat() / totalBytes))\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        try {\n            // ATTEMPT 1: Try as Bundle (XAPK/APKS/APKM)\n            val bundleStream: InputStream? = context.contentResolver.openInputStream(uri)\n\n            if (bundleStream != null) {\n                try {\n                    ZipInputStream(bundleStream).use { zipStream ->\n                        var entry = zipStream.nextEntry\n                        while (entry != null) {\n                            val name = entry.name\n                            if (name.endsWith(\".apk\", ignoreCase = true)) {\n                                filesWritten = true\n                                val size = entry.size\n\n                                if (size == -1L) {\n                                    // Unknown size in Zip: Buffer to temp\n                                    val tempFile = File(\n                                        context.cacheDir,\n                                        \"temp_${System.currentTimeMillis()}_${File(name).name}\"\n                                    )\n                                    FileOutputStream(tempFile).use { fos -> zipStream.copyTo(fos) }\n\n                                    val actualSize = tempFile.length()\n                                    val outStream = session.openWrite(name, 0, actualSize)\n                                    tempFile.inputStream().use { fis -> fis.copyTo(outStream) }\n                                    session.fsync(outStream)\n                                    outStream.close()\n                                    tempFile.delete()\n                                } else {\n                                    // Known size: Stream directly\n                                    val outStream = session.openWrite(name, 0, size)\n                                    val buffer = ByteArray(65536)\n                                    var len: Int\n                                    while (zipStream.read(buffer).also { len = it } > 0) {\n                                        outStream.write(buffer, 0, len)\n                                    }\n                                    session.fsync(outStream)\n                                    outStream.close()\n                                }\n                            }\n                            zipStream.closeEntry()\n                            entry = zipStream.nextEntry\n                        }\n                    }\n                } catch (e: Exception) {\n                    Logger.e(\"thor\", \"Not a valid bundle zip, trying fallback. Error: ${e.message}\")\n                }\n            }\n\n            // ATTEMPT 2: Fallback to Monolithic APK\n            if (!filesWritten) {\n                Logger.d(\"thor\", \"Fallback: Treating stream as monolithic base.apk\")\n\n                bytesProcessed = 0\n                lastProgressEmitted = 0\n\n                val rawStream = context.contentResolver.openInputStream(uri)\n                if (rawStream == null) {\n                    session.abandon()\n                    Logger.e(\"thor\", \"Could not open file stream.\")\n                    if (emitErrors) {\n                        eventBus.emit(InstallState.Error(\"Could not open file stream.\"))\n                        return\n                    } else throw Exception(\"Could not open file stream.\")\n                }\n\n                val trackedStream = getTrackedStream(rawStream)\n\n                trackedStream.use { input ->\n                    val size = if (totalBytes > 0) totalBytes else -1L\n                    val outStream = session.openWrite(\"base.apk\", 0, size)\n                    input.copyTo(outStream)\n                    session.fsync(outStream)\n                    outStream.close()\n                    filesWritten = true\n                }\n            }\n\n            eventBus.emit(InstallState.Installing(1.0f))\n\n            val intent = Intent(context, InstallReceiver::class.java).apply {\n                action = ACTION_INSTALL_STATUS\n                setPackage(context.packageName)\n            }\n\n            val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE\n            } else {\n                PendingIntent.FLAG_UPDATE_CURRENT\n            }\n\n            val pendingIntent = PendingIntent.getBroadcast(\n                context,\n                sessionId,\n                intent,\n                flags\n            )\n\n            session.commit(pendingIntent.intentSender)\n            session.close()\n\n        } catch (e: Exception) {\n            if (e is CancellationException) throw e\n            try {\n                session.abandon()\n            } catch (_: Exception) {\n            }\n            Logger.e(\"thorInstaller\", \"Install failed\", e)\n            if (emitErrors) {\n                eventBus.emit(InstallState.Error(e.message ?: \"Unknown installation error\"))\n            } else throw e\n        }\n    }\n\n    private fun getFileSize(uri: Uri): Long {\n        var size = -1L\n        val cursor: Cursor? = context.contentResolver.query(uri, null, null, null, null)\n        cursor?.use {\n            if (it.moveToFirst()) {\n                val sizeIndex = it.getColumnIndex(OpenableColumns.SIZE)\n                if (sizeIndex != -1) {\n                    size = it.getLong(sizeIndex)\n                }\n            }\n        }\n        return size\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/data/repository/PreferenceRepositoryImpl.kt",
    "content": "package com.valhalla.thor.data.repository\n\nimport android.content.Context\nimport androidx.datastore.core.DataStore\nimport androidx.datastore.preferences.core.Preferences\nimport androidx.datastore.preferences.core.booleanPreferencesKey\nimport androidx.datastore.preferences.core.edit\nimport androidx.datastore.preferences.core.stringPreferencesKey\nimport androidx.datastore.preferences.preferencesDataStore\nimport com.valhalla.thor.domain.model.FilterType\nimport com.valhalla.thor.domain.model.PrivilegeMode\nimport com.valhalla.thor.domain.model.SortBy\nimport com.valhalla.thor.domain.model.SortOrder\nimport com.valhalla.thor.domain.model.ThemeMode\nimport com.valhalla.thor.domain.model.UserPreferences\nimport com.valhalla.thor.domain.repository.PreferenceRepository\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.map\n\nprivate val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = \"thor_preferences\")\n\nclass PreferenceRepositoryImpl(\n    private val context: Context\n) : PreferenceRepository {\n\n    private object Keys {\n        // App List\n        val SORT_BY = stringPreferencesKey(\"sort_by\")\n        val SORT_ORDER = stringPreferencesKey(\"sort_order\")\n        val FILTER_TYPE = stringPreferencesKey(\"filter_type\")\n        val SELECTED_FILTER = stringPreferencesKey(\"selected_filter\")\n        val SHOW_REINSTALL_ALL = booleanPreferencesKey(\"show_reinstall_all\")\n\n        // Theme\n        val THEME_MODE = stringPreferencesKey(\"theme_mode\")\n        val USE_DYNAMIC_COLOR = booleanPreferencesKey(\"use_dynamic_color\")\n        val USE_AMOLED = booleanPreferencesKey(\"use_amoled\")\n\n        // Security\n        val BIOMETRIC_LOCK = booleanPreferencesKey(\"biometric_lock\")\n\n        // Work Mode\n        val PRIVILEGE_MODE = stringPreferencesKey(\"privilege_mode\")\n\n        // Localization\n        val LANGUAGE = stringPreferencesKey(\"language\")\n    }\n\n    override val userPreferences: Flow<UserPreferences> = context.dataStore.data\n        .map { prefs ->\n            val sortBy = prefs[Keys.SORT_BY]\n                ?.let { runCatching { SortBy.valueOf(it) }.getOrNull() }\n                ?: SortBy.NAME\n\n            val sortOrder = prefs[Keys.SORT_ORDER]\n                ?.let { runCatching { SortOrder.valueOf(it) }.getOrNull() }\n                ?: SortOrder.ASCENDING\n\n            val filterType = when (prefs[Keys.FILTER_TYPE]) {\n                \"STATE\" -> FilterType.State\n                else -> FilterType.Source\n            }\n\n            val themeMode = prefs[Keys.THEME_MODE]\n                ?.let { runCatching { ThemeMode.valueOf(it) }.getOrNull() }\n                ?: ThemeMode.SYSTEM\n\n            val privilegeMode = prefs[Keys.PRIVILEGE_MODE]\n                ?.let { runCatching { PrivilegeMode.valueOf(it) }.getOrNull() }\n\n            UserPreferences(\n                appSortBy = sortBy,\n                appSortOrder = sortOrder,\n                appFilterType = filterType,\n                appSelectedFilter = prefs[Keys.SELECTED_FILTER] ?: \"All\",\n                showReinstallAllCard = prefs[Keys.SHOW_REINSTALL_ALL] ?: true,\n                themeMode = themeMode,\n                useDynamicColor = prefs[Keys.USE_DYNAMIC_COLOR] ?: false,\n                useAmoled = prefs[Keys.USE_AMOLED] ?: false,\n                biometricLockEnabled = prefs[Keys.BIOMETRIC_LOCK] ?: false,\n                preferredPrivilegeMode = privilegeMode,\n                language = prefs[Keys.LANGUAGE]\n            )\n        }\n\n    // --- App List ---\n\n    override suspend fun updateAppSort(sortBy: SortBy) {\n        context.dataStore.edit { it[Keys.SORT_BY] = sortBy.name }\n    }\n\n    override suspend fun updateAppSortOrder(sortOrder: SortOrder) {\n        context.dataStore.edit { it[Keys.SORT_ORDER] = sortOrder.name }\n    }\n\n    override suspend fun updateAppFilter(filterType: FilterType, selectedFilter: String) {\n        context.dataStore.edit {\n            it[Keys.FILTER_TYPE] = if (filterType is FilterType.State) \"STATE\" else \"SOURCE\"\n            it[Keys.SELECTED_FILTER] = selectedFilter\n        }\n    }\n\n    override suspend fun setReinstallAllCardVisibility(isVisible: Boolean) {\n        context.dataStore.edit { it[Keys.SHOW_REINSTALL_ALL] = isVisible }\n    }\n\n    // --- Theme ---\n\n    override suspend fun setThemeMode(themeMode: ThemeMode) {\n        context.dataStore.edit { it[Keys.THEME_MODE] = themeMode.name }\n    }\n\n    override suspend fun setDynamicColor(enabled: Boolean) {\n        context.dataStore.edit { it[Keys.USE_DYNAMIC_COLOR] = enabled }\n    }\n\n    override suspend fun setUseAmoled(enabled: Boolean) {\n        context.dataStore.edit { it[Keys.USE_AMOLED] = enabled }\n    }\n\n    // --- Security ---\n\n    override suspend fun setBiometricLock(enabled: Boolean) {\n        context.dataStore.edit { it[Keys.BIOMETRIC_LOCK] = enabled }\n    }\n\n    // --- Work Mode ---\n\n    override suspend fun setPrivilegeMode(mode: PrivilegeMode?) {\n        context.dataStore.edit {\n            if (mode == null) it.remove(Keys.PRIVILEGE_MODE)\n            else it[Keys.PRIVILEGE_MODE] = mode.name\n        }\n    }\n\n    override suspend fun setLanguage(language: String?) {\n        context.dataStore.edit {\n            if (language == null) it.remove(Keys.LANGUAGE)\n            else it[Keys.LANGUAGE] = language\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/data/repository/SystemRepositoryImpl.kt",
    "content": "package com.valhalla.thor.data.repository\n\nimport com.valhalla.thor.data.gateway.DhizukuSystemGateway\nimport com.valhalla.thor.data.gateway.RootSystemGateway\nimport com.valhalla.thor.data.gateway.ShizukuSystemGateway\nimport com.valhalla.thor.domain.gateway.SystemGateway\nimport com.valhalla.thor.domain.model.PrivilegeMode\nimport com.valhalla.thor.domain.repository.PreferenceRepository\nimport com.valhalla.thor.domain.repository.SystemRepository\nimport kotlinx.coroutines.flow.first\n\nclass SystemRepositoryImpl(\n    private val rootGateway: RootSystemGateway,\n    private val shizukuGateway: ShizukuSystemGateway,\n    private val dhizukuGateway: DhizukuSystemGateway,\n    private val preferenceRepository: PreferenceRepository\n) : SystemRepository {\n\n    override suspend fun isRootAvailable(): Boolean {\n        return rootGateway.isRootAvailable()\n    }\n\n    override fun isShizukuAvailable(): Boolean = shizukuGateway.isShizukuAvailable()\n\n    override fun isDhizukuAvailable(): Boolean = dhizukuGateway.isDhizukuAvailable()\n\n    // Dynamic Resolution Strategy: Respect user preference if available, else auto-detect.\n    // Must be suspend because checking root and reading preferences are suspend operations.\n    private suspend fun getActiveGateway(): SystemGateway {\n        val prefs = preferenceRepository.userPreferences.first()\n\n        // 1. Try User Preference\n        prefs.preferredPrivilegeMode?.let { mode ->\n            when (mode) {\n                PrivilegeMode.ROOT -> if (rootGateway.isRootAvailable()) return rootGateway\n                PrivilegeMode.SHIZUKU -> if (shizukuGateway.isShizukuAvailable()) return shizukuGateway\n                PrivilegeMode.DHIZUKU -> if (dhizukuGateway.isDhizukuAvailable()) return dhizukuGateway\n            }\n        }\n\n        // 2. Fallback to Auto-Detection\n        return when {\n            rootGateway.isRootAvailable() -> rootGateway\n            shizukuGateway.isShizukuAvailable() -> shizukuGateway\n            dhizukuGateway.isDhizukuAvailable() -> dhizukuGateway\n            else -> throw IllegalStateException(\"No privileged gateway available (Root, Shizuku or Dhizuku required)\")\n        }\n    }\n\n    override suspend fun forceStopApp(packageName: String): Result<Unit> =\n        getActiveGateway().forceStopApp(packageName)\n\n    override suspend fun clearCache(packageName: String): Result<Unit> =\n        getActiveGateway().clearCache(packageName)\n\n    override suspend fun clearAppData(packageName: String): Result<Unit> =\n        getActiveGateway().clearAppData(packageName)\n\n    override suspend fun setAppDisabled(packageName: String, isDisabled: Boolean): Result<Unit> =\n        getActiveGateway().setAppDisabled(packageName, isDisabled)\n\n    override suspend fun setAppSuspended(packageName: String, isSuspended: Boolean): Result<Unit> =\n        getActiveGateway().setAppSuspended(packageName, isSuspended)\n\n    override suspend fun setAppRestricted(\n        packageName: String,\n        isRestricted: Boolean\n    ): Result<Unit> =\n        getActiveGateway().setAppRestricted(packageName, isRestricted)\n\n    override suspend fun uninstallApp(packageName: String): Result<Unit> =\n        getActiveGateway().uninstallApp(packageName)\n\n    override suspend fun rebootDevice(reason: String): Result<Unit> {\n        return if (rootGateway.isRootAvailable()) {\n            rootGateway.rebootDevice(reason)\n        } else {\n            Result.failure(Exception(\"Reboot requires Root access\"))\n        }\n    }\n\n    override suspend fun aggressiveCleanup(packageName: String): Result<Unit> {\n        return try {\n            val gateway = getActiveGateway()\n            gateway.forceStopApp(packageName)\n            gateway.clearCache(packageName)\n            Result.success(Unit)\n        } catch (e: Exception) {\n            Result.failure(e)\n        }\n    }\n\n    override suspend fun reinstallAppWithGoogle(packageName: String): Result<Unit> =\n        getActiveGateway().reinstallAppWithGoogle(packageName)\n\n    override suspend fun copyFileWithRoot(\n        sourcePath: String,\n        destinationPath: String\n    ): Result<Unit> {\n        return if (rootGateway.isRootAvailable()) {\n            try {\n                rootGateway.copyFile(sourcePath, destinationPath)\n                Result.success(Unit)\n            } catch (e: Exception) {\n                Result.failure(e)\n            }\n        } else {\n            Result.failure(Exception(\"Root required for privileged copy\"))\n        }\n    }\n\n    override suspend fun getAppPaths(packageName: String): Result<List<String>> {\n        return try {\n            if (rootGateway.isRootAvailable()) {\n                val paths = rootGateway.getAppPaths(packageName)\n                if (paths.isNotEmpty()) Result.success(paths)\n                else Result.failure(Exception(\"No paths found\"))\n            } else {\n                Result.failure(Exception(\"Root required to fetch split paths reliably\"))\n            }\n        } catch (e: Exception) {\n            Result.failure(e)\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/data/security/BiometricHelper.kt",
    "content": "package com.valhalla.thor.data.security\n\nimport android.content.Context\nimport androidx.biometric.BiometricManager\nimport androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG\nimport androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL\n\n/**\n * Thin wrapper around [BiometricManager] that answers capability questions\n * without touching any UI. Lives in the data layer — no Compose dependency.\n */\nclass BiometricHelper(private val context: Context) {\n\n    private val allowedAuthenticators = BIOMETRIC_STRONG or DEVICE_CREDENTIAL\n\n    /** Returns true if the device can authenticate via biometric or device credential. */\n    fun canAuthenticate(): Boolean {\n        return BiometricManager.from(context)\n            .canAuthenticate(allowedAuthenticators) == BiometricManager.BIOMETRIC_SUCCESS\n    }\n\n    /** Returns true if the device has biometric hardware, regardless of enrollment state. */\n    fun hasHardware(): Boolean {\n        val status = BiometricManager.from(context).canAuthenticate(allowedAuthenticators)\n        return status != BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/data/source/local/ShellDataSource.kt",
    "content": "package com.valhalla.thor.data.source.local\n\nimport com.valhalla.superuser.Shell\nimport com.valhalla.thor.BuildConfig\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\n\n/**\n * A clean data source for executing shell commands.\n * No logic, no formatting, just execution.\n */\nclass ShellDataSource {\n\n    init {\n        // Initialize LibSu configuration once, cleanly.\n        // In a real app, you might want to do this in your Application class,\n        // but it's safe to ensure it's set here.\n        Shell.enableVerboseLogging = BuildConfig.DEBUG\n        Shell.setDefaultBuilder(Shell.Builder.create().setFlags(Shell.FLAG_MOUNT_MASTER))\n    }\n\n    suspend fun isRootAvailable(): Boolean = withContext(Dispatchers.IO) {\n        // LibSu caches this, so it's safe to call.\n        Shell.isAppGrantedRoot == true || Shell.shell.isRoot\n    }\n\n    /**\n     * Executes a command with Root privileges.\n     * Returns true if exit code is 0 (Success).\n     */\n    suspend fun executeRootCommand(command: String): Boolean = withContext(Dispatchers.IO) {\n        val result = Shell.cmd(command).exec()\n        result.isSuccess\n    }\n\n    /**\n     * Executes a command and returns the output (STDOUT).\n     * Useful for things like getting file paths or disk stats.\n     */\n    suspend fun executeRootCommandWithOutput(command: String): String =\n        withContext(Dispatchers.IO) {\n            val result = Shell.cmd(command).exec()\n            if (result.isSuccess) {\n                result.out.joinToString(\"\\n\")\n            } else {\n                throw Exception(\"Command failed: $command | Error: ${result.err.joinToString(\"\\n\")}\")\n            }\n        }\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/data/source/local/dhizuku/Dhizuku.kt",
    "content": "package com.valhalla.thor.data.source.local.dhizuku\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.pm.PackageManager\nimport android.os.IBinder\nimport com.valhalla.bypass.Bypass\nimport com.valhalla.thor.BuildConfig\nimport com.valhalla.thor.data.source.local.shizuku.Packages\nimport rikka.shizuku.ShizukuBinderWrapper\nimport rikka.shizuku.SystemServiceHelper\nimport com.rosan.dhizuku.api.Dhizuku as DhizukuAPI\n\n/**\n * Helper to interact with Dhizuku service using the actual API.\n */\nobject DhizukuHelper {\n\n    fun isDhizukuAvailable(): Boolean {\n        return try {\n            DhizukuAPI.isPermissionGranted()\n        } catch (_: Exception) {\n            false\n        }\n    }\n\n    fun getSystemService(serviceName: String): IBinder? {\n        return try {\n            val binder = SystemServiceHelper.getSystemService(serviceName)\n            DhizukuAPI.binderWrapper(binder)\n        } catch (_: Exception) {\n            null\n        }\n    }\n\n    private fun asInterface(className: String, original: IBinder): Any {\n        val clazz = Class.forName(\"$className\\$Stub\")\n        return Bypass.invoke(\n            clazz,\n            null,\n            \"asInterface\",\n            arrayOf(IBinder::class.java),\n            ShizukuBinderWrapper(original)\n        )\n    }\n\n    private fun asInterface(className: String, serviceName: String): Any? {\n        val binder = getSystemService(serviceName) ?: return null\n        return asInterface(className, binder)\n    }\n\n    fun forceStopApp(context: Context, packageName: String): Boolean {\n        val userId = Packages(context).myUserId\n        val result = execute(\"am force-stop --user $userId $packageName\")\n        if (result.first == 0) return true\n\n        // Fallback to reflection\n        return runCatching {\n            val am = asInterface(\"android.app.IActivityManager\", Context.ACTIVITY_SERVICE)\n                ?: return false\n            Bypass.invoke<Any?>(\n                am::class.java, am, \"forceStopPackage\", packageName, userId\n            )\n            true\n        }.getOrElse {\n            com.valhalla.thor.util.Logger.e(\n                \"DhizukuHelper\",\n                \"forceStopApp failed for $packageName\",\n                it\n            )\n            false\n        }\n    }\n\n    fun setAppDisabled(context: Context, packageName: String, disabled: Boolean): Boolean {\n        Packages(context).getApplicationInfoOrNull(packageName) ?: return false\n        val userId = Packages(context).myUserId\n        val command = if (disabled) {\n            \"pm disable-user --user $userId $packageName\"\n        } else {\n            \"pm enable --user $userId $packageName\"\n        }\n        val result = execute(command)\n\n        if (result.first != 0) {\n            // Fallback to Bypass reflection\n            runCatching {\n                val pm =\n                    asInterface(\"android.content.pm.IPackageManager\", \"package\") ?: return false\n                val newState = when {\n                    !disabled -> PackageManager.COMPONENT_ENABLED_STATE_ENABLED\n                    else -> PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER\n                }\n                Bypass.invoke<Any?>(\n                    pm.javaClass,\n                    pm,\n                    \"setApplicationEnabledSetting\",\n                    arrayOf(\n                        String::class.java,\n                        Int::class.javaPrimitiveType!!,\n                        Int::class.javaPrimitiveType!!,\n                        Int::class.javaPrimitiveType!!,\n                        String::class.java\n                    ),\n                    packageName,\n                    newState,\n                    0,\n                    userId,\n                    BuildConfig.APPLICATION_ID\n                )\n            }.onFailure {\n                com.valhalla.thor.util.Logger.e(\n                    \"DhizukuHelper\",\n                    \"setAppDisabled fallback failed for $packageName\",\n                    it\n                )\n            }\n        }\n        return Packages(context).isAppDisabled(packageName) == disabled\n    }\n\n    fun uninstallApp(packageName: String): Boolean {\n        return execute(\n            \"pm uninstall --user current ${\n                com.valhalla.superuser.ShellUtils.escapedString(\n                    packageName\n                )\n            }\"\n        ).first == 0\n    }\n\n    fun execute(command: String): Pair<Int, String?> = runCatching {\n        // Dhizuku 2.x supports newProcess for shell commands\n        val process = DhizukuAPI.newProcess(arrayOf(\"sh\", \"-c\", command), null, null)\n        val exitCode = process.waitFor()\n        val output = process.inputStream.bufferedReader().readText()\n        val error = process.errorStream.bufferedReader().readText()\n        exitCode to (output.ifBlank { error })\n    }.getOrElse { -1 to it.stackTraceToString() }\n\n    @SuppressLint(\"PrivateApi\")\n    fun clearCache(packageName: String): Boolean {\n        val reflectionResult = runCatching {\n            val pm = asInterface(\"android.content.pm.IPackageManager\", \"package\") ?: return false\n            val observerClass = Class.forName(\"android.content.pm.IPackageDataObserver\")\n\n            try {\n                Bypass.invoke<Any?>(\n                    pm.javaClass,\n                    pm,\n                    \"deleteApplicationCacheFiles\",\n                    arrayOf(String::class.java, observerClass),\n                    packageName,\n                    null /* IPackageDataObserver */\n                )\n            } catch (_: NoSuchMethodException) {\n                Bypass.invoke(\n                    pm.javaClass,\n                    pm,\n                    \"deleteApplicationCacheFilesAsUser\",\n                    arrayOf(String::class.java, Int::class.javaPrimitiveType!!, observerClass),\n                    packageName,\n                    android.os.Process.myUserHandle().hashCode(),\n                    null\n                )\n            }\n            true\n        }.getOrDefault(false)\n\n        if (reflectionResult) return true\n\n        // Fallback to shell rm -rf on common cache paths\n        val userId = android.os.Process.myUserHandle().hashCode()\n        val paths = listOf(\n            \"/data/data/$packageName/cache\",\n            \"/data/user/$userId/$packageName/cache\",\n            \"/sdcard/Android/data/$packageName/cache\"\n        )\n        val command = \"rm -rf ${paths.joinToString(\" \")}\"\n        return execute(command).first == 0\n    }\n\n    fun clearAppData(packageName: String): Boolean {\n        val result = execute(\"pm clear $packageName\")\n        if (result.first == 0) return true\n\n        // Fallback to reflection\n        return runCatching {\n            val pm = asInterface(\"android.content.pm.IPackageManager\", \"package\") ?: return false\n            val observerClass = Class.forName(\"android.content.pm.IPackageDataObserver\")\n            Bypass.invoke<Any?>(\n                pm.javaClass,\n                pm,\n                \"clearApplicationUserData\",\n                arrayOf(String::class.java, observerClass, Int::class.javaPrimitiveType!!),\n                packageName,\n                null,\n                android.os.Process.myUserHandle().hashCode()\n            )\n            true\n        }.getOrElse { false }\n    }\n\n    fun setAppSuspended(context: Context, packageName: String, suspended: Boolean): Boolean {\n        Packages(context).getApplicationInfoOrNull(packageName) ?: return false\n        val userId = Packages(context).myUserId\n\n        // Try reflection first through Dhizuku's binder wrapper to show proper branding\n        if (suspended && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {\n            val reflectionResult = runCatching {\n                val pm =\n                    asInterface(\"android.content.pm.IPackageManager\", \"package\") ?: return false\n                val dialogInfoClass = Class.forName(\"android.content.pm.SuspendDialogInfo\")\n                val builderClass = Class.forName(\"android.content.pm.SuspendDialogInfo\\$Builder\")\n                val dialogInfo = Bypass.newInstance<Any>(builderClass).let { b ->\n                    Bypass.invoke<Any>(builderClass, b, \"setTitle\", \"Thor\")\n                    Bypass.invoke<Any>(\n                        builderClass,\n                        b,\n                        \"setMessage\",\n                        \"This app has been suspended by Thor.\"\n                    )\n                    Bypass.invoke<Any>(builderClass, b, \"build\")\n                }\n\n                // In Dhizuku mode, we can try to use Thor's package name since it's a device owner proxy\n                val caller = com.valhalla.thor.BuildConfig.APPLICATION_ID\n\n                try {\n                    // Try Android 13+ (8 args)\n                    Bypass.invoke<Array<String>>(\n                        pm.javaClass, pm, \"setPackagesSuspendedAsUser\",\n                        arrayOf(\n                            Array<String>::class.java,\n                            Boolean::class.javaPrimitiveType!!,\n                            android.os.PersistableBundle::class.java,\n                            android.os.PersistableBundle::class.java,\n                            dialogInfoClass,\n                            Int::class.javaPrimitiveType!!,\n                            String::class.java,\n                            Int::class.javaPrimitiveType!!\n                        ),\n                        arrayOf(packageName), true, null, null, dialogInfo, 0, caller, userId\n                    )\n                } catch (_: NoSuchMethodException) {\n                    // Try Android 10-12 (7 args)\n                    Bypass.invoke<Array<String>>(\n                        pm.javaClass, pm, \"setPackagesSuspendedAsUser\",\n                        arrayOf(\n                            Array<String>::class.java,\n                            Boolean::class.javaPrimitiveType!!,\n                            android.os.PersistableBundle::class.java,\n                            android.os.PersistableBundle::class.java,\n                            dialogInfoClass,\n                            String::class.java,\n                            Int::class.javaPrimitiveType!!\n                        ),\n                        arrayOf(packageName), true, null, null, dialogInfo, caller, userId\n                    )\n                }\n                true\n            }.getOrDefault(false)\n\n            if (reflectionResult) return true\n        }\n\n        val command = if (suspended) {\n            \"pm suspend --user $userId $packageName\"\n        } else {\n            \"pm unsuspend --user $userId $packageName\"\n        }\n        val result = execute(command)\n        return result.first == 0\n    }\n\n    fun setAppRestricted(context: Context, packageName: String, restricted: Boolean): Boolean {\n        val result =\n            execute(\"appops set $packageName RUN_ANY_IN_BACKGROUND ${if (restricted) \"ignore\" else \"allow\"}\")\n        if (result.first == 0) return true\n\n        // Fallback to reflection\n        return runCatching {\n            val appops =\n                asInterface(\"com.android.internal.app.IAppOpsService\", Context.APP_OPS_SERVICE)\n                    ?: return false\n            val userId = Packages(context).myUserId\n            val uid = Packages(context).packageUid(packageName)\n            Bypass.invoke<Any?>(\n                appops::class.java,\n                appops,\n                \"setMode\",\n                arrayOf(\n                    Int::class.javaPrimitiveType!!,\n                    Int::class.javaPrimitiveType!!,\n                    String::class.java,\n                    Int::class.javaPrimitiveType!!\n                ),\n                Bypass.invoke<Int>(\n                    android.app.AppOpsManager::class.java,\n                    null,\n                    \"strOpToOp\",\n                    \"android:run_any_in_background\"\n                ),\n                uid,\n                packageName,\n                if (restricted) android.app.AppOpsManager.MODE_IGNORED else android.app.AppOpsManager.MODE_ALLOWED\n            )\n            true\n        }.getOrElse { false }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/data/source/local/dhizuku/DhizukuReflector.kt",
    "content": "package com.valhalla.thor.data.source.local.dhizuku\n\nimport android.content.Context\nimport com.valhalla.thor.BuildConfig\nimport com.valhalla.thor.util.Logger\n\nclass DhizukuReflector(\n    private val context: Context\n) {\n\n    fun forceStop(packageName: String): Boolean {\n        return try {\n            DhizukuHelper.forceStopApp(context, packageName)\n        } catch (e: Exception) {\n            Logger.e(\"DhizukuReflector\", \"forceStop failed\", e)\n            false\n        }\n    }\n\n    fun clearCache(packageName: String): Boolean {\n        return try {\n            DhizukuHelper.clearCache(packageName)\n        } catch (e: Exception) {\n            Logger.e(\"DhizukuReflector\", \"clearCache failed\", e)\n            false\n        }\n    }\n\n    fun clearData(packageName: String): Boolean {\n        return try {\n            DhizukuHelper.clearAppData(packageName)\n        } catch (e: Exception) {\n            Logger.e(\"DhizukuReflector\", \"clearData failed\", e)\n            false\n        }\n    }\n\n    fun setAppEnabled(packageName: String, enabled: Boolean): Boolean {\n        return try {\n            DhizukuHelper.setAppDisabled(context, packageName, !enabled)\n        } catch (e: Exception) {\n            if (BuildConfig.DEBUG)\n                Logger.e(\"DhizukuReflector\", \"setAppEnabled failed\", e)\n            false\n        }\n    }\n\n    fun uninstallApp(packageName: String): Boolean {\n        return try {\n            DhizukuHelper.uninstallApp(packageName)\n        } catch (_: Exception) {\n            false\n        }\n    }\n\n    fun setAppSuspended(packageName: String, suspended: Boolean): Boolean {\n        return try {\n            DhizukuHelper.setAppSuspended(context, packageName, suspended)\n        } catch (e: Exception) {\n            Logger.e(\"DhizukuReflector\", \"setAppSuspended failed\", e)\n            false\n        }\n    }\n\n    fun setAppRestricted(packageName: String, restricted: Boolean): Boolean {\n        return try {\n            DhizukuHelper.setAppRestricted(context, packageName, restricted)\n        } catch (e: Exception) {\n            Logger.e(\"DhizukuReflector\", \"setAppRestricted failed\", e)\n            false\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/data/source/local/room/AppDao.kt",
    "content": "package com.valhalla.thor.data.source.local.room\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport kotlinx.coroutines.flow.Flow\n\n@Dao\ninterface AppDao {\n    @Query(\"SELECT * FROM apps\")\n    fun getAllAppsFlow(): Flow<List<AppEntity>>\n\n    @Query(\"SELECT * FROM apps\")\n    suspend fun getAllApps(): List<AppEntity>\n\n    @Query(\"SELECT * FROM apps WHERE packageName = :packageName\")\n    suspend fun getApp(packageName: String): AppEntity?\n\n    @Insert(onConflict = OnConflictStrategy.REPLACE)\n    suspend fun insertApps(apps: List<AppEntity>)\n\n    @Query(\"DELETE FROM apps WHERE packageName = :packageName\")\n    suspend fun deleteApp(packageName: String)\n\n    @Transaction\n    suspend fun syncCache(toUpdate: List<AppEntity>, toDelete: List<String>) {\n        if (toUpdate.isNotEmpty()) insertApps(toUpdate)\n        toDelete.forEach { deleteApp(it) }\n    }\n\n    @Query(\"DELETE FROM apps\")\n    suspend fun clearAll()\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/data/source/local/room/AppDatabase.kt",
    "content": "package com.valhalla.thor.data.source.local.room\n\nimport androidx.room.Database\nimport androidx.room.RoomDatabase\nimport androidx.room.TypeConverters\n\n@Database(entities = [AppEntity::class], version = 2, exportSchema = true)\n@TypeConverters(AppTypeConverters::class)\nabstract class AppDatabase : RoomDatabase() {\n    abstract fun appDao(): AppDao\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/data/source/local/room/AppEntity.kt",
    "content": "package com.valhalla.thor.data.source.local.room\n\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\nimport com.valhalla.thor.domain.model.AppInfo\n\n@Entity(tableName = \"apps\")\ndata class AppEntity(\n    @PrimaryKey val packageName: String,\n    val appName: String?,\n    val versionName: String?,\n    val versionCode: Int,\n    val minSdk: Int,\n    val targetSdk: Int,\n    val isSystem: Boolean,\n    val installerPackageName: String?,\n    val publicSourceDir: String?,\n    val splitPublicSourceDirs: List<String>,\n    val enabled: Boolean,\n    val dataDir: String?,\n    val nativeLibraryDir: String?,\n    val deviceProtectedDataDir: String?,\n    val sharedLibraryFiles: List<String>?,\n    val obbFilePath: String?,\n    val sourceDir: String?,\n    val sharedDataDir: String,\n    val lastUpdateTime: Long,\n    val firstInstallTime: Long,\n    val isDebuggable: Boolean,\n    val isSuspended: Boolean\n) {\n    fun toDomain(): AppInfo {\n        return AppInfo(\n            appName = appName,\n            packageName = packageName,\n            versionName = versionName,\n            versionCode = versionCode,\n            minSdk = minSdk,\n            targetSdk = targetSdk,\n            isSystem = isSystem,\n            installerPackageName = installerPackageName,\n            publicSourceDir = publicSourceDir,\n            splitPublicSourceDirs = splitPublicSourceDirs,\n            enabled = enabled,\n            dataDir = dataDir,\n            nativeLibraryDir = nativeLibraryDir,\n            deviceProtectedDataDir = deviceProtectedDataDir,\n            sharedLibraryFiles = sharedLibraryFiles,\n            obbFilePath = obbFilePath,\n            sourceDir = sourceDir,\n            sharedDataDir = sharedDataDir,\n            lastUpdateTime = lastUpdateTime,\n            firstInstallTime = firstInstallTime,\n            isDebuggable = isDebuggable,\n            isSuspended = isSuspended\n        )\n    }\n\n    companion object {\n        fun fromDomain(appInfo: AppInfo): AppEntity {\n            return AppEntity(\n                packageName = appInfo.packageName,\n                appName = appInfo.appName,\n                versionName = appInfo.versionName,\n                versionCode = appInfo.versionCode,\n                minSdk = appInfo.minSdk,\n                targetSdk = appInfo.targetSdk,\n                isSystem = appInfo.isSystem,\n                installerPackageName = appInfo.installerPackageName,\n                publicSourceDir = appInfo.publicSourceDir,\n                splitPublicSourceDirs = appInfo.splitPublicSourceDirs,\n                enabled = appInfo.enabled,\n                dataDir = appInfo.dataDir,\n                nativeLibraryDir = appInfo.nativeLibraryDir,\n                deviceProtectedDataDir = appInfo.deviceProtectedDataDir,\n                sharedLibraryFiles = appInfo.sharedLibraryFiles,\n                obbFilePath = appInfo.obbFilePath,\n                sourceDir = appInfo.sourceDir,\n                sharedDataDir = appInfo.sharedDataDir,\n                lastUpdateTime = appInfo.lastUpdateTime,\n                firstInstallTime = appInfo.firstInstallTime,\n                isDebuggable = appInfo.isDebuggable,\n                isSuspended = appInfo.isSuspended\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/data/source/local/room/AppTypeConverters.kt",
    "content": "package com.valhalla.thor.data.source.local.room\n\nimport androidx.room.TypeConverter\nimport kotlinx.serialization.json.Json\n\nclass AppTypeConverters {\n    @TypeConverter\n    fun fromStringList(value: List<String>?): String? {\n        return value?.let { Json.encodeToString(it) }\n    }\n\n    @TypeConverter\n    fun toStringList(value: String?): List<String>? {\n        return value?.let { Json.decodeFromString(it) }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/data/source/local/root/RootMain.kt",
    "content": "package com.valhalla.thor.data.source.local.root\n\nimport android.os.Build\nimport android.os.IBinder\nimport com.valhalla.thor.BuildConfig\n\n/**\n * Entry point for one-shot Root commands.\n * This runs in a separate process as root, bypassing Hidden API restrictions.\n */\nobject RootMain {\n    @JvmStatic\n    fun main(args: Array<String>) {\n        if (args.isEmpty()) return\n        \n        try {\n            when (args[0]) {\n                \"suspend\" -> {\n                    val packageName = args[1]\n                    val suspended = args[2].toBoolean()\n                    setAppSuspended(packageName, suspended)\n                }\n                \"clear-data\" -> {\n                    val packageName = args[1]\n                    clearData(packageName)\n                }\n            }\n        } catch (e: Exception) {\n            e.printStackTrace()\n            System.exit(1)\n        }\n        System.exit(0)\n    }\n\n    private fun setAppSuspended(packageName: String, suspended: Boolean) {\n        val pmStub = Class.forName(\"android.content.pm.IPackageManager\\$Stub\")\n        val serviceManager = Class.forName(\"android.os.ServiceManager\")\n        val getService = serviceManager.getMethod(\"getService\", String::class.java)\n        val binder = getService.invoke(null, \"package\") as IBinder\n        val asInterface = pmStub.getMethod(\"asInterface\", IBinder::class.java)\n        val pm = asInterface.invoke(null, binder)\n        val pmClass = Class.forName(\"android.content.pm.IPackageManager\")\n\n        val userId = 0 // Root\n\n        if (suspended && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n            val dialogInfoClass = Class.forName(\"android.content.pm.SuspendDialogInfo\")\n            val builderClass = Class.forName(\"android.content.pm.SuspendDialogInfo\\$Builder\")\n            val builder = builderClass.getDeclaredConstructor().newInstance()\n            \n            builderClass.getMethod(\"setTitle\", CharSequence::class.java).invoke(builder, \"Thor\")\n            builderClass.getMethod(\"setMessage\", CharSequence::class.java).invoke(builder, \"This app has been suspended by Thor.\")\n            val dialogInfo = builderClass.getMethod(\"build\").invoke(builder)\n\n            val caller = \"com.android.shell\" // Use shell identity for better compatibility from root\n\n            try {\n                // Android 13+ (8 args)\n                val method = pmClass.getDeclaredMethod(\n                    \"setPackagesSuspendedAsUser\",\n                    Array<String>::class.java,\n                    Boolean::class.javaPrimitiveType,\n                    android.os.PersistableBundle::class.java,\n                    android.os.PersistableBundle::class.java,\n                    dialogInfoClass,\n                    Int::class.javaPrimitiveType,\n                    String::class.java,\n                    Int::class.javaPrimitiveType\n                )\n                method.invoke(pm, arrayOf(packageName), true, null, null, dialogInfo, 0, caller, userId)\n            } catch (e: NoSuchMethodException) {\n                // Android 10-12 (7 args)\n                val method = pmClass.getDeclaredMethod(\n                    \"setPackagesSuspendedAsUser\",\n                    Array<String>::class.java,\n                    Boolean::class.javaPrimitiveType,\n                    android.os.PersistableBundle::class.java,\n                    android.os.PersistableBundle::class.java,\n                    dialogInfoClass,\n                    String::class.java,\n                    Int::class.javaPrimitiveType\n                )\n                method.invoke(pm, arrayOf(packageName), true, null, null, dialogInfo, caller, userId)\n            }\n        } else {\n            // Unsuspend logic\n            val suspendDialogInfoClass = Class.forName(\"android.content.pm.SuspendDialogInfo\")\n            val method = pmClass.getDeclaredMethod(\n                \"setPackagesSuspendedAsUser\",\n                Array<String>::class.java,\n                Boolean::class.javaPrimitiveType,\n                android.os.PersistableBundle::class.java,\n                android.os.PersistableBundle::class.java,\n                suspendDialogInfoClass,\n                String::class.java,\n                Int::class.javaPrimitiveType\n            )\n            method.invoke(pm, arrayOf(packageName), suspended, null, null, null, \"com.android.shell\", userId)\n        }\n    }\n\n    private fun clearData(packageName: String) {\n        val pmStub = Class.forName(\"android.content.pm.IPackageManager\\$Stub\")\n        val serviceManager = Class.forName(\"android.os.ServiceManager\")\n        val getService = serviceManager.getMethod(\"getService\", String::class.java)\n        val binder = getService.invoke(null, \"package\") as IBinder\n        val asInterface = pmStub.getMethod(\"asInterface\", IBinder::class.java)\n        val pm = asInterface.invoke(null, binder)\n        val pmClass = Class.forName(\"android.content.pm.IPackageManager\")\n\n        val method = pmClass.getDeclaredMethod(\"clearApplicationUserData\", String::class.java, Class.forName(\"android.content.pm.IPackageDataObserver\"), Int::class.javaPrimitiveType)\n        method.invoke(pm, packageName, null, 0)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/data/source/local/shizuku/PackageManagerExt.kt",
    "content": "package com.valhalla.thor.data.source.local.shizuku\n\nimport android.content.pm.PackageInfo\nimport android.content.pm.PackageManager\nimport android.content.pm.PackageManager.NameNotFoundException\nimport android.os.Build\nimport com.valhalla.thor.domain.model.AppInfo\n\n\nprivate fun PackageManager.getUninstalledPackages(installedPackages: List<PackageInfo>): List<PackageInfo> {\n    val flags = PackageManager.MATCH_UNINSTALLED_PACKAGES\n\n    // Get uninstalled packages + installed packages\n    val uninstalledPackages = getPackages(flags).toSet()\n\n    val installed = installedPackages.map { it.packageName }\n    val minus = uninstalledPackages.filter { !installed.contains(it.packageName) }\n\n    // Return only apps that have been uninstalled\n    return minus.toList()\n}\n\nfun PackageManager.getAllPackagesInfo(): List<AppInfo> {\n    val installedPackages = getInstalledPackages()\n    val uninstalledPackages = getUninstalledPackages(installedPackages)\n\n    val all = (uninstalledPackages.map { app ->\n        val appInfo = app.applicationInfo\n        if (appInfo != null)\n            AppInfo.mapToAppInfo(\n                packInfo = app,\n                appInfo = appInfo,\n                pm = this\n            )\n        else null\n    } + installedPackages.map { app ->\n        val appInfo = app.applicationInfo\n        if (appInfo != null)\n            AppInfo.mapToAppInfo(\n                packInfo = app,\n                appInfo = appInfo,\n                pm = this\n            )\n        else null\n    }).filterNotNull()\n\n    return all\n}\n\nfun PackageManager.getInstalledPackages(): List<PackageInfo> {\n    val flags = PackageManager.GET_META_DATA\n    return getPackages(flags)\n}\n\nprivate fun PackageManager.getPackages(flags: Int): List<PackageInfo> {\n    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n        this.getInstalledPackages(\n            PackageManager.PackageInfoFlags.of(flags.toLong())\n        )\n    } else {\n        this.getInstalledPackages(flags)\n    }\n}\n\nfun PackageManager.getInfoForPackage(\n    packageName: String,\n): PackageInfo? {\n    return try {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n            this.getPackageInfo(\n                packageName,\n                PackageManager.PackageInfoFlags.of(PackageManager.GET_META_DATA.toLong())\n            )\n        } else {\n            this.getPackageInfo(\n                packageName,\n                PackageManager.GET_META_DATA\n            )\n        }\n    } catch (e: NameNotFoundException) {\n        null\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/data/source/local/shizuku/Packages.kt",
    "content": "package com.valhalla.thor.data.source.local.shizuku\n\nimport android.app.ActivityManager\nimport android.app.AppOpsManager\nimport android.content.Context\nimport android.content.pm.ApplicationInfo\nimport android.content.pm.PackageManager\nimport android.os.Process\nimport androidx.core.content.getSystemService\nimport com.valhalla.bypass.Bypass\n\nclass Packages(private val app: Context) {\n\n    val myUserId get() = Process.myUserHandle().hashCode()\n\n    fun packageUri(packageName: String) = \"package:$packageName\"\n\n    fun packageUid(packageName: String) = if (Targets.T) app.packageManager.getPackageUid(\n        packageName,\n        PackageManager.PackageInfoFlags.of(PackageManager.MATCH_UNINSTALLED_PACKAGES.toLong())\n    ) else app.packageManager.getPackageUid(packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES)\n\n    fun getInstalledApplications(flags: Int = PackageManager.MATCH_UNINSTALLED_PACKAGES): List<ApplicationInfo> =\n        if (Targets.T) app.packageManager.getInstalledApplications(\n            PackageManager.ApplicationInfoFlags.of(flags.toLong())\n        )\n        else app.packageManager.getInstalledApplications(flags)\n\n    fun getUnhiddenPackageInfoOrNull(\n        packageName: String, flags: Int = PackageManager.MATCH_UNINSTALLED_PACKAGES\n    ) = runCatching {\n        if (Targets.T) app.packageManager.getPackageInfo(\n            packageName, PackageManager.PackageInfoFlags.of(flags.toLong())\n        )\n        else app.packageManager.getPackageInfo(packageName, flags)\n    }.getOrNull()\n\n    fun getApplicationInfoOrNull(\n        packageName: String, flags: Int = PackageManager.MATCH_UNINSTALLED_PACKAGES\n    ) = runCatching {\n        if (Targets.T) app.packageManager.getApplicationInfo(\n            packageName, PackageManager.ApplicationInfoFlags.of(flags.toLong())\n        )\n        else app.packageManager.getApplicationInfo(packageName, flags)\n    }.getOrNull()\n\n    fun isAppDisabled(packageName: String): Boolean =\n        getApplicationInfoOrNull(packageName)?.enabled?.not() ?: false\n\n    fun isAppHidden(packageName: String): Boolean = getApplicationInfoOrNull(packageName)?.let {\n        (ApplicationInfo::class.java.getField(\"privateFlags\").get(it) as Int) and 1 == 1\n    } ?: false\n\n    fun isAppStopped(packageName: String): Boolean =\n        getApplicationInfoOrNull(packageName)?.run { flags and ApplicationInfo.FLAG_STOPPED == ApplicationInfo.FLAG_STOPPED }\n            ?: false\n\n    fun isAppUninstalled(packageName: String): Boolean =\n        getApplicationInfoOrNull(packageName)?.run { flags and ApplicationInfo.FLAG_INSTALLED != ApplicationInfo.FLAG_INSTALLED }\n            ?: true\n\n    fun isPrivilegedApp(packageName: String): Boolean = getApplicationInfoOrNull(packageName)?.let {\n        (ApplicationInfo::class.java.getField(\"privateFlags\").get(it) as Int) and 8 == 8\n    } ?: false\n\n    fun canUninstallNormally(packageName: String): Boolean =\n        getApplicationInfoOrNull(packageName)?.sourceDir?.startsWith(\"/data\") ?: false\n\n    fun forceStopApp(packageName: String): Boolean = runCatching {\n        app.getSystemService<ActivityManager>()?.let {\n            Bypass.invoke<Any?>(\n                it::class.java,\n                it,\n                \"forceStopPackage\",\n                packageName\n            )\n        }\n        true\n    }.getOrElse {\n        it.printStackTrace()\n        false\n    }\n\n    fun setAppDisabled(packageName: String, disabled: Boolean): Boolean {\n        getApplicationInfoOrNull(packageName) ?: return false\n        if (disabled) forceStopApp(packageName)\n        runCatching {\n            val newState = when {\n                !disabled -> PackageManager.COMPONENT_ENABLED_STATE_ENABLED\n                else -> PackageManager.COMPONENT_ENABLED_STATE_DISABLED\n            }\n            app.packageManager.setApplicationEnabledSetting(packageName, newState, 0)\n        }.onFailure {\n            it.printStackTrace()\n        }\n        return isAppDisabled(packageName) == disabled\n    }\n\n    fun setAppRestricted(packageName: String, restricted: Boolean): Boolean = runCatching {\n        app.getSystemService<AppOpsManager>()?.let {\n            Bypass.invoke<Any?>(\n                it::class.java,\n                it,\n                \"setMode\",\n                \"android:run_any_in_background\",\n                packageUid(packageName),\n                packageName,\n                if (restricted) AppOpsManager.MODE_IGNORED else AppOpsManager.MODE_ALLOWED\n            )\n        }\n        true\n    }.getOrElse {\n        it.printStackTrace()\n        false\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/data/source/local/shizuku/Shizuku.kt",
    "content": "package com.valhalla.thor.data.source.local.shizuku\n\nimport android.content.Context\nimport android.os.IBinder\nimport android.os.ParcelFileDescriptor\nimport com.valhalla.bypass.Bypass\nimport moe.shizuku.server.IShizukuService\nimport rikka.shizuku.Shizuku\nimport rikka.shizuku.ShizukuBinderWrapper\nimport rikka.shizuku.SystemServiceHelper\nimport java.text.NumberFormat\nimport java.util.Locale\n\nobject Shizuku {\n\n    val isRoot get() = Shizuku.getUid() == 0\n\n    private fun asInterface(className: String, original: IBinder): Any {\n        val clazz = Class.forName(\"$className\\$Stub\")\n        return Bypass.invoke(\n            clazz,\n            null,\n            \"asInterface\",\n            arrayOf(IBinder::class.java),\n            ShizukuBinderWrapper(original)\n        )\n    }\n\n    private fun asInterface(className: String, serviceName: String): Any =\n        asInterface(className, SystemServiceHelper.getSystemService(serviceName))\n\n    val lockScreen\n        get() = runCatching {\n            execute(\"input keyevent 26\").first == 0\n        }.getOrElse {\n            it.printStackTrace()\n            false\n        }\n\n    fun forceStopApp(context: Context, packageName: String): Boolean {\n        val userId = Packages(context).myUserId\n        val result = execute(\"am force-stop --user $userId $packageName\")\n        if (result.first == 0) return true\n\n        // Fallback to reflection\n        return runCatching {\n            val am = asInterface(\"android.app.IActivityManager\", Context.ACTIVITY_SERVICE)\n            Bypass.invoke<Any?>(\n                am::class.java, am, \"forceStopPackage\", packageName, userId\n            )\n            true\n        }.getOrElse {\n            it.printStackTrace()\n            false\n        }\n    }\n\n    fun setAppDisabled(context: Context, packageName: String, disabled: Boolean): Boolean {\n        Packages(context).getApplicationInfoOrNull(packageName) ?: return false\n        val userId = Packages(context).myUserId\n        val command = if (disabled) {\n            \"pm disable-user --user $userId $packageName\"\n        } else {\n            \"pm enable --user $userId $packageName\"\n        }\n        val result = execute(command)\n\n        if (result.first != 0) {\n            // Fallback to Bypass reflection\n            runCatching {\n                val pm = asInterface(\"android.content.pm.IPackageManager\", \"package\")\n                val newState = when {\n                    !disabled -> android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED\n                    isRoot -> android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED\n                    else -> android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER\n                }\n                Bypass.invoke<Any?>(\n                    pm.javaClass,\n                    pm,\n                    \"setApplicationEnabledSetting\",\n                    arrayOf(\n                        String::class.java,\n                        Int::class.javaPrimitiveType!!,\n                        Int::class.javaPrimitiveType!!,\n                        Int::class.javaPrimitiveType!!,\n                        String::class.java\n                    ),\n                    packageName,\n                    newState,\n                    0,\n                    userId,\n                    com.valhalla.thor.BuildConfig.APPLICATION_ID\n                )\n            }.onFailure { it.printStackTrace() }\n        }\n\n        return Packages(context).isAppDisabled(packageName) == disabled\n    }\n\n    fun setAppSuspended(context: Context, packageName: String, suspended: Boolean): Boolean {\n        Packages(context).getApplicationInfoOrNull(packageName) ?: return false\n        val userId = Packages(context).myUserId\n\n        // Try reflection first for both Root and Shizuku to show proper branding\n        if (suspended && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {\n            val reflectionResult = runCatching {\n                val pm = asInterface(\"android.content.pm.IPackageManager\", \"package\")\n                val dialogInfoClass = Class.forName(\"android.content.pm.SuspendDialogInfo\")\n                val builderClass = Class.forName(\"android.content.pm.SuspendDialogInfo\\$Builder\")\n                val dialogInfo = Bypass.newInstance<Any>(builderClass).let { b ->\n                    Bypass.invoke<Any>(builderClass, b, \"setTitle\", \"Thor\")\n                    Bypass.invoke<Any>(\n                        builderClass,\n                        b,\n                        \"setMessage\",\n                        \"This app has been suspended by Thor.\"\n                    )\n                    Bypass.invoke<Any>(builderClass, b, \"build\")\n                }\n\n                val caller =\n                    if (isRoot) com.valhalla.thor.BuildConfig.APPLICATION_ID else \"com.android.shell\"\n\n                try {\n                    // Try Android 13+ (8 args)\n                    Bypass.invoke<Array<String>>(\n                        pm.javaClass,\n                        pm,\n                        \"setPackagesSuspendedAsUser\",\n                        arrayOf(\n                            Array<String>::class.java,\n                            Boolean::class.javaPrimitiveType!!,\n                            android.os.PersistableBundle::class.java,\n                            android.os.PersistableBundle::class.java,\n                            dialogInfoClass,\n                            Int::class.javaPrimitiveType!!,\n                            String::class.java,\n                            Int::class.javaPrimitiveType!!\n                        ),\n                        arrayOf(packageName),\n                        true,\n                        null, null,\n                        dialogInfo,\n                        0,\n                        caller,\n                        userId\n                    )\n                } catch (_: NoSuchMethodException) {\n                    // Try Android 10-12 (7 args)\n                    Bypass.invoke<Array<String>>(\n                        pm.javaClass,\n                        pm,\n                        \"setPackagesSuspendedAsUser\",\n                        arrayOf(\n                            Array<String>::class.java,\n                            Boolean::class.javaPrimitiveType!!,\n                            android.os.PersistableBundle::class.java,\n                            android.os.PersistableBundle::class.java,\n                            dialogInfoClass,\n                            String::class.java,\n                            Int::class.javaPrimitiveType!!\n                        ),\n                        arrayOf(packageName),\n                        true,\n                        null, null,\n                        dialogInfo,\n                        caller,\n                        userId\n                    )\n                }\n                true\n            }.getOrDefault(false)\n\n            if (reflectionResult) return true\n        }\n\n        val command = if (suspended) {\n            \"pm suspend --user $userId $packageName\"\n        } else {\n            \"pm unsuspend --user $userId $packageName\"\n        }\n        return execute(command).first == 0\n    }\n\n    fun clearCache(packageName: String): Boolean {\n        val reflectionResult = runCatching {\n            val pm = asInterface(\"android.content.pm.IPackageManager\", \"package\")\n            val observerClass = Class.forName(\"android.content.pm.IPackageDataObserver\")\n\n            try {\n                Bypass.invoke<Any?>(\n                    pm.javaClass,\n                    pm,\n                    \"deleteApplicationCacheFiles\",\n                    arrayOf(String::class.java, observerClass),\n                    packageName,\n                    null\n                )\n            } catch (_: NoSuchMethodException) {\n                Bypass.invoke<Any?>(\n                    pm.javaClass,\n                    pm,\n                    \"deleteApplicationCacheFilesAsUser\",\n                    arrayOf(String::class.java, Int::class.javaPrimitiveType!!, observerClass),\n                    packageName,\n                    android.os.Process.myUserHandle().hashCode(),\n                    null\n                )\n            }\n            true\n        }.getOrDefault(false)\n\n        if (reflectionResult) return true\n\n        // Fallback to shell rm -rf on common cache paths\n        val userId = android.os.Process.myUserHandle().hashCode()\n        val paths = listOf(\n            \"/data/data/$packageName/cache\",\n            \"/data/user/$userId/$packageName/cache\",\n            \"/sdcard/Android/data/$packageName/cache\"\n        )\n        val command = \"rm -rf ${paths.joinToString(\" \")}\"\n        return execute(command).first == 0\n    }\n\n    fun clearAppData(packageName: String): Boolean {\n        val result = execute(\"pm clear $packageName\")\n        if (result.first == 0) return true\n\n        // Fallback to reflection\n        return runCatching {\n            val pm = asInterface(\"android.content.pm.IPackageManager\", \"package\")\n            val observerClass = Class.forName(\"android.content.pm.IPackageDataObserver\")\n            Bypass.invoke<Any?>(\n                pm.javaClass,\n                pm,\n                \"clearApplicationUserData\",\n                arrayOf(String::class.java, observerClass, Int::class.javaPrimitiveType!!),\n                packageName,\n                null,\n                android.os.Process.myUserHandle().hashCode()\n            )\n            true\n        }.getOrElse { false }\n    }\n\n    fun getTotalCacheSizeWithShizuku(): Long {\n        var totalCacheBytes = 0L\n        val result = execute(\"dumpsys diskstats\")\n\n        result.second?.lines()?.forEach { line ->\n            val trimmedLine = line.trim()\n            if (trimmedLine.startsWith(\"Cache Size:\")) {\n                try {\n                    val sizeString = trimmedLine.substringAfter(\":\").trim()\n                    val bytes =\n                        NumberFormat.getNumberInstance(Locale.US).parse(sizeString)?.toLong() ?: 0L\n                    totalCacheBytes += bytes\n                } catch (e: Exception) {\n                    e.printStackTrace()\n                }\n            }\n        }\n        return totalCacheBytes\n    }\n\n    fun setAppRestricted(context: Context, packageName: String, restricted: Boolean): Boolean {\n        val result =\n            execute(\"appops set $packageName RUN_ANY_IN_BACKGROUND ${if (restricted) \"ignore\" else \"allow\"}\")\n        if (result.first == 0) return true\n\n        // Fallback to reflection\n        return runCatching {\n            val appops =\n                asInterface(\"com.android.internal.app.IAppOpsService\", Context.APP_OPS_SERVICE)\n            val uid = Packages(context).packageUid(packageName)\n            Bypass.invoke<Any?>(\n                appops::class.java,\n                appops,\n                \"setMode\",\n                arrayOf(\n                    Int::class.javaPrimitiveType!!,\n                    Int::class.javaPrimitiveType!!,\n                    String::class.java,\n                    Int::class.javaPrimitiveType!!\n                ),\n                Bypass.invoke<Int>(\n                    android.app.AppOpsManager::class.java,\n                    null,\n                    \"strOpToOp\",\n                    \"android:run_any_in_background\"\n                ),\n                uid,\n                packageName,\n                if (restricted) android.app.AppOpsManager.MODE_IGNORED else android.app.AppOpsManager.MODE_ALLOWED\n            )\n            true\n        }.getOrElse { false }\n    }\n\n    fun uninstallApp(context: Context, packageName: String): Boolean =\n        execute(\"pm ${if (Packages(context).canUninstallNormally(packageName)) \"uninstall\" else \"uninstall --user current\"} $packageName\").first == 0\n\n    fun reinstallApp(packageName: String): Boolean =\n        execute(\"pm install-existing --user current $packageName\").first == 0\n\n    fun execute(command: String, root: Boolean = isRoot): Pair<Int, String?> = runCatching {\n        IShizukuService.Stub.asInterface(Shizuku.getBinder())\n            .newProcess(arrayOf(if (root) \"su\" else \"sh\"), null, null)\n            .run {\n                ParcelFileDescriptor.AutoCloseOutputStream(outputStream).use {\n                    it.write(command.toByteArray())\n                }\n                waitFor() to inputStream.text.ifBlank { errorStream.text }.also { destroy() }\n            }\n    }.getOrElse { -1 to it.stackTraceToString() }\n\n    private val ParcelFileDescriptor.text\n        get() = ParcelFileDescriptor.AutoCloseInputStream(this)\n            .use { it.bufferedReader().readText() }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/data/source/local/shizuku/ShizukuPackageInstallerUtils.kt",
    "content": "package com.valhalla.thor.data.source.local.shizuku\n\nimport android.content.pm.IPackageInstaller\nimport android.content.pm.PackageInstaller\nimport android.os.Build\nimport android.os.IBinder\nimport android.os.IInterface\nimport com.valhalla.bypass.Bypass\nimport rikka.shizuku.ShizukuBinderWrapper\nimport rikka.shizuku.SystemServiceHelper\n\n/**\n * Taken from <a href=\"https://github.com/depau/fdroid_shizuku_privileged_extension/blob/main/app/src/main/java/org/fdroid/fdroid/privileged/ShizukuPackageInstallerUtils.kt\">FDroid Priv</a>.\n */\nobject ShizukuPackageInstallerUtils {\n\n    private fun asInterface(className: String, binder: IBinder): Any {\n        val clazz = Class.forName(\"$className\\$Stub\")\n        return Bypass.invoke(\n            clazz,\n            null,\n            \"asInterface\",\n            arrayOf(IBinder::class.java),\n            ShizukuBinderWrapper(binder)\n        )\n    }\n\n    fun getPrivilegedPackageInstaller(): IPackageInstaller {\n        val pmBinder = SystemServiceHelper.getSystemService(\"package\")\n        val pm = asInterface(\"android.content.pm.IPackageManager\", pmBinder)\n\n        val packageInstallerProxy = Bypass.invoke<Any>(\n            pm.javaClass,\n            pm,\n            \"getPackageInstaller\"\n        )\n\n        val binder = (packageInstallerProxy as IInterface).asBinder()\n        return asInterface(\"android.content.pm.IPackageInstaller\", binder) as IPackageInstaller\n    }\n\n    /**\n     * Taken from https://github.com/RikkaApps/Shizuku-API/blob/01e08879d58a5cb11a333535c6ddce9f7b7c88ff/demo/src/main/java/rikka/shizuku/demo/util/PackageInstallerUtils.java#L15\n     * @author RikkaW\n     */\n    fun createPackageInstaller(\n        installer: IPackageInstaller?,\n        installerPackageName: String?,\n        userId: Int\n    ): PackageInstaller {\n        val iPackageInstallerClass = Class.forName(\"android.content.pm.IPackageInstaller\")\n        return if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) {\n            Bypass.newInstance(\n                PackageInstaller::class.java,\n                arrayOf(\n                    iPackageInstallerClass,\n                    String::class.java,\n                    String::class.java,\n                    Int::class.javaPrimitiveType!!\n                ),\n                installer, installerPackageName, null, userId\n            )\n        } else {\n            Bypass.newInstance(\n                PackageInstaller::class.java,\n                arrayOf(iPackageInstallerClass, String::class.java, Int::class.javaPrimitiveType!!),\n                installer, installerPackageName, userId\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/data/source/local/shizuku/ShizukuReflector.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage com.valhalla.thor.data.source.local.shizuku\n\nimport android.annotation.SuppressLint\nimport android.app.PendingIntent\nimport android.content.Context\nimport android.content.Intent\nimport android.content.pm.ApplicationInfo\nimport android.content.pm.IPackageInstaller\nimport android.content.pm.PackageInstaller\nimport android.content.pm.PackageManager\nimport android.os.Build\nimport com.valhalla.bypass.Bypass\nimport com.valhalla.thor.BuildConfig\nimport com.valhalla.thor.util.Logger\n\n@SuppressLint(\"PrivateApi\")\nclass ShizukuReflector(\n    val context: Context\n) {\n\n    fun clearCache(packageName: String): Boolean {\n        return try {\n            Shizuku.clearCache(packageName)\n        } catch (e: Exception) {\n            if (BuildConfig.DEBUG)\n                Logger.e(\"ShizukuReflector\", \"clearCache failed: ${e.message}\")\n            false\n        }\n    }\n\n    fun clearData(packageName: String): Boolean {\n        return try {\n            Shizuku.clearAppData(packageName)\n        } catch (e: Exception) {\n            if (BuildConfig.DEBUG)\n                Logger.e(\"ShizukuReflector\", \"clearData failed: ${e.message}\")\n            false\n        }\n    }\n\n    fun forceStop(packageName: String): Boolean {\n        return try {\n            Shizuku.forceStopApp(context, packageName)\n        } catch (e: Exception) {\n            if (BuildConfig.DEBUG)\n                Logger.e(\"ShizukuReflector\", \"forceStop failed\", e)\n            false\n        }\n    }\n\n    fun setAppEnabled(packageName: String, enabled: Boolean): Boolean {\n        return try {\n            Shizuku.setAppDisabled(context, packageName, !enabled)\n        } catch (e: Exception) {\n            if (BuildConfig.DEBUG)\n                Logger.e(\"ShizukuReflector\", \"setAppEnabled failed\", e)\n            false\n        }\n    }\n\n    fun packageUid(packageName: String) =\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) context.packageManager.getPackageUid(\n            packageName,\n            PackageManager.PackageInfoFlags.of(PackageManager.MATCH_UNINSTALLED_PACKAGES.toLong())\n        ) else context.packageManager.getPackageUid(\n            packageName,\n            PackageManager.MATCH_UNINSTALLED_PACKAGES\n        )\n\n\n    fun getApplicationInfoOrNull(\n        packageName: String, flags: Int = PackageManager.MATCH_UNINSTALLED_PACKAGES\n    ) = runCatching {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) context.packageManager.getApplicationInfo(\n            packageName, PackageManager.ApplicationInfoFlags.of(flags.toLong())\n        )\n        else context.packageManager.getApplicationInfo(packageName, flags)\n    }.getOrNull()\n\n    fun isAppDisabled(packageName: String): Boolean =\n        getApplicationInfoOrNull(packageName)?.enabled?.not() ?: false\n\n    fun isAppHidden(packageName: String): Boolean = getApplicationInfoOrNull(packageName)?.let {\n        (Bypass.getField<Int>(it, \"privateFlags\")) and 1 == 1\n    } ?: false\n\n    fun isAppStopped(packageName: String): Boolean =\n        getApplicationInfoOrNull(packageName)?.run { flags and ApplicationInfo.FLAG_STOPPED == ApplicationInfo.FLAG_STOPPED }\n            ?: false\n\n    fun isAppUninstalled(packageName: String): Boolean =\n        getApplicationInfoOrNull(packageName)?.run { flags and ApplicationInfo.FLAG_INSTALLED != ApplicationInfo.FLAG_INSTALLED }\n            ?: true\n\n    fun isPrivilegedApp(packageName: String): Boolean = getApplicationInfoOrNull(packageName)?.let {\n        (Bypass.getField<Int>(it, \"privateFlags\")) and 8 == 8\n    } ?: false\n\n    fun setAppRestricted(packageName: String, restricted: Boolean): Boolean =\n        Shizuku.setAppRestricted(context, packageName, restricted)\n\n    fun setAppSuspended(packageName: String, suspended: Boolean): Boolean =\n        Shizuku.setAppSuspended(context, packageName, suspended)\n\n    fun uninstallApp(packageName: String, resetToFactory: Boolean = false): Boolean {\n        return runCatching {\n            val packageInfo = context.packageManager.getInfoForPackage(packageName) ?: return false\n            val isSystem =\n                (packageInfo.applicationInfo!!.flags and ApplicationInfo.FLAG_SYSTEM) != 0\n            val hasUpdates =\n                (packageInfo.applicationInfo!!.flags and ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0\n\n            val shouldReset = resetToFactory && isSystem && hasUpdates\n            val broadcastIntent = Intent(\"io.github.samolego.canta.UNINSTALL_RESULT_ACTION\")\n            val intent = PendingIntent.getBroadcast(\n                context,\n                0,\n                broadcastIntent,\n                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE\n            )\n            val packageInstaller = getPackageInstaller()\n\n            // 0x00000004 = PackageManager.DELETE_SYSTEM_APP\n            // 0x00000002 = PackageManager.DELETE_ALL_USERS\n            val flags = if (isSystem) 0x00000004 else 0x00000002\n\n            if (shouldReset) {\n                Bypass.invoke<Any?>(\n                    PackageInstaller::class.java,\n                    packageInstaller,\n                    \"uninstall\",\n                    packageName,\n                    flags,\n                    intent.intentSender\n                )\n            }\n\n            Bypass.invoke<Any?>(\n                PackageInstaller::class.java,\n                packageInstaller,\n                \"uninstall\",\n                packageName,\n                flags,\n                intent.intentSender\n            )\n            true\n        }.getOrElse {\n            // Fallback to Shell uninstallation\n            Logger.w(\n                \"ShizukuReflector\",\n                \"Reflection uninstall failed, falling back to shell: ${it.message}\"\n            )\n            Shizuku.uninstallApp(context, packageName)\n        }\n    }\n\n    /**\n     * Installs an APK using the 'pm install' command via Shizuku.\n     * Note: The file at [apkPath] must be readable by the shell user (e.g. /sdcard/).\n     *\n     * @param apkPath Absolute path to the APK file.\n     * @param canDowngrade Whether to allow downgrade.\n     * @return true if installation command exited with 0 (Success).\n     */\n    fun installPackage(apkPath: String, canDowngrade: Boolean = false): Boolean {\n        return try {\n            val command = \"pm install -r -g${if (canDowngrade) \" -d\" else \"\"} ${\n                com.valhalla.superuser.ShellUtils.escapedString(apkPath)\n            }\"\n            val result = Shizuku.execute(command)\n            result.first == 0\n        } catch (e: Exception) {\n            e.printStackTrace()\n            false\n        }\n    }\n\n    /**\n     * Reinstalls app using Shizuku. See <a\n     * href=\"https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java;drc=bcb2b436bde55ee40050400783a9c083e77ce2fe;l=1408>PackageManagerShellCommand.java</a>\n     * @param packageName package name of the app to reinstall (must pre-install on the phone)\n     */\n    private fun reinstallApp(packageName: String): Boolean {\n        val broadcastIntent = Intent(\"io.github.samolego.canta.INSTALL_RESULT_ACTION\")\n        val intent =\n            PendingIntent.getBroadcast(\n                context,\n                0,\n                broadcastIntent,\n                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE\n            )\n\n\n        // PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS\n        val installFlags = 0x00400000\n\n        return try {\n            val installReason = PackageManager.INSTALL_REASON_UNKNOWN\n            Bypass.invoke<Any?>(\n                IPackageInstaller::class.java,\n                ShizukuPackageInstallerUtils.getPrivilegedPackageInstaller(),\n                \"installExistingPackage\",\n                packageName,\n                installFlags,\n                installReason,\n                intent.intentSender,\n                0,\n                null\n            )\n            true\n        } catch (e: Exception) {\n            e.printStackTrace()\n            false\n        }\n    }\n\n    fun getPackageInstaller(): PackageInstaller {\n        val iPackageInstaller = ShizukuPackageInstallerUtils.getPrivilegedPackageInstaller()\n        val root = try {\n            rikka.shizuku.Shizuku.getUid() == 0\n        } catch (_: Exception) {\n            false\n        }\n        val userId = if (root) android.os.Process.myUserHandle().hashCode() else 0\n\n        // The reason for use \"com.android.shell\" as installer package under adb is that\n        // getMySessions will check installer package's owner\n        return ShizukuPackageInstallerUtils.createPackageInstaller(\n            iPackageInstaller,\n            \"com.android.shell\",\n            userId\n        )\n    }\n\n    /**\n     * Create a privileged PackageInstaller using the provided installer package name.\n     * This mirrors `getPackageInstaller()` but allows specifying the installer package\n     * (so sessions can be created as belonging to the app's package).\n     */\n    fun createPackageInstallerFor(installerPackageName: String): PackageInstaller {\n        val iPackageInstaller = ShizukuPackageInstallerUtils.getPrivilegedPackageInstaller()\n        val root = try {\n            rikka.shizuku.Shizuku.getUid() == 0\n        } catch (_: Exception) {\n            false\n        }\n        val userId = if (root) android.os.Process.myUserHandle().hashCode() else 0\n\n        return ShizukuPackageInstallerUtils.createPackageInstaller(\n            iPackageInstaller,\n            installerPackageName,\n            userId\n        )\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/data/source/local/shizuku/Targets.kt",
    "content": "package com.valhalla.thor.data.source.local.shizuku\n\nimport android.os.Build\nimport androidx.annotation.ChecksSdkIntAtLeast\n\nobject Targets {\n\n    @get:ChecksSdkIntAtLeast(api = Build.VERSION_CODES.Q)\n    val Q = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q\n\n    @get:ChecksSdkIntAtLeast(api = Build.VERSION_CODES.S)\n    val S = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S\n\n    @get:ChecksSdkIntAtLeast(api = Build.VERSION_CODES.TIRAMISU)\n    val T = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU\n\n    @get:ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)\n    val U = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE\n\n    @get:ChecksSdkIntAtLeast(api = 35)\n    val V = Build.VERSION.SDK_INT >= 35\n\n    @get:ChecksSdkIntAtLeast(api = 36)\n    val B = Build.VERSION.SDK_INT >= 36\n\n    @get:ChecksSdkIntAtLeast(api = 37)\n    val B_MINOR = Build.VERSION.SDK_INT >= 37\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/data/util/ApksMetadataGenerator.kt",
    "content": "package com.valhalla.thor.data.util\n\nimport com.valhalla.thor.domain.model.AppInfo\nimport kotlinx.serialization.SerialName\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.json.Json\nimport java.io.File\n\nclass ApksMetadataGenerator {\n\n    @Serializable\n    data class ApksMetadata(\n        @SerialName(\"info_version\") val infoVersion: Int = 1,\n        @SerialName(\"package_name\") val packageName: String,\n        @SerialName(\"display_name\") val displayName: String,\n        @SerialName(\"version_name\") val versionName: String,\n        @SerialName(\"version_code\") val versionCode: Int,\n        @SerialName(\"min_sdk\") val minSdkVersion: Int,\n        @SerialName(\"target_sdk\") val targetSdkVersion: Int,\n    )\n\n\n    fun generateJson(appInfo: AppInfo) = Json.encodeToString(\n        ApksMetadata(\n            packageName = appInfo.packageName,\n            displayName = appInfo.appName ?: \"\",\n            versionName = appInfo.versionName ?: \"\",\n            versionCode = appInfo.versionCode,\n            minSdkVersion = appInfo.minSdk,\n            targetSdkVersion = appInfo.targetSdk\n        )\n    )\n\n    fun generateJson(appInfo: AppInfo, targetFile: File) {\n        targetFile.writeText(generateJson(appInfo))\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/data/util/PackageVerifier.kt",
    "content": "package com.valhalla.thor.data.util\n\nimport android.content.pm.ApplicationInfo\nimport android.content.pm.PackageManager\nimport com.valhalla.thor.domain.model.AppInstallable\nimport java.io.File\n\n/**\n * Checks APKs for the debuggable flag.\n * Keep this decoupled so you can test it easily.\n */\nclass PackageVerifier(private val packageManager: PackageManager) {\n    fun scanForDebuggableApps(apkPaths: List<String>): List<AppInstallable> {\n        return apkPaths.mapNotNull { path ->\n            val file = File(path)\n            if (!file.exists()) return@mapNotNull null\n\n            // Use GET_META_DATA or 0. parsing headers is expensive, do not do on UI thread.\n            val info = packageManager.getPackageArchiveInfo(path, 0)\n            val appInfo = info?.applicationInfo\n\n            // Bitwise check for the debuggable flag\n            val isDebuggable = appInfo?.let {\n                (it.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0\n            } ?: false\n\n            if (info != null) {\n                AppInstallable(\n                    name = appInfo?.packageName ?: file.name,\n                    apkPath = path,\n                    isDebuggable = isDebuggable\n                )\n            } else null\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/di/Modules.kt",
    "content": "package com.valhalla.thor.di\n\nimport android.content.pm.PackageManager\nimport androidx.room.Room\nimport com.valhalla.superuser.ktx.RealShellRepository\nimport com.valhalla.superuser.ktx.ShellRepository\nimport com.valhalla.thor.data.gateway.DhizukuSystemGateway\nimport com.valhalla.thor.data.gateway.RootSystemGateway\nimport com.valhalla.thor.data.gateway.ShizukuSystemGateway\nimport com.valhalla.thor.data.repository.AppAnalyzerImpl\nimport com.valhalla.thor.data.repository.AppRepositoryImpl\nimport com.valhalla.thor.data.repository.InstallerRepositoryImpl\nimport com.valhalla.thor.data.repository.PreferenceRepositoryImpl\nimport com.valhalla.thor.data.repository.SystemRepositoryImpl\nimport com.valhalla.thor.data.security.BiometricHelper\nimport com.valhalla.thor.data.source.local.dhizuku.DhizukuReflector\nimport com.valhalla.thor.data.source.local.room.AppDatabase\nimport com.valhalla.thor.data.source.local.shizuku.ShizukuReflector\nimport com.valhalla.thor.data.util.ApksMetadataGenerator\nimport com.valhalla.thor.domain.InstallerEventBus\nimport com.valhalla.thor.domain.repository.AppAnalyzer\nimport com.valhalla.thor.domain.repository.AppRepository\nimport com.valhalla.thor.domain.repository.InstallerRepository\nimport com.valhalla.thor.domain.repository.PreferenceRepository\nimport com.valhalla.thor.domain.repository.SystemRepository\nimport com.valhalla.thor.domain.usecase.GetAppDetailsUseCase\nimport com.valhalla.thor.domain.usecase.GetInstalledAppsUseCase\nimport com.valhalla.thor.domain.usecase.ManageAppUseCase\nimport com.valhalla.thor.domain.usecase.ShareAppUseCase\nimport com.valhalla.thor.presentation.appList.AppListViewModel\nimport com.valhalla.thor.presentation.freezer.FreezerViewModel\nimport com.valhalla.thor.presentation.home.HomeViewModel\nimport com.valhalla.thor.presentation.installer.InstallerViewModel\nimport com.valhalla.thor.presentation.main.MainViewModel\nimport com.valhalla.thor.presentation.security.SecurityViewModel\nimport com.valhalla.thor.presentation.settings.SettingsViewModel\nimport com.valhalla.thor.util.LocaleManager\nimport org.koin.android.ext.koin.androidContext\nimport org.koin.core.module.dsl.factoryOf\nimport org.koin.core.module.dsl.singleOf\nimport org.koin.core.module.dsl.viewModelOf\nimport org.koin.dsl.bind\nimport org.koin.dsl.module\n\nval commonModule = module {\n    single<PackageManager> { androidContext().packageManager }\n    singleOf(::LocaleManager)\n    singleOf(::ApksMetadataGenerator)\n    single<AppRepository> { AppRepositoryImpl(androidContext(), get()) }\n    factory { GetInstalledAppsUseCase(get()) }\n    factory { GetAppDetailsUseCase(get()) }\n    factory { ManageAppUseCase(get()) }\n    factoryOf(::ShareAppUseCase)\n}\n\nval roomModule = module {\n    single {\n        Room.databaseBuilder(\n            androidContext(),\n            AppDatabase::class.java,\n            \"thor_database\"\n        ).fallbackToDestructiveMigration(dropAllTables = true).build()\n    }\n    single { get<AppDatabase>().appDao() }\n}\n\nval installerModule = module {\n    // 1. The Singleton Event Bus (Critical for Receiver <-> VM comms)\n    singleOf(::InstallerEventBus)\n    single<InstallerRepository> {\n        InstallerRepositoryImpl(\n            context = androidContext(),\n            eventBus = get(),\n            rootGateway = get(),\n            shizukuReflector = get()\n        )\n    }\n    single<PackageManager> {\n        androidContext().packageManager\n    }\n    single<AppAnalyzer> { AppAnalyzerImpl(androidContext()) }\n}\n\nval preferenceModule = module {\n    single<PreferenceRepository> { PreferenceRepositoryImpl(get()) }\n}\n\nval presentationModule = module {\n    viewModelOf(::MainViewModel)\n    viewModelOf(::HomeViewModel)\n    viewModelOf(::AppListViewModel)\n    viewModelOf(::FreezerViewModel)\n    viewModelOf(::InstallerViewModel)\n    viewModelOf(::SettingsViewModel)\n    viewModelOf(::SecurityViewModel)\n}\n\nval coreModule = module {\n    singleOf(::RealShellRepository).bind<ShellRepository>()\n    singleOf(::ShizukuReflector)\n    singleOf(::DhizukuReflector)\n    singleOf(::BiometricHelper)\n    // Singletons for the Gateways\n    single { RootSystemGateway(androidContext(), get()) }\n    single { ShizukuSystemGateway(get()) }\n    single { DhizukuSystemGateway(get()) }\n    // The Repository interacts with the Gateways\n    singleOf(::SystemRepositoryImpl).bind<SystemRepository>()\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/domain/InstallState.kt",
    "content": "package com.valhalla.thor.domain\n\nimport android.content.Intent\nimport com.valhalla.thor.domain.model.AppMetadata\n\n/**\n * Represents the distinct states of the installation process.\n * We use a Sealed Interface for strict state management in the UI.\\n */\nsealed interface InstallState {\n    data object Idle : InstallState\n    data object Parsing : InstallState\n\n    data class ReadyToInstall(\n        val meta: AppMetadata,\n        val isUpdate: Boolean,\n        val isDowngrade: Boolean = false,\n        val oldVersion: String? = null\n    ) : InstallState {\n\n        @Suppress(\"unused\")\n        fun getVersionInfo(): String {\n            return if (isUpdate) {\n                \"Update available: ${meta.version} (current: $oldVersion)\"\n            } else if (isDowngrade) {\n                \"Downgrade detected: ${meta.version} (current: $oldVersion)\"\n            } else {\n                \"Ready to install version ${meta.version}\"\n            }\n        }\n\n        fun getActionButtonText(): String {\n            return when {\n                isDowngrade -> \"Install Anyway\"\n                isUpdate -> \"Update\"\n                else -> \"Install\"\n            }\n        }\n\n        fun getWarningMessage(): String? {\n            return when {\n                isDowngrade -> \"Warning: Installing an older version may cause issues.\"\n                isUpdate -> null\n                else -> null\n            }\n        }\n\n        fun shouldShowWarning(): Boolean {\n            return isDowngrade\n        }\n\n        fun getActionWord(): String {\n            return when {\n                isDowngrade -> \"downgrade\"\n                isUpdate -> \"update\"\n                else -> \"install\"\n            }\n        }\n\n    }\n\n    data class Installing(val progress: Float) : InstallState // 0.0 to 1.0\n    data object Success : InstallState\n    data class Error(val message: String) : InstallState\n\n    // Critical: The OS has paused the session to ask the user for permission.\n    data class UserConfirmationRequired(val intent: Intent) : InstallState\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/domain/InstallerEventBus.kt",
    "content": "package com.valhalla.thor.domain\n\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.flow.SharedFlow\n\n/**\n * A Singleton Event Bus to bridge the gap between the Android System (BroadcastReceiver)\n * and our App Scope (ViewModel).\n * * Since BroadcastReceivers are instantiated by the OS, we cannot scope them to the ViewModel.\n * This Bus acts as the synapse.\n */\nclass InstallerEventBus {\n    val events: SharedFlow<InstallState>\n        field = MutableSharedFlow<InstallState>(replay = 1)\n\n    suspend fun emit(state: InstallState) {\n        events.emit(state)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/domain/gateway/SystemGateway.kt",
    "content": "package com.valhalla.thor.domain.gateway\n\n/**\n * The Contract: This defines every privileged action Thor can perform.\n * No Android dependencies (Context, Toast, Intent) allowed here.\n */\ninterface SystemGateway {\n\n    // Status Checks\n    suspend fun isRootAvailable(): Boolean\n    fun isShizukuAvailable(): Boolean\n    fun isDhizukuAvailable(): Boolean\n\n    // Core Actions\n    suspend fun forceStopApp(packageName: String): Result<Unit>\n    suspend fun clearCache(packageName: String): Result<Unit>\n    suspend fun clearAppData(packageName: String): Result<Unit>\n    suspend fun setAppDisabled(packageName: String, isDisabled: Boolean): Result<Unit>\n    suspend fun setAppSuspended(packageName: String, isSuspended: Boolean): Result<Unit>\n    suspend fun setAppRestricted(packageName: String, isRestricted: Boolean): Result<Unit>\n    suspend fun rebootDevice(reason: String): Result<Unit>\n\n    // Advanced\n    suspend fun uninstallApp(packageName: String): Result<Unit>\n    suspend fun installApp(apkPath: String, canDowngrade: Boolean = false): Result<Unit>\n    suspend fun reinstallAppWithGoogle(packageName: String): Result<Unit>\n\n    // Metrics\n    suspend fun getAppCacheSize(packageName: String): Long\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/domain/model/ApkDetails.kt",
    "content": "package com.valhalla.thor.domain.model\n\nimport android.graphics.drawable.Drawable\n\ndata class ApkDetails(\n    val appName: String?,\n    val packageName: String?,\n    val versionName: String?,\n    val versionCode: Long?,\n    val appIcon: Drawable?,\n    val permissions: List<String>?,\n    val minSdk: Int?,\n    val targetSdk: Int?\n)\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/domain/model/AppClickAction.kt",
    "content": "package com.valhalla.thor.domain.model\n\nsealed interface AppClickAction {\n    //data class Logcat(val appInfo: AppInfo): AppClickAction\n    data class Launch(val appInfo: AppInfo) : AppClickAction\n    data class Share(val appInfo: AppInfo) : AppClickAction\n    data class Uninstall(val appInfo: AppInfo) : AppClickAction\n    data class Reinstall(val appInfo: AppInfo) : AppClickAction\n    data class Freeze(val appInfo: AppInfo) : AppClickAction\n    data class UnFreeze(val appInfo: AppInfo) : AppClickAction\n    data class Kill(val appInfo: AppInfo) : AppClickAction\n    data class AppInfoSettings(val appInfo: AppInfo) : AppClickAction\n    data object ReinstallAll : AppClickAction\n\n    data class ClearCache(val appInfo: AppInfo) : AppClickAction\n    data class ClearData(val appInfo: AppInfo) : AppClickAction\n    data class Suspend(val appInfo: AppInfo) : AppClickAction\n    data class UnSuspend(val appInfo: AppInfo) : AppClickAction\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/domain/model/AppInfo.kt",
    "content": "package com.valhalla.thor.domain.model\n\nimport android.content.pm.ApplicationInfo\nimport android.content.pm.PackageInfo\nimport android.content.pm.PackageManager\nimport android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT\nimport android.os.Build\nimport android.os.Environment\nimport kotlinx.serialization.Serializable\nimport java.io.File\n\n@Serializable\ndata class AppInfo(\n    var appName: String? = null,\n    var packageName: String = \"\",\n    var versionName: String? = \"\",\n    var versionCode: Int = 0,\n    var minSdk: Int = 0,\n    var targetSdk: Int = 0,\n    var isSystem: Boolean = false,\n    var installerPackageName: String? = null,\n    var publicSourceDir: String? = null,\n    var splitPublicSourceDirs: List<String> = emptyList(),\n    var enabled: Boolean = true,\n    var enabledState: Int = COMPONENT_ENABLED_STATE_DEFAULT,\n    var dataDir: String? = null,\n    var nativeLibraryDir: String? = null,\n    var deviceProtectedDataDir: String? = null,\n    var sharedLibraryFiles: List<String>? = emptyList(),\n    var obbFilePath: String? = null,\n    var sourceDir: String? = null,\n    var sharedDataDir: String = \"\",\n    var lastUpdateTime: Long = 0L,\n    var firstInstallTime: Long = 0L,\n    val isDebuggable: Boolean = false,\n    var isSuspended: Boolean = false,\n) {\n    companion object {\n\n        fun mapToAppInfo(\n            packInfo: PackageInfo,\n            appInfo: ApplicationInfo,\n            pm: PackageManager,\n            isLightweight: Boolean = false\n        ): AppInfo {\n            val isDebuggable = (appInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0\n            @Suppress(\"DEPRECATION\") val mapped = AppInfo(\n                appName = appInfo.loadLabel(pm).toString(),\n                packageName = packInfo.packageName,\n                versionName = packInfo.versionName,\n                versionCode = packInfo.longVersionCode.toInt(),\n                minSdk = appInfo.minSdkVersion,\n                targetSdk = appInfo.targetSdkVersion,\n                isSystem = (appInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0,\n                installerPackageName = getInstallerPackageName(packInfo.packageName, pm),\n                publicSourceDir = appInfo.publicSourceDir,\n                splitPublicSourceDirs = appInfo.splitPublicSourceDirs?.toList() ?: emptyList(),\n                enabled = appInfo.enabled,\n                // enabledState = pm.getApplicationEnabledSetting(packInfo.packageName), // Warning: This can be slow, use cautiously\n                dataDir = appInfo.dataDir,\n                nativeLibraryDir = appInfo.nativeLibraryDir,\n                deviceProtectedDataDir = appInfo.deviceProtectedDataDir,\n                sourceDir = appInfo.sourceDir,\n                lastUpdateTime = packInfo.lastUpdateTime,\n                firstInstallTime = packInfo.firstInstallTime,\n                isDebuggable = isDebuggable,\n                isSuspended = (appInfo.flags and ApplicationInfo.FLAG_SUSPENDED) != 0\n            )\n\n            // The \"Heavy\" Logic - Only run if explicitly requested\n            if (!isLightweight) {\n                mapped.sharedLibraryFiles = appInfo.sharedLibraryFiles?.toList() ?: emptyList()\n\n                // OBB Check\n                val obbFile = File(\n                    Environment.getExternalStorageDirectory(),\n                    \"Android/obb/${appInfo.packageName}\"\n                )\n                if (obbFile.exists()) {\n                    mapped.obbFilePath = obbFile.absolutePath\n                }\n\n                // Data Dir Check\n                val dataFile = File(\n                    Environment.getExternalStorageDirectory(),\n                    \"Android/data/${appInfo.packageName}\"\n                )\n                mapped.sharedDataDir = dataFile.absolutePath\n            }\n\n            return mapped\n        }\n\n        fun getInstallerPackageName(packageName: String, pm: PackageManager): String? {\n            return try {\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {\n                    pm.getInstallSourceInfo(packageName).installingPackageName\n                } else {\n                    @Suppress(\"DEPRECATION\")\n                    pm.getInstallerPackageName(packageName)\n                }\n            } catch (_: Exception) {\n                null\n            }\n        }\n\n    }\n}\n\nfun AppInfo.formattedAppName() = appName?.replace(\" \", \"_\") ?: packageName"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/domain/model/AppInstallable.kt",
    "content": "package com.valhalla.thor.domain.model\n\ndata class AppInstallable(\n    val name: String,\n    val apkPath: String,\n    val isDebuggable: Boolean\n)\n\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/domain/model/AppListType.kt",
    "content": "package com.valhalla.thor.domain.model\n\nenum class AppListType {\n    USER, SYSTEM\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/domain/model/AppMetadata.kt",
    "content": "package com.valhalla.thor.domain.model\n\nimport android.graphics.Bitmap\n\ndata class AppMetadata(\n    val label: String,\n    val packageName: String,\n    val version: String,\n    val versionCode: Long,\n    val icon: Bitmap?,\n    val permissions: List<String> = emptyList()\n)"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/domain/model/FilterType.kt",
    "content": "package com.valhalla.thor.domain.model\n\nsealed interface FilterType {\n    data object Source : FilterType\n    data object State : FilterType {\n        val types = listOf(\n            \"All\", \"Active\", \"Frozen\", \"Suspended\"\n        )\n    };\n\n\n}\n\nval filterTypes = listOf(\n    FilterType.State,\n    FilterType.Source\n)\n\nfun FilterType.asGeneralName() = when (this) {\n    FilterType.State -> \"Active State\"\n    FilterType.Source -> \"Installation Source\"\n}\n\n\n\n\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/domain/model/HistoryRecord.kt",
    "content": "package com.valhalla.thor.domain.model\n\nimport kotlinx.serialization.Serializable\n\n\n@Serializable\nenum class OperationType {\n    INSTALL,\n    UPDATE\n}\n\n@Serializable\ndata class HistoryRecord(\n    val id: Long = 0,\n    val packageName: String,\n    val label: String,\n    val version: String,\n    val timestamp: Long,\n    val type: OperationType,\n    val path: String // Optional: Path to the file installed (for reference)\n)\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/domain/model/MultiAppAction.kt",
    "content": "package com.valhalla.thor.domain.model\n\nsealed interface MultiAppAction {\n    data class ReInstall(val appList: List<AppInfo>) : MultiAppAction\n    data class Uninstall(val appList: List<AppInfo>) : MultiAppAction\n    data class Freeze(val appList: List<AppInfo>) : MultiAppAction\n    data class UnFreeze(val appList: List<AppInfo>) : MultiAppAction\n    data class Share(val appList: List<AppInfo>) : MultiAppAction\n    data class Kill(val appList: List<AppInfo>) : MultiAppAction\n\n    data class ClearCache(val appList: List<AppInfo>) : MultiAppAction\n    data class ClearData(val appList: List<AppInfo>) : MultiAppAction\n    data class Suspend(val appList: List<AppInfo>) : MultiAppAction\n    data class UnSuspend(val appList: List<AppInfo>) : MultiAppAction\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/domain/model/PrivilegeMode.kt",
    "content": "package com.valhalla.thor.domain.model\n\nenum class PrivilegeMode {\n    ROOT,\n    SHIZUKU,\n    DHIZUKU\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/domain/model/SortBy.kt",
    "content": "package com.valhalla.thor.domain.model\n\nimport com.valhalla.thor.R\nimport kotlinx.serialization.Serializable\n\n@Serializable\nenum class SortBy {\n    NAME,\n\n    //SIZE,\n    INSTALL_DATE,\n    LAST_UPDATED,\n    VERSION_CODE,\n    VERSION_NAME,\n    TARGET_SDK_VERSION,\n    MIN_SDK_VERSION;\n\n    fun asGeneralName(): String = when (this) {\n        NAME -> \"Name\"\n        //SIZE -> \"Size\"\n        INSTALL_DATE -> \"Install Date\"\n        LAST_UPDATED -> \"Last Updated\"\n        VERSION_CODE -> \"Version Code\"\n        VERSION_NAME -> \"Version Name\"\n        TARGET_SDK_VERSION -> \"Target SDK Version\"\n        MIN_SDK_VERSION -> \"Min SDK Version\"\n    }\n}\n\nfun SortBy.isDateBased(): Boolean = this == SortBy.INSTALL_DATE || this == SortBy.LAST_UPDATED\n\nfun SortBy.isVersionBased(): Boolean = this == SortBy.VERSION_CODE || this == SortBy.VERSION_NAME\n\nfun SortBy.isSdkBased(): Boolean =\n    this == SortBy.TARGET_SDK_VERSION || this == SortBy.MIN_SDK_VERSION\n\nfun SortBy.isNameBased(): Boolean = this == SortBy.NAME\n\n@Serializable\nenum class SortOrder {\n    ASCENDING,\n    DESCENDING;\n\n    fun asGeneralName(): String = when (this) {\n        ASCENDING -> \"Ascending\"\n        DESCENDING -> \"Descending\"\n    }\n\n    fun icon() = when (this) {\n        ASCENDING -> R.drawable.arrow_upward\n        DESCENDING -> R.drawable.arrow_downward\n    }\n\n    fun flip(): SortOrder = when (this) {\n        ASCENDING -> DESCENDING\n        DESCENDING -> ASCENDING\n    }\n\n    fun angle(): Float = when (this) {\n        ASCENDING -> 0f\n        DESCENDING -> 180f\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/domain/model/ThemeMode.kt",
    "content": "package com.valhalla.thor.domain.model\n\nenum class ThemeMode {\n    LIGHT,\n    DARK,\n    SYSTEM;\n\n    fun label(): String = when (this) {\n        LIGHT -> \"Light\"\n        DARK -> \"Dark\"\n        SYSTEM -> \"System\"\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/domain/model/UserPreferences.kt",
    "content": "package com.valhalla.thor.domain.model\n\ndata class UserPreferences(\n    // App List Sorting & Filtering\n    val appSortBy: SortBy = SortBy.NAME,\n    val appSortOrder: SortOrder = SortOrder.ASCENDING,\n    val appFilterType: FilterType = FilterType.Source,\n    val appSelectedFilter: String = \"All\",\n\n    // Home Screen Config\n    val showReinstallAllCard: Boolean = true,\n\n    // Theme\n    val themeMode: ThemeMode = ThemeMode.SYSTEM,\n    val useDynamicColor: Boolean = false,\n    val useAmoled: Boolean = false,\n\n    // Security\n    val biometricLockEnabled: Boolean = false,\n\n    // Work Mode\n    val preferredPrivilegeMode: PrivilegeMode? = null,\n\n    // Localization\n    val language: String? = null, // null means System Default\n)\n\n\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/domain/repository/AppAnalyzer.kt",
    "content": "package com.valhalla.thor.domain.repository\n\nimport android.net.Uri\nimport com.valhalla.thor.domain.model.AppMetadata\n\ninterface AppAnalyzer {\n    /**\n     * Extracts metadata from a URI (APK, XAPK, APKS) without installing it.\n     */\n    suspend fun analyze(uri: Uri): Result<AppMetadata>\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/domain/repository/AppRepository.kt",
    "content": "package com.valhalla.thor.domain.repository\n\nimport com.valhalla.thor.domain.model.AppInfo\nimport kotlinx.coroutines.flow.Flow\n\ninterface AppRepository {\n    /**\n     * Fetches all installed applications.\n     * Returns a Flow to allow emitting updates if packages change (optional),\n     * or just a single emission for now.\n     */\n    fun getAllApps(): Flow<List<AppInfo>>\n\n    /**\n     * Get details for a specific package.\n     * This is where we will do the heavy lifting (OBB checks, etc.)\n     * so we don't slow down the main list.\n     */\n    suspend fun getAppDetails(packageName: String): AppInfo?\n\n    // Parser for XAPK/APK installation features\n    suspend fun getApkDetails(apkPath: String): AppInfo?\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/domain/repository/InstallerRepository.kt",
    "content": "package com.valhalla.thor.domain.repository\n\nimport android.net.Uri\n\nenum class InstallMode {\n    NORMAL,\n    SHIZUKU,\n    DHIZUKU,\n    ROOT,\n    EXTERNAL\n}\n\n/**\n * The Repository Contract.\n * The Domain layer doesn't care about PackageInstaller APIs, only that we can install a URI.\n */\ninterface InstallerRepository {\n    suspend fun installPackage(uri: Uri, mode: InstallMode, canDowngrade: Boolean = false)\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/domain/repository/PreferenceRepository.kt",
    "content": "package com.valhalla.thor.domain.repository\n\nimport com.valhalla.thor.domain.model.FilterType\nimport com.valhalla.thor.domain.model.PrivilegeMode\nimport com.valhalla.thor.domain.model.SortBy\nimport com.valhalla.thor.domain.model.SortOrder\nimport com.valhalla.thor.domain.model.ThemeMode\nimport com.valhalla.thor.domain.model.UserPreferences\nimport kotlinx.coroutines.flow.Flow\n\ninterface PreferenceRepository {\n\n    // Observe all preferences as a single stream\n    val userPreferences: Flow<UserPreferences>\n\n    // --- App List ---\n    suspend fun updateAppSort(sortBy: SortBy)\n    suspend fun updateAppSortOrder(sortOrder: SortOrder)\n    suspend fun updateAppFilter(filterType: FilterType, selectedFilter: String)\n    suspend fun setReinstallAllCardVisibility(isVisible: Boolean)\n\n    // --- Theme ---\n    suspend fun setThemeMode(themeMode: ThemeMode)\n    suspend fun setDynamicColor(enabled: Boolean)\n    suspend fun setUseAmoled(enabled: Boolean)\n\n    // --- Security ---\n    suspend fun setBiometricLock(enabled: Boolean)\n\n    // --- Work Mode ---\n    suspend fun setPrivilegeMode(mode: PrivilegeMode?)\n\n    // --- Localization ---\n    suspend fun setLanguage(language: String?)\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/domain/repository/SystemRepository.kt",
    "content": "package com.valhalla.thor.domain.repository\n\ninterface SystemRepository {\n\n    suspend fun isRootAvailable(): Boolean\n    fun isShizukuAvailable(): Boolean\n    fun isDhizukuAvailable(): Boolean\n\n    // Core Actions\n    suspend fun forceStopApp(packageName: String): Result<Unit>\n    suspend fun clearCache(packageName: String): Result<Unit>\n    suspend fun clearAppData(packageName: String): Result<Unit>\n    suspend fun setAppDisabled(packageName: String, isDisabled: Boolean): Result<Unit>\n    suspend fun setAppSuspended(packageName: String, isSuspended: Boolean): Result<Unit>\n    suspend fun setAppRestricted(packageName: String, isRestricted: Boolean): Result<Unit>\n\n    // Advanced\n    suspend fun uninstallApp(packageName: String): Result<Unit>\n    suspend fun rebootDevice(reason: String): Result<Unit>\n\n    // Composite Actions\n    suspend fun aggressiveCleanup(packageName: String): Result<Unit>\n    suspend fun reinstallAppWithGoogle(packageName: String): Result<Unit>\n    suspend fun copyFileWithRoot(sourcePath: String, destinationPath: String): Result<Unit>\n    suspend fun getAppPaths(packageName: String): Result<List<String>>\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/domain/usecase/GetAppDetailsUseCase.kt",
    "content": "package com.valhalla.thor.domain.usecase\n\nimport com.valhalla.thor.domain.model.AppInfo\nimport com.valhalla.thor.domain.repository.AppRepository\n\nclass GetAppDetailsUseCase(\n    private val appRepository: AppRepository\n) {\n    suspend operator fun invoke(packageName: String): Result<AppInfo> {\n        return try {\n            val info = appRepository.getAppDetails(packageName)\n            if (info != null) {\n                Result.success(info)\n            } else {\n                Result.failure(Exception(\"App not found\"))\n            }\n        } catch (e: Exception) {\n            Result.failure(e)\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/domain/usecase/GetInstalledAppsUseCase.kt",
    "content": "package com.valhalla.thor.domain.usecase\n\nimport com.valhalla.thor.domain.model.AppInfo\nimport com.valhalla.thor.domain.repository.AppRepository\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.map\n\nclass GetInstalledAppsUseCase(\n    private val appRepository: AppRepository\n) {\n    /**\n     * Returns a generic Pair: (UserApps, SystemApps)\n     */\n    operator fun invoke(): Flow<Pair<List<AppInfo>, List<AppInfo>>> {\n        return appRepository.getAllApps().map { allApps ->\n            val (system, user) = allApps.partition { it.isSystem }\n            // Additional filtering can go here (e.g. exclude own app?)\n            Pair(user, system)\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/domain/usecase/ManageAppUseCase.kt",
    "content": "package com.valhalla.thor.domain.usecase\n\nclass ManageAppUseCase(\n    private val systemRepository: com.valhalla.thor.domain.repository.SystemRepository\n) {\n    suspend fun forceStop(packageName: String): Result<Unit> =\n        systemRepository.forceStopApp(packageName)\n\n    suspend fun clearCache(packageName: String): Result<Unit> =\n        systemRepository.clearCache(packageName)\n\n    suspend fun clearAppData(packageName: String): Result<Unit> =\n        systemRepository.clearAppData(packageName)\n\n    suspend fun setAppDisabled(packageName: String, disabled: Boolean): Result<Unit> =\n        systemRepository.setAppDisabled(packageName, disabled)\n\n    suspend fun setAppSuspended(packageName: String, suspended: Boolean): Result<Unit> =\n        systemRepository.setAppSuspended(packageName, suspended)\n\n    suspend fun uninstallApp(packageName: String): Result<Unit> =\n        systemRepository.uninstallApp(packageName)\n\n    /**\n     * Reinstalls an app (usually via Play Store mechanism or existing APK).\n     * For clean arch, we might need a specific method in Repo for \"Reinstall\".\n     * Assuming for now we rely on the repository's generic install or a specific reinstall method.\n     */\n    suspend fun reinstallAppWithGoogle(packageName: String): Result<Unit> {\n        return systemRepository.reinstallAppWithGoogle(packageName)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/domain/usecase/ShareAppUseCase.kt",
    "content": "package com.valhalla.thor.domain.usecase\n\nimport android.content.Context\nimport androidx.core.content.FileProvider\nimport com.valhalla.thor.BuildConfig\nimport com.valhalla.thor.data.util.ApksMetadataGenerator\nimport com.valhalla.thor.domain.model.AppInfo\nimport com.valhalla.thor.domain.model.formattedAppName\nimport com.valhalla.thor.domain.repository.SystemRepository\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport java.io.BufferedInputStream\nimport java.io.BufferedOutputStream\nimport java.io.File\nimport java.io.FileInputStream\nimport java.io.FileOutputStream\nimport java.util.zip.ZipEntry\nimport java.util.zip.ZipOutputStream\n\nclass ShareAppUseCase(\n    private val context: Context,\n    private val systemRepository: SystemRepository,\n    private val apksMetadataGenerator: ApksMetadataGenerator\n) {\n\n    suspend operator fun invoke(appInfo: AppInfo): Result<android.net.Uri> =\n        withContext(Dispatchers.IO) {\n            try {\n                // 1. Prepare Cache Directory\n                val cacheDir = File(context.cacheDir, \"share_temp\")\n                if (cacheDir.exists()) cacheDir.deleteRecursively()\n                cacheDir.mkdirs()\n\n                val finalFile: File\n\n                // 2. Check for Splits\n                if (appInfo.splitPublicSourceDirs.isNullOrEmpty()) {\n                    // --- Single APK Mode ---\n                    val sourcePath = appInfo.publicSourceDir ?: appInfo.sourceDir\n                    ?: return@withContext Result.failure(Exception(\"No source path found\"))\n\n                    val fileName = \"${appInfo.formattedAppName()}_${appInfo.versionName}.apk\"\n                    finalFile = File(cacheDir, fileName)\n\n                    if (!copyFileSafely(sourcePath, finalFile)) {\n                        return@withContext Result.failure(Exception(\"Failed to copy base APK\"))\n                    }\n\n                } else {\n                    // --- Split APK Mode (Zip/APKS) ---\n                    val fileName = \"${appInfo.formattedAppName()}_${appInfo.versionName}.apks\"\n                    finalFile = File(cacheDir, fileName)\n\n                    // Temp staging folder for individual parts\n                    val tempSplitDir = File(cacheDir, \"splits_staging\")\n                    tempSplitDir.mkdirs()\n\n                    // A. Gather APK Paths\n                    val allPaths = mutableListOf<String>()\n                    appInfo.sourceDir?.let { allPaths.add(it) }\n                    allPaths.addAll(appInfo.splitPublicSourceDirs)\n\n                    // B. Copy APKs to staging\n                    val filesToZip = allPaths.mapNotNull { path ->\n                        val name = path.substringAfterLast(\"/\")\n                        val destFile = File(tempSplitDir, name)\n                        if (copyFileSafely(path, destFile)) destFile else null\n                    }.toMutableList()\n\n                    if (filesToZip.isEmpty()) {\n                        return@withContext Result.failure(Exception(\"Failed to copy any APK files\"))\n                    }\n\n                    // C. GENERATE METADATA (The Missing Piece)\n                    // We generate \"metadata.json\" so installers know what this bundle is.\n                    val metadataFile = File(tempSplitDir, \"metadata.json\")\n                    apksMetadataGenerator.generateJson(appInfo, metadataFile)\n                    filesToZip.add(metadataFile)\n\n                    // D. Zip Everything (APKs + JSON)\n                    zipFiles(filesToZip, finalFile)\n                }\n\n                // 3. Generate URI\n                val uri = FileProvider.getUriForFile(\n                    context,\n                    \"${BuildConfig.APPLICATION_ID}.provider\",\n                    finalFile\n                )\n                Result.success(uri)\n\n            } catch (e: Exception) {\n                if (BuildConfig.DEBUG)\n                    e.printStackTrace()\n                Result.failure(e)\n            }\n        }\n\n    /**\n     * Tries standard copy, falls back to Root if permission denied.\n     */\n    private suspend fun copyFileSafely(sourcePath: String, destFile: File): Boolean {\n        return try {\n            File(sourcePath).copyTo(destFile, overwrite = true)\n            true\n        } catch (_: Exception) {\n            systemRepository.copyFileWithRoot(sourcePath, destFile.absolutePath).isSuccess\n        }\n    }\n\n    private fun zipFiles(files: List<File>, zipFile: File) {\n        ZipOutputStream(BufferedOutputStream(FileOutputStream(zipFile))).use { out ->\n            val data = ByteArray(1024)\n            files.forEach { file ->\n                FileInputStream(file).use { fi ->\n                    BufferedInputStream(fi).use { origin ->\n                        val entry = ZipEntry(file.name)\n                        out.putNextEntry(entry)\n                        while (true) {\n                            val readBytes = origin.read(data)\n                            if (readBytes == -1) break\n                            out.write(data, 0, readBytes)\n                        }\n                    }\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/appList/AppListScreen.kt",
    "content": "package com.valhalla.thor.presentation.appList\n\nimport android.widget.Toast\nimport androidx.compose.foundation.Image\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.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.AlertDialog\nimport androidx.compose.material3.CircularProgressIndicator\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.material3.pulltorefresh.PullToRefreshBox\nimport androidx.compose.material3.pulltorefresh.rememberPullToRefreshState\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.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.painterResource\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.compose.ui.window.Dialog\nimport androidx.lifecycle.compose.collectAsStateWithLifecycle\nimport coil3.ImageLoader\nimport coil3.compose.rememberAsyncImagePainter\nimport coil3.request.crossfade\nimport com.valhalla.thor.R\nimport com.valhalla.thor.domain.model.AppClickAction\nimport com.valhalla.thor.domain.model.AppInfo\nimport com.valhalla.thor.domain.model.MultiAppAction\nimport com.valhalla.thor.presentation.utils.AppIconFetcher\nimport com.valhalla.thor.presentation.utils.AppIconKeyer\nimport com.valhalla.thor.presentation.utils.getAppIcon\nimport com.valhalla.thor.presentation.widgets.AppInfoDialog\nimport com.valhalla.thor.presentation.widgets.AppList\nimport org.koin.androidx.compose.koinViewModel\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun AppListScreen(\n    modifier: Modifier = Modifier,\n    title: String = stringResource(R.string.apps),\n    icon: Int = R.drawable.thor_mono,\n    viewModel: AppListViewModel = koinViewModel(),\n    // These actions bubble up to MainScreen/HomeViewModel for execution\n    onAppAction: (AppClickAction) -> Unit = {},\n    onMultiAppAction: (MultiAppAction) -> Unit = {}\n) {\n    val state by viewModel.uiState.collectAsStateWithLifecycle()\n    val context = LocalContext.current\n\n    // Create a custom ImageLoader that knows how to fetch App Icons in the background.\n    // We use 'remember' so we don't recreate the loader on every recomposition.\n    val imageLoader = remember(context) {\n        ImageLoader.Builder(context)\n            .components {\n                add(AppIconKeyer())\n                add(AppIconFetcher.Factory(context))\n            }\n            .crossfade(true)\n            .build()\n    }\n\n    LaunchedEffect(Unit) {\n        if (state.allUserApps.isEmpty() && state.allSystemApps.isEmpty() && state.isLoading) {\n            viewModel.loadApps()\n        }\n    }\n\n    // Handle Feedback (Toasts)\n    LaunchedEffect(state.actionMessage) {\n        state.actionMessage?.let { message ->\n            Toast.makeText(context, message, Toast.LENGTH_SHORT).show()\n            viewModel.dismissMessage()\n        }\n    }\n\n    // UI-Specific State (Dialogs that don't need to persist in VM)\n    var reinstallCandidate: AppInfo? by remember { mutableStateOf(null) }\n\n    Column(\n        modifier = modifier\n            .fillMaxSize()\n            .background(MaterialTheme.colorScheme.background)\n    ) {\n\n        // 1. Header Row\n        Row(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(horizontal = 24.dp, vertical = 16.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.SpaceBetween\n        ) {\n            // LEFT: Brand/Title Block\n            Row(\n                verticalAlignment = Alignment.CenterVertically,\n                horizontalArrangement = Arrangement.spacedBy(8.dp)\n            ) {\n                Icon(\n                    painter = painterResource(icon),\n                    contentDescription = title,\n                    modifier = Modifier.size(24.dp),\n                    tint = MaterialTheme.colorScheme.primary\n                )\n                Text(\n                    text = title,\n                    style = MaterialTheme.typography.headlineMedium,\n                    fontWeight = androidx.compose.ui.text.font.FontWeight.Black,\n                    color = MaterialTheme.colorScheme.primary,\n                    letterSpacing = (-1).sp\n                )\n            }\n\n            // RIGHT: App Type Switcher moved to config\n            Spacer(Modifier.width(48.dp))\n        }\n\n        // 2. The List Content\n        val refreshState = rememberPullToRefreshState()\n\n        PullToRefreshBox(\n            isRefreshing = state.isLoading,\n            onRefresh = { viewModel.loadApps() },\n            state = refreshState,\n            modifier = Modifier.weight(1f) // Fill remaining space\n        ) {\n            // Using your existing AppList widget, but feeding it PURE STATE\n            AppList(\n                appListType = state.appListType,\n                installers = state.availableInstallers,\n                selectedFilter = state.selectedFilter,\n                filterType = state.filterType,\n                sortBy = state.sortBy,\n                sortOrder = state.sortOrder,\n                searchQuery = state.searchQuery,\n                isLoading = state.isLoading,\n                appList = state.displayedApps,\n                isRoot = state.isRoot,\n                isShizuku = state.isShizuku,\n                startAsGrid = true,\n                imageLoader = imageLoader,\n                installerNameMap = state.installerNameMap,\n                // Actions forwarded to ViewModel\n                onFilterTypeChanged = viewModel::updateFilterType,\n                onSortByChanged = viewModel::updateSort,\n                onSortOrderSelected = viewModel::updateSortOrder,\n                onSearchQueryChange = viewModel::updateSearchQuery,\n                onFilterSelected = {\n                    it?.let { filter ->\n                        viewModel.updateFilter(filter)\n                    }\n                },\n                onAppInfoSelected = { appInfo ->\n                    viewModel.selectApp(appInfo.packageName)\n                },\n                onListTypeChanged = { viewModel.updateListType(it) },\n                onMultiAppAction = { action ->\n                    if (action is MultiAppAction.Freeze || action is MultiAppAction.UnFreeze) {\n                        viewModel.performMultiAction(action)\n                    } else {\n                        onMultiAppAction(action)\n                    }\n                }\n            )\n        }\n    }\n\n    // --- DIALOGS ---\n\n    // 1. Loading Dialog (When fetching heavy App Details)\n    if (state.isLoadingDetails) {\n        Dialog(onDismissRequest = { /* Prevent dismiss while loading */ }) {\n            Box(\n                contentAlignment = Alignment.Center,\n                modifier = Modifier\n                    .size(100.dp)\n                    .clip(RoundedCornerShape(16.dp))\n            ) {\n                CircularProgressIndicator()\n            }\n        }\n    }\n\n    // 2. App Info Dialog (Only shows when details are ready)\n    state.selectedAppDetails?.let { details ->\n        AppInfoDialog(\n            appInfo = details,\n            onDismiss = { viewModel.clearSelection() },\n            isRoot = state.isRoot,\n            isShizuku = state.isShizuku,\n            onAppAction = { action ->\n                when (action) {\n                    is AppClickAction.Reinstall -> {\n                        // Intercept Reinstall to show confirmation locally\n                        reinstallCandidate = action.appInfo\n                        // Don't dismiss main dialog yet? Or dismiss it?\n                        // Typically, we dismiss the info dialog to show the alert\n                        viewModel.clearSelection()\n                    }\n\n                    is AppClickAction.Freeze -> {\n                        viewModel.freezeApp(\n                            action.appInfo.packageName,\n                            action.appInfo.appName,\n                            true\n                        )\n                        viewModel.clearSelection()\n                    }\n\n                    is AppClickAction.UnFreeze -> {\n                        viewModel.freezeApp(\n                            action.appInfo.packageName,\n                            action.appInfo.appName,\n                            false\n                        )\n                        viewModel.clearSelection()\n                    }\n\n                    else -> {\n                        // Forward all other actions (Freeze, Kill, etc)\n                        onAppAction(action)\n                        viewModel.clearSelection()\n                    }\n                }\n            }\n        )\n    }\n\n    // 3. Reinstall Confirmation Alert\n    reinstallCandidate?.let { app ->\n        AlertDialog(\n            icon = {\n                Image(\n                    painter = rememberAsyncImagePainter(getAppIcon(app.packageName, context)),\n                    contentDescription = null,\n                    modifier = Modifier.size(48.dp)\n                )\n            },\n            onDismissRequest = { reinstallCandidate = null },\n            title = { Text(stringResource(R.string.reinstall_play_store_title)) },\n            text = {\n                Text(\n                    stringResource(R.string.reinstall_play_store_desc, app.appName ?: \"\"),\n                    textAlign = TextAlign.Center\n                )\n            },\n            confirmButton = {\n                TextButton(onClick = {\n                    onAppAction(AppClickAction.Reinstall(app))\n                    reinstallCandidate = null\n                }) {\n                    Text(stringResource(R.string.action_reinstall))\n                }\n            },\n            dismissButton = {\n                TextButton(onClick = { reinstallCandidate = null }) {\n                    Text(stringResource(R.string.cancel))\n                }\n            }\n        )\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/appList/AppListViewModel.kt",
    "content": "package com.valhalla.thor.presentation.appList\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.valhalla.thor.domain.model.AppInfo\nimport com.valhalla.thor.domain.model.AppListType\nimport com.valhalla.thor.domain.model.FilterType\nimport com.valhalla.thor.domain.model.MultiAppAction\nimport com.valhalla.thor.domain.model.SortBy\nimport com.valhalla.thor.domain.model.SortOrder\nimport com.valhalla.thor.domain.repository.PreferenceRepository\nimport com.valhalla.thor.domain.repository.SystemRepository\nimport com.valhalla.thor.domain.usecase.GetAppDetailsUseCase\nimport com.valhalla.thor.domain.usecase.GetInstalledAppsUseCase\nimport com.valhalla.thor.domain.usecase.ManageAppUseCase\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.flow.update\nimport kotlinx.coroutines.launch\n\n// ... AppListUiState remains same ...\ndata class AppListUiState(\n    val isLoading: Boolean = true,\n    // Privileges\n    val isRoot: Boolean = false,\n    val isShizuku: Boolean = false,\n    // Raw Data\n    val allUserApps: List<AppInfo> = emptyList(),\n    val allSystemApps: List<AppInfo> = emptyList(),\n    // Filter State\n    val appListType: AppListType = AppListType.USER,\n    val filterType: FilterType = FilterType.Source,\n    val selectedFilter: String = \"All\",\n    val searchQuery: String = \"\",\n    val sortBy: SortBy = SortBy.NAME,\n    val sortOrder: SortOrder = SortOrder.ASCENDING,\n    // Display Data\n    val displayedApps: List<AppInfo> = emptyList(),\n    val availableInstallers: List<String> = listOf(\"All\"),\n    val installerNameMap: Map<String, String> = emptyMap(),\n    // Detail View State\n    val selectedAppDetails: AppInfo? = null,\n    val isLoadingDetails: Boolean = false,\n    // Action Feedback\n    val actionMessage: String? = null\n)\n\nclass AppListViewModel(\n    private val getInstalledAppsUseCase: GetInstalledAppsUseCase,\n    private val getAppDetailsUseCase: GetAppDetailsUseCase,\n    private val systemRepository: SystemRepository,\n    private val manageAppUseCase: ManageAppUseCase,\n    private val preferenceRepository: PreferenceRepository // Injected\n) : ViewModel() {\n\n    private val _rawState = MutableStateFlow(AppListUiState())\n\n    // Combine raw app data with user preferences from DataStore\n    // OPTIMIZATION: flowOn(Dispatchers.Default) ensures sorting/filtering happens on background thread\n    val uiState = combine(_rawState, preferenceRepository.userPreferences) { state, prefs ->\n        val mergedState = state.copy(\n            sortBy = prefs.appSortBy,\n            sortOrder = prefs.appSortOrder,\n            filterType = prefs.appFilterType,\n            selectedFilter = prefs.appSelectedFilter\n        )\n        processList(mergedState)\n    }\n        .flowOn(Dispatchers.Default) // Move computation off Main Thread\n        .stateIn(\n            viewModelScope,\n            SharingStarted.WhileSubscribed(5000),\n            AppListUiState()\n        )\n\n    init {\n        loadApps()\n    }\n\n    fun loadApps() {\n        viewModelScope.launch {\n            _rawState.update { it.copy(isLoading = true) }\n\n            // Allow navigation/bottom bar animations to finish fluidly\n            delay(800)\n\n            val hasRoot = systemRepository.isRootAvailable()\n            val hasShizuku = systemRepository.isShizukuAvailable()\n            getInstalledAppsUseCase().collect { (user, system) ->\n                _rawState.update {\n                    it.copy(\n                        isLoading = false,\n                        isRoot = hasRoot,\n                        isShizuku = hasShizuku,\n                        allUserApps = user,\n                        allSystemApps = system\n                    )\n                }\n            }\n        }\n    }\n\n    fun freezeApp(packageName: String, appName: String?, freeze: Boolean) {\n        viewModelScope.launch(Dispatchers.IO) {\n            val result = manageAppUseCase.setAppDisabled(packageName, freeze)\n            if (result.isSuccess) {\n                _rawState.update { it.copy(actionMessage = \"${if (freeze) \"Frozen\" else \"Unfrozen\"} ${appName ?: packageName}\") }\n                loadApps()\n            } else {\n                _rawState.update { it.copy(actionMessage = \"Error: ${result.exceptionOrNull()?.message}\") }\n            }\n        }\n    }\n\n    fun dismissMessage() {\n        _rawState.update { it.copy(actionMessage = null) }\n    }\n\n    fun performMultiAction(action: MultiAppAction) {\n        viewModelScope.launch(Dispatchers.IO) {\n            when (action) {\n                is MultiAppAction.Freeze -> {\n                    action.appList.forEach { manageAppUseCase.setAppDisabled(it.packageName, true) }\n                    _rawState.update { it.copy(actionMessage = \"Froze ${action.appList.size} apps\") }\n                    loadApps()\n                }\n\n                is MultiAppAction.UnFreeze -> {\n                    action.appList.forEach {\n                        manageAppUseCase.setAppDisabled(it.packageName, false)\n                    }\n                    _rawState.update { it.copy(actionMessage = \"Unfrozen ${action.appList.size} apps\") }\n                    loadApps()\n                }\n\n                else -> {\n                    // Fallback or forward? If we forward, we need a callback. \n                    // For now let's just stay consistent with single app actions.\n                }\n            }\n        }\n    }\n\n    // --- Actions (Write to DataStore) ---\n\n    fun selectApp(packageName: String) {\n        viewModelScope.launch {\n            _rawState.update { it.copy(isLoadingDetails = true, selectedAppDetails = null) }\n            getAppDetailsUseCase(packageName).onSuccess { fullDetails ->\n                _rawState.update {\n                    it.copy(\n                        isLoadingDetails = false,\n                        selectedAppDetails = fullDetails\n                    )\n                }\n            }.onFailure {\n                _rawState.update { it.copy(isLoadingDetails = false) }\n            }\n        }\n    }\n\n    fun clearSelection() {\n        _rawState.update { it.copy(selectedAppDetails = null) }\n    }\n\n    fun updateListType(type: AppListType) {\n        // AppListType is usually session-only, but we reset filter to \"All\" when switching\n        _rawState.update { it.copy(appListType = type) }\n        viewModelScope.launch {\n            preferenceRepository.updateAppFilter(FilterType.Source, \"All\")\n        }\n    }\n\n    fun updateFilter(filter: String) {\n        viewModelScope.launch {\n            // We need to know current filter type to update properly\n            // We read from _rawState because uiState might be updating asynchronously\n            val currentType = _rawState.value.filterType\n            preferenceRepository.updateAppFilter(currentType, filter)\n        }\n    }\n\n    fun updateFilterType(type: FilterType) {\n        viewModelScope.launch {\n            preferenceRepository.updateAppFilter(type, \"All\")\n        }\n    }\n\n    fun updateSort(sortBy: SortBy) {\n        viewModelScope.launch {\n            preferenceRepository.updateAppSort(sortBy)\n        }\n    }\n\n    fun updateSortOrder(order: SortOrder) {\n        viewModelScope.launch {\n            preferenceRepository.updateAppSortOrder(order)\n        }\n    }\n\n    fun updateSearchQuery(query: String) {\n        _rawState.update { it.copy(searchQuery = query) }\n    }\n\n    private fun processList(state: AppListUiState): AppListUiState {\n        // 1. Pick Source\n        val rawList =\n            if (state.appListType == AppListType.USER) state.allUserApps else state.allSystemApps\n\n        // 2. Filter by Search Query (Early out for performance)\n        val searched = if (state.searchQuery.isBlank()) {\n            rawList\n        } else {\n            rawList.filter {\n                it.appName?.contains(state.searchQuery, ignoreCase = true) == true ||\n                        it.packageName.contains(state.searchQuery, ignoreCase = true)\n            }\n        }\n\n        // 3. Filter by Source/State\n        val filtered = when (state.filterType) {\n            FilterType.Source -> {\n                if (state.selectedFilter == \"All\") searched\n                else searched.filter { it.installerPackageName == state.selectedFilter }\n            }\n\n            FilterType.State -> {\n                when (state.selectedFilter) {\n                    \"Active\" -> searched.filter { it.enabled }\n                    \"Frozen\" -> searched.filter { !it.enabled }\n                    \"Suspended\" -> searched.filter { it.isSuspended }\n                    else -> searched\n                }\n            }\n        }\n\n        // 4. Sort\n        val sorted = getSortedList(filtered, state.sortBy, state.sortOrder)\n\n        // 5. Calculate Installers (Metadata) - OPTIMIZED\n        // Only recalculate map if the full list changed (avoid doing this on search)\n        val installers =\n            rawList.mapNotNull { it.installerPackageName }.distinct().sorted().toMutableList()\n\n        // Fast lookup map for app names to avoid O(N^2) associative logic\n        val nameMap = rawList.associateBy({ it.packageName }, { it.appName })\n        val installerNames = installers.associateWith { pkg -> nameMap[pkg] ?: pkg }\n\n        installers.add(0, \"All\")\n\n        return state.copy(\n            displayedApps = sorted,\n            availableInstallers = installers,\n            installerNameMap = installerNames\n        )\n    }\n\n    private fun getSortedList(\n        list: List<AppInfo>,\n        sortBy: SortBy,\n        order: SortOrder\n    ): List<AppInfo> {\n        val comparator = when (sortBy) {\n            SortBy.NAME -> compareBy<AppInfo> { it.appName?.lowercase() }\n            SortBy.INSTALL_DATE -> compareBy { it.firstInstallTime }\n            SortBy.LAST_UPDATED -> compareBy { it.lastUpdateTime }\n            SortBy.VERSION_CODE -> compareBy { it.versionCode }\n            SortBy.VERSION_NAME -> compareBy { it.versionName }\n            SortBy.TARGET_SDK_VERSION -> compareBy { it.targetSdk }\n            SortBy.MIN_SDK_VERSION -> compareBy { it.minSdk }\n        }\n        return if (order == SortOrder.ASCENDING) list.sortedWith(comparator)\n        else list.sortedWith(comparator).reversed()\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/common/ShizukuPermissionHandler.kt",
    "content": "package com.valhalla.thor.presentation.common\n\nimport android.content.pm.PackageManager\nimport rikka.shizuku.Shizuku\n\n/**\n * Handles the messy boilerplate of listening to Shizuku's binder and requesting permissions.\n */\nclass ShizukuPermissionHandler(\n    private val onPermissionGranted: () -> Unit = {},\n    private val onPermissionDenied: () -> Unit = {},\n    private val onBinderDead: () -> Unit = {}\n) {\n\n    private var isRequestInProgress = false\n\n    private val binderReceivedListener = Shizuku.OnBinderReceivedListener {\n        if (checkPermission()) {\n            onPermissionGranted()\n        }\n    }\n\n    private val binderDeadListener = Shizuku.OnBinderDeadListener {\n        onBinderDead()\n    }\n\n    private val requestPermissionResultListener =\n        Shizuku.OnRequestPermissionResultListener { requestCode, grantResult ->\n            isRequestInProgress = false // Reset flag\n            if (grantResult == PackageManager.PERMISSION_GRANTED) {\n                onPermissionGranted()\n            } else {\n                onPermissionDenied()\n            }\n        }\n\n    fun register() {\n        Shizuku.addBinderReceivedListener(binderReceivedListener)\n        Shizuku.addBinderDeadListener(binderDeadListener)\n        Shizuku.addRequestPermissionResultListener(requestPermissionResultListener)\n    }\n\n    fun unregister() {\n        Shizuku.removeBinderReceivedListener(binderReceivedListener)\n        Shizuku.removeBinderDeadListener(binderDeadListener)\n        Shizuku.removeRequestPermissionResultListener(requestPermissionResultListener)\n    }\n\n    /**\n     * Checks if we have permission. If not, requests it.\n     * Returns true if already granted.\n     */\n    fun checkAndRequestPermission(requestCode: Int): Boolean {\n        // If binder isn't ready, we can't do anything yet.\n        if (!Shizuku.pingBinder()) {\n            return false\n        }\n\n        if (checkPermission()) {\n            onPermissionGranted()\n            return true\n        }\n\n        // Prevent spamming requests if one is already pending\n        if (isRequestInProgress) return false\n\n        try {\n            if (Shizuku.shouldShowRequestPermissionRationale()) {\n                // Ideally show UI rationale here.\n            }\n            isRequestInProgress = true // Set flag\n            Shizuku.requestPermission(requestCode)\n        } catch (_: Exception) {\n            isRequestInProgress = false\n            return false\n        }\n        return false\n    }\n\n    private fun checkPermission(): Boolean {\n        return try {\n            if (Shizuku.pingBinder()) {\n                Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED\n            } else {\n                false\n            }\n        } catch (e: Throwable) {\n            false\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/common/components/ConnectedButtonGroup.kt",
    "content": "package com.valhalla.thor.presentation.common.components\n\nimport androidx.compose.foundation.interaction.MutableInteractionSource\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.material3.ButtonDefaults\nimport androidx.compose.material3.ButtonGroupDefaults\nimport androidx.compose.material3.DropdownMenuItem\nimport androidx.compose.material3.ExperimentalMaterial3ExpressiveApi\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.ToggleButton\nimport androidx.compose.material3.ToggleButtonShapes\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.res.painterResource\n\n/**\n * A single-select Connected Button Group built on top of [ButtonGroup] + [ToggleButton].\n *\n * Shape logic, press animation, and overflow menu wiring are handled internally.\n * Callers only describe *what* each button shows via [ConnectedButtonGroupItem] and\n * respond to selection changes.\n *\n * The expressive press animation (buttons physically expand and compress their\n * neighbours) is activated automatically via [Modifier.animateWidth].\n *\n * ---\n *\n * **Icon-only** (DashboardHeader, AppListScreen):\n * ```kotlin\n * ConnectedButtonGroup(\n *     items = AppListType.entries.map { type ->\n *         ConnectedButtonGroupItem.Icon(\n *             iconRes = if (type == AppListType.USER) R.drawable.apps else R.drawable.android,\n *             contentDescription = type.name\n *         )\n *     },\n *     selectedIndex = AppListType.entries.indexOf(selectedType),\n *     onItemSelected = { onTypeChanged(AppListType.entries[it]) }\n * )\n * ```\n *\n * **Text labels** (SettingsScreen ThemeMode, AppFilterSheet tabs):\n * ```kotlin\n * ConnectedButtonGroup(\n *     items = ThemeMode.entries.map { ConnectedButtonGroupItem.Label(it.label()) },\n *     selectedIndex = ThemeMode.entries.indexOf(prefs.themeMode),\n *     onItemSelected = { viewModel.setThemeMode(ThemeMode.entries[it]) }\n * )\n * ```\n */\n@OptIn(ExperimentalMaterial3ExpressiveApi::class)\n@Composable\nfun ConnectedButtonGroup(\n    items: List<ConnectedButtonGroupItem>,\n    selectedIndex: Int,\n    onItemSelected: (index: Int) -> Unit,\n    modifier: Modifier = Modifier,\n) {\n    require(items.isNotEmpty()) { \"ConnectedButtonGroup requires at least one item\" }\n\n    val lastIndex = items.lastIndex\n\n    androidx.compose.material3.ButtonGroup(\n        overflowIndicator = {},\n        modifier = modifier,\n        horizontalArrangement = Arrangement.spacedBy(ButtonGroupDefaults.ConnectedSpaceBetween),\n    ) {\n        items.forEachIndexed { index, item ->\n            customItem(\n                buttonGroupContent = {\n                    val interactionSource = remember { MutableInteractionSource() }\n                    ToggleButton(\n                        checked = index == selectedIndex,\n                        onCheckedChange = { checked -> if (checked) onItemSelected(index) },\n                        modifier = Modifier.animateWidth(interactionSource),\n                        shapes = connectedShapesFor(index, lastIndex),\n                        interactionSource = interactionSource,\n                    ) {\n                        ItemContent(item)\n                    }\n                },\n                menuContent = { menuState ->\n                    // Overflow fallback — never visible for fixed-count groups,\n                    // but required by the ButtonGroup API contract.\n                    DropdownMenuItem(\n                        text = { Text(item.menuLabel) },\n                        leadingIcon = item.menuIcon?.let { res ->\n                            { Icon(painterResource(res), contentDescription = null) }\n                        },\n                        onClick = {\n                            onItemSelected(index)\n                            menuState.dismiss()\n                        }\n                    )\n                }\n            )\n        }\n    }\n}\n\n// ─── Item descriptor ──────────────────────────────────────────────────────────\n\n/**\n * Sealed hierarchy that describes the visual content of a single button.\n * Keeps the reusable composable generic without requiring caller-side lambdas.\n */\nsealed interface ConnectedButtonGroupItem {\n\n    /** Label shown in the overflow [DropdownMenuItem]. */\n    val menuLabel: String\n\n    /** Optional icon shown in the overflow [DropdownMenuItem]. */\n    val menuIcon: Int? get() = null\n\n    // ── Concrete variants ─────────────────────────────────────────────────────\n\n    /** Button shows only an icon (e.g. User / System app-type switcher). */\n    data class Icon(\n        val iconRes: Int,\n        val contentDescription: String,\n    ) : ConnectedButtonGroupItem {\n        override val menuLabel: String get() = contentDescription\n        override val menuIcon: Int get() = iconRes\n    }\n\n    /** Button shows only a text label (e.g. ThemeMode picker, tab switcher). */\n    data class Label(val text: String) : ConnectedButtonGroupItem {\n        override val menuLabel: String get() = text\n    }\n\n    /** Button shows an icon followed by a text label. */\n    data class IconWithLabel(\n        val iconRes: Int,\n        val contentDescription: String,\n        val text: String,\n    ) : ConnectedButtonGroupItem {\n        override val menuLabel: String get() = text\n        override val menuIcon: Int get() = iconRes\n    }\n}\n\n// ─── Private helpers ──────────────────────────────────────────────────────────\n\n/** Renders the correct inner content for each [ConnectedButtonGroupItem] variant. */\n@Composable\nprivate fun ItemContent(item: ConnectedButtonGroupItem) {\n    when (item) {\n        is ConnectedButtonGroupItem.Icon ->\n            Icon(\n                painter = painterResource(item.iconRes),\n                contentDescription = item.contentDescription\n            )\n\n        is ConnectedButtonGroupItem.Label ->\n            Text(item.text, maxLines = 1)\n\n        is ConnectedButtonGroupItem.IconWithLabel ->\n            Row(\n                verticalAlignment = Alignment.CenterVertically,\n                horizontalArrangement = Arrangement.spacedBy(ButtonDefaults.IconSpacing)\n            ) {\n                Icon(\n                    painter = painterResource(item.iconRes),\n                    contentDescription = item.contentDescription\n                )\n                Text(item.text, maxLines = 1)\n            }\n    }\n}\n\n/**\n * Maps a button's position within the group to the correct [ToggleButtonShapes],\n * following the Connected Button Group spec:\n *\n * - `index == 0`             → pill-left, small inner-right  *(leading)*\n * - `0 < index < lastIndex`  → small corners on all sides    *(middle)*\n * - `index == lastIndex`     → small inner-left, pill-right  *(trailing)*\n */\n@OptIn(ExperimentalMaterial3ExpressiveApi::class)\n@Composable\nprivate fun connectedShapesFor(index: Int, lastIndex: Int): ToggleButtonShapes = when {\n    index == 0 -> ButtonGroupDefaults.connectedLeadingButtonShapes()\n    index == lastIndex -> ButtonGroupDefaults.connectedTrailingButtonShapes()\n    else -> ButtonGroupDefaults.connectedMiddleButtonShapes()\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/freezer/FreezerScreen.kt",
    "content": "package com.valhalla.thor.presentation.freezer\n\nimport android.widget.Toast\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.background\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.fillMaxSize\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.material3.AlertDialog\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.material3.pulltorefresh.PullToRefreshBox\nimport androidx.compose.material3.pulltorefresh.rememberPullToRefreshState\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.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.painterResource\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.lifecycle.compose.collectAsStateWithLifecycle\nimport coil3.ImageLoader\nimport coil3.compose.rememberAsyncImagePainter\nimport coil3.request.crossfade\nimport com.valhalla.thor.R\nimport com.valhalla.thor.domain.model.AppClickAction\nimport com.valhalla.thor.domain.model.AppInfo\nimport com.valhalla.thor.domain.model.MultiAppAction\nimport com.valhalla.thor.presentation.utils.AppIconFetcher\nimport com.valhalla.thor.presentation.utils.AppIconKeyer\nimport com.valhalla.thor.presentation.utils.getAppIcon\nimport com.valhalla.thor.presentation.widgets.AppInfoDialog\nimport com.valhalla.thor.presentation.widgets.AppList\nimport org.koin.androidx.compose.koinViewModel\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun FreezerScreen(\n    modifier: Modifier = Modifier,\n    viewModel: FreezerViewModel = koinViewModel(),\n    onAppAction: (AppClickAction) -> Unit = {},\n    onMultiAppAction: (MultiAppAction) -> Unit = {}\n) {\n    val state by viewModel.uiState.collectAsStateWithLifecycle()\n    val context = LocalContext.current\n\n    // Local State for Dialogs\n    var selectedAppInfo by remember { mutableStateOf<AppInfo?>(null) }\n    var reinstallCandidate by remember { mutableStateOf<AppInfo?>(null) }\n\n    LaunchedEffect(Unit) {\n        if (state.allUserApps.isEmpty() && state.allSystemApps.isEmpty() && state.isLoading) {\n            viewModel.loadApps()\n        }\n    }\n\n    // Create a custom ImageLoader that knows how to fetch App Icons in the background.\n    val imageLoader = remember(context) {\n        ImageLoader.Builder(context)\n            .components {\n                add(AppIconKeyer())\n                add(AppIconFetcher.Factory(context))\n            }\n            .crossfade(true)\n            .build()\n    }\n\n    // Handle Feedback (Toasts from ViewModel actions)\n    LaunchedEffect(state.actionMessage) {\n        state.actionMessage?.let { message ->\n            Toast.makeText(context, message, Toast.LENGTH_SHORT).show()\n            viewModel.dismissMessage()\n        }\n    }\n\n    Column(\n        modifier = modifier\n            .fillMaxSize()\n            .background(MaterialTheme.colorScheme.background)\n    ) {\n\n        // --- Header ---\n        Row(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(horizontal = 24.dp, vertical = 16.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.SpaceBetween\n        ) {\n            // LEFT: Brand/Title Block\n            Row(\n                verticalAlignment = Alignment.CenterVertically,\n                horizontalArrangement = Arrangement.spacedBy(8.dp)\n            ) {\n                Icon(\n                    painter = painterResource(R.drawable.frozen),\n                    contentDescription = stringResource(R.string.freezer),\n                    modifier = Modifier.size(24.dp),\n                    tint = MaterialTheme.colorScheme.primary\n                )\n                Text(\n                    text = stringResource(R.string.freezer),\n                    style = MaterialTheme.typography.headlineMedium,\n                    fontWeight = androidx.compose.ui.text.font.FontWeight.Black,\n                    color = MaterialTheme.colorScheme.primary,\n                    letterSpacing = (-1).sp\n                )\n            }\n\n            // RIGHT: App Source Switcher moved to config\n            Spacer(Modifier.width(48.dp))\n        }\n\n        // --- List Content ---\n        val refreshState = rememberPullToRefreshState()\n\n        PullToRefreshBox(\n            isRefreshing = state.isLoading,\n            onRefresh = { viewModel.loadApps() },\n            state = refreshState,\n            modifier = Modifier.weight(1f)\n        ) {\n            AppList(\n                appListType = state.appListType,\n                installers = state.availableInstallers,\n                selectedFilter = state.selectedFilter,\n                filterType = state.filterType,\n                sortBy = state.sortBy,\n                sortOrder = state.sortOrder,\n                searchQuery = state.searchQuery,\n                isLoading = state.isLoading,\n                appList = state.displayedApps,\n                isRoot = state.isRoot,\n                isShizuku = state.isShizuku,\n                imageLoader = imageLoader,\n                installerNameMap = state.installerNameMap,\n                // Filtering / Sorting Actions\n                onFilterTypeChanged = viewModel::updateFilterType,\n                onSortByChanged = viewModel::updateSort,\n                onSortOrderSelected = viewModel::updateSortOrder,\n                onSearchQueryChange = viewModel::updateSearchQuery,\n                onFilterSelected = { it?.let { filter -> viewModel.updateFilter(filter) } },\n                onListTypeChanged = { viewModel.updateListType(it) },\n                // Multi-Selection Actions\n                onMultiAppAction = { action ->\n                    if (action is MultiAppAction.Freeze || action is MultiAppAction.UnFreeze) {\n                        viewModel.performMultiAction(action)\n                    } else {\n                        onMultiAppAction(action) // Forward others (e.g. Uninstall All)\n                    }\n                },\n                // Single App Selection (Opens Dialog)\n                onAppInfoSelected = { app ->\n                    selectedAppInfo = app\n                }\n            )\n        }\n    }\n\n    // --- DIALOGS ---\n\n    // 1. App Info Dialog\n    selectedAppInfo?.let { app ->\n        AppInfoDialog(\n            appInfo = app,\n            isRoot = state.isRoot,\n            isShizuku = state.isShizuku,\n            onDismiss = { selectedAppInfo = null },\n            onAppAction = { action ->\n                when (action) {\n                    // CASE A: Local Logic (Freeze/Unfreeze)\n                    is AppClickAction.Freeze -> {\n                        viewModel.toggleAppFreezeState(action.appInfo)\n                        selectedAppInfo = null\n                    }\n\n                    is AppClickAction.UnFreeze -> {\n                        viewModel.toggleAppFreezeState(action.appInfo)\n                        selectedAppInfo = null\n                    }\n                    // CASE B: Reinstall (Needs Confirmation)\n                    is AppClickAction.Reinstall -> {\n                        reinstallCandidate = action.appInfo\n                        selectedAppInfo = null // Dismiss info dialog, show confirmation\n                    }\n                    // CASE C: Forward everything else (Launch, Uninstall, etc.)\n                    else -> {\n                        onAppAction(action)\n                        selectedAppInfo = null\n                    }\n                }\n            }\n        )\n    }\n\n    // 2. Reinstall Confirmation (Matches AppListScreen behavior)\n    reinstallCandidate?.let { app ->\n        AlertDialog(\n            icon = {\n                Image(\n                    painter = rememberAsyncImagePainter(getAppIcon(app.packageName, context)),\n                    contentDescription = null,\n                    modifier = Modifier.size(48.dp)\n                )\n            },\n            onDismissRequest = { reinstallCandidate = null },\n            title = { Text(stringResource(R.string.reinstall_play_store_title)) },\n            text = {\n                Text(\n                    stringResource(R.string.reinstall_play_store_desc, app.appName ?: \"\"),\n                    textAlign = TextAlign.Center\n                )\n            },\n            confirmButton = {\n                TextButton(onClick = {\n                    onAppAction(AppClickAction.Reinstall(app)) // Forward to Main -> HomeViewModel\n                    reinstallCandidate = null\n                }) {\n                    Text(stringResource(R.string.yes))\n                }\n            },\n            dismissButton = {\n                TextButton(onClick = { reinstallCandidate = null }) {\n                    Text(stringResource(R.string.cancel))\n                }\n            }\n        )\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/freezer/FreezerViewModel.kt",
    "content": "package com.valhalla.thor.presentation.freezer\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.valhalla.thor.domain.model.AppInfo\nimport com.valhalla.thor.domain.model.AppListType\nimport com.valhalla.thor.domain.model.FilterType\nimport com.valhalla.thor.domain.model.MultiAppAction\nimport com.valhalla.thor.domain.model.SortBy\nimport com.valhalla.thor.domain.model.SortOrder\nimport com.valhalla.thor.domain.repository.SystemRepository\nimport com.valhalla.thor.domain.usecase.GetInstalledAppsUseCase\nimport com.valhalla.thor.domain.usecase.ManageAppUseCase\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.flow.update\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\n\ndata class FreezerUiState(\n    val isLoading: Boolean = true,\n    // Privileges\n    val isRoot: Boolean = false,\n    val isShizuku: Boolean = false,\n    // Complete App Lists\n    val allUserApps: List<AppInfo> = emptyList(),\n    val allSystemApps: List<AppInfo> = emptyList(),\n    // Display State\n    val displayedApps: List<AppInfo> = emptyList(),\n    val appListType: AppListType = AppListType.USER,\n    // Freezer defaults to showing \"Frozen\" apps first, usually\n    val filterType: FilterType = FilterType.State,\n    val selectedFilter: String = \"Frozen\",\n    val searchQuery: String = \"\",\n    val sortBy: SortBy = SortBy.NAME,\n    val sortOrder: SortOrder = SortOrder.ASCENDING,\n    val availableInstallers: List<String> = listOf(\"All\"),\n    val installerNameMap: Map<String, String> = emptyMap(),\n    // Action Feedback\n    val actionMessage: String? = null\n)\n\nclass FreezerViewModel(\n    private val getInstalledAppsUseCase: GetInstalledAppsUseCase,\n    private val manageAppUseCase: ManageAppUseCase,\n    private val systemRepository: SystemRepository\n) : ViewModel() {\n\n    private val _uiState = MutableStateFlow(FreezerUiState())\n    val uiState: StateFlow<FreezerUiState> = _uiState.asStateFlow()\n\n    private var filterJob: Job? = null\n    private var loadAppsJob: Job? = null\n\n    init {\n        loadApps()\n    }\n\n    fun loadApps() {\n        // Cancel any existing collection to prevent duplicates\n        loadAppsJob?.cancel()\n\n        // RUTHLESS: IO Dispatcher for heavy data fetching\n        loadAppsJob = viewModelScope.launch(Dispatchers.IO) {\n            _uiState.update { it.copy(isLoading = true) }\n\n            // Allow bottom nav animations to finish fluidly\n            delay(800)\n\n            // Check privileges\n            val hasRoot = systemRepository.isRootAvailable()\n            val hasShizuku = systemRepository.isShizukuAvailable()\n\n            getInstalledAppsUseCase().collect { (user, system) ->\n                // Data arrived on IO.\n                val partialState = _uiState.value.copy(\n                    isLoading = false,\n                    isRoot = hasRoot,\n                    isShizuku = hasShizuku,\n                    allUserApps = user,\n                    allSystemApps = system\n                )\n\n                // Switch to Default (CPU) for sorting\n                val finalState = processListState(partialState)\n\n                _uiState.update { finalState }\n            }\n        }\n    }\n\n    // --- Actions ---\n\n    fun toggleAppFreezeState(app: AppInfo) {\n        viewModelScope.launch {\n            val shouldFreeze = app.enabled // If enabled, we freeze. If disabled, we unfreeze.\n            // Note: We are using packageName, assuming ManageAppUseCase handles it correctly.\n            val result = manageAppUseCase.setAppDisabled(app.packageName, shouldFreeze)\n\n            result.onSuccess {\n                _uiState.update { s -> s.copy(actionMessage = \"${if (shouldFreeze) \"Frozen\" else \"Unfrozen\"} ${app.appName}\") }\n                // No need to reload manually, repository flow triggers update\n            }.onFailure { e ->\n                _uiState.update { s -> s.copy(actionMessage = \"Error: ${e.message}\") }\n            }\n        }\n    }\n\n    fun performMultiAction(action: MultiAppAction) {\n        viewModelScope.launch {\n            when (action) {\n                is MultiAppAction.Freeze -> {\n                    action.appList.forEach { manageAppUseCase.setAppDisabled(it.packageName, true) }\n                    _uiState.update { it.copy(actionMessage = \"Froze ${action.appList.size} apps\") }\n                }\n\n                is MultiAppAction.UnFreeze -> {\n                    action.appList.forEach {\n                        manageAppUseCase.setAppDisabled(it.packageName, false)\n                    }\n                    _uiState.update { it.copy(actionMessage = \"Unfrozen ${action.appList.size} apps\") }\n                }\n\n                else -> {\n                    _uiState.update { it.copy(actionMessage = \"Action not supported in Freezer yet\") }\n                }\n            }\n        }\n    }\n\n    fun dismissMessage() {\n        _uiState.update { it.copy(actionMessage = null) }\n    }\n\n    // --- Filter / Sort Updates (Async) ---\n\n    fun updateListType(type: AppListType) {\n        triggerAsyncUpdate { it.copy(appListType = type) }\n    }\n\n    fun updateFilter(filter: String) {\n        triggerAsyncUpdate { it.copy(selectedFilter = filter) }\n    }\n\n    fun updateFilterType(type: FilterType) {\n        triggerAsyncUpdate {\n            it.copy(\n                filterType = type,\n                selectedFilter = if (type == FilterType.State) \"Frozen\" else \"All\"\n            )\n        }\n    }\n\n    fun updateSort(sortBy: SortBy) {\n        triggerAsyncUpdate { it.copy(sortBy = sortBy) }\n    }\n\n    fun updateSortOrder(order: SortOrder) {\n        triggerAsyncUpdate { it.copy(sortOrder = order) }\n    }\n\n    fun updateSearchQuery(query: String) {\n        triggerAsyncUpdate { it.copy(searchQuery = query) }\n    }\n\n    private fun triggerAsyncUpdate(reducer: (FreezerUiState) -> FreezerUiState) {\n        filterJob?.cancel()\n        filterJob = viewModelScope.launch(Dispatchers.Default) {\n            // Apply the change to the CURRENT state\n            val pendingState = reducer(_uiState.value)\n            // Re-process list\n            val finalState = processListState(pendingState)\n            _uiState.update { finalState }\n        }\n    }\n\n    // --- Core Logic (CPU Bound) ---\n    private suspend fun processListState(state: FreezerUiState): FreezerUiState =\n        withContext(Dispatchers.Default) {\n            val rawList =\n                if (state.appListType == AppListType.USER) state.allUserApps else state.allSystemApps\n\n            // 1. Search Query Filter\n            val searched = if (state.searchQuery.isBlank()) {\n                rawList\n            } else {\n                rawList.filter {\n                    it.appName?.contains(state.searchQuery, ignoreCase = true) == true ||\n                            it.packageName.contains(state.searchQuery, ignoreCase = true)\n                }\n            }\n\n            // 2. State/Source Filter\n            val filtered = when (state.filterType) {\n                FilterType.State -> {\n                    when (state.selectedFilter) {\n                        \"Frozen\" -> searched.filter { !it.enabled }\n                        \"Active\" -> searched.filter { it.enabled }\n                        \"Suspended\" -> searched.filter { it.isSuspended }\n                        else -> searched\n                    }\n                }\n\n                FilterType.Source -> {\n                    if (state.selectedFilter == \"All\") searched\n                    else searched.filter { it.installerPackageName == state.selectedFilter }\n                }\n            }\n\n            // 3. Sort\n            val sorted = getSortedList(filtered, state.sortBy, state.sortOrder)\n\n            // 4. Metadata - OPTIMIZED\n            val installers =\n                rawList.mapNotNull { it.installerPackageName }.distinct().sorted().toMutableList()\n\n            val nameMap = rawList.associateBy({ it.packageName }, { it.appName })\n            val installerNames = installers.associateWith { pkg -> nameMap[pkg] ?: pkg }\n\n            installers.add(0, \"All\")\n\n            state.copy(\n                displayedApps = sorted,\n                availableInstallers = installers,\n                installerNameMap = installerNames\n            )\n        }\n\n    private fun getSortedList(\n        list: List<AppInfo>,\n        sortBy: SortBy,\n        order: SortOrder\n    ): List<AppInfo> {\n        val comparator = when (sortBy) {\n            SortBy.NAME -> compareBy<AppInfo> { it.appName?.lowercase() }\n            SortBy.INSTALL_DATE -> compareBy { it.firstInstallTime }\n            SortBy.LAST_UPDATED -> compareBy { it.lastUpdateTime }\n            SortBy.VERSION_CODE -> compareBy { it.versionCode }\n            SortBy.VERSION_NAME -> compareBy { it.versionName }\n            SortBy.TARGET_SDK_VERSION -> compareBy { it.targetSdk }\n            SortBy.MIN_SDK_VERSION -> compareBy { it.minSdk }\n        }\n        return if (order == SortOrder.ASCENDING) list.sortedWith(comparator)\n        else list.sortedWith(comparator).reversed()\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/home/AppDestinations.kt",
    "content": "package com.valhalla.thor.presentation.home\n\nimport com.valhalla.thor.R\nimport kotlinx.serialization.Serializable\n\n@Serializable\nenum class AppDestinations(\n    val label: Int,\n    val icon: Int,\n    val selectedIcon: Int,\n    val contentDescription: Int\n) {\n    HOME(R.string.home, R.drawable.home_outline, R.drawable.home, R.string.home_desc),\n    APPS(R.string.apps, R.drawable.apps, R.drawable.apps, R.string.apps_desc),\n    FREEZER(R.string.freezer, R.drawable.frozen, R.drawable.snowflake, R.string.freezer_desc),\n\n    SETTINGS(\n        R.string.settings,\n        R.drawable.settings_outline,\n        R.drawable.settings,\n        R.string.settings_desc\n    )\n\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/home/HomeScreen.kt",
    "content": "package com.valhalla.thor.presentation.home\n\nimport androidx.activity.compose.rememberLauncherForActivityResult\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.compose.animation.AnimatedVisibility\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.rememberScrollState\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material3.AlertDialog\nimport androidx.compose.material3.Button\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.OutlinedButton\nimport androidx.compose.material3.Text\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.Brush\nimport androidx.compose.ui.graphics.Color\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.collectAsStateWithLifecycle\nimport com.valhalla.thor.R\nimport com.valhalla.thor.domain.model.AppListType\nimport com.valhalla.thor.presentation.home.components.AppDistributionChart\nimport com.valhalla.thor.presentation.home.components.DashboardHeader\nimport com.valhalla.thor.presentation.home.components.SocialLinksRow\nimport com.valhalla.thor.presentation.home.components.SummaryStatRow\nimport com.valhalla.thor.presentation.installer.InstallerViewModel\nimport com.valhalla.thor.presentation.installer.PortableInstaller\nimport org.koin.androidx.compose.koinViewModel\n\n@Composable\nfun HomeScreen(\n    onNavigateToApps: () -> Unit,\n    onNavigateToFreezer: () -> Unit,\n    onReinstallAll: () -> Unit,\n    onClearAllCache: (AppListType) -> Unit,\n    viewModel: HomeViewModel = koinViewModel(),\n    installerViewModel: InstallerViewModel = koinViewModel()\n) {\n    val state by viewModel.state.collectAsStateWithLifecycle()\n    var showCacheDialog by remember { mutableStateOf(false) }\n    var showPrivilegeDialog by remember { mutableStateOf(false) }\n\n    var showInstallerSheet by remember { mutableStateOf(false) }\n\n    val filePickerLauncher = rememberLauncherForActivityResult(\n        contract = ActivityResultContracts.OpenDocument()\n    ) { uri ->\n        uri?.let {\n            installerViewModel.installFile(it)\n            showInstallerSheet = true\n        }\n    }\n\n    Column(\n        modifier = Modifier\n            .fillMaxSize()\n            .background(MaterialTheme.colorScheme.background)\n            .verticalScroll(rememberScrollState())\n            .padding(bottom = 100.dp) // Nav bar space\n    ) {\n        // 1. Header\n        DashboardHeader(\n            isRoot = state.isRootAvailable,\n            isShizuku = state.isShizukuAvailable,\n            isDhizuku = state.isDhizukuAvailable,\n            activeMode = state.activePrivilegeMode,\n            selectedType = state.selectedType,\n            onTypeChanged = { viewModel.onTypeChanged(it) },\n            onPrivilegeChanged = { viewModel.onPrivilegeModeChanged(it) },\n            onRestrictedStatusClick = { showPrivilegeDialog = true }\n        )\n\n        Spacer(Modifier.height(8.dp))\n\n        // 2. Summary Cards\n        SummaryStatRow(\n            activeCount = state.activeAppCount,\n            frozenCount = state.frozenAppCount,\n            suspendedCount = state.suspendedAppCount,\n            onActiveClick = onNavigateToApps,\n            onFrozenClick = onNavigateToFreezer,\n            onSuspendedClick = onNavigateToFreezer // For now just go to freezer\n        )\n\n        Spacer(Modifier.height(12.dp))\n\n        // --- ACTIONS ---\n\n        // B. Reinstall All (Warning style card)\n        AnimatedVisibility(state.activePrivilegeMode != null && state.unknownInstallerCount > 0 && state.showReinstallCard) {\n            Column {\n                ActionCard(\n                    title = stringResource(R.string.reinstall_all),\n                    subtitle = stringResource(\n                        R.string.reinstall_all_subtitle,\n                        state.unknownInstallerCount,\n                        state.selectedType.name.lowercase()\n                    ),\n                    icon = R.drawable.apk_install,\n                    isWarning = true,\n                    onClick = onReinstallAll,\n                    onClose = { viewModel.dismissReinstallCard() }\n                )\n                Spacer(Modifier.height(12.dp))\n            }\n\n        }\n\n        // C. Portable Installer (Primary style card)\n        ActionCard(\n            title = stringResource(R.string.install_from_file),\n            subtitle = stringResource(R.string.install_from_file_subtitle),\n            icon = R.drawable.apk_install,\n            isPrimary = true,\n            onClick = {\n                filePickerLauncher.launch(arrayOf(\"*/*\"))\n            }\n        )\n\n        Spacer(Modifier.height(24.dp))\n\n        // 3. Distribution Chart\n        AnimatedVisibility(state.distributionData.isNotEmpty() && !state.isLoading) {\n            Column(\n                modifier = Modifier\n                    .padding(horizontal = 24.dp)\n                    .clip(RoundedCornerShape(48.dp))\n                    .background(MaterialTheme.colorScheme.surfaceContainerLow)\n                    .padding(24.dp)\n            ) {\n                Row(\n                    modifier = Modifier.fillMaxWidth(),\n                    horizontalArrangement = Arrangement.SpaceBetween,\n                    verticalAlignment = Alignment.Bottom\n                ) {\n                    Text(\n                        text = stringResource(R.string.app_distribution),\n                        style = MaterialTheme.typography.headlineSmall,\n                        fontWeight = FontWeight.Bold,\n                        maxLines = 1,\n                        overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,\n                        modifier = Modifier.weight(1f)\n                    )\n                    Text(\n                        text = stringResource(\n                            R.string.total_apps,\n                            state.activeAppCount + state.frozenAppCount\n                        ),\n                        style = MaterialTheme.typography.labelSmall,\n                        color = MaterialTheme.colorScheme.onSurfaceVariant,\n                        maxLines = 1\n                    )\n                }\n                Spacer(Modifier.height(24.dp))\n                AppDistributionChart(\n                    data = state.distributionData,\n                    modifier = Modifier.fillMaxWidth()\n                )\n            }\n        }\n\n        // 4. Social Links\n        Spacer(Modifier.height(8.dp))\n        SocialLinksRow()\n        Spacer(Modifier.height(32.dp))\n    }\n\n    // --- Dialogs ---\n    if (showCacheDialog) {\n        AlertDialog(\n            onDismissRequest = { showCacheDialog = false },\n            icon = { Icon(painterResource(R.drawable.clear_all), null) },\n            title = { Text(stringResource(R.string.clear_all_cache)) },\n            text = { Text(stringResource(R.string.clear_cache_prompt)) },\n            confirmButton = {\n                Button(onClick = {\n                    onClearAllCache(AppListType.USER)\n                    showCacheDialog = false\n                }) { Text(stringResource(R.string.user_apps)) }\n            },\n            dismissButton = {\n                OutlinedButton(onClick = {\n                    onClearAllCache(AppListType.SYSTEM)\n                    showCacheDialog = false\n                }) { Text(stringResource(R.string.system_apps)) }\n            }\n        )\n    }\n\n    if (showPrivilegeDialog) {\n        AlertDialog(\n            onDismissRequest = { showPrivilegeDialog = false },\n            icon = { Icon(painterResource(R.drawable.privacy_tip), null) },\n            title = { Text(stringResource(R.string.privilege_check)) },\n            text = {\n                Text(stringResource(R.string.privilege_check_desc))\n            },\n            confirmButton = {\n                Button(onClick = {\n                    viewModel.loadDashboardData()\n                    showPrivilegeDialog = false\n                }) {\n                    Text(stringResource(R.string.refresh))\n                }\n            },\n            dismissButton = {\n                OutlinedButton(onClick = { showPrivilegeDialog = false }) {\n                    Text(stringResource(R.string.cancel))\n                }\n            }\n        )\n    }\n\n    if (showInstallerSheet) {\n        PortableInstaller(\n            onDismiss = { showInstallerSheet = false },\n            viewModel = installerViewModel\n        )\n    }\n}\n\n@Composable\nprivate fun ActionCard(\n    title: String,\n    subtitle: String,\n    icon: Int,\n    isPrimary: Boolean = false,\n    isWarning: Boolean = false,\n    onClick: () -> Unit,\n    onClose: (() -> Unit)? = null\n) {\n    val containerColor = when {\n        isPrimary -> MaterialTheme.colorScheme.primaryContainer\n        isWarning -> MaterialTheme.colorScheme.tertiaryContainer.copy(alpha = 0.1f)\n        else -> MaterialTheme.colorScheme.surfaceContainerHigh\n    }\n\n    val contentColor = when {\n        isPrimary -> MaterialTheme.colorScheme.onPrimaryContainer\n        isWarning -> MaterialTheme.colorScheme.tertiary\n        else -> MaterialTheme.colorScheme.onSurface\n    }\n\n    Box(\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(horizontal = 24.dp)\n            .clip(RoundedCornerShape(32.dp))\n            .background(containerColor)\n            .then(\n                if (isWarning) {\n                    Modifier.background(\n                        Brush.linearGradient(\n                            colors = listOf(\n                                MaterialTheme.colorScheme.tertiary.copy(alpha = 0.05f),\n                                Color.Transparent\n                            )\n                        )\n                    )\n                } else Modifier\n            )\n            .clickable(onClick = onClick)\n            .padding(if (isPrimary) 24.dp else 20.dp)\n    ) {\n        Row(\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.spacedBy(16.dp)\n        ) {\n            Box(\n                modifier = Modifier\n                    .clip(RoundedCornerShape(24.dp))\n                    .background(\n                        if (isPrimary) MaterialTheme.colorScheme.onPrimaryContainer\n                        else MaterialTheme.colorScheme.tertiary.copy(alpha = 0.2f)\n                    )\n                    .padding(if (isPrimary) 16.dp else 12.dp)\n            ) {\n                Icon(\n                    painter = painterResource(icon),\n                    contentDescription = null,\n                    modifier = Modifier.size(if (isPrimary) 24.dp else 20.dp),\n                    tint = if (isPrimary) MaterialTheme.colorScheme.primaryContainer else contentColor\n                )\n            }\n\n            Column(modifier = Modifier.weight(1f)) {\n                Text(\n                    text = title,\n                    style = if (isPrimary) MaterialTheme.typography.titleLarge else MaterialTheme.typography.titleMedium,\n                    fontWeight = FontWeight.ExtraBold,\n                    color = contentColor\n                )\n                Text(\n                    text = subtitle,\n                    style = MaterialTheme.typography.bodySmall,\n                    color = if (isPrimary) contentColor.copy(alpha = 0.8f) else MaterialTheme.colorScheme.onSurfaceVariant\n                )\n            }\n\n            if (onClose != null) {\n                IconButton(onClick = onClose) {\n                    Icon(\n                        painter = painterResource(R.drawable.round_close),\n                        contentDescription = stringResource(R.string.dismiss),\n                        tint = MaterialTheme.colorScheme.onSurfaceVariant\n                    )\n                }\n            } else if (isPrimary) {\n                Icon(\n                    painter = painterResource(R.drawable.open_in_new), // Arrow forward fallback\n                    contentDescription = null,\n                    tint = contentColor.copy(alpha = 0.4f)\n                )\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/home/HomeViewModel.kt",
    "content": "package com.valhalla.thor.presentation.home\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.valhalla.thor.domain.model.AppInfo\nimport com.valhalla.thor.domain.model.AppListType\nimport com.valhalla.thor.domain.model.PrivilegeMode\nimport com.valhalla.thor.domain.repository.PreferenceRepository\nimport com.valhalla.thor.domain.repository.SystemRepository\nimport com.valhalla.thor.domain.usecase.GetInstalledAppsUseCase\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.flow.update\nimport kotlinx.coroutines.launch\n\ndata class HomeUiState(\n    val isLoading: Boolean = true,\n    val selectedType: AppListType = AppListType.USER,\n    // Stats\n    val activeAppCount: Int = 0,\n    val frozenAppCount: Int = 0,\n    val suspendedAppCount: Int = 0,\n    val unknownInstallerCount: Int = 0,\n    val distributionData: Map<String, Int> = emptyMap(),\n    // Status\n    val isRootAvailable: Boolean = false,\n    val isShizukuAvailable: Boolean = false,\n    val isDhizukuAvailable: Boolean = false,\n    val activePrivilegeMode: PrivilegeMode? = null,\n\n    // Preferences\n    val showReinstallCard: Boolean = true // <--- Controlled by DataStore\n)\n\nclass HomeViewModel(\n    private val getInstalledAppsUseCase: GetInstalledAppsUseCase,\n    private val systemRepository: SystemRepository,\n    private val preferenceRepository: PreferenceRepository // Injected\n) : ViewModel() {\n\n    private var dashboardJob: Job? = null\n    private var typeChangeJob: Job? = null\n    private val _internalState = MutableStateFlow(HomeUiState())\n\n    private var lastUserApps: List<AppInfo> = emptyList()\n    private var lastSystemApps: List<AppInfo> = emptyList()\n\n    // Combine internal data processing with user preferences\n    val state = combine(_internalState, preferenceRepository.userPreferences) { internal, prefs ->\n        val activeMode = prefs.preferredPrivilegeMode ?: when {\n            internal.isRootAvailable -> PrivilegeMode.ROOT\n            internal.isShizukuAvailable -> PrivilegeMode.SHIZUKU\n            internal.isDhizukuAvailable -> PrivilegeMode.DHIZUKU\n            else -> null\n        }\n        internal.copy(\n            showReinstallCard = prefs.showReinstallAllCard,\n            activePrivilegeMode = activeMode\n        )\n    }.stateIn(\n        viewModelScope,\n        SharingStarted.WhileSubscribed(5000),\n        HomeUiState()\n    )\n\n    init {\n        loadDashboardData()\n    }\n\n    fun loadDashboardData() {\n        // Cancel any existing job to ensure we restart with fresh system status\n        dashboardJob?.cancel()\n\n        dashboardJob = viewModelScope.launch(Dispatchers.IO) {\n            _internalState.update { it.copy(isLoading = true) }\n            val hasRoot = systemRepository.isRootAvailable()\n            val hasShizuku = systemRepository.isShizukuAvailable()\n            val hasDhizuku = systemRepository.isDhizukuAvailable()\n\n            getInstalledAppsUseCase().collect { (userApps, systemApps) ->\n                lastUserApps = userApps\n                lastSystemApps = systemApps\n                processData(\n                    userApps,\n                    systemApps,\n                    _internalState.value.selectedType,\n                    hasRoot,\n                    hasShizuku,\n                    hasDhizuku\n                )\n            }\n        }\n    }\n\n    fun onTypeChanged(type: AppListType) {\n        _internalState.update { it.copy(selectedType = type) }\n        typeChangeJob?.cancel()\n        typeChangeJob = viewModelScope.launch(Dispatchers.IO) {\n            val s = _internalState.value\n            processData(\n                lastUserApps,\n                lastSystemApps,\n                type,\n                s.isRootAvailable,\n                s.isShizukuAvailable,\n                s.isDhizukuAvailable\n            )\n        }\n    }\n\n    fun onPrivilegeModeChanged(mode: PrivilegeMode) {\n        viewModelScope.launch {\n            preferenceRepository.setPrivilegeMode(mode)\n            loadDashboardData() // Refresh everything\n        }\n    }\n\n    fun dismissReinstallCard() {\n        viewModelScope.launch {\n            preferenceRepository.setReinstallAllCardVisibility(false)\n        }\n    }\n\n    private fun processData(\n        userApps: List<AppInfo>,\n        systemApps: List<AppInfo>,\n        selectedType: AppListType,\n        hasRoot: Boolean,\n        hasShizuku: Boolean,\n        hasDhizuku: Boolean\n    ) {\n        val filteredApps = if (selectedType == AppListType.USER) userApps else systemApps\n\n        val activeCount = filteredApps.count { it.enabled && !it.isSuspended }\n        val frozenCount = filteredApps.count { !it.enabled }\n        val suspendedCount = filteredApps.count { it.isSuspended && it.enabled }\n\n        val unknownCount = if (selectedType == AppListType.USER) {\n            userApps.count {\n                it.installerPackageName != \"com.android.vending\" &&\n                        it.installerPackageName != \"com.google.android.packageinstaller\"\n            }\n        } else 0\n\n        val labelCounts = filteredApps\n            .groupBy {\n                when (val pkg = it.installerPackageName) {\n                    \"com.android.vending\" -> \"Play Store\"\n                    \"org.fdroid.fdroid\" -> \"F-Droid\"\n                    \"com.google.android.packageinstaller\" -> \"Sideloaded\"\n                    null, \"Unknown\" -> \"Others\"\n                    else -> pkg.substringAfterLast(\".\").uppercase()\n                }\n            }\n            .mapValues { it.value.size }\n\n        // --- TOP 3 / 4 GROUPING LOGIC ---\n        val sortedLabels = labelCounts.entries.sortedByDescending { it.value }\n\n        val distribution = if (sortedLabels.size <= 4) {\n            // If 4 or fewer categories, show them exactly as they are\n            labelCounts\n        } else {\n            // If more than 4, take top 3 and bunch the rest into \"Others\"\n            val top3Entries = sortedLabels.take(3)\n            val restEntries = sortedLabels.drop(3)\n\n            val result = mutableMapOf<String, Int>()\n            top3Entries.forEach { result[it.key] = it.value }\n\n            val othersCount = restEntries.sumOf { it.value }\n            // Add 'othersCount' to 'Others' label (merge if 'Others' was already in top 3)\n            result[\"Others\"] = result.getOrDefault(\"Others\", 0) + othersCount\n            result\n        }\n\n        _internalState.update {\n            it.copy(\n                isLoading = false,\n                selectedType = selectedType,\n                activeAppCount = activeCount,\n                frozenAppCount = frozenCount,\n                suspendedAppCount = suspendedCount,\n                unknownInstallerCount = unknownCount,\n                distributionData = distribution,\n                isRootAvailable = hasRoot,\n                isShizukuAvailable = hasShizuku,\n                isDhizukuAvailable = hasDhizuku\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/home/components/AnimatedCounter.kt",
    "content": "package com.valhalla.thor.presentation.home.components\n\nimport androidx.compose.animation.core.FastOutSlowInEasing\nimport androidx.compose.animation.core.animateIntAsState\nimport androidx.compose.animation.core.tween\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.text.TextStyle\n\n@Composable\nfun AnimatedCounter(\n    modifier: Modifier = Modifier,\n    count: Int,\n    style: TextStyle = MaterialTheme.typography.displaySmall\n) {\n    // This animates the value from current (0) to target (count) over 1 second\n    val animatedValue by animateIntAsState(\n        targetValue = count,\n        animationSpec = tween(\n            durationMillis = 1000,\n            easing = FastOutSlowInEasing\n        ),\n        label = \"CounterAnimation\"\n    )\n\n    Text(\n        text = animatedValue.toString(),\n        style = style,\n        modifier = modifier,\n        maxLines = 1\n    )\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/home/components/AppDistributionChart.kt",
    "content": "package com.valhalla.thor.presentation.home.components\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.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.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.shape.CircleShape\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.getValue\nimport androidx.compose.runtime.mutableFloatStateOf\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.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport com.valhalla.thor.R\n\nprivate data class ChartSlice(\n    val label: String,\n    val count: Int,\n    val color: Color\n)\n\n@Composable\nfun AppDistributionChart(\n    data: Map<String, Int>,\n    modifier: Modifier = Modifier\n) {\n    val colorScheme = MaterialTheme.colorScheme\n\n    // 1. Prepare Data & Colors\n    val chartSlices = remember(data, colorScheme) {\n        val sortedData = data.toList().sortedByDescending { it.second }\n\n        sortedData.mapIndexed { index, (label, count) ->\n            val color = when (label.uppercase()) {\n                \"PLAY STORE\" -> colorScheme.primary\n                \"F-DROID\" -> colorScheme.secondary\n                \"SIDELOADED\" -> colorScheme.tertiary\n                \"OTHERS\" -> colorScheme.error\n                else -> {\n                    val colors = listOf(\n                        colorScheme.primary,\n                        colorScheme.secondary,\n                        colorScheme.tertiary,\n                        colorScheme.error,\n                        colorScheme.outline,\n                        colorScheme.inversePrimary\n                    )\n                    colors[index % colors.size]\n                }\n            }\n            ChartSlice(label, count, color)\n        }\n    }\n\n    Column(\n        modifier = modifier.fillMaxWidth(),\n        verticalArrangement = Arrangement.spacedBy(32.dp)\n    ) {\n        // 1. The Horizontal Bar\n        DistributionBar(slices = chartSlices)\n\n        // 2. The Legend Grid\n        LegendGrid(slices = chartSlices)\n    }\n}\n\n@Composable\nprivate fun DistributionBar(\n    slices: List<ChartSlice>,\n    modifier: Modifier = Modifier\n) {\n    val total = slices.sumOf { it.count }.toFloat()\n    var startAnimation by remember { mutableFloatStateOf(0f) }\n\n    val animatedProgress by animateFloatAsState(\n        targetValue = startAnimation,\n        animationSpec = spring(\n            dampingRatio = Spring.DampingRatioLowBouncy,\n            stiffness = Spring.StiffnessLow\n        ),\n        label = \"barAnimation\"\n    )\n\n    LaunchedEffect(Unit) {\n        startAnimation = 1f\n    }\n\n    Box(\n        modifier = modifier\n            .fillMaxWidth()\n            .height(44.dp)\n            .clip(RoundedCornerShape(22.dp))\n            .background(Color.Black.copy(alpha = 0.5f))\n    ) {\n        Row(modifier = Modifier.fillMaxSize()) {\n            slices.forEachIndexed { index, slice ->\n                val weight = if (total > 0) slice.count / total else 0f\n                val animWeight = weight * animatedProgress\n                if (animWeight > 0f) {\n                    Box(\n                        modifier = Modifier\n                            .fillMaxHeight()\n                            .weight(animWeight)\n                            .background(slice.color)\n                            .padding(end = if (index < slices.lastIndex) 4.dp else 0.dp)\n                    )\n                }\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun LegendGrid(\n    slices: List<ChartSlice>,\n    modifier: Modifier = Modifier\n) {\n    Column(modifier = modifier.fillMaxWidth()) {\n        slices.chunked(2).forEach { rowSlices ->\n            Row(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .padding(vertical = 8.dp),\n                horizontalArrangement = Arrangement.spacedBy(16.dp)\n            ) {\n                rowSlices.forEach { slice ->\n                    LegendItem(\n                        slice = slice,\n                        modifier = Modifier.weight(1f)\n                    )\n                }\n                if (rowSlices.size == 1) {\n                    Spacer(modifier = Modifier.weight(1f))\n                }\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun LegendItem(\n    slice: ChartSlice,\n    modifier: Modifier = Modifier\n) {\n    val localizedLabel = when (slice.label.uppercase()) {\n        \"PLAY STORE\" -> stringResource(R.string.play_store)\n        \"F-DROID\" -> stringResource(R.string.f_droid)\n        \"SIDELOADED\" -> stringResource(R.string.sideloaded)\n        \"OTHERS\" -> stringResource(R.string.others)\n        else -> slice.label\n    }\n\n    Row(\n        modifier = modifier,\n        verticalAlignment = Alignment.CenterVertically\n    ) {\n        Box(\n            modifier = Modifier\n                .size(10.dp)\n                .background(slice.color, CircleShape)\n        )\n        Spacer(Modifier.width(12.dp))\n        Column {\n            Text(\n                text = localizedLabel.uppercase(),\n                style = MaterialTheme.typography.labelSmall,\n                color = MaterialTheme.colorScheme.onSurfaceVariant,\n                fontWeight = FontWeight.Bold,\n                letterSpacing = 1.sp,\n                maxLines = 1,\n                overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis\n            )\n            Text(\n                text = slice.count.toString(),\n                style = MaterialTheme.typography.headlineSmall,\n                fontWeight = FontWeight.Black,\n                color = MaterialTheme.colorScheme.onSurface,\n                maxLines = 1\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/home/components/DashboardHeader.kt",
    "content": "package com.valhalla.thor.presentation.home.components\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.Row\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.material3.ExperimentalMaterial3Api\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.draw.clip\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.compose.ui.unit.sp\nimport com.valhalla.thor.R\nimport com.valhalla.thor.domain.model.AppListType\nimport com.valhalla.thor.domain.model.PrivilegeMode\nimport com.valhalla.thor.presentation.common.components.ConnectedButtonGroup\nimport com.valhalla.thor.presentation.common.components.ConnectedButtonGroupItem\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun DashboardHeader(\n    isRoot: Boolean,\n    isShizuku: Boolean,\n    isDhizuku: Boolean,\n    activeMode: PrivilegeMode?,\n    selectedType: AppListType,\n    onTypeChanged: (AppListType) -> Unit,\n    onPrivilegeChanged: (PrivilegeMode) -> Unit,\n    onRestrictedStatusClick: () -> Unit,\n    modifier: Modifier = Modifier\n) {\n    Row(\n        modifier = modifier\n            .fillMaxWidth()\n            .background(MaterialTheme.colorScheme.background)\n            .padding(horizontal = 24.dp, vertical = 16.dp),\n        verticalAlignment = Alignment.CenterVertically,\n        horizontalArrangement = Arrangement.SpaceBetween\n    ) {\n        // LEFT: Brand Block\n        // ... (existing code)\n        Row(\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.spacedBy(8.dp)\n        ) {\n            Icon(\n                painter = painterResource(R.drawable.thor_mono),\n                contentDescription = null,\n                modifier = Modifier.size(24.dp),\n                tint = MaterialTheme.colorScheme.primary\n            )\n            Text(\n                text = stringResource(R.string.app_name),\n                style = MaterialTheme.typography.headlineMedium,\n                fontWeight = FontWeight.Black,\n                color = MaterialTheme.colorScheme.primary,\n                letterSpacing = (-1).sp\n            )\n        }\n\n        // RIGHT: Controls (Status + Type Switcher)\n        Row(\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.spacedBy(12.dp)\n        ) {\n            // Work Mode Icon/Selector\n            StatusIcon(\n                isRoot = isRoot,\n                isShizuku = isShizuku,\n                isDhizuku = isDhizuku,\n                activeMode = activeMode,\n                onModeSelected = onPrivilegeChanged,\n                onClick = onRestrictedStatusClick\n            )\n\n            // App Type Switcher\n            ConnectedButtonGroup(\n                items = AppListType.entries.map { type ->\n                    ConnectedButtonGroupItem.Icon(\n                        iconRes = if (type == AppListType.USER) R.drawable.apps else R.drawable.android,\n                        contentDescription = type.name\n                    )\n                },\n                selectedIndex = AppListType.entries.indexOf(selectedType),\n                onItemSelected = { onTypeChanged(AppListType.entries[it]) }\n            )\n        }\n    }\n}\n\n@Composable\nprivate fun StatusIcon(\n    isRoot: Boolean,\n    isShizuku: Boolean,\n    isDhizuku: Boolean,\n    activeMode: PrivilegeMode?,\n    onModeSelected: (PrivilegeMode) -> Unit,\n    onClick: () -> Unit\n) {\n    val availableModes = buildList {\n        if (isRoot) add(PrivilegeMode.ROOT)\n        if (isShizuku) add(PrivilegeMode.SHIZUKU)\n        if (isDhizuku) add(PrivilegeMode.DHIZUKU)\n    }\n\n    val (icon, color) = when (activeMode) {\n        PrivilegeMode.ROOT -> R.drawable.magisk_icon to MaterialTheme.colorScheme.primary\n        PrivilegeMode.SHIZUKU -> R.drawable.shizuku to MaterialTheme.colorScheme.primary\n        PrivilegeMode.DHIZUKU -> R.drawable.dhizuku to MaterialTheme.colorScheme.primary\n        else -> R.drawable.round_close to MaterialTheme.colorScheme.error\n    }\n\n    Box(\n        modifier = Modifier\n            .size(40.dp)\n            .clip(CircleShape)\n            .background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f))\n            .clickable {\n                if (availableModes.size > 1) {\n                    // Cycle through available modes\n                    val currentIndex = availableModes.indexOf(activeMode)\n                    val nextIndex = (currentIndex + 1) % availableModes.size\n                    onModeSelected(availableModes[nextIndex])\n                } else if (availableModes.isEmpty()) {\n                    onClick()\n                }\n            },\n        contentAlignment = Alignment.Center\n    ) {\n        Icon(\n            painter = painterResource(icon),\n            contentDescription = stringResource(R.string.privilege_check),\n            modifier = Modifier.size(20.dp),\n            tint = color\n        )\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/home/components/SocialLinksRow.kt",
    "content": "package com.valhalla.thor.presentation.home.components\n\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalUriHandler\nimport androidx.compose.ui.res.painterResource\nimport androidx.compose.ui.unit.dp\nimport com.valhalla.thor.R\n\n// You can define a simple data class for links\ndata class SocialLink(\n    val url: String,\n    val icon: Int, // Resource ID\n    val description: String\n)\n\n@Composable\nfun SocialLinksRow(\n    modifier: Modifier = Modifier\n) {\n    val uriHandler = LocalUriHandler.current\n\n    // Define your links here\n    val links = listOf(\n        SocialLink(\"https://github.com/trinadhthatakula\", R.drawable.brand_github, \"GitHub\"),\n        SocialLink(\"https://patreon.com/trinadh\", R.drawable.brand_patreon, \"Patreon\"),\n        SocialLink(\"https://t.me/thorAppDev\", R.drawable.brand_telegram, \"Telegram\")\n        // Add more as needed\n    )\n\n    Row(\n        modifier = modifier\n            .fillMaxWidth()\n            .padding(vertical = 16.dp),\n        horizontalArrangement = Arrangement.Center\n    ) {\n        links.forEach { link ->\n            IconButton(\n                onClick = { uriHandler.openUri(link.url) },\n                modifier = Modifier.padding(horizontal = 8.dp)\n            ) {\n                // If you don't have these drawables yet, replace with Icons.Default.Link temporarily\n                Icon(\n                    painter = painterResource(link.icon),\n                    contentDescription = link.description,\n                    tint = MaterialTheme.colorScheme.onBackground\n                )\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/home/components/SummaryStatRow.kt",
    "content": "package com.valhalla.thor.presentation.home.components\n\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.shape.RoundedCornerShape\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\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.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport com.valhalla.thor.R\nimport com.valhalla.thor.presentation.theme.animateExpressiveResize\n\n@Composable\nfun SummaryStatRow(\n    activeCount: Int,\n    frozenCount: Int,\n    suspendedCount: Int,\n    onActiveClick: () -> Unit,\n    onFrozenClick: () -> Unit,\n    onSuspendedClick: () -> Unit\n) {\n    Row(\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(horizontal = 24.dp),\n        horizontalArrangement = Arrangement.spacedBy(12.dp)\n    ) {\n        StatCard(\n            title = stringResource(R.string.active),\n            count = activeCount,\n            color = MaterialTheme.colorScheme.primary,\n            modifier = Modifier\n                .weight(1f)\n                .animateExpressiveResize(),\n            onClick = onActiveClick\n        )\n        if (frozenCount > 0)\n            StatCard(\n                title = stringResource(R.string.frozen),\n                count = frozenCount,\n                color = MaterialTheme.colorScheme.secondary,\n                modifier = Modifier\n                    .weight(1f)\n                    .animateExpressiveResize(),\n                onClick = onFrozenClick\n            )\n        if (suspendedCount > 0)\n            StatCard(\n                title = stringResource(R.string.suspended),\n                count = suspendedCount,\n                color = MaterialTheme.colorScheme.tertiary,\n                modifier = Modifier\n                    .weight(1f)\n                    .animateExpressiveResize(),\n                onClick = onSuspendedClick\n            )\n    }\n}\n\n@Composable\nfun StatCard(\n    title: String,\n    count: Int,\n    color: Color,\n    modifier: Modifier,\n    onClick: () -> Unit\n) {\n    Column(\n        modifier = modifier\n            .clip(RoundedCornerShape(32.dp)) // squircle-lg approx\n            .background(MaterialTheme.colorScheme.surfaceContainerLow)\n            .clickable { onClick() }\n            .padding(24.dp)\n    ) {\n\n        AnimatedCounter(\n            count = count,\n            style = MaterialTheme.typography.displayMedium.copy(\n                color = color,\n                fontWeight = FontWeight.ExtraBold\n            )\n        )\n\n        Text(\n            text = title.uppercase(),\n            style = MaterialTheme.typography.labelSmall,\n            color = MaterialTheme.colorScheme.onSurfaceVariant,\n            fontWeight = FontWeight.Medium,\n            maxLines = 1,\n            letterSpacing = androidx.compose.ui.unit.TextUnit.Unspecified // tracking-wider\n        )\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/installer/InstallerViewModel.kt",
    "content": "package com.valhalla.thor.presentation.installer\n\nimport android.content.pm.PackageManager\nimport android.net.Uri\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.valhalla.thor.domain.InstallState\nimport com.valhalla.thor.domain.InstallerEventBus\nimport com.valhalla.thor.domain.repository.AppAnalyzer\nimport com.valhalla.thor.domain.repository.InstallMode\nimport com.valhalla.thor.domain.repository.InstallerRepository\nimport com.valhalla.thor.domain.repository.SystemRepository\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.launch\n\nclass InstallerViewModel(\n    private val repository: InstallerRepository,\n    private val analyzer: AppAnalyzer,\n    private val eventBus: InstallerEventBus,\n    private val packageManager: PackageManager,\n    private val systemRepository: SystemRepository\n) : ViewModel() {\n\n    val installState = eventBus.events\n\n    val installMode: StateFlow<InstallMode>\n        field = MutableStateFlow(InstallMode.NORMAL)\n\n    val availableModes: StateFlow<List<InstallMode>>\n        field = MutableStateFlow(listOf(InstallMode.NORMAL))\n\n    var currentPackageName: String? = null\n        private set\n\n    private var pendingUri: Uri? = null\n    private var isUpdateOperation: Boolean = false\n    private var isDowngrade: Boolean = false\n\n    init {\n        // Clear sticky state logic\n        val lastState = eventBus.events.replayCache.firstOrNull()\n        if (lastState is InstallState.Success || lastState is InstallState.Error) {\n            viewModelScope.launch { eventBus.emit(InstallState.Idle) }\n        }\n\n        checkAvailableModes()\n    }\n\n    private fun checkAvailableModes() {\n        viewModelScope.launch {\n            val modes = mutableListOf(InstallMode.NORMAL, InstallMode.EXTERNAL)\n\n            if (systemRepository.isRootAvailable()) {\n                modes.add(InstallMode.ROOT)\n            }\n\n            if (systemRepository.isShizukuAvailable()) {\n                modes.add(InstallMode.SHIZUKU)\n            }\n\n            if (systemRepository.isDhizukuAvailable()) {\n                modes.add(InstallMode.DHIZUKU)\n            }\n\n            availableModes.value = modes\n\n            if (availableModes.value.contains(InstallMode.ROOT)) {\n                installMode.value = InstallMode.ROOT\n            } else if (availableModes.value.contains(InstallMode.SHIZUKU)) {\n                installMode.value = InstallMode.SHIZUKU\n            } else if (availableModes.value.contains(InstallMode.DHIZUKU)) {\n                installMode.value = InstallMode.DHIZUKU\n            } else {\n                installMode.value = InstallMode.NORMAL\n            }\n        }\n    }\n\n    @Suppress(\"unused\")\n    fun setInstallMode(mode: InstallMode) {\n        if (availableModes.value.contains(mode)) {\n            installMode.value = mode\n        }\n    }\n\n    fun setInstallModeAlsoInstall(mode: InstallMode) {\n        if (availableModes.value.contains(mode)) {\n            installMode.value = mode\n        }\n        confirmInstall()\n    }\n\n    fun installFile(uri: Uri) {\n        viewModelScope.launch {\n            currentPackageName = null\n            eventBus.emit(InstallState.Parsing)\n\n            val analysis = analyzer.analyze(uri)\n\n            analysis.onSuccess { meta ->\n                pendingUri = uri\n                currentPackageName = meta.packageName\n                var oldVersion: String? = null\n                isDowngrade = false\n\n                isUpdateOperation = try {\n                    val installedPkg = packageManager.getPackageInfo(meta.packageName, 0)\n                    oldVersion = installedPkg.versionName\n\n                    val installedVersionCode = installedPkg.longVersionCode\n                    isDowngrade = meta.versionCode < installedVersionCode\n                    true\n                } catch (_: PackageManager.NameNotFoundException) {\n                    false\n                }\n\n                eventBus.emit(\n                    InstallState.ReadyToInstall(\n                        meta,\n                        isUpdateOperation,\n                        isDowngrade,\n                        oldVersion\n                    )\n                )\n            }.onFailure {\n                eventBus.emit(InstallState.Error(\"Failed to parse package.\"))\n            }\n        }\n    }\n\n    fun confirmInstall() {\n        val uri = pendingUri ?: return\n\n        // Validation: Only allow downgrade with Root, Shizuku or Dhizuku\n        if (isDowngrade && (installMode.value == InstallMode.NORMAL || installMode.value == InstallMode.EXTERNAL)) {\n            viewModelScope.launch {\n                eventBus.emit(InstallState.Error(\"Downgrade is only supported with privileged access (Root, Shizuku, or Dhizuku mode).\"))\n            }\n            return\n        }\n\n        viewModelScope.launch {\n            repository.installPackage(uri, installMode.value, canDowngrade = isDowngrade)\n        }\n    }\n\n    fun resetState() {\n        viewModelScope.launch {\n            eventBus.emit(InstallState.Idle)\n            pendingUri = null\n            currentPackageName = null\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/installer/PortableInstaller.kt",
    "content": "package com.valhalla.thor.presentation.installer\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport androidx.activity.ComponentActivity\nimport androidx.compose.animation.core.animateFloatAsState\nimport androidx.compose.foundation.Image\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.material3.Button\nimport androidx.compose.material3.ButtonDefaults\nimport androidx.compose.material3.CircularProgressIndicator\nimport androidx.compose.material3.DropdownMenu\nimport androidx.compose.material3.DropdownMenuItem\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.LinearProgressIndicator\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.ModalBottomSheet\nimport androidx.compose.material3.OutlinedButton\nimport androidx.compose.material3.SplitButtonDefaults\nimport androidx.compose.material3.SplitButtonLayout\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.rememberModalBottomSheetState\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.DisposableEffect\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.collectAsState\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.graphics.Color\nimport androidx.compose.ui.graphics.asImageBitmap\nimport androidx.compose.ui.graphics.graphicsLayer\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.painterResource\nimport androidx.compose.ui.semantics.contentDescription\nimport androidx.compose.ui.semantics.semantics\nimport androidx.compose.ui.semantics.stateDescription\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.lifecycle.compose.collectAsStateWithLifecycle\nimport com.valhalla.thor.R\nimport com.valhalla.thor.domain.InstallState\nimport com.valhalla.thor.domain.repository.InstallMode\nimport kotlinx.coroutines.delay\nimport org.koin.androidx.compose.koinViewModel\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun PortableInstaller(\n    onDismiss: () -> Unit,\n    viewModel: InstallerViewModel = koinViewModel()\n) {\n    val state by viewModel.installState.collectAsState(initial = InstallState.Idle)\n    val availableModes by viewModel.availableModes.collectAsStateWithLifecycle()\n    val installerMode by viewModel.installMode.collectAsStateWithLifecycle()\n\n    val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)\n    val context = LocalContext.current\n\n    // Auto-start installation process if intent is present\n    LaunchedEffect(Unit) {\n        val activity = context as? ComponentActivity\n        val intent = activity?.intent\n        if (state is InstallState.Idle && intent?.action == Intent.ACTION_VIEW) {\n            intent.data?.let { uri ->\n                viewModel.installFile(uri)\n            }\n        }\n    }\n\n    // Handle System Dialogs\n    LaunchedEffect(state) {\n        if (state is InstallState.UserConfirmationRequired) {\n            val intent = (state as InstallState.UserConfirmationRequired).intent\n            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)\n            context.startActivity(intent)\n        }\n    }\n\n    // Launch Button Logic\n    var launchIntent by remember { mutableStateOf<Intent?>(null) }\n    val currentPackageName = viewModel.currentPackageName\n\n    fun refreshLaunchState() {\n        if (currentPackageName != null) {\n            launchIntent = context.packageManager.getLaunchIntentForPackage(currentPackageName)\n        }\n    }\n\n    DisposableEffect(currentPackageName) {\n        val receiver = object : BroadcastReceiver() {\n            override fun onReceive(context: Context, intent: Intent) {\n                if (intent.action == Intent.ACTION_PACKAGE_ADDED ||\n                    intent.action == Intent.ACTION_PACKAGE_REPLACED\n                ) {\n                    val installedPkg = intent.data?.schemeSpecificPart\n                    if (installedPkg == currentPackageName) {\n                        refreshLaunchState()\n                    }\n                }\n            }\n        }\n        val filter = IntentFilter().apply {\n            addAction(Intent.ACTION_PACKAGE_ADDED)\n            addAction(Intent.ACTION_PACKAGE_REPLACED)\n            addDataScheme(\"package\")\n        }\n        context.registerReceiver(receiver, filter)\n        onDispose { context.unregisterReceiver(receiver) }\n    }\n\n    // Check launch state on Success\n    LaunchedEffect(state) {\n        if (state is InstallState.Success) {\n            refreshLaunchState()\n            if (launchIntent == null) {\n                delay(500)\n                refreshLaunchState()\n            }\n        }\n    }\n\n    // The Bottom Sheet\n    ModalBottomSheet(\n        onDismissRequest = {\n            viewModel.resetState()\n            onDismiss()\n        },\n        sheetState = sheetState,\n        containerColor = MaterialTheme.colorScheme.surface,\n        dragHandle = null // Optional: remove handle for cleaner look\n    ) {\n        Column(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(24.dp)\n                .padding(bottom = 24.dp), // Extra padding for nav bar\n            horizontalAlignment = Alignment.CenterHorizontally,\n            verticalArrangement = Arrangement.spacedBy(16.dp)\n        ) {\n            // Header\n            Row(\n                modifier = Modifier.fillMaxWidth(),\n                horizontalArrangement = Arrangement.SpaceBetween,\n                verticalAlignment = Alignment.CenterVertically\n            ) {\n                Text(\n                    text = \"THOR INSTALLER\",\n                    style = MaterialTheme.typography.labelSmall,\n                    color = MaterialTheme.colorScheme.primary,\n                    fontWeight = FontWeight.Bold,\n                    letterSpacing = 2.sp\n                )\n\n                // Optional Close Button\n                // IconButton(onClick = onDismiss) { Icon(Icons.Default.Close, null) }\n            }\n\n            // Content\n            when (val s = state) {\n                is InstallState.Idle, is InstallState.Parsing -> {\n                    Column(horizontalAlignment = Alignment.CenterHorizontally) {\n                        CircularProgressIndicator(\n                            modifier = Modifier.size(48.dp),\n                            color = MaterialTheme.colorScheme.primary\n                        )\n                        Spacer(Modifier.height(16.dp))\n                        Text(\"Analyzing Package...\", style = MaterialTheme.typography.bodyMedium)\n                    }\n                }\n\n                is InstallState.ReadyToInstall -> {\n                    val meta = s.meta\n                    Row(\n                        modifier = Modifier.fillMaxWidth(),\n                        verticalAlignment = Alignment.CenterVertically,\n                        horizontalArrangement = Arrangement.spacedBy(16.dp)\n                    ) {\n                        if (meta.icon != null) {\n                            Image(\n                                bitmap = meta.icon.asImageBitmap(),\n                                contentDescription = null,\n                                modifier = Modifier.size(64.dp)\n                            )\n                        }\n                        Column(modifier = Modifier.weight(1f)) {\n                            Text(\n                                text = meta.label,\n                                style = MaterialTheme.typography.titleLarge,\n                                fontWeight = FontWeight.Bold\n                            )\n                            Text(\n                                text = \"${meta.version} (${meta.versionCode})\",\n                                style = MaterialTheme.typography.bodyMedium,\n                                color = MaterialTheme.colorScheme.onSurfaceVariant\n                            )\n                            Text(\n                                text = meta.packageName,\n                                style = MaterialTheme.typography.labelSmall,\n                                color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),\n                                maxLines = 1,\n                                overflow = TextOverflow.Ellipsis\n                            )\n                        }\n                    }\n\n                    if (s.isUpdate) {\n                        Text(\n                            text = if (s.isDowngrade) \"This will downgrade the app.\" else \"This will update the existing app.\",\n                            style = MaterialTheme.typography.bodySmall,\n                            color = if (s.isDowngrade) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.primary,\n                            modifier = Modifier.fillMaxWidth(),\n                            textAlign = TextAlign.Center\n                        )\n                    }\n                    val totalPermissions = remember(s.meta) { s.meta.permissions.size }\n                    Row(\n                        verticalAlignment = Alignment.CenterVertically\n                    ) {\n                        if (totalPermissions > 0) {\n                            val warningMessage =\n                                if (s.shouldShowWarning()) s.getWarningMessage().orEmpty() else \"\"\n                            Text(\n                                \"This package requests $totalPermissions permission${if (totalPermissions > 1) \"s\" else \"\"}. $warningMessage\",\n                                style = MaterialTheme.typography.bodySmall,\n                                color = MaterialTheme.colorScheme.onSurfaceVariant,\n                                modifier = Modifier\n                                    .weight(1f)\n                                    .padding(horizontal = 4.dp)\n                            )\n                        }\n                        if (availableModes.size == 1) {\n                            Button(\n                                onClick = { viewModel.confirmInstall() },\n                                colors = ButtonDefaults.buttonColors(\n                                    containerColor = MaterialTheme.colorScheme.primary,\n                                    contentColor = MaterialTheme.colorScheme.onPrimary\n                                )\n                            ) {\n                                Text(\n                                    s.getActionButtonText()\n                                )\n                            }\n                        } else {\n                            Box(\n                                modifier = Modifier\n                            ) {\n                                var checked by remember { mutableStateOf(false) }\n                                SplitButtonLayout(\n                                    leadingButton = {\n                                        SplitButtonDefaults.ElevatedLeadingButton(\n                                            onClick = { viewModel.confirmInstall() },\n                                            colors = ButtonDefaults.buttonColors(\n                                                containerColor = MaterialTheme.colorScheme.primary,\n                                                contentColor = MaterialTheme.colorScheme.onPrimary\n                                            )\n                                        ) {\n                                            Icon(\n                                                painterResource(\n                                                    when (installerMode) {\n                                                        InstallMode.ROOT -> R.drawable.magisk_icon\n                                                        InstallMode.SHIZUKU -> R.drawable.shizuku\n                                                        InstallMode.DHIZUKU -> R.drawable.dhizuku // Placeholder\n                                                        InstallMode.NORMAL -> R.drawable.ic_launcher_foreground // Fallback\n                                                        InstallMode.EXTERNAL -> R.drawable.open_in\n                                                    }\n                                                ),\n                                                modifier = Modifier.size(SplitButtonDefaults.LeadingIconSize),\n                                                contentDescription = \"Install Mode Icon\",\n                                            )\n                                            Spacer(Modifier.size(ButtonDefaults.IconSpacing))\n                                            Text(s.getActionButtonText())\n                                        }\n                                    },\n                                    trailingButton = {\n                                        val description =\n                                            \"Install Options: \" + availableModes.joinToString(\", \") { mode ->\n                                                when (mode) {\n                                                    InstallMode.NORMAL -> \"Normal\"\n                                                    InstallMode.SHIZUKU -> \"Shizuku\"\n                                                    InstallMode.DHIZUKU -> \"Dhizuku\"\n                                                    InstallMode.ROOT -> \"Root\"\n                                                    InstallMode.EXTERNAL -> \"External\"\n                                                }\n                                            }\n                                        SplitButtonDefaults.ElevatedTrailingButton(\n                                            checked = checked,\n                                            onCheckedChange = { checked = it },\n                                            modifier =\n                                                Modifier.semantics {\n                                                    stateDescription =\n                                                        if (checked) \"Expanded\" else \"Collapsed\"\n                                                    this.contentDescription = description\n                                                },\n                                            colors = ButtonDefaults.buttonColors(\n                                                containerColor = MaterialTheme.colorScheme.primary,\n                                                contentColor = MaterialTheme.colorScheme.onPrimary\n                                            )\n                                        ) {\n                                            val rotation: Float by\n                                            animateFloatAsState(\n                                                targetValue = if (checked) 180f else 0f,\n                                                label = \"Trailing Icon Rotation\",\n                                            )\n                                            Icon(\n                                                painterResource(R.drawable.arrow_drop_down),\n                                                modifier =\n                                                    Modifier\n                                                        .size(SplitButtonDefaults.TrailingIconSize)\n                                                        .graphicsLayer {\n                                                            this.rotationZ = rotation\n                                                        },\n                                                contentDescription = \"Localized description\",\n                                            )\n                                        }\n                                    },\n                                    modifier = Modifier\n                                )\n                                DropdownMenu(\n                                    expanded = checked,\n                                    onDismissRequest = { checked = false }) {\n                                    availableModes.forEach { mode ->\n                                        DropdownMenuItem(\n                                            text = {\n                                                Text(\n                                                    when (mode) {\n                                                        InstallMode.NORMAL -> \"Normal ${s.getActionWord()}\"\n                                                        InstallMode.SHIZUKU -> \"${s.getActionWord()} via Shizuku\"\n                                                        InstallMode.DHIZUKU -> \"${s.getActionWord()} via Dhizuku\"\n                                                        InstallMode.ROOT -> \"${s.getActionWord()} with Root\"\n                                                        InstallMode.EXTERNAL -> \"Open with...\"\n                                                    }\n                                                )\n                                            },\n                                            onClick = {\n                                                viewModel.setInstallModeAlsoInstall(mode)\n                                                checked = false\n                                            }\n                                        )\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n\n                is InstallState.Installing -> {\n                    Column(horizontalAlignment = Alignment.CenterHorizontally) {\n                        val percentage = (s.progress * 100).toInt()\n                        LinearProgressIndicator(\n                            progress = { s.progress },\n                            modifier = Modifier.fillMaxWidth(),\n                            color = MaterialTheme.colorScheme.primary,\n                            trackColor = MaterialTheme.colorScheme.surfaceVariant,\n                        )\n                        Spacer(Modifier.height(8.dp))\n                        Text(\n                            \"Assembling: $percentage%\",\n                            style = MaterialTheme.typography.bodySmall,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant\n                        )\n                    }\n                }\n\n                is InstallState.UserConfirmationRequired -> {\n                    Column(\n                        horizontalAlignment = Alignment.CenterHorizontally,\n                        modifier = Modifier.fillMaxWidth()\n                    ) {\n                        LinearProgressIndicator(\n                            modifier = Modifier.fillMaxWidth(),\n                            color = MaterialTheme.colorScheme.tertiary,\n                            trackColor = MaterialTheme.colorScheme.surfaceVariant\n                        )\n                        Spacer(Modifier.height(16.dp))\n                        Text(\n                            \"Please confirm installation in the system dialog.\",\n                            style = MaterialTheme.typography.bodyMedium,\n                            color = MaterialTheme.colorScheme.tertiary,\n                            fontWeight = FontWeight.Medium\n                        )\n                    }\n                }\n\n                is InstallState.Success -> {\n                    Column(horizontalAlignment = Alignment.CenterHorizontally) {\n                        Text(\n                            \"Installed Successfully\",\n                            color = Color.Green,\n                            fontWeight = FontWeight.Bold\n                        )\n\n                        Spacer(Modifier.height(16.dp))\n\n                        Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {\n                            OutlinedButton(\n                                onClick = {\n                                    viewModel.resetState()\n                                    onDismiss()\n                                },\n                                modifier = Modifier.weight(1f)\n                            ) {\n                                Text(\"Done\")\n                            }\n\n                            if (launchIntent != null) {\n                                Button(\n                                    onClick = {\n                                        launchIntent?.let {\n                                            it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)\n                                            context.startActivity(it)\n                                            viewModel.resetState()\n                                            onDismiss()\n                                        }\n                                    },\n                                    modifier = Modifier.weight(1f)\n                                ) {\n                                    Text(\"Open\")\n                                }\n                            }\n                        }\n                    }\n                }\n\n                is InstallState.Error -> {\n                    Text(\n                        \"Error: ${s.message}\",\n                        color = MaterialTheme.colorScheme.error,\n                        style = MaterialTheme.typography.bodyMedium\n                    )\n                    Button(onClick = onDismiss) {\n                        Text(\"Close\")\n                    }\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/installer/PortableInstallerActivity.kt",
    "content": "package com.valhalla.thor.presentation.installer\n\nimport android.os.Bundle\nimport androidx.activity.ComponentActivity\nimport androidx.activity.compose.setContent\nimport androidx.activity.enableEdgeToEdge\nimport com.valhalla.thor.presentation.theme.ThorTheme\n\nclass PortableInstallerActivity : ComponentActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        enableEdgeToEdge()\n        setContent {\n            ThorTheme {\n                PortableInstaller(\n                    onDismiss = {\n                        finish()\n                    }\n                )\n            }\n        }\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/main/MainScreen.kt",
    "content": "package com.valhalla.thor.presentation.main\n\nimport android.content.Intent\nimport android.provider.Settings\nimport android.widget.Toast\nimport androidx.activity.compose.BackHandler\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.material3.Scaffold\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.Modifier\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.stringResource\nimport androidx.core.net.toUri\nimport androidx.lifecycle.compose.collectAsStateWithLifecycle\nimport com.valhalla.thor.R\nimport com.valhalla.thor.domain.model.AppClickAction\nimport com.valhalla.thor.domain.model.MultiAppAction\nimport com.valhalla.thor.presentation.appList.AppListScreen\nimport com.valhalla.thor.presentation.freezer.FreezerScreen\nimport com.valhalla.thor.presentation.home.AppDestinations\nimport com.valhalla.thor.presentation.home.HomeScreen\nimport com.valhalla.thor.presentation.home.HomeViewModel\nimport com.valhalla.thor.presentation.settings.SettingsScreen\nimport com.valhalla.thor.presentation.widgets.AffirmationDialog\nimport com.valhalla.thor.presentation.widgets.MultiAppAffirmationDialog\nimport com.valhalla.thor.presentation.widgets.TermLoggerDialog\nimport org.koin.androidx.compose.koinViewModel\n\n@Composable\nfun MainScreen(\n    mainViewModel: MainViewModel = koinViewModel(),\n    homeViewModel: HomeViewModel = koinViewModel(),\n    onExit: () -> Unit,\n) {\n    val state by mainViewModel.uiState.collectAsStateWithLifecycle()\n    val context = LocalContext.current\n\n    // --- Safety Gates (Dialog State) ---\n    var pendingMultiAction by remember { mutableStateOf<MultiAppAction?>(null) }\n    var pendingSingleAction by remember { mutableStateOf<AppClickAction?>(null) }\n    var showExitConfirmation by remember { mutableStateOf(false) }\n\n    // 1. Handle Back Button (Return to Home before Exiting)\n    BackHandler(enabled = state.selectedDestination != AppDestinations.HOME) {\n        mainViewModel.onDestinationSelected(AppDestinations.HOME)\n    }\n    // Secondary BackHandler for Home tab to Exit\n    BackHandler(enabled = state.selectedDestination == AppDestinations.HOME) {\n        showExitConfirmation = true\n    }\n\n    val canNotLaunchApp = stringResource(R.string.cannot_launch_app)\n    val shareApp = stringResource(R.string.share_app)\n\n    // 4. Handle Side Effects\n    LaunchedEffect(Unit) {\n        mainViewModel.effect.collect { effect ->\n            when (effect) {\n                is MainSideEffect.LaunchApp -> {\n                    val intent =\n                        context.packageManager.getLaunchIntentForPackage(effect.packageName)\n                    if (intent != null) context.startActivity(intent)\n                    else Toast.makeText(context, canNotLaunchApp, Toast.LENGTH_SHORT).show()\n                }\n\n                is MainSideEffect.OpenAppSettings -> {\n                    val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {\n                        data = \"package:${effect.packageName}\".toUri()\n                    }\n                    context.startActivity(intent)\n                }\n\n                is MainSideEffect.ShareApp -> {\n                    val intent = Intent(Intent.ACTION_SEND).apply {\n                        type = \"application/vnd.android.package-archive\"\n                        putExtra(Intent.EXTRA_STREAM, effect.uri)\n                        addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)\n                    }\n                    context.startActivity(Intent.createChooser(intent, shareApp))\n                }\n\n                is MainSideEffect.NormalUninstall -> {\n                    val intent = Intent(Intent.ACTION_DELETE).apply {\n                        data = \"package:${effect.packageName}\".toUri()\n                    }\n                    context.startActivity(intent)\n                }\n            }\n        }\n    }\n\n    LaunchedEffect(state.actionMessage) {\n        state.actionMessage?.let { msg ->\n            Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()\n            mainViewModel.consumeMessage()\n        }\n    }\n\n    Scaffold(\n        bottomBar = {\n            ThorNavigationBar(\n                destinations = AppDestinations.entries,\n                selectedDestination = state.selectedDestination,\n                onDestinationSelected = { dest ->\n                    mainViewModel.onDestinationSelected(dest)\n                }\n            )\n        }\n    ) { innerPadding ->\n\n        Box(\n            modifier = Modifier\n                .padding(innerPadding)\n                .fillMaxSize()\n        ) {\n            when (state.selectedDestination) {\n                AppDestinations.HOME -> {\n                    HomeScreen(\n                        viewModel = homeViewModel,\n                        onNavigateToApps = {\n                            mainViewModel.onDestinationSelected(AppDestinations.APPS)\n                        },\n                        onNavigateToFreezer = {\n                            mainViewModel.onDestinationSelected(AppDestinations.FREEZER)\n                        },\n                        onReinstallAll = { mainViewModel.onAppAction(AppClickAction.ReinstallAll) },\n                        onClearAllCache = { type -> mainViewModel.clearAllCache(type) }\n                    )\n                }\n\n                AppDestinations.APPS -> {\n                    AppListScreen(\n                        onAppAction = { action ->\n                            checkAndProcessAction(action, { pendingSingleAction = it }) {\n                                mainViewModel.onAppAction(it)\n                            }\n                        },\n                        onMultiAppAction = { pendingMultiAction = it }\n                    )\n                }\n\n                AppDestinations.FREEZER -> {\n                    FreezerScreen(\n                        onAppAction = { action ->\n                            checkAndProcessAction(action, { pendingSingleAction = it }) {\n                                mainViewModel.onAppAction(it)\n                            }\n                        },\n                        onMultiAppAction = { pendingMultiAction = it }\n                    )\n                }\n\n                AppDestinations.SETTINGS -> {\n                    SettingsScreen()\n                }\n            }\n\n            // --- GLOBAL OVERLAYS (Unchanged) ---\n            if (pendingMultiAction != null) {\n                MultiAppAffirmationDialog(\n                    multiAppAction = pendingMultiAction!!,\n                    onConfirm = {\n                        mainViewModel.onMultiAppAction(pendingMultiAction!!)\n                        pendingMultiAction = null\n                    },\n                    onRejected = { pendingMultiAction = null }\n                )\n            }\n\n            if (pendingSingleAction != null) {\n                val action = pendingSingleAction!!\n                val (title, text, icon) = when (action) {\n                    is AppClickAction.Kill -> Triple(\n                        stringResource(R.string.kill_app_title),\n                        stringResource(R.string.kill_app_desc, action.appInfo.appName ?: \"\"),\n                        R.drawable.danger\n                    )\n\n                    else -> Triple(\n                        stringResource(R.string.confirm),\n                        stringResource(R.string.are_you_sure),\n                        R.drawable.thor_mono\n                    )\n                }\n\n                AffirmationDialog(\n                    title = title,\n                    text = text,\n                    icon = icon,\n                    onConfirm = {\n                        mainViewModel.onAppAction(action)\n                        pendingSingleAction = null\n                    },\n                    onRejected = { pendingSingleAction = null }\n                )\n            }\n\n            if (state.loggerState.isVisible) {\n                TermLoggerDialog(\n                    title = state.loggerState.title,\n                    logs = state.loggerState.logs,\n                    isOperationComplete = state.loggerState.isComplete,\n                    onDismiss = { mainViewModel.dismissLogger() }\n                )\n            }\n\n            if (showExitConfirmation) {\n                AffirmationDialog(\n                    title = stringResource(R.string.exit_thor_title),\n                    text = stringResource(R.string.exit_thor_desc),\n                    icon = R.drawable.exit_to_app,\n                    onConfirm = {\n                        showExitConfirmation = false\n                        onExit()\n                    },\n                    onRejected = { showExitConfirmation = false }\n                )\n            }\n        }\n    }\n\n}\n\nprivate fun checkAndProcessAction(\n    action: AppClickAction,\n    onRequireConfirmation: (AppClickAction) -> Unit,\n    onExecute: (AppClickAction) -> Unit\n) {\n    when (action) {\n        is AppClickAction.Kill -> onRequireConfirmation(action)\n        else -> onExecute(action)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/main/MainViewModel.kt",
    "content": "package com.valhalla.thor.presentation.main\n\nimport android.content.pm.ApplicationInfo\nimport android.content.pm.PackageManager\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.valhalla.thor.domain.model.AppClickAction\nimport com.valhalla.thor.domain.model.AppInfo\nimport com.valhalla.thor.domain.model.AppListType\nimport com.valhalla.thor.domain.model.MultiAppAction\nimport com.valhalla.thor.domain.usecase.GetInstalledAppsUseCase\nimport com.valhalla.thor.domain.usecase.ManageAppUseCase\nimport com.valhalla.thor.domain.usecase.ShareAppUseCase\nimport com.valhalla.thor.presentation.home.AppDestinations\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.channels.Channel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.first\nimport kotlinx.coroutines.flow.receiveAsFlow\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.flow.update\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\n\n/**\n * Side Effects: One-time events that the UI must handle (Navigation, Intents).\n */\nsealed interface MainSideEffect {\n    data class LaunchApp(val packageName: String) : MainSideEffect\n    data class OpenAppSettings(val packageName: String) : MainSideEffect\n    data class ShareApp(val uri: android.net.Uri) : MainSideEffect\n    data class NormalUninstall(val packageName: String) : MainSideEffect\n}\n\n/**\n * State for the Terminal Logger Dialog.\n */\ndata class LoggerState(\n    val isVisible: Boolean = false,\n    val title: String = \"\",\n    val logs: List<String> = emptyList(),\n    val isComplete: Boolean = false\n)\n\n/**\n * Main UI State holding global feedback.\n */\ndata class MainUiState(\n    val actionMessage: String? = null, // For transient Toasts\n    val loggerState: LoggerState = LoggerState(), // For persistent Logs\n    val selectedDestination: AppDestinations = AppDestinations.HOME // For Bottom Nav\n)\n\nclass MainViewModel(\n    private val manageAppUseCase: ManageAppUseCase,\n    private val getInstalledAppsUseCase: GetInstalledAppsUseCase,\n    private val shareAppUseCase: ShareAppUseCase,\n    private val packageManager: PackageManager\n) : ViewModel() {\n\n    private val _uiState = MutableStateFlow(MainUiState())\n    val uiState = _uiState.stateIn(\n        viewModelScope,\n        SharingStarted.WhileSubscribed(5000),\n        MainUiState()\n    )\n\n    private val _effect = Channel<MainSideEffect>()\n    val effect = _effect.receiveAsFlow()\n\n    // --- State Management Helpers ---\n\n    fun consumeMessage() {\n        _uiState.update { it.copy(actionMessage = null) }\n    }\n\n    fun dismissLogger() {\n        _uiState.update { it.copy(loggerState = LoggerState(isVisible = false)) }\n    }\n\n    fun onDestinationSelected(destination: AppDestinations) {\n        _uiState.update { it.copy(selectedDestination = destination) }\n    }\n\n    private fun startLogger(title: String) {\n        _uiState.update {\n            it.copy(\n                loggerState = LoggerState(\n                    isVisible = true,\n                    title = title,\n                    logs = listOf(\"Initializing...\")\n                )\n            )\n        }\n    }\n\n    private fun addLog(message: String) {\n        _uiState.update { state ->\n            val newLogs = state.loggerState.logs + message\n            state.copy(loggerState = state.loggerState.copy(logs = newLogs))\n        }\n    }\n\n    private fun finishLogger() {\n        addLog(\"\\nOperation Complete.\")\n        _uiState.update { state ->\n            state.copy(loggerState = state.loggerState.copy(isComplete = true))\n        }\n    }\n\n    fun clearAllCache(type: AppListType) {\n        viewModelScope.launch {\n            startLogger(\"Preparing Cache Cleanup...\")\n\n            // 1. Fetch current list\n            val (userApps, systemApps) = getInstalledAppsUseCase().first()\n            val targetList = if (type == AppListType.USER) userApps else systemApps\n\n            // 2. Filter out self and Play Store to be safe\n            val safeList = targetList.filter {\n                it.packageName != \"com.valhalla.thor\" &&\n                        it.packageName != \"com.android.vending\"\n            }\n\n            if (safeList.isEmpty()) {\n                addLog(\"No eligible apps found.\")\n                finishLogger()\n                return@launch\n            }\n\n            dismissLogger() // Switch to batch logger\n            onMultiAppAction(MultiAppAction.ClearCache(safeList))\n        }\n    }\n\n    // --- Single App Action Handler ---\n\n    fun onAppAction(action: AppClickAction) {\n        viewModelScope.launch {\n            when (action) {\n                // 1. SMART LAUNCH\n                is AppClickAction.Launch -> {\n                    if (!action.appInfo.enabled) {\n                        // Quick toast for feedback, or could use logger if preferred.\n                        // Using Toast here for speed.\n                        _uiState.update { it.copy(actionMessage = \"Unfreezing ${action.appInfo.appName}...\") }\n\n                        val result =\n                            manageAppUseCase.setAppDisabled(action.appInfo.packageName, false)\n                        if (result.isSuccess) {\n                            _effect.send(MainSideEffect.LaunchApp(action.appInfo.packageName))\n                        } else {\n                            _uiState.update { it.copy(actionMessage = \"Failed to unfreeze: ${result.exceptionOrNull()?.message}\") }\n                        }\n                    } else {\n                        _effect.send(MainSideEffect.LaunchApp(action.appInfo.packageName))\n                    }\n                }\n\n                // 2. SETTINGS\n                is AppClickAction.AppInfoSettings -> {\n                    _effect.send(MainSideEffect.OpenAppSettings(action.appInfo.packageName))\n                }\n\n                // 3. SHARE (Heavy I/O -> Use Logger)\n                is AppClickAction.Share -> {\n                    startLogger(\"Sharing ${action.appInfo.appName}\")\n                    addLog(\"Preparing files...\")\n\n                    val result = shareAppUseCase(action.appInfo)\n\n                    if (result.isSuccess) {\n                        addLog(\"✔ Files Ready\")\n                        // Dismiss logger immediately so user sees the Share Sheet\n                        dismissLogger()\n                        _effect.send(MainSideEffect.ShareApp(result.getOrThrow()))\n                    } else {\n                        addLog(\"✘ Error: ${result.exceptionOrNull()?.message}\")\n                        finishLogger() // Keep open to show error\n                    }\n                }\n\n                // 4. REINSTALL (Complex -> Use Logger)\n                is AppClickAction.Reinstall -> {\n                    startLogger(\"Reinstalling ${action.appInfo.appName}\")\n                    addLog(\"Applying Google Play Store signature...\")\n\n                    withContext(Dispatchers.IO) {\n                        val result =\n                            manageAppUseCase.reinstallAppWithGoogle(action.appInfo.packageName)\n                        if (result.isSuccess) addLog(\"✔ Reinstall successful\")\n                        else addLog(\"✘ Failed: ${result.exceptionOrNull()?.message}\")\n                    }\n                    finishLogger()\n                }\n\n                // 5. UNINSTALL (System = Risky -> Logger / User = Fast -> Toast)\n                is AppClickAction.Uninstall -> {\n                    if (action.appInfo.isSystem) {\n                        startLogger(\"Uninstalling System App\")\n                        addLog(\"Target: ${action.appInfo.appName}\")\n                        withContext(Dispatchers.IO) {\n                            val result = manageAppUseCase.uninstallApp(action.appInfo.packageName)\n                            if (result.isSuccess) addLog(\"✔ Uninstall successful\")\n                            else {\n                                addLog(\"✘ Privileged uninstall failed\")\n                                addLog(\"Attempting system uninstall...\")\n                                _effect.send(MainSideEffect.NormalUninstall(action.appInfo.packageName))\n                            }\n                        }\n                        finishLogger()\n                    } else {\n                        viewModelScope.launch(Dispatchers.IO) {\n                            val result = manageAppUseCase.uninstallApp(action.appInfo.packageName)\n                            if (result.isSuccess) {\n                                _uiState.update { it.copy(actionMessage = \"Uninstalled ${action.appInfo.appName}\") }\n                            } else {\n                                _effect.send(MainSideEffect.NormalUninstall(action.appInfo.packageName))\n                            }\n                        }\n                    }\n                }\n\n                // 6. REINSTALL ALL (Batch Logic Triggered via Single Action Enum)\n                AppClickAction.ReinstallAll -> {\n                    startLogger(\"Scanning Apps...\")\n                    // Fetch user apps flows, take first emission\n                    val (userApps, _) = getInstalledAppsUseCase().first()\n\n                    val targets = userApps.filter {\n                        it.installerPackageName != \"com.android.vending\" &&\n                                it.installerPackageName != \"com.google.android.packageinstaller\"\n                    }\n\n                    if (targets.isEmpty()) {\n                        addLog(\"No apps found that require fixing.\")\n                        finishLogger()\n                    } else {\n                        addLog(\"Found ${targets.size} apps to fix.\")\n                        dismissLogger() // Dismiss scan logger, start batch logger\n                        onMultiAppAction(MultiAppAction.ReInstall(targets))\n                    }\n                }\n\n                // 7. QUICK ACTIONS (Kill, Freeze, Unfreeze, Cache) -> Toast\n                is AppClickAction.Kill -> quickAction(action) { manageAppUseCase.forceStop(it.packageName) }\n                is AppClickAction.Freeze -> quickAction(action) {\n                    manageAppUseCase.setAppDisabled(\n                        it.packageName,\n                        true\n                    )\n                }\n\n                is AppClickAction.UnFreeze -> quickAction(action) {\n                    manageAppUseCase.setAppDisabled(\n                        it.packageName,\n                        false\n                    )\n                }\n\n                is AppClickAction.ClearCache -> quickAction(action) { manageAppUseCase.clearCache(it.packageName) }\n                is AppClickAction.ClearData -> quickAction(action) {\n                    manageAppUseCase.clearAppData(\n                        it.packageName\n                    )\n                }\n\n                is AppClickAction.Suspend -> quickAction(action) {\n                    manageAppUseCase.setAppSuspended(\n                        it.packageName,\n                        true\n                    )\n                }\n\n                is AppClickAction.UnSuspend -> quickAction(action) {\n                    manageAppUseCase.setAppSuspended(\n                        it.packageName,\n                        false\n                    )\n                }\n            }\n        }\n    }\n\n    // --- Multi App Action Handler ---\n\n    fun onMultiAppAction(action: MultiAppAction) {\n        viewModelScope.launch {\n            when (action) {\n                is MultiAppAction.ReInstall -> performLoggedMultiAction(\n                    \"Reinstalling Apps\",\n                    action.appList\n                ) { appInfo ->\n                    val result = manageAppUseCase.reinstallAppWithGoogle(appInfo.packageName)\n                    if (result.isSuccess) {\n                        result\n                    } else {\n                        try {\n                            packageManager.getPackageInfo(\n                                appInfo.packageName,\n                                0\n                            ).applicationInfo?.let { appInfo ->\n                                val isDebuggable =\n                                    (appInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0\n                                if (isDebuggable) {\n                                    Result.failure(Exception(\"App is Debuggable (Signature mismatch likely)\"))\n                                } else {\n                                    result // Return original error\n                                }\n                            } ?: result\n\n                        } catch (_: Exception) {\n                            result // Return original error if package check fails\n                        }\n                    }\n                }\n\n                is MultiAppAction.Freeze -> performLoggedMultiAction(\n                    \"Freezing Apps\",\n                    action.appList\n                ) {\n                    manageAppUseCase.setAppDisabled(it.packageName, disabled = true)\n                }\n\n                is MultiAppAction.UnFreeze -> performLoggedMultiAction(\n                    \"Unfreezing Apps\",\n                    action.appList\n                ) {\n                    manageAppUseCase.setAppDisabled(it.packageName, disabled = false)\n                }\n\n                is MultiAppAction.Kill -> performLoggedMultiAction(\"Killing Apps\", action.appList) {\n                    manageAppUseCase.forceStop(it.packageName)\n                }\n\n                is MultiAppAction.ClearCache -> performLoggedMultiAction(\n                    \"Clearing Caches\",\n                    action.appList\n                ) {\n                    manageAppUseCase.clearCache(it.packageName)\n                }\n\n                is MultiAppAction.Uninstall -> performLoggedMultiAction(\n                    \"Uninstalling Apps\",\n                    action.appList\n                ) {\n                    manageAppUseCase.uninstallApp(it.packageName)\n                }\n\n                is MultiAppAction.Suspend -> performLoggedMultiAction(\n                    \"Suspending Apps\",\n                    action.appList\n                ) {\n                    manageAppUseCase.setAppSuspended(it.packageName, true)\n                }\n\n                is MultiAppAction.UnSuspend -> performLoggedMultiAction(\n                    \"Unsuspending Apps\",\n                    action.appList\n                ) {\n                    manageAppUseCase.setAppSuspended(it.packageName, false)\n                }\n\n                is MultiAppAction.ClearData -> performLoggedMultiAction(\n                    \"Clearing Data\",\n                    action.appList\n                ) {\n                    manageAppUseCase.clearAppData(it.packageName)\n                }\n\n                else -> {\n                    _uiState.update { it.copy(actionMessage = \"Batch Share not supported yet\") }\n                }\n            }\n        }\n    }\n\n    // --- Helper Implementations ---\n\n    /**\n     * Executes a batch operation and updates the Logger Dialog.\n     */\n    private suspend fun performLoggedMultiAction(\n        title: String,\n        apps: List<AppInfo>,\n        block: suspend (AppInfo) -> Result<Unit>\n    ) {\n        startLogger(title)\n\n        withContext(Dispatchers.IO) {\n            apps.forEachIndexed { index, app ->\n                addLog(\"[${index + 1}/${apps.size}] ${app.appName}...\")\n                val result = block(app)\n                if (result.isSuccess) {\n                    addLog(\" -> Success\")\n                } else {\n                    addLog(\" -> Failed: ${result.exceptionOrNull()?.message}\")\n                }\n            }\n        }\n\n        finishLogger()\n    }\n\n    /**\n     * Executes a quick single action and shows a Toast on completion.\n     */\n    private suspend fun quickAction(\n        action: AppClickAction,\n        block: suspend (AppInfo) -> Result<Unit>\n    ) {\n        val app = action.appInfo()\n        val actionName = action.javaClass.simpleName\n        if (app != null)\n            block(app)\n                .onSuccess {\n                    _uiState.update { it.copy(actionMessage = \"$actionName ${app.appName}\") }\n                }\n                .onFailure { e ->\n                    _uiState.update { it.copy(actionMessage = \"Error: ${e.message}\") }\n                }\n        else {\n            _uiState.update { it.copy(actionMessage = \"Error: App info missing for action\") }\n        }\n    }\n\n    // Helper to extract AppInfo from the sealed interface safely\n    private fun AppClickAction.appInfo(): AppInfo? = when (this) {\n        is AppClickAction.Kill -> appInfo\n        is AppClickAction.Freeze -> appInfo\n        is AppClickAction.UnFreeze -> appInfo\n        is AppClickAction.ClearCache -> appInfo\n        is AppClickAction.Uninstall -> appInfo\n        is AppClickAction.Launch -> appInfo\n        is AppClickAction.Share -> appInfo\n        is AppClickAction.Reinstall -> appInfo\n        is AppClickAction.AppInfoSettings -> appInfo\n        is AppClickAction.ClearData -> appInfo\n        is AppClickAction.Suspend -> appInfo\n        is AppClickAction.UnSuspend -> appInfo\n        else -> null\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/main/ThorNavigationBar.kt",
    "content": "package com.valhalla.thor.presentation.main\n\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.animation.animateColorAsState\nimport androidx.compose.animation.animateContentSize\nimport androidx.compose.animation.core.Spring\nimport androidx.compose.animation.core.animateFloatAsState\nimport androidx.compose.animation.core.spring\nimport androidx.compose.animation.expandHorizontally\nimport androidx.compose.animation.fadeIn\nimport androidx.compose.animation.fadeOut\nimport androidx.compose.animation.shrinkHorizontally\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.interaction.MutableInteractionSource\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.navigationBarsPadding\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Surface\nimport androidx.compose.material3.Text\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.draw.clip\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.graphicsLayer\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 com.valhalla.thor.presentation.home.AppDestinations\nimport com.valhalla.thor.presentation.theme.expressivePress\n\n@Composable\nfun ThorNavigationBar(\n    destinations: List<AppDestinations>,\n    selectedDestination: AppDestinations,\n    onDestinationSelected: (AppDestinations) -> Unit,\n    modifier: Modifier = Modifier\n) {\n    Surface(\n        modifier = modifier\n            .fillMaxWidth()\n            .clip(RoundedCornerShape(topStart = 32.dp, topEnd = 32.dp)),\n        color = MaterialTheme.colorScheme.surfaceContainer,\n    ) {\n        Row(\n            modifier = Modifier\n                .fillMaxWidth()\n                .navigationBarsPadding()\n                .padding(horizontal = 12.dp, vertical = 12.dp)\n                .height(64.dp),\n            horizontalArrangement = Arrangement.SpaceEvenly,\n            verticalAlignment = Alignment.CenterVertically\n        ) {\n            destinations.forEach { destination ->\n                ThorNavigationBarItem(\n                    destination = destination,\n                    selected = destination == selectedDestination,\n                    onClick = { onDestinationSelected(destination) }\n                )\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun ThorNavigationBarItem(\n    destination: AppDestinations,\n    selected: Boolean,\n    onClick: () -> Unit,\n    modifier: Modifier = Modifier\n) {\n    val interactionSource = remember { MutableInteractionSource() }\n\n    // Snappier spring for transitions\n    val snappySpring = spring<Float>(\n        dampingRatio = Spring.DampingRatioNoBouncy,\n        stiffness = Spring.StiffnessMedium\n    )\n\n    val containerColor by animateColorAsState(\n        targetValue = if (selected) MaterialTheme.colorScheme.primaryContainer else Color.Transparent,\n        animationSpec = spring(stiffness = Spring.StiffnessMedium),\n        label = \"containerColor\"\n    )\n\n    val contentColor by animateColorAsState(\n        targetValue = if (selected) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.onSurfaceVariant,\n        animationSpec = spring(stiffness = Spring.StiffnessMedium),\n        label = \"contentColor\"\n    )\n\n    val contentAlpha by animateFloatAsState(\n        targetValue = if (selected) 1f else 0.7f,\n        animationSpec = snappySpring,\n        label = \"contentAlpha\"\n    )\n\n    Box(\n        modifier = modifier\n            .clip(RoundedCornerShape(32.dp))\n            .background(containerColor)\n            .expressivePress(interactionSource)\n            .clickable(\n                interactionSource = interactionSource,\n                indication = null,\n                onClick = onClick\n            )\n            .animateContentSize(\n                animationSpec = spring(\n                    dampingRatio = Spring.DampingRatioNoBouncy,\n                    stiffness = Spring.StiffnessMedium\n                )\n            )\n            .padding(horizontal = if (selected) 20.dp else 16.dp, vertical = 10.dp),\n        contentAlignment = Alignment.Center\n    ) {\n        Row(\n            modifier = Modifier.graphicsLayer { alpha = contentAlpha },\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Center\n        ) {\n            Icon(\n                painter = painterResource(if (selected) destination.selectedIcon else destination.icon),\n                contentDescription = stringResource(destination.contentDescription),\n                tint = contentColor\n            )\n\n            AnimatedVisibility(\n                visible = selected,\n                enter = fadeIn(animationSpec = snappySpring) +\n                        expandHorizontally(\n                            animationSpec = spring(\n                                stiffness = Spring.StiffnessMedium,\n                                dampingRatio = Spring.DampingRatioNoBouncy\n                            )\n                        ),\n                exit = fadeOut(animationSpec = snappySpring) +\n                        shrinkHorizontally(\n                            animationSpec = spring(\n                                stiffness = Spring.StiffnessMedium,\n                                dampingRatio = Spring.DampingRatioNoBouncy\n                            )\n                        )\n            ) {\n                Text(\n                    text = stringResource(destination.label),\n                    color = contentColor,\n                    style = MaterialTheme.typography.labelLarge,\n                    fontWeight = FontWeight.Bold,\n                    modifier = Modifier.padding(start = 8.dp),\n                    maxLines = 1\n                )\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/security/AuthState.kt",
    "content": "package com.valhalla.thor.presentation.security\n\n/**\n * Represents the authentication state gate for the app.\n * The UI tree uses this to decide whether to show BiometricScreen or MainScreen.\n */\nsealed interface AuthState {\n    /** Biometric lock is disabled — proceed directly to the app. */\n    data object NotRequired : AuthState\n\n    /** Biometric lock is enabled but the user has not yet authenticated this session. */\n    data object Locked : AuthState\n\n    /** User has successfully authenticated this session. */\n    data object Unlocked : AuthState\n\n    /** Authentication failed or the device has no enrolled biometrics. */\n    data class Error(val message: String) : AuthState\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/security/BiometricPromptHandler.kt",
    "content": "package com.valhalla.thor.presentation.security\n\nimport android.content.Context\nimport android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG\nimport android.hardware.biometrics.BiometricManager.Authenticators.DEVICE_CREDENTIAL\nimport android.hardware.biometrics.BiometricPrompt\nimport android.os.Build\nimport android.os.CancellationSignal\nimport androidx.core.content.ContextCompat\n\n/**\n * Handles biometric authentication using the framework BiometricPrompt API (API 28+).\n * This implementation does NOT require FragmentActivity, making it compatible with ComponentActivity.\n */\ninternal class BiometricPromptHandler(private val context: Context) {\n\n    private var cancellationSignal: CancellationSignal? = null\n\n    fun authenticate(\n        title: String,\n        subtitle: String,\n        onAuthenticated: () -> Unit,\n        onError: (String) -> Unit\n    ) {\n        val executor = ContextCompat.getMainExecutor(context)\n\n        val callback = object : BiometricPrompt.AuthenticationCallback() {\n            override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult?) {\n                super.onAuthenticationSucceeded(result)\n                onAuthenticated()\n            }\n\n            override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {\n                super.onAuthenticationError(errorCode, errString)\n                // Error code 5 is developer-initiated cancellation, ignore it.\n                if (errorCode != 5) {\n                    onError(errString?.toString() ?: \"Authentication error\")\n                }\n            }\n\n            override fun onAuthenticationFailed() {\n                super.onAuthenticationFailed()\n                // Framework handles internal retries; we could notify UI if needed.\n            }\n        }\n\n        val builder = BiometricPrompt.Builder(context)\n            .setTitle(title)\n            .setSubtitle(subtitle)\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {\n            builder.setAllowedAuthenticators(BIOMETRIC_STRONG or DEVICE_CREDENTIAL)\n        } else {\n            // On API 28-29, DEVICE_CREDENTIAL wasn't supported in setAllowedAuthenticators.\n            // We fall back to a negative button if only BIOMETRIC_STRONG is possible.\n            builder.setNegativeButton(\"Cancel\", executor) { _, _ ->\n                onError(\"User cancelled\")\n            }\n        }\n\n        cancellationSignal = CancellationSignal()\n        builder.build().authenticate(cancellationSignal!!, executor, callback)\n    }\n\n    fun cancel() {\n        cancellationSignal?.cancel()\n        cancellationSignal = null\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/security/BiometricScreen.kt",
    "content": "package com.valhalla.thor.presentation.security\n\nimport androidx.compose.animation.core.RepeatMode\nimport androidx.compose.animation.core.animateFloat\nimport androidx.compose.animation.core.infiniteRepeatable\nimport androidx.compose.animation.core.rememberInfiniteTransition\nimport androidx.compose.animation.core.tween\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.Spacer\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.alpha\nimport androidx.compose.ui.draw.blur\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.draw.scale\nimport androidx.compose.ui.graphics.Brush\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.painterResource\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 com.valhalla.thor.R\nimport com.valhalla.thor.presentation.theme.greenDark\n\n@Composable\nfun BiometricScreen(\n    isError: Boolean,\n    errorMessage: String,\n    onAuthenticated: () -> Unit,\n    onError: (String) -> Unit,\n    onRetry: () -> Unit,\n    onExit: () -> Unit\n) {\n    val context = LocalContext.current\n    val handler = remember { BiometricPromptHandler(context) }\n\n    // Clean up on dispose\n    androidx.compose.runtime.DisposableEffect(handler) {\n        onDispose { handler.cancel() }\n    }\n\n    Box(\n        modifier = Modifier\n            .fillMaxSize()\n            .background(MaterialTheme.colorScheme.background)\n    ) {\n        // 1. Ambient Glow\n        AmbientGlow()\n\n        if (isError) {\n            BiometricErrorView(\n                message = errorMessage,\n                onRetry = onRetry,\n                onExit = onExit\n            )\n        } else {\n            BiometricLockView(\n                onAuthenticated = onAuthenticated,\n                onError = onError,\n                handler = handler\n            )\n        }\n    }\n}\n\n@Composable\nprivate fun AmbientGlow() {\n    Box(\n        modifier = Modifier.fillMaxSize(),\n        contentAlignment = Alignment.Center\n    ) {\n        Box(\n            modifier = Modifier\n                .size(400.dp)\n                .alpha(0.05f)\n                .blur(120.dp)\n                .background(greenDark, CircleShape)\n        )\n    }\n}\n\n@Composable\nprivate fun BiometricLockView(\n    onAuthenticated: () -> Unit,\n    onError: (String) -> Unit,\n    handler: BiometricPromptHandler\n) {\n    Column(\n        modifier = Modifier.fillMaxSize(),\n        horizontalAlignment = Alignment.CenterHorizontally,\n        verticalArrangement = Arrangement.Center\n    ) {\n        // Logo Section\n        Box(contentAlignment = Alignment.Center) {\n            // Identity Ring (Pulsing)\n            val infiniteTransition = rememberInfiniteTransition(label = \"ring\")\n            val pulseAlpha by infiniteTransition.animateFloat(\n                initialValue = 0.1f,\n                targetValue = 0.3f,\n                animationSpec = infiniteRepeatable(\n                    animation = tween(2000),\n                    repeatMode = RepeatMode.Reverse\n                ),\n                label = \"alpha\"\n            )\n\n            Box(\n                modifier = Modifier\n                    .size(110.dp)\n                    .alpha(pulseAlpha)\n                    .background(Color.Transparent, CircleShape)\n                    .padding(2.dp)\n                    .background(greenDark.copy(alpha = 0.2f), CircleShape)\n            )\n\n            Box(\n                modifier = Modifier\n                    .size(96.dp)\n                    .clip(CircleShape)\n                    .background(MaterialTheme.colorScheme.surfaceVariant)\n                    .padding(8.dp)\n            ) {\n                Box(\n                    modifier = Modifier\n                        .fillMaxSize()\n                        .clip(CircleShape)\n                        .background(Color.Black),\n                    contentAlignment = Alignment.Center\n                ) {\n                    Icon(\n                        painter = painterResource(R.drawable.thor_mono),\n                        contentDescription = null,\n                        modifier = Modifier.size(48.dp),\n                        tint = Color.White\n                    )\n                }\n            }\n        }\n\n        Spacer(modifier = Modifier.height(48.dp))\n\n        // Typography Header\n        Text(\n            text = \"Thor\",\n            style = MaterialTheme.typography.displayLarge,\n            fontWeight = FontWeight.Black,\n            color = MaterialTheme.colorScheme.onSurface,\n            letterSpacing = (-2).sp\n        )\n        Text(\n            text = \"Unlock to continue\",\n            style = MaterialTheme.typography.titleMedium,\n            color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.8f)\n        )\n\n        Spacer(modifier = Modifier.height(64.dp))\n\n        // Fingerprint Button\n        Box(contentAlignment = Alignment.Center) {\n            // Pulsing Background\n            val infiniteTransition = rememberInfiniteTransition(label = \"fingerprint\")\n            val scale by infiniteTransition.animateFloat(\n                initialValue = 1f,\n                targetValue = 1.2f,\n                animationSpec = infiniteRepeatable(\n                    animation = tween(1500),\n                    repeatMode = RepeatMode.Reverse\n                ),\n                label = \"scale\"\n            )\n\n            Box(\n                modifier = Modifier\n                    .size(96.dp)\n                    .scale(scale)\n                    .alpha(0.1f)\n                    .background(greenDark, RoundedCornerShape(24.dp))\n            )\n\n            Box(\n                modifier = Modifier\n                    .size(96.dp)\n                    .clip(RoundedCornerShape(24.dp))\n                    .background(\n                        Brush.linearGradient(\n                            listOf(greenDark, greenDark.copy(alpha = 0.8f))\n                        )\n                    )\n                    .clickable {\n                        handler.authenticate(\n                            title = \"Unlock Thor\",\n                            subtitle = \"Authenticate to continue\",\n                            onAuthenticated = onAuthenticated,\n                            onError = onError\n                        )\n                    },\n                contentAlignment = Alignment.Center\n            ) {\n                Icon(\n                    painter = painterResource(R.drawable.round_key), // Fingerprint icon fallback\n                    contentDescription = \"Unlock\",\n                    modifier = Modifier.size(48.dp),\n                    tint = Color.Black\n                )\n            }\n        }\n    }\n\n    // Auto-trigger on first launch\n    LaunchedEffect(Unit) {\n        handler.authenticate(\n            title = \"Unlock Thor\",\n            subtitle = \"Authenticate to continue\",\n            onAuthenticated = onAuthenticated,\n            onError = onError\n        )\n    }\n}\n\n@Composable\nprivate fun BiometricErrorView(\n    message: String,\n    onRetry: () -> Unit,\n    onExit: () -> Unit\n) {\n    Column(\n        modifier = Modifier.fillMaxSize(),\n        horizontalAlignment = Alignment.CenterHorizontally,\n        verticalArrangement = Arrangement.Center\n    ) {\n        Icon(\n            painter = painterResource(R.drawable.danger),\n            contentDescription = null,\n            modifier = Modifier.size(72.dp),\n            tint = MaterialTheme.colorScheme.error\n        )\n\n        Spacer(modifier = Modifier.height(24.dp))\n\n        Text(\n            text = \"Authentication Failed\",\n            style = MaterialTheme.typography.headlineSmall,\n            fontWeight = FontWeight.Bold,\n            color = MaterialTheme.colorScheme.onSurface\n        )\n\n        Spacer(modifier = Modifier.height(8.dp))\n\n        Text(\n            text = message,\n            style = MaterialTheme.typography.bodyMedium,\n            color = MaterialTheme.colorScheme.onSurfaceVariant,\n            textAlign = TextAlign.Center,\n            modifier = Modifier.padding(horizontal = 48.dp)\n        )\n\n        Spacer(modifier = Modifier.height(48.dp))\n\n        Box(\n            modifier = Modifier\n                .clip(CircleShape)\n                .background(MaterialTheme.colorScheme.errorContainer)\n                .clickable { onRetry() }\n                .padding(horizontal = 32.dp, vertical = 12.dp)\n        ) {\n            Text(\n                text = \"TRY AGAIN\",\n                style = MaterialTheme.typography.labelLarge,\n                fontWeight = FontWeight.Bold,\n                color = MaterialTheme.colorScheme.onErrorContainer\n            )\n        }\n\n        Spacer(modifier = Modifier.height(16.dp))\n\n        Text(\n            text = \"EXIT\",\n            modifier = Modifier\n                .clickable { onExit() }\n                .padding(16.dp),\n            style = MaterialTheme.typography.labelMedium,\n            color = MaterialTheme.colorScheme.onSurfaceVariant\n        )\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/security/SecurityViewModel.kt",
    "content": "package com.valhalla.thor.presentation.security\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.valhalla.thor.domain.repository.PreferenceRepository\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.stateIn\n\nclass SecurityViewModel(\n    preferenceRepository: PreferenceRepository\n) : ViewModel() {\n\n    // Tracks whether the user has authenticated in this session.\n    private val _isSessionAuthenticated = MutableStateFlow(false)\n\n    // Holds the last error message when auth fails permanently.\n    private val _authError = MutableStateFlow<String?>(null)\n\n    private val _biometricEnabled = preferenceRepository.userPreferences\n        .map { it.biometricLockEnabled }\n        .stateIn(viewModelScope, SharingStarted.Eagerly, false)\n\n    /**\n     * The single source of truth for auth state, derived from:\n     *  - Whether biometric lock is enabled in preferences\n     *  - Whether the user has authenticated this session\n     *  - Whether the last auth attempt produced an error\n     */\n    val authState = combine(\n        _biometricEnabled,\n        _isSessionAuthenticated,\n        _authError\n    ) { enabled, authenticated, error ->\n        when {\n            !enabled -> AuthState.NotRequired\n            authenticated -> AuthState.Unlocked\n            error != null -> AuthState.Error(error)\n            else -> AuthState.Locked\n        }\n    }.stateIn(\n        viewModelScope,\n        SharingStarted.Eagerly,\n        AuthState.Locked\n    )\n\n    /** Called by BiometricScreen on successful authentication. */\n    fun onAuthenticated() {\n        _authError.value = null\n        _isSessionAuthenticated.value = true\n    }\n\n    /**\n     * Called when the biometric prompt is dismissed with an error (user cancel,\n     * too many attempts, lockout, etc.). Surfaces the message to the UI so the\n     * user can choose to retry or exit.\n     */\n    fun onAuthError(message: String) {\n        if (_biometricEnabled.value && !_isSessionAuthenticated.value) {\n            _authError.value = message\n        }\n    }\n\n    /**\n     * Called when the user taps \"Retry\" on the error screen.\n     * Clears the error and returns to Locked so BiometricScreen re-triggers the prompt.\n     */\n    fun onRetry() {\n        _authError.value = null\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/settings/SettingsScreen.kt",
    "content": "package com.valhalla.thor.presentation.settings\n\nimport android.content.Intent\nimport android.os.Build\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.rememberScrollState\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.ModalBottomSheet\nimport androidx.compose.material3.Switch\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.rememberModalBottomSheetState\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.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.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.core.net.toUri\nimport androidx.lifecycle.compose.collectAsStateWithLifecycle\nimport com.valhalla.thor.R\nimport com.valhalla.thor.domain.model.PrivilegeMode\nimport com.valhalla.thor.domain.model.ThemeMode\nimport com.valhalla.thor.presentation.common.components.ConnectedButtonGroup\nimport com.valhalla.thor.presentation.common.components.ConnectedButtonGroupItem\nimport org.koin.androidx.compose.koinViewModel\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun SettingsScreen(\n    viewModel: SettingsViewModel = koinViewModel()\n) {\n    val state by viewModel.uiState.collectAsStateWithLifecycle()\n    val prefs = state.prefs\n    val context = LocalContext.current\n    var showLanguageSheet by remember { mutableStateOf(false) }\n\n    val versionName = remember(context) {\n        runCatching {\n            context.packageManager.getPackageInfo(context.packageName, 0).versionName ?: \"—\"\n        }.getOrDefault(\"—\")\n    }\n\n    Column(\n        modifier = Modifier\n            .fillMaxSize()\n            .background(MaterialTheme.colorScheme.background)\n            .verticalScroll(rememberScrollState())\n            .padding(horizontal = 24.dp)\n            .padding(top = 64.dp, bottom = 120.dp)\n    ) {\n\n        // Header Section\n        Text(\n            text = stringResource(R.string.settings),\n            style = MaterialTheme.typography.displayMedium,\n            fontWeight = FontWeight.Bold,\n            letterSpacing = (-1).sp\n        )\n        Text(\n            text = stringResource(R.string.config_engine_v, versionName),\n            style = MaterialTheme.typography.labelSmall,\n            color = MaterialTheme.colorScheme.onSurfaceVariant,\n            letterSpacing = 2.sp\n        )\n\n        Spacer(Modifier.height(48.dp))\n\n        // ── GENERAL ─────────────────────────────────────────────────────────\n        SettingsSectionLabel(stringResource(R.string.general))\n\n        Column(\n            modifier = Modifier\n                .fillMaxWidth()\n                .clip(RoundedCornerShape(32.dp))\n                .background(MaterialTheme.colorScheme.surfaceContainerLow)\n                .padding(8.dp),\n            verticalArrangement = Arrangement.spacedBy(4.dp)\n        ) {\n            SettingsSwitchRow(\n                icon = R.drawable.apk_install,\n                title = stringResource(R.string.show_reinstall_card),\n                subtitle = stringResource(R.string.show_reinstall_card_desc),\n                checked = prefs.showReinstallAllCard,\n                onCheckedChange = { viewModel.setReinstallAllCardVisibility(it) }\n            )\n        }\n\n        Spacer(Modifier.height(32.dp))\n\n        // ── APPEARANCE ──────────────────────────────────────────────────────\n        SettingsSectionLabel(stringResource(R.string.appearance))\n\n        Column(\n            modifier = Modifier\n                .fillMaxWidth()\n                .clip(RoundedCornerShape(32.dp))\n                .background(MaterialTheme.colorScheme.surfaceContainerLow)\n                .padding(8.dp),\n            verticalArrangement = Arrangement.spacedBy(4.dp)\n        ) {\n            // Theme Row\n            Column(modifier = Modifier.padding(16.dp)) {\n                Row(verticalAlignment = Alignment.CenterVertically) {\n                    IconBox(R.drawable.theme_panel)\n                    Spacer(Modifier.width(16.dp))\n                    Column {\n                        Text(\n                            stringResource(R.string.theme),\n                            style = MaterialTheme.typography.titleMedium,\n                            fontWeight = FontWeight.Bold,\n                            maxLines = 1,\n                            overflow = TextOverflow.Ellipsis\n                        )\n                        Text(\n                            stringResource(R.string.theme_desc),\n                            style = MaterialTheme.typography.bodySmall,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant,\n                            maxLines = 1,\n                            overflow = TextOverflow.Ellipsis\n                        )\n                    }\n                }\n                Spacer(Modifier.height(16.dp))\n                ConnectedButtonGroup(\n                    items = ThemeMode.entries.map { ConnectedButtonGroupItem.Label(it.label()) },\n                    selectedIndex = ThemeMode.entries.indexOf(prefs.themeMode),\n                    onItemSelected = { viewModel.setThemeMode(ThemeMode.entries[it]) },\n                    modifier = Modifier.fillMaxWidth()\n                )\n            }\n\n            SettingsSwitchRow(\n                icon = R.drawable.theme_panel,\n                title = stringResource(R.string.amoled_mode),\n                subtitle = stringResource(R.string.amoled_desc),\n                checked = prefs.useAmoled,\n                onCheckedChange = { viewModel.setAmoledMode(it) }\n            )\n\n            SettingsSwitchRow(\n                icon = R.drawable.shield_with_heart,\n                title = stringResource(R.string.dynamic_colors),\n                subtitle = stringResource(R.string.dynamic_colors_desc),\n                checked = prefs.useDynamicColor,\n                enabled = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S,\n                onCheckedChange = { viewModel.setDynamicColor(it) }\n            )\n\n            SettingsClickRow(\n                icon = R.drawable.settings_backup_restore,\n                title = stringResource(R.string.app_language),\n                subtitle = when (prefs.language) {\n                    \"en\" -> stringResource(R.string.english)\n                    \"zh\" -> stringResource(R.string.chinese)\n                    \"fr\" -> stringResource(R.string.french)\n                    \"es\" -> stringResource(R.string.spanish)\n                    \"ar\" -> stringResource(R.string.arabic)\n                    else -> stringResource(R.string.system_default)\n                },\n                onClick = { showLanguageSheet = true }\n            )\n        }\n\n        Spacer(Modifier.height(32.dp))\n\n        // ── SECURITY ────────────────────────────────────────────────────────\n        SettingsSectionLabel(stringResource(R.string.security))\n\n        Column(\n            modifier = Modifier\n                .fillMaxWidth()\n                .clip(RoundedCornerShape(32.dp))\n                .background(MaterialTheme.colorScheme.surfaceContainerLow)\n                .padding(8.dp)\n        ) {\n            SettingsSwitchRow(\n                icon = R.drawable.round_key,\n                title = stringResource(R.string.biometric_lock),\n                subtitle = if (state.canUseBiometric) {\n                    stringResource(R.string.biometric_lock_desc)\n                } else {\n                    stringResource(R.string.biometric_not_available)\n                },\n                checked = prefs.biometricLockEnabled,\n                enabled = state.canUseBiometric,\n                onCheckedChange = { viewModel.setBiometricLock(it) }\n            )\n        }\n\n        Spacer(Modifier.height(32.dp))\n\n        // ── WORK MODE ───────────────────────────────────────────────────────\n        val availableModes = buildList {\n            if (state.isRootAvailable) add(PrivilegeMode.ROOT)\n            if (state.isShizukuAvailable) add(PrivilegeMode.SHIZUKU)\n            if (state.isDhizukuAvailable) add(PrivilegeMode.DHIZUKU)\n        }\n\n        if (availableModes.size > 1) {\n            SettingsSectionLabel(stringResource(R.string.work_mode))\n            Column(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .clip(RoundedCornerShape(32.dp))\n                    .background(MaterialTheme.colorScheme.surfaceContainerLow)\n                    .padding(16.dp)\n            ) {\n                Row(verticalAlignment = Alignment.CenterVertically) {\n                    val activeMode = prefs.preferredPrivilegeMode ?: availableModes.first()\n                    val icon = when (activeMode) {\n                        PrivilegeMode.ROOT -> R.drawable.magisk_icon\n                        PrivilegeMode.SHIZUKU -> R.drawable.shizuku\n                        PrivilegeMode.DHIZUKU -> R.drawable.dhizuku\n                    }\n                    IconBox(icon)\n                    Spacer(Modifier.width(16.dp))\n                    Column {\n                        Text(\n                            stringResource(R.string.active_engine),\n                            style = MaterialTheme.typography.titleMedium,\n                            fontWeight = FontWeight.Bold,\n                            maxLines = 1,\n                            overflow = TextOverflow.Ellipsis\n                        )\n                        Text(\n                            stringResource(R.string.active_engine_desc),\n                            style = MaterialTheme.typography.bodySmall,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant,\n                            maxLines = 1,\n                            overflow = TextOverflow.Ellipsis\n                        )\n                    }\n                }\n                Spacer(Modifier.height(16.dp))\n                ConnectedButtonGroup(\n                    items = availableModes.map { mode ->\n                        ConnectedButtonGroupItem.Label(mode.name)\n                    },\n                    selectedIndex = availableModes.indexOf(\n                        prefs.preferredPrivilegeMode ?: availableModes.first()\n                    ),\n                    onItemSelected = { viewModel.setPrivilegeMode(availableModes[it]) },\n                    modifier = Modifier.fillMaxWidth()\n                )\n            }\n            Spacer(Modifier.height(32.dp))\n        }\n\n        // ── ABOUT ───────────────────────────────────────────────────────────\n        SettingsSectionLabel(stringResource(R.string.about))\n\n        Column(\n            modifier = Modifier.fillMaxWidth(),\n            verticalArrangement = Arrangement.spacedBy(12.dp)\n        ) {\n            // Version Tile\n            Row(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .clip(RoundedCornerShape(32.dp))\n                    .background(MaterialTheme.colorScheme.surfaceContainerLow)\n                    .padding(20.dp),\n                verticalAlignment = Alignment.CenterVertically,\n                horizontalArrangement = Arrangement.SpaceBetween\n            ) {\n                Row(verticalAlignment = Alignment.CenterVertically) {\n                    IconBox(R.drawable.thor_mono)\n                    Spacer(Modifier.width(16.dp))\n                    Column {\n                        Text(\n                            stringResource(R.string.version),\n                            style = MaterialTheme.typography.titleMedium,\n                            fontWeight = FontWeight.Bold,\n                            maxLines = 1,\n                            overflow = TextOverflow.Ellipsis\n                        )\n                        Text(\n                            stringResource(R.string.release_candidate),\n                            style = MaterialTheme.typography.bodySmall,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant,\n                            maxLines = 1,\n                            overflow = TextOverflow.Ellipsis\n                        )\n                    }\n                }\n                Box(\n                    modifier = Modifier\n                        .clip(CircleShape)\n                        .background(MaterialTheme.colorScheme.primary.copy(alpha = 0.1f))\n                        .padding(horizontal = 12.dp, vertical = 4.dp)\n                ) {\n                    Text(\n                        versionName,\n                        style = MaterialTheme.typography.labelSmall,\n                        color = MaterialTheme.colorScheme.primary\n                    )\n                }\n            }\n\n            Row(\n                modifier = Modifier.fillMaxWidth(),\n                horizontalArrangement = Arrangement.spacedBy(12.dp)\n            ) {\n                AboutTile(\n                    title = stringResource(R.string.github),\n                    subtitle = stringResource(R.string.source_code),\n                    icon = R.drawable.brand_github,\n                    modifier = Modifier.weight(1f),\n                    onClick = {\n                        context.startActivity(\n                            Intent(\n                                Intent.ACTION_VIEW,\n                                \"https://github.com/trinadhthatakula/Thor\".toUri()\n                            )\n                        )\n                    }\n                )\n                AboutTile(\n                    title = stringResource(R.string.telegram),\n                    subtitle = stringResource(R.string.community),\n                    icon = R.drawable.brand_telegram,\n                    modifier = Modifier.weight(1f),\n                    onClick = {\n                        context.startActivity(\n                            Intent(\n                                Intent.ACTION_VIEW,\n                                \"https://t.me/thorAppDev\".toUri()\n                            )\n                        )\n                    }\n                )\n            }\n        }\n\n        // Technical Stats Footer\n        Spacer(Modifier.height(48.dp))\n        Column(\n            modifier = Modifier.fillMaxWidth(),\n            horizontalAlignment = Alignment.CenterHorizontally,\n            verticalArrangement = Arrangement.spacedBy(8.dp)\n        ) {\n            Row(\n                verticalAlignment = Alignment.CenterVertically,\n                horizontalArrangement = Arrangement.spacedBy(8.dp)\n            ) {\n                Box(\n                    modifier = Modifier\n                        .size(8.dp)\n                        .clip(CircleShape)\n                        .background(MaterialTheme.colorScheme.primary)\n                )\n                Text(\n                    stringResource(R.string.kernel_status),\n                    style = MaterialTheme.typography.labelSmall,\n                    color = MaterialTheme.colorScheme.onSurfaceVariant\n                )\n            }\n            Text(\n                stringResource(R.string.built_with_precision),\n                style = MaterialTheme.typography.labelSmall,\n                color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.4f),\n                letterSpacing = 4.sp\n            )\n        }\n\n        Spacer(Modifier.height(32.dp))\n    }\n\n    if (showLanguageSheet) {\n        LanguageBottomSheet(\n            selectedLanguage = prefs.language,\n            onLanguageSelected = { lang ->\n                viewModel.setLanguage(lang)\n                showLanguageSheet = false\n            },\n            onDismiss = { showLanguageSheet = false }\n        )\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun LanguageBottomSheet(\n    selectedLanguage: String?,\n    onLanguageSelected: (String?) -> Unit,\n    onDismiss: () -> Unit\n) {\n    val languages = listOf(\n        null to stringResource(R.string.system_default),\n        \"en\" to stringResource(R.string.english),\n        \"zh\" to stringResource(R.string.chinese),\n        \"fr\" to stringResource(R.string.french),\n        \"es\" to stringResource(R.string.spanish),\n        \"ar\" to stringResource(R.string.arabic),\n    )\n\n    ModalBottomSheet(\n        onDismissRequest = onDismiss,\n        sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),\n        containerColor = MaterialTheme.colorScheme.surfaceContainerLow,\n        shape = RoundedCornerShape(topStart = 32.dp, topEnd = 32.dp)\n    ) {\n        Column(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(bottom = 48.dp)\n                .padding(horizontal = 24.dp)\n        ) {\n            Text(\n                stringResource(R.string.select_language),\n                style = MaterialTheme.typography.headlineSmall,\n                fontWeight = FontWeight.Bold,\n                modifier = Modifier.padding(bottom = 24.dp)\n            )\n\n            languages.forEach { (code, label) ->\n                val isSelected = selectedLanguage == code\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .clip(RoundedCornerShape(16.dp))\n                        .background(\n                            if (isSelected) MaterialTheme.colorScheme.primaryContainer\n                            else Color.Transparent\n                        )\n                        .clickable { onLanguageSelected(code) }\n                        .padding(16.dp),\n                    verticalAlignment = Alignment.CenterVertically\n                ) {\n                    Text(\n                        text = label,\n                        style = MaterialTheme.typography.bodyLarge,\n                        fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal,\n                        color = if (isSelected) MaterialTheme.colorScheme.onPrimaryContainer\n                        else MaterialTheme.colorScheme.onSurface\n                    )\n                }\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun SettingsClickRow(\n    icon: Int,\n    title: String,\n    subtitle: String,\n    onClick: () -> Unit\n) {\n    Row(\n        modifier = Modifier\n            .fillMaxWidth()\n            .clip(RoundedCornerShape(24.dp))\n            .background(MaterialTheme.colorScheme.surfaceContainerHigh.copy(alpha = 0.5f))\n            .clickable { onClick() }\n            .padding(16.dp),\n        verticalAlignment = Alignment.CenterVertically\n    ) {\n        IconBox(icon)\n        Spacer(Modifier.width(16.dp))\n        Column {\n            Text(title, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)\n            Text(\n                subtitle,\n                style = MaterialTheme.typography.bodySmall,\n                color = MaterialTheme.colorScheme.onSurfaceVariant\n            )\n        }\n    }\n}\n\n@Composable\nprivate fun SettingsSectionLabel(label: String) {\n    Text(\n        text = label,\n        style = MaterialTheme.typography.labelSmall,\n        color = MaterialTheme.colorScheme.primary,\n        fontWeight = FontWeight.Bold,\n        modifier = Modifier.padding(start = 8.dp, bottom = 12.dp),\n        letterSpacing = 2.sp\n    )\n}\n\n@Composable\nprivate fun IconBox(icon: Int) {\n    Box(\n        modifier = Modifier\n            .size(40.dp)\n            .clip(CircleShape)\n            .background(MaterialTheme.colorScheme.secondaryContainer),\n        contentAlignment = Alignment.Center\n    ) {\n        Icon(\n            painter = painterResource(icon),\n            contentDescription = null,\n            modifier = Modifier.size(20.dp),\n            tint = MaterialTheme.colorScheme.primary\n        )\n    }\n}\n\n@Composable\nprivate fun SettingsSwitchRow(\n    icon: Int,\n    title: String,\n    subtitle: String,\n    checked: Boolean,\n    enabled: Boolean = true,\n    onCheckedChange: (Boolean) -> Unit\n) {\n    Row(\n        modifier = Modifier\n            .fillMaxWidth()\n            .clip(RoundedCornerShape(24.dp))\n            .background(MaterialTheme.colorScheme.surfaceContainerHigh.copy(alpha = 0.5f))\n            .clickable(enabled = enabled) { onCheckedChange(!checked) }\n            .padding(16.dp),\n        verticalAlignment = Alignment.CenterVertically,\n        horizontalArrangement = Arrangement.SpaceBetween\n    ) {\n        Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.weight(1f)) {\n            IconBox(icon)\n            Spacer(Modifier.width(16.dp))\n            Column {\n                Text(\n                    title,\n                    style = MaterialTheme.typography.titleMedium,\n                    fontWeight = FontWeight.Bold,\n                    maxLines = 1,\n                    overflow = TextOverflow.Ellipsis\n                )\n                Text(\n                    subtitle,\n                    style = MaterialTheme.typography.bodySmall,\n                    color = MaterialTheme.colorScheme.onSurfaceVariant,\n                    maxLines = 1,\n                    overflow = TextOverflow.Ellipsis\n                )\n            }\n        }\n        Switch(checked = checked, onCheckedChange = onCheckedChange, enabled = enabled)\n    }\n}\n\n@Composable\nprivate fun AboutTile(\n    title: String,\n    subtitle: String,\n    icon: Int,\n    modifier: Modifier = Modifier,\n    onClick: () -> Unit\n) {\n    Column(\n        modifier = modifier\n            .clip(RoundedCornerShape(32.dp))\n            .background(MaterialTheme.colorScheme.surfaceContainerLow)\n            .clickable { onClick() }\n            .padding(20.dp),\n        verticalArrangement = Arrangement.spacedBy(16.dp)\n    ) {\n        Box(\n            modifier = Modifier\n                .size(48.dp)\n                .clip(RoundedCornerShape(16.dp))\n                .background(MaterialTheme.colorScheme.secondaryContainer),\n            contentAlignment = Alignment.Center\n        ) {\n            Icon(\n                painter = painterResource(icon),\n                contentDescription = null,\n                modifier = Modifier.size(28.dp),\n                tint = MaterialTheme.colorScheme.primary\n            )\n        }\n        Column {\n            Text(\n                title,\n                style = MaterialTheme.typography.titleLarge,\n                fontWeight = FontWeight.Bold,\n                maxLines = 1,\n                overflow = TextOverflow.Ellipsis\n            )\n            Text(\n                subtitle,\n                style = MaterialTheme.typography.labelSmall,\n                color = MaterialTheme.colorScheme.onSurfaceVariant,\n                letterSpacing = 1.sp,\n                maxLines = 1,\n                overflow = TextOverflow.Ellipsis\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/settings/SettingsViewModel.kt",
    "content": "package com.valhalla.thor.presentation.settings\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.valhalla.thor.data.security.BiometricHelper\nimport com.valhalla.thor.domain.model.PrivilegeMode\nimport com.valhalla.thor.domain.model.ThemeMode\nimport com.valhalla.thor.domain.model.UserPreferences\nimport com.valhalla.thor.domain.repository.PreferenceRepository\nimport com.valhalla.thor.domain.repository.SystemRepository\nimport com.valhalla.thor.util.LocaleManager\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\n\nclass SettingsViewModel(\n    private val preferenceRepository: PreferenceRepository,\n    private val systemRepository: SystemRepository,\n    private val biometricHelper: BiometricHelper,\n    private val localeManager: LocaleManager\n) : ViewModel() {\n\n    data class SettingsUiState(\n        val prefs: UserPreferences = UserPreferences(),\n        val isRootAvailable: Boolean = false,\n        val isShizukuAvailable: Boolean = false,\n        val isDhizukuAvailable: Boolean = false,\n        val canUseBiometric: Boolean = false,\n        val hasBiometricHardware: Boolean = false\n    )\n\n    private val _systemStatus = combine(\n        preferenceRepository.userPreferences,\n        // We can't really observe system status easily without a flow, \n        // but we can check it once or periodically.\n        // For simplicity, let's just use a flow that emits once and then combine.\n        kotlinx.coroutines.flow.flow {\n            emit(\n                Triple(\n                    systemRepository.isRootAvailable(),\n                    systemRepository.isShizukuAvailable(),\n                    systemRepository.isDhizukuAvailable()\n                )\n            )\n        }\n    ) { prefs, status ->\n        SettingsUiState(\n            prefs = prefs,\n            isRootAvailable = status.first,\n            isShizukuAvailable = status.second,\n            isDhizukuAvailable = status.third,\n            canUseBiometric = biometricHelper.canAuthenticate(),\n            hasBiometricHardware = biometricHelper.hasHardware()\n        )\n    }\n\n    val uiState: StateFlow<SettingsUiState> = _systemStatus\n        .stateIn(\n            viewModelScope,\n            SharingStarted.WhileSubscribed(5000),\n            SettingsUiState()\n        )\n\n    val preferences = preferenceRepository.userPreferences\n        .stateIn(\n            viewModelScope,\n            SharingStarted.WhileSubscribed(5000),\n            UserPreferences()\n        )\n\n    /** True only if the device has enrolled biometrics or a device credential. */\n    val canUseBiometric: Boolean get() = biometricHelper.canAuthenticate()\n\n    /** True if the device has biometric hardware at all (even if not enrolled). */\n    val hasBiometricHardware: Boolean get() = biometricHelper.hasHardware()\n\n    fun setThemeMode(mode: ThemeMode) {\n        viewModelScope.launch { preferenceRepository.setThemeMode(mode) }\n    }\n\n    fun setDynamicColor(enabled: Boolean) {\n        viewModelScope.launch { preferenceRepository.setDynamicColor(enabled) }\n    }\n\n    fun setAmoledMode(enabled: Boolean) {\n        viewModelScope.launch { preferenceRepository.setUseAmoled(enabled) }\n    }\n\n    fun setBiometricLock(enabled: Boolean) {\n        viewModelScope.launch { preferenceRepository.setBiometricLock(enabled) }\n    }\n\n    fun setPrivilegeMode(mode: PrivilegeMode?) {\n        viewModelScope.launch { preferenceRepository.setPrivilegeMode(mode) }\n    }\n\n    fun setReinstallAllCardVisibility(visible: Boolean) {\n        viewModelScope.launch { preferenceRepository.setReinstallAllCardVisibility(visible) }\n    }\n\n    fun setLanguage(language: String?) {\n        viewModelScope.launch {\n            preferenceRepository.setLanguage(language)\n            localeManager.applyLocale(language)\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/theme/Color.kt",
    "content": "package com.valhalla.thor.presentation.theme\n\nimport androidx.compose.ui.graphics.Color\n\nval greenLight = Color(0xff4c662b)\nval greenDark = Color(0xffb1d18a)\n\nval OnTertiary = Color(0xff3f3386)\nval OnPrimaryFixed = Color(0xff2d460f)\nval TertiaryFixedDim = Color(0xffaca0fb)\nval SurfaceContainerLow = Color(0xff131313)\nval Secondary = Color(0xffc7c4dd)\nval Background = Color(0xff0e0e0e)\nval OnSecondaryFixedVariant = Color(0xff5e5d72)\nval ErrorDim = Color(0xffc74c2f)\nval OutlineVariant = Color(0xff484848)\nval TertiaryFixed = Color(0xffbaafff)\nval OnPrimaryFixedVariant = Color(0xff486329)\nval Tertiary = Color(0xffc8bfff)\nval OnBackground = Color(0xffe5e5e5)\nval PrimaryFixed = Color(0xffcceda4)\nval OnSecondary = Color(0xff3f3e52)\nval InverseSurface = Color(0xfff9f9f9)\nval OnPrimary = Color(0xff4c672c)\nval PrimaryDim = Color(0xffcff0a6)\nval OnTertiaryFixedVariant = Color(0xff3e3285)\nval OnTertiaryFixed = Color(0xff1e0b66)\nval OnTertiaryContainer = Color(0xff35287c)\nval OnPrimaryContainer = Color(0xff445e25)\nval TertiaryContainer = Color(0xffbaafff)\nval SurfaceBright = Color(0xff2c2c2c)\nval SurfaceTint = Color(0xfff0ffd7)\nval SurfaceDim = Color(0xff0e0e0e)\nval SurfaceContainerHigh = Color(0xff1f1f1f)\nval SurfaceContainer = Color(0xff191919)\nval Error = Color(0xfffe7453)\nval SurfaceContainerLowest = Color(0xff000000)\nval InverseOnSurface = Color(0xff555555)\nval Outline = Color(0xff757575)\nval SecondaryFixedDim = Color(0xffdad7f1)\nval Primary = Color(0xfff0ffd7)\nval SecondaryDim = Color(0xffb9b6ce)\nval SecondaryFixed = Color(0xffe9e5ff)\nval InversePrimary = Color(0xff4c672c)\nval OnError = Color(0xff450900)\nval Surface = Color(0xff0e0e0e)\nval SecondaryContainer = Color(0xff242436)\nval OnSecondaryContainer = Color(0xffa4a1b9)\nval TertiaryDim = Color(0xffa195ef)\nval OnSurfaceVariant = Color(0xffababab)\nval OnSurface = Color(0xffe5e5e5)\nval ErrorContainer = Color(0xff881f05)\nval PrimaryFixedDim = Color(0xffbfdf97)\nval SurfaceContainerHighest = Color(0xff262626)\nval PrimaryContainer = Color(0xffd5f6ab)\nval SurfaceVariant = Color(0xff262626)\nval OnSecondaryFixed = Color(0xff424155)\nval OnErrorContainer = Color(0xffff9b82)\n\n// --- LIGHT THEME (Asgardian Technical Alchemist) ---\nval LightSurface = Color(0xfff8faf3)\nval LightSurfaceContainer = Color(0xffedefe8)\nval LightSurfaceContainerHighest = Color(0xffe1e3dd)\nval LightSurfaceContainerHigh = Color(0xffe7e9e2)\nval LightSurfaceContainerLow = Color(0xfff2f4ed)\nval LightSurfaceContainerLowest = Color(0xffffffff)\nval LightPrimary = Color(0xff354e15)\nval LightOnPrimary = Color(0xffffffff)\nval LightPrimaryContainer = Color(0xff4c662b)\nval LightOnPrimaryContainer = Color(0xfff0ffd7)\nval LightSecondary = Color(0xff55624c)\nval LightOnSecondary = Color(0xffffffff)\nval LightSecondaryContainer = Color(0xffd9e7cb)\nval LightOnSecondaryContainer = Color(0xff131f0d)\nval LightTertiary = Color(0xff66355d)\nval LightOnTertiary = Color(0xffffffff)\nval LightTertiaryContainer = Color(0xfff8d8ee)\nval LightOnTertiaryContainer = Color(0xff2d112b)\nval LightOnSurface = Color(0xff191c18)\nval LightOnSurfaceVariant = Color(0xff43493e)\nval LightOutline = Color(0xff74796d)\nval LightOutlineVariant = Color(0xffc3c8bc)\nval LightError = Color(0xffba1a1a)\nval LightOnError = Color(0xffffffff)\nval LightErrorContainer = Color(0xffffdad6)\nval LightOnErrorContainer = Color(0xff410002)\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/theme/Motion.kt",
    "content": "package com.valhalla.thor.presentation.theme\n\nimport androidx.compose.animation.animateContentSize\nimport androidx.compose.animation.core.animateFloatAsState\nimport androidx.compose.foundation.interaction.MutableInteractionSource\nimport androidx.compose.foundation.interaction.collectIsPressedAsState\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.composed\nimport androidx.compose.ui.graphics.graphicsLayer\nimport androidx.compose.ui.unit.IntSize\n\n/**\n * Applies the Expressive 'Spatial' spring to layout changes.\n * Use this instead of the standard [animateContentSize].\n */\n@Composable\nfun Modifier.animateExpressiveResize(): Modifier {\n    // Explicitly resolving the spec from the composition local\n    val spatialSpec = MaterialTheme.motionScheme.defaultSpatialSpec<IntSize>()\n    return this.animateContentSize(animationSpec = spatialSpec)\n}\n\n/**\n * Adds a physical \"squish\" scale effect on press.\n * Best for Cards, Boxes, or custom Buttons that need tactile feedback.\n */\nfun Modifier.expressivePress(\n    interactionSource: MutableInteractionSource,\n    scaleOnPress: Float = 0.95f\n): Modifier = composed {\n    val isPressed by interactionSource.collectIsPressedAsState()\n\n    // Use 'fastSpatialSpec' for micro-interactions like touches\n    val scale by animateFloatAsState(\n        targetValue = if (isPressed) scaleOnPress else 1f,\n        animationSpec = MaterialTheme.motionScheme.fastSpatialSpec(),\n        label = \"expressivePressScale\"\n    )\n\n    this\n        .graphicsLayer {\n            scaleX = scale\n            scaleY = scale\n        }\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/theme/Theme.kt",
    "content": "package com.valhalla.thor.presentation.theme\n\nimport android.os.Build\nimport androidx.activity.compose.LocalActivity\nimport androidx.compose.foundation.isSystemInDarkTheme\nimport androidx.compose.material3.MaterialExpressiveTheme\nimport androidx.compose.material3.MotionScheme\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.dynamicDarkColorScheme\nimport androidx.compose.material3.dynamicLightColorScheme\nimport androidx.compose.material3.lightColorScheme\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.SideEffect\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalView\nimport androidx.core.view.WindowCompat\n\nprivate val AsgardianLightColorScheme = lightColorScheme(\n    primary = LightPrimary,\n    onPrimary = LightOnPrimary,\n    primaryContainer = LightPrimaryContainer,\n    onPrimaryContainer = LightOnPrimaryContainer,\n    secondary = LightSecondary,\n    onSecondary = LightOnSecondary,\n    secondaryContainer = LightSecondaryContainer,\n    onSecondaryContainer = LightOnSecondaryContainer,\n    tertiary = LightTertiary,\n    onTertiary = LightOnTertiary,\n    tertiaryContainer = LightTertiaryContainer,\n    onTertiaryContainer = LightOnTertiaryContainer,\n    error = LightError,\n    onError = LightOnError,\n    errorContainer = LightErrorContainer,\n    onBackground = LightOnSurface,\n    surface = LightSurface,\n    onSurface = LightOnSurface,\n    surfaceVariant = LightSurfaceContainer,\n    onSurfaceVariant = LightOnSurfaceVariant,\n    outline = LightOutline,\n    outlineVariant = LightOutlineVariant,\n    background = LightSurface,\n    surfaceContainerLowest = LightSurfaceContainerLowest,\n    surfaceContainerLow = LightSurfaceContainerLow,\n    surfaceContainer = LightSurfaceContainer,\n    surfaceContainerHigh = LightSurfaceContainerHigh,\n    surfaceContainerHighest = LightSurfaceContainerHighest,\n)\n\nprivate val AsgardianDarkColorScheme = darkColorScheme(\n    primary = Primary,\n    onPrimary = OnPrimary,\n    primaryContainer = PrimaryContainer,\n    onPrimaryContainer = OnPrimaryContainer,\n    secondary = Secondary,\n    onSecondary = OnSecondary,\n    secondaryContainer = SecondaryContainer,\n    onSecondaryContainer = OnSecondaryContainer,\n    tertiary = Tertiary,\n    onTertiary = OnTertiary,\n    tertiaryContainer = TertiaryContainer,\n    onTertiaryContainer = OnTertiaryContainer,\n    error = Error,\n    onError = OnError,\n    errorContainer = ErrorContainer,\n    onBackground = OnBackground,\n    surface = Surface,\n    onSurface = OnSurface,\n    surfaceVariant = SurfaceVariant,\n    onSurfaceVariant = OnSurfaceVariant,\n    outline = Outline,\n    outlineVariant = OutlineVariant,\n    inverseSurface = InverseSurface,\n    inverseOnSurface = InverseOnSurface,\n    inversePrimary = InversePrimary,\n    surfaceTint = SurfaceTint,\n    background = Background,\n    surfaceContainerLowest = SurfaceContainerLowest,\n    surfaceContainerLow = SurfaceContainerLow,\n    surfaceContainer = SurfaceContainer,\n    surfaceContainerHigh = SurfaceContainerHigh,\n    surfaceContainerHighest = SurfaceContainerHighest,\n)\n\n@Composable\nfun ThorTheme(\n    darkTheme: Boolean = isSystemInDarkTheme(),\n    // Dynamic color is available on Android 12+\n    dynamicColor: Boolean = false, // Disabled for Asgardian Terminal look\n    amoledMode: Boolean = true,\n    content: @Composable () -> Unit\n) {\n    val context = LocalContext.current\n    val colorScheme = when {\n        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {\n            if (darkTheme) {\n                dynamicDarkColorScheme(context).run {\n                    if(amoledMode){\n                        copy(\n                            background = Color.Black,\n                            surface = Color.Black,\n                            surfaceVariant = Color.Black\n                        )\n                    } else {\n                        this\n                    }\n                }\n            } else {\n                dynamicLightColorScheme(context)\n            }\n        }\n\n        darkTheme -> {\n            if (amoledMode) {\n                AsgardianDarkColorScheme.copy(\n                    background = Color.Black,\n                    surface = Color.Black,\n                    surfaceVariant = Color.Black\n                )\n            } else {\n                AsgardianDarkColorScheme\n            }\n        }\n\n        else -> AsgardianLightColorScheme\n    }\n\n    //make status bar icon match the dark theme mode\n    val view = LocalView.current\n    val activity = LocalActivity.current\n    if (!view.isInEditMode) {\n        SideEffect {\n            activity?.window?.let {\n                WindowCompat.getInsetsController(it, view)\n                    .isAppearanceLightStatusBars = !darkTheme\n            }\n        }\n    }\n\n    MaterialExpressiveTheme(\n        colorScheme = colorScheme,\n        motionScheme = MotionScheme.expressive(),\n        typography = AppTypography,\n        content = content\n    )\n}\n\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/theme/Type.kt",
    "content": "package com.valhalla.thor.presentation.theme\n\nimport androidx.compose.material3.Typography\nimport androidx.compose.ui.text.font.FontFamily\nimport androidx.compose.ui.text.font.FontStyle\nimport androidx.compose.ui.text.font.FontWeight\nimport com.valhalla.thor.R\nimport androidx.compose.ui.text.font.Font as ResFont\n\nval firaMonoFontFamily = FontFamily(\n    ResFont(resId = R.font.firacode_variable)\n)\n\nval bodyFontFamily = FontFamily(\n    ResFont(resId = R.font.outfit_regular, weight = FontWeight.Normal, style = FontStyle.Normal),\n    ResFont(resId = R.font.outfit_black, weight = FontWeight.Black, style = FontStyle.Normal),\n    ResFont(resId = R.font.outfit_bold, weight = FontWeight.Bold, style = FontStyle.Normal),\n    ResFont(\n        resId = R.font.outfit_extrabold,\n        weight = FontWeight.ExtraBold,\n        style = FontStyle.Normal\n    ),\n    ResFont(\n        resId = R.font.outfit_extralight,\n        weight = FontWeight.ExtraLight,\n        style = FontStyle.Normal\n    ),\n    ResFont(resId = R.font.outfit_light, weight = FontWeight.Light, style = FontStyle.Normal),\n    ResFont(resId = R.font.outfit_medium, weight = FontWeight.Medium, style = FontStyle.Normal),\n    ResFont(resId = R.font.outfit_semibold, weight = FontWeight.SemiBold, style = FontStyle.Normal),\n    ResFont(resId = R.font.outfit_thin, weight = FontWeight.Thin, style = FontStyle.Normal),\n    ResFont(resId = R.font.outfit_regular, weight = FontWeight.Normal, style = FontStyle.Italic),\n    ResFont(resId = R.font.outfit_black, weight = FontWeight.Black, style = FontStyle.Italic),\n    ResFont(resId = R.font.outfit_bold, weight = FontWeight.Bold, style = FontStyle.Italic),\n    ResFont(\n        resId = R.font.outfit_extrabold,\n        weight = FontWeight.ExtraBold,\n        style = FontStyle.Italic\n    ),\n    ResFont(\n        resId = R.font.outfit_extralight,\n        weight = FontWeight.ExtraLight,\n        style = FontStyle.Italic\n    ),\n    ResFont(resId = R.font.outfit_light, weight = FontWeight.Light, style = FontStyle.Italic),\n    ResFont(resId = R.font.outfit_medium, weight = FontWeight.Medium, style = FontStyle.Italic),\n    ResFont(resId = R.font.outfit_semibold, weight = FontWeight.SemiBold, style = FontStyle.Italic),\n    ResFont(resId = R.font.outfit_thin, weight = FontWeight.Thin, style = FontStyle.Italic),\n)\n\nval displayFontFamily = bodyFontFamily\n\n// Default Material 3 typography values\nval baseline = Typography()\n\nval AppTypography = Typography(\n    displayLarge = baseline.displayLarge.copy(\n        fontFamily = displayFontFamily,\n        fontWeight = FontWeight.Black\n    ),\n    displayMedium = baseline.displayMedium.copy(\n        fontFamily = displayFontFamily,\n        fontWeight = FontWeight.ExtraBold\n    ),\n    displaySmall = baseline.displaySmall.copy(\n        fontFamily = displayFontFamily,\n        fontWeight = FontWeight.Bold\n    ),\n    headlineLarge = baseline.headlineLarge.copy(\n        fontFamily = displayFontFamily,\n        fontWeight = FontWeight.Bold\n    ),\n    headlineMedium = baseline.headlineMedium.copy(\n        fontFamily = displayFontFamily,\n        fontWeight = FontWeight.Bold\n    ),\n    headlineSmall = baseline.headlineSmall.copy(\n        fontFamily = displayFontFamily,\n        fontWeight = FontWeight.Medium\n    ),\n    titleLarge = baseline.titleLarge.copy(\n        fontFamily = displayFontFamily,\n        fontWeight = FontWeight.SemiBold\n    ),\n    titleMedium = baseline.titleMedium.copy(\n        fontFamily = displayFontFamily,\n        fontWeight = FontWeight.Medium\n    ),\n    titleSmall = baseline.titleSmall.copy(\n        fontFamily = displayFontFamily,\n        fontWeight = FontWeight.Normal\n    ),\n    bodyLarge = baseline.bodyLarge.copy(fontFamily = bodyFontFamily),\n    bodyMedium = baseline.bodyMedium.copy(fontFamily = bodyFontFamily),\n    bodySmall = baseline.bodySmall.copy(fontFamily = bodyFontFamily),\n    labelLarge = baseline.labelLarge.copy(\n        fontFamily = firaMonoFontFamily,\n        fontWeight = FontWeight.Medium\n    ),\n    labelMedium = baseline.labelMedium.copy(\n        fontFamily = firaMonoFontFamily,\n        fontWeight = FontWeight.Normal\n    ),\n    labelSmall = baseline.labelSmall.copy(\n        fontFamily = firaMonoFontFamily,\n        fontWeight = FontWeight.Normal\n    ),\n)"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/utils/AppIconLoader.kt",
    "content": "package com.valhalla.thor.presentation.utils\n\nimport android.content.Context\nimport android.content.pm.PackageManager\nimport coil3.ImageLoader\nimport coil3.asImage\nimport coil3.decode.DataSource\nimport coil3.fetch.FetchResult\nimport coil3.fetch.Fetcher\nimport coil3.fetch.ImageFetchResult\nimport coil3.key.Keyer\nimport coil3.request.Options\n\n/**\n * A lightweight data class to trigger our custom fetcher.\n */\ndata class AppIconModel(val packageName: String)\n\n/**\n * The worker that loads the icon on the IO thread.\n */\nclass AppIconFetcher(\n    private val model: AppIconModel,\n    private val context: Context\n) : Fetcher {\n\n    override suspend fun fetch(): FetchResult? {\n        return try {\n            // RUTHLESS: This is where the heavy lifting happens, safely on the IO thread\n            // because Coil calls fetch() on its own dispatcher.\n            val drawable = context.packageManager.getApplicationIcon(model.packageName)\n\n            // Return the result to Coil\n            ImageFetchResult(\n                image = drawable.asImage(),\n                isSampled = false,\n                dataSource = DataSource.DISK\n            )\n        } catch (_: PackageManager.NameNotFoundException) {\n            null // Let Coil handle the error/fallback\n        }\n    }\n\n    class Factory(private val context: Context) : Fetcher.Factory<AppIconModel> {\n        override fun create(\n            data: AppIconModel,\n            options: Options,\n            imageLoader: ImageLoader\n        ): Fetcher {\n            return AppIconFetcher(data, context)\n        }\n    }\n}\n\n/**\n * Tells Coil how to cache this image in memory.\n * Using just the packageName is usually enough, but technically if the app updates,\n * the icon might change. For a system manager, packageName is sufficient for session cache.\n */\nclass AppIconKeyer : Keyer<AppIconModel> {\n    override fun key(data: AppIconModel, options: Options): String {\n        return \"app_icon:${data.packageName}\"\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/utils/CacheScanner.kt",
    "content": "package com.valhalla.thor.presentation.utils\n\nimport com.valhalla.thor.domain.repository.SystemRepository\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport java.io.BufferedReader\nimport java.io.InputStreamReader\n\nclass CacheScanner(\n    private val systemRepository: SystemRepository\n) {\n\n    /**\n     * Scans cache sizes using Root.\n     * @param filterPackageNames If provided, only calculates cache for these specific packages.\n     * This filters out \"zombie\" folders from uninstalled apps.\n     */\n    suspend fun getCacheSize(filterPackageNames: List<String>? = null): String =\n        withContext(Dispatchers.IO) {\n            if (!systemRepository.isRootAvailable()) {\n                return@withContext \"N/A\"\n            }\n\n            try {\n                // Command: List summary (-s) in KB (-k) for all directories in standard cache locations.\n                // 2>/dev/null suppresses \"Permission denied\" or \"No such file\" errors\n                val command = \"du -k -s /data/data/*/cache /sdcard/Android/data/*/cache 2>/dev/null\"\n\n                val process = Runtime.getRuntime().exec(arrayOf(\"su\", \"-c\", command))\n                val reader = BufferedReader(InputStreamReader(process.inputStream))\n\n                var totalSizeKb = 0L\n                val filterSet = filterPackageNames?.toSet()\n\n                var line: String? = reader.readLine()\n                while (line != null) {\n                    // Output format: \"1234   /data/data/com.package/cache\"\n                    val parts = line.trim().split(\"\\\\s+\".toRegex())\n\n                    if (parts.size >= 2) {\n                        val size = parts[0].toLongOrNull() ?: 0L\n                        val path = parts[1]\n\n                        if (filterSet != null) {\n                            // Robustly extract package name.\n                            // It's the segment immediately preceding \"/cache\"\n                            val packageName =\n                                path.substringBeforeLast(\"/cache\").substringAfterLast(\"/\")\n\n                            if (packageName in filterSet) {\n                                totalSizeKb += size\n                            }\n                        } else {\n                            // No filter -> Add everything\n                            totalSizeKb += size\n                        }\n                    }\n                    line = reader.readLine()\n                }\n                process.waitFor()\n\n                formatSize(totalSizeKb * 1024)\n            } catch (e: Exception) {\n                e.printStackTrace()\n                \"Error\"\n            }\n        }\n\n    private fun formatSize(sizeBytes: Long): String {\n        if (sizeBytes <= 0) return \"0 B\"\n        val units = arrayOf(\"B\", \"KB\", \"MB\", \"GB\", \"TB\")\n        val digitGroups = (Math.log10(sizeBytes.toDouble()) / Math.log10(1024.0)).toInt()\n        return String.format(\n            \"%.1f %s\",\n            sizeBytes / Math.pow(1024.0, digitGroups.toDouble()),\n            units[digitGroups]\n        )\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/utils/UiUtils.kt",
    "content": "package com.valhalla.thor.presentation.utils\n\nimport android.content.Context\nimport android.graphics.drawable.Drawable\nimport com.valhalla.thor.BuildConfig\n\nfun getAppIcon(packageName: String?, context: Context): Drawable? {\n    return packageName?.let {\n        try {\n            context.packageManager.getApplicationIcon(packageName)\n        } catch (e: Exception) {\n            if (BuildConfig.DEBUG)\n                e.printStackTrace()\n            null\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/widgets/AffirmationDialog.kt",
    "content": "package com.valhalla.thor.presentation.widgets\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.AlertDialog\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\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.res.painterResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport com.valhalla.thor.domain.model.MultiAppAction\n\n@Composable\nfun AffirmationDialog(\n    modifier: Modifier = Modifier,\n    title: String = \"Are you sure?\",\n    text: String = \"Some Message\",\n    icon: Int? = null,\n    onConfirm: () -> Unit,\n    onRejected: () -> Unit\n) {\n    AlertDialog(\n        modifier = modifier,\n        onDismissRequest = onRejected,\n        containerColor = MaterialTheme.colorScheme.surfaceContainer,\n        shape = RoundedCornerShape(48.dp),\n        confirmButton = {\n            TextButton(onClick = onConfirm) {\n                Text(\n                    \"Confirm\",\n                    fontWeight = FontWeight.Bold,\n                    color = MaterialTheme.colorScheme.primary\n                )\n            }\n        },\n        dismissButton = {\n            TextButton(onClick = onRejected) {\n                Text(\"Cancel\", color = MaterialTheme.colorScheme.onSurfaceVariant)\n            }\n        },\n        icon = {\n            icon?.let {\n                Box(\n                    modifier = Modifier\n                        .size(64.dp)\n                        .clip(CircleShape)\n                        .background(MaterialTheme.colorScheme.surfaceContainerHigh),\n                    contentAlignment = Alignment.Center\n                ) {\n                    Icon(\n                        painter = painterResource(it),\n                        contentDescription = null,\n                        modifier = Modifier.size(32.dp),\n                        tint = MaterialTheme.colorScheme.primary\n                    )\n                }\n            }\n        },\n        title = {\n            Text(\n                title,\n                style = MaterialTheme.typography.headlineSmall,\n                fontWeight = FontWeight.Black,\n                letterSpacing = (-1).sp\n            )\n        },\n        text = {\n            Text(\n                text,\n                style = MaterialTheme.typography.bodyMedium,\n                color = MaterialTheme.colorScheme.onSurfaceVariant\n            )\n        }\n    )\n}\n\n@Composable\nfun MultiAppAffirmationDialog(\n    modifier: Modifier = Modifier,\n    multiAppAction: MultiAppAction,\n    title: String = \"Are you sure?\",\n    onConfirm: () -> Unit,\n    onRejected: () -> Unit\n) {\n    AlertDialog(\n        modifier = modifier,\n        onDismissRequest = onRejected,\n        containerColor = MaterialTheme.colorScheme.surfaceContainer,\n        shape = RoundedCornerShape(48.dp),\n        confirmButton = {\n            TextButton(onClick = onConfirm) {\n                Text(\n                    \"Confirm\",\n                    fontWeight = FontWeight.Bold,\n                    color = MaterialTheme.colorScheme.primary\n                )\n            }\n        },\n        dismissButton = {\n            TextButton(onClick = onRejected) {\n                Text(\"Cancel\", color = MaterialTheme.colorScheme.onSurfaceVariant)\n            }\n        },\n        title = {\n            Text(\n                title,\n                style = MaterialTheme.typography.headlineSmall,\n                fontWeight = FontWeight.Black,\n                letterSpacing = (-1).sp\n            )\n        },\n        text = {\n            Text(\n                text = when (multiAppAction) {\n                    is MultiAppAction.ClearCache -> {\n                        \"This will clear Cache of ${multiAppAction.appList.size} apps. Proceed?\"\n                    }\n\n                    is MultiAppAction.Freeze -> {\n                        val activeAppsCount = multiAppAction.appList.count { it.enabled }\n                        \"$activeAppsCount of ${multiAppAction.appList.size} apps are active. Freeze them?\"\n                    }\n\n                    is MultiAppAction.Kill -> {\n                        \"Force stop ${multiAppAction.appList.size} apps?\"\n                    }\n\n                    is MultiAppAction.ReInstall -> {\n                        \"Reinstall ${multiAppAction.appList.size} apps with Google Play Store signature?\"\n                    }\n\n                    is MultiAppAction.Share -> \"Share ${multiAppAction.appList.size} apps?\"\n                    is MultiAppAction.UnFreeze -> {\n                        val frozenAppsCount = multiAppAction.appList.count { !it.enabled }\n                        \"$frozenAppsCount of ${multiAppAction.appList.size} apps are frozen. Unfreeze them?\"\n                    }\n\n                    is MultiAppAction.Uninstall -> \"Uninstall ${multiAppAction.appList.size} apps?\"\n\n                    is MultiAppAction.ClearData -> \"Permanently clear data for ${multiAppAction.appList.size} apps? This cannot be undone.\"\n\n                    is MultiAppAction.Suspend -> \"Suspend ${multiAppAction.appList.size} apps? This will restrict background activities.\"\n\n                    is MultiAppAction.UnSuspend -> \"Unsuspend ${multiAppAction.appList.size} apps?\"\n                },\n                style = MaterialTheme.typography.bodyMedium,\n                color = MaterialTheme.colorScheme.onSurfaceVariant\n            )\n        }\n    )\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/widgets/AnimateLottieRaw.kt",
    "content": "package com.valhalla.thor.presentation.widgets\n\nimport androidx.annotation.RawRes\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.layout.ContentScale\nimport com.airbnb.lottie.compose.LottieAnimation\nimport com.airbnb.lottie.compose.LottieCompositionSpec\nimport com.airbnb.lottie.compose.LottieConstants\nimport com.airbnb.lottie.compose.animateLottieCompositionAsState\nimport com.airbnb.lottie.compose.rememberLottieComposition\n\n@Composable\nfun AnimateLottieRaw(\n    modifier: Modifier = Modifier,\n    @RawRes resId: Int,\n    shouldLoop: Boolean = false,\n    repeatCount: Int = LottieConstants.IterateForever,\n    contentScale: ContentScale = ContentScale.None\n) {\n    val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(resId))\n    val progress by animateLottieCompositionAsState(\n        composition = composition,\n        iterations = if (shouldLoop) repeatCount else 1\n    )\n    LottieAnimation(\n        composition = composition,\n        progress = { progress },\n        modifier = modifier,\n        contentScale = contentScale\n    )\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/widgets/AppInfoDialog.kt",
    "content": "package com.valhalla.thor.presentation.widgets\n\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.horizontalScroll\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.rememberScrollState\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.AlertDialog\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.ModalBottomSheet\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.material3.rememberModalBottomSheetState\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.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.unit.dp\nimport androidx.compose.ui.unit.sp\nimport coil3.compose.rememberAsyncImagePainter\nimport com.valhalla.thor.R\nimport com.valhalla.thor.domain.model.AppClickAction\nimport com.valhalla.thor.domain.model.AppInfo\nimport com.valhalla.thor.presentation.utils.getAppIcon\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun AppInfoDialog(\n    appInfo: AppInfo,\n    isRoot: Boolean = false,\n    isShizuku: Boolean = false,\n    onDismiss: () -> Unit,\n    onAppAction: (AppClickAction) -> Unit = {}\n) {\n    // FIX: skipPartiallyExpanded = true prevents the \"offset not initialized\" crash\n    // by avoiding ambiguous anchor calculations for dynamic content.\n    val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)\n\n    var showUninstallConfirmation by remember { mutableStateOf(false) }\n    var showReinstallWarning by remember { mutableStateOf(false) }\n    var showClearDataConfirmation by remember { mutableStateOf(false) }\n\n    ModalBottomSheet(\n        onDismissRequest = onDismiss,\n        sheetState = sheetState,\n        containerColor = MaterialTheme.colorScheme.surfaceContainer,\n        contentColor = MaterialTheme.colorScheme.onSurface,\n        shape = RoundedCornerShape(topStart = 48.dp, topEnd = 48.dp),\n        tonalElevation = 0.dp\n    ) {\n        Column(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(bottom = 32.dp), // Add bottom padding for nav bar\n            horizontalAlignment = Alignment.CenterHorizontally\n        ) {\n            // 1. Header (Icon + Title)\n            AppHeader(appInfo) {\n                onAppAction(AppClickAction.AppInfoSettings(appInfo))\n            }\n\n            Spacer(modifier = Modifier.height(16.dp))\n\n            // 2. Action Buttons (Scrollable Row)\n            AppActionRow(\n                appInfo = appInfo,\n                isRoot = isRoot,\n                isShizuku = isShizuku,\n                onAction = { action ->\n                    // Intercept dangerous actions for local confirmation\n                    when (action) {\n                        is AppClickAction.Uninstall -> {\n                            if (appInfo.isSystem) showUninstallConfirmation = true\n                            else {\n                                onAppAction(action)\n                                onDismiss()\n                            }\n                        }\n\n                        is AppClickAction.Reinstall -> showReinstallWarning = true\n                        is AppClickAction.ClearData -> showClearDataConfirmation = true\n                        else -> {\n                            onAppAction(action)\n                            if (action is AppClickAction.Launch) onDismiss()\n                        }\n                    }\n                }\n            )\n        }\n    }\n\n    // --- ALERTS ---\n\n    if (showClearDataConfirmation) {\n        AlertDialog(\n            onDismissRequest = { showClearDataConfirmation = false },\n            icon = {\n                Icon(\n                    painterResource(R.drawable.danger),\n                    null,\n                    tint = MaterialTheme.colorScheme.error\n                )\n            },\n            title = { Text(stringResource(R.string.clear_app_data_title)) },\n            text = { Text(stringResource(R.string.clear_app_data_desc, appInfo.appName ?: \"\")) },\n            confirmButton = {\n                TextButton(onClick = {\n                    onAppAction(AppClickAction.ClearData(appInfo))\n                    showClearDataConfirmation = false\n                    onDismiss()\n                }) { Text(stringResource(R.string.clear_all_data)) }\n            },\n            dismissButton = {\n                TextButton(onClick = {\n                    showClearDataConfirmation = false\n                }) { Text(stringResource(R.string.cancel)) }\n            }\n        )\n    }\n\n    if (showUninstallConfirmation) {\n        AlertDialog(\n            onDismissRequest = { showUninstallConfirmation = false },\n            title = { Text(stringResource(R.string.uninstall_system_app_title)) },\n            text = { Text(stringResource(R.string.uninstall_system_app_desc)) },\n            confirmButton = {\n                TextButton(onClick = {\n                    onAppAction(AppClickAction.Uninstall(appInfo))\n                    showUninstallConfirmation = false\n                    onDismiss()\n                }) { Text(stringResource(R.string.yes)) }\n            },\n            dismissButton = {\n                TextButton(onClick = {\n                    showUninstallConfirmation = false\n                }) { Text(stringResource(R.string.no)) }\n            }\n        )\n    }\n\n    if (showReinstallWarning) {\n        AlertDialog(\n            icon = {\n                Icon(\n                    painterResource(R.drawable.warning),\n                    null,\n                    tint = MaterialTheme.colorScheme.error\n                )\n            },\n            onDismissRequest = { showReinstallWarning = false },\n            title = { Text(stringResource(R.string.risk_warning_title)) },\n            text = {\n                Text(stringResource(R.string.risk_warning_desc))\n            },\n            confirmButton = {\n                TextButton(onClick = {\n                    onAppAction(AppClickAction.Reinstall(appInfo))\n                    showReinstallWarning = false\n                    onDismiss()\n                }) { Text(stringResource(R.string.proceed)) }\n            },\n            dismissButton = {\n                TextButton(onClick = {\n                    showReinstallWarning = false\n                }) { Text(stringResource(R.string.cancel)) }\n            }\n        )\n    }\n}\n\n@Composable\nprivate fun AppHeader(\n    appInfo: AppInfo,\n    onSettingsClick: () -> Unit\n) {\n    val context = LocalContext.current\n\n    Column(\n        horizontalAlignment = Alignment.CenterHorizontally,\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(horizontal = 24.dp)\n    ) {\n        // Top row with settings and close? Or just settings.\n        Row(\n            modifier = Modifier.fillMaxWidth(),\n            horizontalArrangement = Arrangement.End\n        ) {\n            IconButton(\n                onClick = onSettingsClick,\n                modifier = Modifier\n                    .clip(CircleShape)\n                    .background(MaterialTheme.colorScheme.surfaceContainerHigh)\n            ) {\n                Icon(\n                    painterResource(R.drawable.settings),\n                    stringResource(R.string.cd_settings),\n                    tint = MaterialTheme.colorScheme.primary\n                )\n            }\n        }\n\n        Spacer(Modifier.height(16.dp))\n\n        // Icon with a nice background\n        Box(\n            modifier = Modifier\n                .size(100.dp)\n                .clip(RoundedCornerShape(32.dp))\n                .background(MaterialTheme.colorScheme.surfaceContainerHigh)\n                .padding(16.dp),\n            contentAlignment = Alignment.Center\n        ) {\n            Image(\n                painter = rememberAsyncImagePainter(getAppIcon(appInfo.packageName, context)),\n                contentDescription = null,\n                modifier = Modifier.fillMaxSize()\n            )\n        }\n\n        Spacer(Modifier.height(24.dp))\n\n        // Title\n        Text(\n            text = appInfo.appName ?: stringResource(R.string.unknown),\n            style = MaterialTheme.typography.headlineMedium,\n            fontWeight = FontWeight.Black,\n            textAlign = TextAlign.Center,\n            letterSpacing = (-1).sp,\n            maxLines = 2,\n            overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis\n        )\n\n        Spacer(Modifier.height(8.dp))\n\n        // Metadata Chips\n        Row(\n            horizontalArrangement = Arrangement.spacedBy(8.dp)\n        ) {\n            if (appInfo.splitPublicSourceDirs.isNotEmpty()) {\n                StatusChip(\n                    text = stringResource(R.string.status_split),\n                    color = MaterialTheme.colorScheme.tertiaryContainer\n                )\n            }\n            if (!appInfo.enabled) {\n                StatusChip(\n                    text = stringResource(R.string.status_frozen),\n                    color = MaterialTheme.colorScheme.errorContainer\n                )\n            }\n            if (appInfo.isSuspended) {\n                StatusChip(\n                    text = stringResource(R.string.status_suspended),\n                    color = MaterialTheme.colorScheme.secondaryContainer\n                )\n            }\n            StatusChip(\n                text = \"v${appInfo.versionName}\",\n                color = MaterialTheme.colorScheme.surfaceContainerHighest,\n                textColor = MaterialTheme.colorScheme.onSurfaceVariant\n            )\n        }\n\n        Spacer(Modifier.height(12.dp))\n\n        // Package Name\n        Text(\n            text = appInfo.packageName,\n            style = MaterialTheme.typography.labelMedium,\n            color = MaterialTheme.colorScheme.onSurfaceVariant,\n            fontFamily = com.valhalla.thor.presentation.theme.firaMonoFontFamily\n        )\n    }\n}\n\n@Composable\nprivate fun StatusChip(\n    text: String,\n    color: Color,\n    textColor: Color = MaterialTheme.colorScheme.onSurface\n) {\n    Text(\n        text = text,\n        style = MaterialTheme.typography.labelSmall,\n        fontWeight = FontWeight.Bold,\n        modifier = Modifier\n            .clip(CircleShape)\n            .background(color)\n            .padding(horizontal = 12.dp, vertical = 4.dp),\n        color = textColor\n    )\n}\n\n@Composable\nprivate fun AppActionRow(\n    appInfo: AppInfo,\n    isRoot: Boolean,\n    isShizuku: Boolean,\n    onAction: (AppClickAction) -> Unit\n) {\n    val hasPrivilege = isRoot || isShizuku\n    val isFrozen = !appInfo.enabled\n    val isSuspended = appInfo.isSuspended // Need to ensure this is in AppInfo\n\n    Row(\n        modifier = Modifier\n            .fillMaxWidth()\n            .horizontalScroll(rememberScrollState())\n            .padding(horizontal = 16.dp, vertical = 8.dp),\n        horizontalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterHorizontally),\n        verticalAlignment = Alignment.CenterVertically\n    ) {\n        // 1. Standard Actions\n        ActionItem(R.drawable.open_in_new, stringResource(R.string.action_launch)) {\n            onAction(\n                AppClickAction.Launch(appInfo)\n            )\n        }\n        ActionItem(R.drawable.share, stringResource(R.string.action_share)) {\n            onAction(\n                AppClickAction.Share(appInfo)\n            )\n        }\n\n        // 2. Privileged Actions\n        if (hasPrivilege) {\n            val (freezeIcon, freezeLabel) = if (isFrozen) R.drawable.freeze_off to stringResource(R.string.action_unfreeze) else R.drawable.frozen to stringResource(\n                R.string.action_freeze\n            )\n            ActionItem(freezeIcon, freezeLabel) {\n                onAction(\n                    if (isFrozen) AppClickAction.UnFreeze(appInfo) else AppClickAction.Freeze(\n                        appInfo\n                    )\n                )\n            }\n\n            val (suspendIcon, suspendLabel) = if (isSuspended) R.drawable.bolt to stringResource(R.string.action_unsuspend) else R.drawable.warning to stringResource(\n                R.string.action_suspend\n            )\n            ActionItem(suspendIcon, suspendLabel) {\n                onAction(\n                    if (isSuspended) AppClickAction.UnSuspend(appInfo) else AppClickAction.Suspend(\n                        appInfo\n                    )\n                )\n            }\n\n            if (appInfo.enabled) {\n                ActionItem(R.drawable.danger, stringResource(R.string.action_kill)) {\n                    onAction(\n                        AppClickAction.Kill(appInfo)\n                    )\n                }\n            }\n\n            ActionItem(R.drawable.clear_all, stringResource(R.string.action_cache)) {\n                onAction(\n                    AppClickAction.ClearCache(appInfo)\n                )\n            }\n            ActionItem(R.drawable.delete, stringResource(R.string.action_data)) {\n                onAction(\n                    AppClickAction.ClearData(appInfo)\n                )\n            }\n        }\n\n        // 3. App Store Fix\n        if (hasPrivilege && !appInfo.isSystem && appInfo.installerPackageName != \"com.android.vending\") {\n            ActionItem(R.drawable.apk_install, stringResource(R.string.fix_store)) {\n                onAction(AppClickAction.Reinstall(appInfo))\n            }\n        }\n\n        // 4. Uninstall\n        if (appInfo.packageName != \"com.valhalla.thor\") {\n            ActionItem(R.drawable.delete_forever, stringResource(R.string.action_uninstall)) {\n                onAction(AppClickAction.Uninstall(appInfo))\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun ActionItem(icon: Int, label: String, onClick: () -> Unit) {\n    Column(\n        horizontalAlignment = Alignment.CenterHorizontally,\n        modifier = Modifier\n            .clip(RoundedCornerShape(24.dp))\n            .clickable(onClick = onClick)\n            .padding(8.dp)\n    ) {\n        // Use a Tonal Button style for better touch targets\n        Box(\n            modifier = Modifier\n                .size(64.dp)\n                .clip(RoundedCornerShape(24.dp))\n                .background(MaterialTheme.colorScheme.surfaceContainerHigh),\n            contentAlignment = Alignment.Center\n        ) {\n            Icon(\n                painter = painterResource(icon),\n                contentDescription = label,\n                modifier = Modifier.size(28.dp),\n                tint = MaterialTheme.colorScheme.primary\n            )\n        }\n        Spacer(Modifier.height(8.dp))\n        Text(\n            text = label,\n            style = MaterialTheme.typography.labelSmall,\n            fontWeight = FontWeight.Bold,\n            maxLines = 1\n        )\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/widgets/AppList.kt",
    "content": "package com.valhalla.thor.presentation.widgets\n\nimport androidx.activity.compose.BackHandler\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.animation.expandVertically\nimport androidx.compose.animation.shrinkVertically\nimport androidx.compose.foundation.ExperimentalFoundationApi\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.combinedClickable\nimport androidx.compose.foundation.horizontalScroll\nimport androidx.compose.foundation.interaction.MutableInteractionSource\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.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.isImeVisible\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.grid.GridCells\nimport androidx.compose.foundation.lazy.grid.LazyVerticalGrid\nimport androidx.compose.foundation.lazy.grid.items\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.foundation.text.BasicTextField\nimport androidx.compose.foundation.text.KeyboardOptions\nimport androidx.compose.material3.Button\nimport androidx.compose.material3.Checkbox\nimport androidx.compose.material3.ContainedLoadingIndicator\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.FilterChip\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.ListItem\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.ModalBottomSheet\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.derivedStateOf\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\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.graphics.Color\nimport androidx.compose.ui.graphics.ColorFilter\nimport androidx.compose.ui.graphics.ColorMatrix\nimport androidx.compose.ui.graphics.SolidColor\nimport androidx.compose.ui.graphics.graphicsLayer\nimport androidx.compose.ui.platform.LocalSoftwareKeyboardController\nimport androidx.compose.ui.res.painterResource\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.input.ImeAction\nimport androidx.compose.ui.text.input.KeyboardCapitalization\nimport androidx.compose.ui.text.input.KeyboardType\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport coil3.ImageLoader\nimport coil3.compose.AsyncImage\nimport com.valhalla.thor.R\nimport com.valhalla.thor.domain.model.AppInfo\nimport com.valhalla.thor.domain.model.AppListType\nimport com.valhalla.thor.domain.model.FilterType\nimport com.valhalla.thor.domain.model.MultiAppAction\nimport com.valhalla.thor.domain.model.SortBy\nimport com.valhalla.thor.domain.model.SortOrder\nimport com.valhalla.thor.domain.model.asGeneralName\nimport com.valhalla.thor.domain.model.filterTypes\nimport com.valhalla.thor.presentation.common.components.ConnectedButtonGroup\nimport com.valhalla.thor.presentation.common.components.ConnectedButtonGroupItem\nimport com.valhalla.thor.presentation.theme.expressivePress\nimport com.valhalla.thor.presentation.utils.AppIconModel\n\n@Composable\nfun AppList(\n    modifier: Modifier = Modifier,\n    appListType: AppListType,\n    installers: List<String?>,\n    appList: List<AppInfo>,\n    selectedFilter: String?,\n    filterType: FilterType = FilterType.Source,\n    sortBy: SortBy = SortBy.NAME,\n    sortOrder: SortOrder = SortOrder.ASCENDING,\n    searchQuery: String = \"\",\n    isLoading: Boolean = false,\n    startAsGrid: Boolean = true,\n    isRoot: Boolean = false,\n    isShizuku: Boolean = false,\n    imageLoader: ImageLoader,\n    installerNameMap: Map<String, String> = emptyMap(),\n    onSortOrderSelected: (SortOrder) -> Unit = {},\n    onSortByChanged: (SortBy) -> Unit = {},\n    onFilterSelected: (String?) -> Unit,\n    onSearchQueryChange: (String) -> Unit = {},\n    onFilterTypeChanged: (FilterType) -> Unit = {},\n    onListTypeChanged: (AppListType) -> Unit = {},\n    onAppInfoSelected: (AppInfo) -> Unit,\n    onMultiAppAction: (MultiAppAction) -> Unit = {}\n) {\n    // 1. Local State\n    var isGrid by rememberSaveable { mutableStateOf(startAsGrid) }\n    var showFilterSheet by rememberSaveable { mutableStateOf(false) }\n    var multiSelection by rememberSaveable { mutableStateOf(emptyList<AppInfo>()) }\n\n    // Optimization: Use a Set for O(1) lookups\n    val selectedPackageNames by remember(multiSelection) {\n        derivedStateOf { multiSelection.map { it.packageName }.toSet() }\n    }\n    val isMultiSelectMode = multiSelection.isNotEmpty()\n\n    // 2. Logic\n    BackHandler(isMultiSelectMode) { multiSelection = emptyList() }\n\n    LaunchedEffect(appListType) { multiSelection = emptyList() }\n\n    // 3. UI Layout\n    Box(modifier = modifier.fillMaxSize()) {\n        Column(modifier = Modifier.fillMaxSize()) {\n\n            // Search Bar\n            AppSearchBar(\n                query = searchQuery,\n                onQueryChange = onSearchQueryChange,\n                onOpenConfig = { showFilterSheet = true }\n            )\n\n            // System App Warning\n            if (appListType == AppListType.SYSTEM) {\n                Text(\n                    text = stringResource(R.string.system_apps_warning),\n                    style = MaterialTheme.typography.labelSmall,\n                    color = MaterialTheme.colorScheme.error,\n                    modifier = Modifier.padding(start = 24.dp, top = 4.dp, bottom = 4.dp),\n                    maxLines = 1,\n                    overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis\n                )\n            }\n\n            // Headers (Control Bar or MultiSelect Header)\n            Box {\n                this@Column.AnimatedVisibility(\n                    visible = !isMultiSelectMode,\n                    enter = expandVertically(),\n                    exit = shrinkVertically()\n                ) {\n                    Row(\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .padding(horizontal = 24.dp, vertical = 8.dp),\n                        verticalAlignment = Alignment.CenterVertically,\n                        horizontalArrangement = Arrangement.SpaceBetween\n                    ) {\n                        AppQuickFilters(\n                            installers = installers,\n                            selectedFilter = selectedFilter,\n                            filterType = filterType,\n                            appListType = appListType,\n                            installerNameMap = installerNameMap,\n                            onFilterSelected = onFilterSelected\n                        )\n                    }\n                }\n\n                this@Column.AnimatedVisibility(\n                    visible = isMultiSelectMode,\n                    enter = expandVertically(),\n                    exit = shrinkVertically()\n                ) {\n                    MultiSelectHeader(\n                        count = multiSelection.size,\n                        isAllSelected = multiSelection.size == appList.size && appList.isNotEmpty(),\n                        onSelectAll = { selectAll ->\n                            multiSelection = if (selectAll) appList else emptyList()\n                        },\n                        onClear = { multiSelection = emptyList() }\n                    )\n                }\n            }\n\n            // App Content (Grid or List)\n            if (appList.isEmpty()) {\n                Box(\n                    modifier = Modifier.fillMaxSize(),\n                    contentAlignment = Alignment.Center\n                ) {\n                    if (isLoading) {\n                        ContainedLoadingIndicator()\n                    } else {\n                        EmptyStatePlaceholder(\n                            isFiltering = searchQuery.isNotEmpty() || selectedFilter != \"All\"\n                        )\n                    }\n                }\n            } else {\n                AppListContent(\n                    list = appList,\n                    isGrid = isGrid,\n                    selectedPackageNames = selectedPackageNames, // Pass Set instead of List\n                    imageLoader = imageLoader,\n                    onAppClick = { app ->\n                        if (isMultiSelectMode) {\n                            multiSelection = toggleSelection(multiSelection, app)\n                        } else {\n                            onAppInfoSelected(app)\n                        }\n                    },\n                    onAppLongClick = { app ->\n                        multiSelection = toggleSelection(multiSelection, app)\n                    }\n                )\n            }\n        }\n\n        // Floating Action Toolbar (Multi-Select)\n        if (isMultiSelectMode) {\n            MultiSelectToolBox(\n                selected = multiSelection,\n                modifier = Modifier\n                    .padding(horizontal = 16.dp, vertical = 32.dp)\n                    .align(Alignment.BottomEnd),\n                isRoot = isRoot,\n                isShizuku = isShizuku,\n                onCancel = { multiSelection = emptyList() },\n                onMultiAppAction = { action ->\n                    onMultiAppAction(action)\n                    multiSelection = emptyList()\n                }\n            )\n        }\n    }\n\n    if (showFilterSheet) {\n        AppFilterSheet(\n            onDismiss = { showFilterSheet = false },\n            filterType = filterType,\n            sortBy = sortBy,\n            sortOrder = sortOrder,\n            isGrid = isGrid,\n            appListType = appListType,\n            onFilterTypeChanged = onFilterTypeChanged,\n            onSortByChanged = onSortByChanged,\n            onSortOrderChanged = onSortOrderSelected,\n            onToggleView = { isGrid = !isGrid },\n            onListTypeChanged = onListTypeChanged\n        )\n    }\n}\n\n// --- SUB-COMPONENTS ---\n\n@Composable\nprivate fun AppQuickFilters(\n    installers: List<String?>,\n    selectedFilter: String?,\n    filterType: FilterType,\n    appListType: AppListType,\n    installerNameMap: Map<String, String>,\n    onFilterSelected: (String?) -> Unit\n) {\n    Row(\n        modifier = Modifier\n            .fillMaxWidth()\n            .horizontalScroll(rememberScrollState()),\n        horizontalArrangement = Arrangement.spacedBy(8.dp)\n    ) {\n        val chips = if (filterType == FilterType.Source) installers\n        else (filterType as? FilterType.State)?.types ?: emptyList()\n\n        chips.forEach { item ->\n            val label = when {\n                filterType == FilterType.Source -> {\n                    if (item == \"All\") \"All\"\n                    else installerNameMap[item] ?: item\n                    ?: if (appListType != AppListType.SYSTEM) \"Others\" else stringResource(R.string.system_apps)\n                }\n\n                else -> item ?: \"\"\n            }\n\n            FilterChip(\n                selected = item == selectedFilter,\n                onClick = { onFilterSelected(item) },\n                label = { Text(label) },\n                colors = androidx.compose.material3.FilterChipDefaults.filterChipColors(\n                    selectedContainerColor = MaterialTheme.colorScheme.primary,\n                    selectedLabelColor = MaterialTheme.colorScheme.onPrimary\n                )\n            )\n        }\n    }\n}\n\n@OptIn(ExperimentalLayoutApi::class)\n@Composable\nprivate fun AppSearchBar(\n    query: String,\n    onQueryChange: (String) -> Unit,\n    onOpenConfig: () -> Unit\n) {\n    val keyboardController = LocalSoftwareKeyboardController.current\n    val isImeVisible = WindowInsets.isImeVisible\n\n    BackHandler(enabled = isImeVisible || query.isNotEmpty()) {\n        if (isImeVisible) keyboardController?.hide()\n        else onQueryChange(\"\")\n    }\n\n    Row(\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(horizontal = 24.dp, vertical = 8.dp),\n        verticalAlignment = Alignment.CenterVertically,\n        horizontalArrangement = Arrangement.spacedBy(12.dp)\n    ) {\n        Box(\n            modifier = Modifier\n                .weight(1f)\n                .clip(RoundedCornerShape(32.dp))\n                .background(MaterialTheme.colorScheme.surfaceContainerLow)\n                .padding(4.dp)\n        ) {\n            BasicTextField(\n                value = query,\n                onValueChange = onQueryChange,\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .padding(12.dp),\n                textStyle = MaterialTheme.typography.bodyLarge.copy(color = MaterialTheme.colorScheme.onSurface),\n                cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),\n                keyboardOptions = KeyboardOptions(\n                    keyboardType = KeyboardType.Text,\n                    capitalization = KeyboardCapitalization.Words,\n                    imeAction = ImeAction.Search\n                ),\n                decorationBox = { innerTextField ->\n                    Row(verticalAlignment = Alignment.CenterVertically) {\n                        Icon(\n                            painter = painterResource(R.drawable.round_search),\n                            contentDescription = \"Search\",\n                            tint = MaterialTheme.colorScheme.primary,\n                            modifier = Modifier.padding(start = 8.dp)\n                        )\n                        Spacer(modifier = Modifier.width(12.dp))\n                        Box(modifier = Modifier.weight(1f)) {\n                            if (query.isEmpty()) {\n                                Text(\n                                    stringResource(R.string.search_apps),\n                                    color = MaterialTheme.colorScheme.onSurfaceVariant,\n                                    maxLines = 1,\n                                    overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis\n                                )\n                            }\n                            innerTextField()\n                        }\n                        if (query.isNotEmpty()) {\n                            Icon(\n                                painter = painterResource(R.drawable.round_close),\n                                contentDescription = stringResource(R.string.cd_clear),\n                                tint = MaterialTheme.colorScheme.onSurface,\n                                modifier = Modifier\n                                    .clip(CircleShape)\n                                    .clickable { onQueryChange(\"\") }\n                                    .padding(8.dp)\n                            )\n                        }\n                    }\n                }\n            )\n        }\n\n        IconButton(\n            onClick = onOpenConfig,\n            modifier = Modifier\n                .size(48.dp)\n                .clip(CircleShape)\n                .background(MaterialTheme.colorScheme.surfaceContainerLow)\n        ) {\n            Icon(\n                painter = painterResource(R.drawable.filter_list),\n                contentDescription = stringResource(R.string.cd_config),\n                tint = MaterialTheme.colorScheme.primary\n            )\n        }\n    }\n}\n\n@Composable\nprivate fun MultiSelectHeader(\n    count: Int,\n    isAllSelected: Boolean,\n    onSelectAll: (Boolean) -> Unit,\n    onClear: () -> Unit\n) {\n    Row(\n        verticalAlignment = Alignment.CenterVertically,\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(horizontal = 24.dp, vertical = 8.dp)\n            .clip(RoundedCornerShape(32.dp))\n            .background(MaterialTheme.colorScheme.secondaryContainer)\n            .padding(horizontal = 16.dp, vertical = 8.dp)\n    ) {\n        Checkbox(\n            checked = isAllSelected,\n            onCheckedChange = onSelectAll\n        )\n        Text(\n            text = stringResource(R.string.selected_count, count),\n            style = MaterialTheme.typography.titleMedium,\n            fontWeight = androidx.compose.ui.text.font.FontWeight.Bold,\n            color = MaterialTheme.colorScheme.onSecondaryContainer,\n            modifier = Modifier\n                .weight(1f)\n                .padding(start = 8.dp),\n            maxLines = 1,\n            overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis\n        )\n        IconButton(onClick = onClear) {\n            Icon(\n                painterResource(R.drawable.round_close),\n                stringResource(R.string.cd_close),\n                tint = MaterialTheme.colorScheme.onSecondaryContainer\n            )\n        }\n    }\n}\n\n@Composable\nprivate fun AppListContent(\n    list: List<AppInfo>,\n    isGrid: Boolean,\n    selectedPackageNames: Set<String>,\n    imageLoader: ImageLoader,\n    onAppClick: (AppInfo) -> Unit,\n    onAppLongClick: (AppInfo) -> Unit\n) {\n    // Shared padding for list/grid\n    val padding = PaddingValues(bottom = 100.dp, top = 8.dp)\n\n    if (isGrid) {\n        LazyVerticalGrid(\n            columns = GridCells.Adaptive(minSize = 100.dp),\n            contentPadding = padding\n        ) {\n            items(list, key = { it.packageName }) { app ->\n                AppItemGrid(\n                    app = app,\n                    isSelected = selectedPackageNames.contains(app.packageName),\n                    imageLoader = imageLoader,\n                    onClick = { onAppClick(app) },\n                    onLongClick = { onAppLongClick(app) }\n                )\n            }\n        }\n    } else {\n        LazyColumn(contentPadding = padding) {\n            items(list, key = { it.packageName }) { app ->\n                AppItemList(\n                    app = app,\n                    isSelected = selectedPackageNames.contains(app.packageName),\n                    imageLoader = imageLoader,\n                    onClick = { onAppClick(app) },\n                    onLongClick = { onAppLongClick(app) }\n                )\n            }\n        }\n    }\n}\n\n@OptIn(ExperimentalFoundationApi::class)\n@Composable\nprivate fun AppItemList(\n    app: AppInfo,\n    isSelected: Boolean,\n    imageLoader: ImageLoader,\n    onClick: () -> Unit,\n    onLongClick: () -> Unit\n) {\n    val interactionSource = remember { MutableInteractionSource() }\n    ListItem(\n        modifier = Modifier\n            .padding(horizontal = 12.dp, vertical = 2.dp)\n            .clip(RoundedCornerShape(24.dp))\n            .expressivePress(interactionSource)\n            .combinedClickable(\n                interactionSource = interactionSource,\n                onClick = onClick,\n                onLongClick = onLongClick\n            )\n            .background(\n                if (isSelected) MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.5f)\n                else MaterialTheme.colorScheme.surfaceContainerLow\n            ),\n        leadingContent = {\n            AppIcon(app.packageName, app.enabled, app.isSuspended, 48.dp, imageLoader)\n        },\n        headlineContent = {\n            Row(verticalAlignment = Alignment.CenterVertically) {\n                Text(\n                    app.appName ?: stringResource(R.string.unknown),\n                    maxLines = 1,\n                    fontWeight = androidx.compose.ui.text.font.FontWeight.Bold,\n                    overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,\n                    modifier = Modifier.weight(1f, fill = false)\n                )\n                if (!app.enabled) {\n                    Icon(\n                        painterResource(R.drawable.frozen),\n                        stringResource(R.string.cd_frozen),\n                        modifier = Modifier\n                            .size(16.dp)\n                            .padding(start = 4.dp),\n                        tint = MaterialTheme.colorScheme.primary\n                    )\n                } else if (app.isSuspended) {\n                    Icon(\n                        painterResource(R.drawable.bolt),\n                        stringResource(R.string.cd_suspended),\n                        modifier = Modifier\n                            .size(16.dp)\n                            .padding(start = 4.dp),\n                        tint = MaterialTheme.colorScheme.secondary\n                    )\n                }\n            }\n        },\n        supportingContent = {\n            Text(\n                app.packageName,\n                maxLines = 1,\n                style = MaterialTheme.typography.bodySmall,\n                color = MaterialTheme.colorScheme.onSurfaceVariant\n            )\n        },\n        trailingContent = {\n            if (isSelected) {\n                Icon(\n                    painterResource(R.drawable.check_circle),\n                    stringResource(R.string.cd_selected),\n                    tint = MaterialTheme.colorScheme.primary\n                )\n            }\n        },\n        colors = androidx.compose.material3.ListItemDefaults.colors(\n            containerColor = Color.Transparent\n        )\n    )\n}\n\n@OptIn(ExperimentalFoundationApi::class)\n@Composable\nprivate fun AppItemGrid(\n    app: AppInfo,\n    isSelected: Boolean,\n    imageLoader: ImageLoader,\n    onClick: () -> Unit,\n    onLongClick: () -> Unit\n) {\n    val interactionSource = remember { MutableInteractionSource() }\n    Column(\n        horizontalAlignment = Alignment.CenterHorizontally,\n        modifier = Modifier\n            .padding(6.dp)\n            .expressivePress(interactionSource)\n            .clip(RoundedCornerShape(32.dp))\n            .background(\n                if (isSelected) MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.5f)\n                else MaterialTheme.colorScheme.surfaceContainerLow\n            )\n            .combinedClickable(\n                interactionSource = interactionSource,\n                onClick = onClick,\n                onLongClick = onLongClick\n            )\n            .padding(16.dp)\n    ) {\n        Box {\n            AppIcon(app.packageName, app.enabled, app.isSuspended, 56.dp, imageLoader)\n            if (isSelected) {\n                Icon(\n                    painterResource(R.drawable.check_circle),\n                    stringResource(R.string.cd_selected),\n                    tint = MaterialTheme.colorScheme.primary,\n                    modifier = Modifier\n                        .align(Alignment.TopEnd)\n                        .background(MaterialTheme.colorScheme.surface, CircleShape)\n                )\n            } else {\n                // Status Indicator\n                if (!app.enabled) {\n                    Icon(\n                        painterResource(R.drawable.frozen),\n                        stringResource(R.string.cd_frozen),\n                        tint = MaterialTheme.colorScheme.primary,\n                        modifier = Modifier\n                            .align(Alignment.TopEnd)\n                            .size(16.dp)\n                            .background(MaterialTheme.colorScheme.surface, CircleShape)\n                            .padding(2.dp)\n                    )\n                } else if (app.isSuspended) {\n                    Icon(\n                        painterResource(R.drawable.bolt),\n                        stringResource(R.string.cd_suspended),\n                        tint = MaterialTheme.colorScheme.secondary,\n                        modifier = Modifier\n                            .align(Alignment.TopEnd)\n                            .size(16.dp)\n                            .background(MaterialTheme.colorScheme.surface, CircleShape)\n                            .padding(2.dp)\n                    )\n                }\n            }\n        }\n        Spacer(Modifier.height(8.dp))\n        Text(\n            text = app.appName ?: stringResource(R.string.unknown),\n            maxLines = 1,\n            style = MaterialTheme.typography.labelSmall,\n            fontWeight = androidx.compose.ui.text.font.FontWeight.Bold,\n            textAlign = TextAlign.Center,\n            overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis\n        )\n    }\n}\n\n@Composable\nprivate fun AppIcon(\n    packageName: String,\n    isEnabled: Boolean,\n    isSuspended: Boolean,\n    size: androidx.compose.ui.unit.Dp,\n    imageLoader: ImageLoader\n) {\n    // Hoisted static matrices to avoid recreation\n    val greyScaleMatrix = remember { ColorMatrix().apply { setToSaturation(0f) } }\n    val dullMatrix = remember { ColorMatrix().apply { setToSaturation(0.3f) } }\n\n    Box(contentAlignment = Alignment.Center) {\n        AsyncImage(\n            model = AppIconModel(packageName),\n            imageLoader = imageLoader,\n            contentDescription = null,\n            modifier = Modifier\n                .size(size)\n                .then(if (isSuspended && isEnabled) Modifier.graphicsLayer(alpha = 0.7f) else Modifier),\n            colorFilter = when {\n                !isEnabled -> ColorFilter.colorMatrix(greyScaleMatrix)\n                isSuspended -> ColorFilter.colorMatrix(dullMatrix)\n                else -> null\n            },\n            error = painterResource(R.drawable.android)\n        )\n    }\n}\n\nprivate fun toggleSelection(currentSelection: List<AppInfo>, item: AppInfo): List<AppInfo> {\n    return if (currentSelection.contains(item)) currentSelection - item else currentSelection + item\n}\n\n@Composable\nprivate fun EmptyStatePlaceholder(\n    isFiltering: Boolean\n) {\n    Column(\n        horizontalAlignment = Alignment.CenterHorizontally,\n        verticalArrangement = Arrangement.Center,\n        modifier = Modifier.padding(32.dp)\n    ) {\n        Icon(\n            painter = painterResource(\n                if (isFiltering) R.drawable.round_search else R.drawable.apps\n            ),\n            contentDescription = null,\n            modifier = Modifier.size(64.dp),\n            tint = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.3f)\n        )\n        Spacer(Modifier.height(16.dp))\n        Text(\n            text = if (isFiltering) stringResource(R.string.no_matching_apps) else stringResource(R.string.no_apps_display),\n            style = MaterialTheme.typography.titleMedium,\n            color = MaterialTheme.colorScheme.onSurfaceVariant\n        )\n        if (isFiltering) {\n            Text(\n                text = stringResource(R.string.adjust_filters_hint),\n                style = MaterialTheme.typography.bodyMedium,\n                color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),\n                modifier = Modifier.padding(top = 4.dp)\n            )\n        }\n    }\n}\n\nprivate enum class SheetTab { FILTERS, SORT }\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun AppFilterSheet(\n    onDismiss: () -> Unit,\n    filterType: FilterType,\n    sortBy: SortBy,\n    sortOrder: SortOrder,\n    isGrid: Boolean,\n    appListType: AppListType,\n    onFilterTypeChanged: (FilterType) -> Unit,\n    onSortByChanged: (SortBy) -> Unit,\n    onSortOrderChanged: (SortOrder) -> Unit,\n    onToggleView: () -> Unit,\n    onListTypeChanged: (AppListType) -> Unit\n) {\n    var activeTab by remember { mutableStateOf(SheetTab.FILTERS) }\n\n    ModalBottomSheet(\n        onDismissRequest = onDismiss,\n        containerColor = MaterialTheme.colorScheme.surfaceContainer,\n        shape = RoundedCornerShape(topStart = 48.dp, topEnd = 48.dp),\n        tonalElevation = 0.dp\n    ) {\n        Column(modifier = Modifier.padding(24.dp)) {\n            Text(\n                stringResource(R.string.configuration),\n                style = MaterialTheme.typography.headlineMedium,\n                fontWeight = androidx.compose.ui.text.font.FontWeight.Black,\n                letterSpacing = (-1).sp\n            )\n            Spacer(Modifier.height(24.dp))\n\n            // 1. App Type Selector (Top Row)\n            Row(\n                modifier = Modifier.fillMaxWidth(),\n                horizontalArrangement = Arrangement.SpaceBetween,\n                verticalAlignment = Alignment.CenterVertically\n            ) {\n                Text(\n                    stringResource(R.string.app_source),\n                    style = MaterialTheme.typography.titleMedium,\n                    fontWeight = androidx.compose.ui.text.font.FontWeight.Bold\n                )\n                ConnectedButtonGroup(\n                    items = AppListType.entries.map { type ->\n                        ConnectedButtonGroupItem.Icon(\n                            iconRes = if (type == AppListType.USER) R.drawable.apps else R.drawable.android,\n                            contentDescription = type.name\n                        )\n                    },\n                    selectedIndex = AppListType.entries.indexOf(appListType),\n                    onItemSelected = { onListTypeChanged(AppListType.entries[it]) }\n                )\n            }\n\n            Spacer(Modifier.height(24.dp))\n\n            // 2. View Toggle\n            Row(\n                modifier = Modifier.fillMaxWidth(),\n                horizontalArrangement = Arrangement.SpaceBetween,\n                verticalAlignment = Alignment.CenterVertically\n            ) {\n                Text(\n                    stringResource(R.string.view_mode),\n                    style = MaterialTheme.typography.titleMedium,\n                    fontWeight = androidx.compose.ui.text.font.FontWeight.Bold\n                )\n                ConnectedButtonGroup(\n                    items = listOf(\n                        ConnectedButtonGroupItem.Icon(\n                            R.drawable.grid_view,\n                            stringResource(R.string.grid)\n                        ),\n                        ConnectedButtonGroupItem.Icon(\n                            R.drawable.view_stream,\n                            stringResource(R.string.list)\n                        )\n                    ),\n                    selectedIndex = if (isGrid) 0 else 1,\n                    onItemSelected = { onToggleView() }\n                )\n            }\n\n            Spacer(Modifier.height(32.dp))\n\n            ConnectedButtonGroup(\n                items = SheetTab.entries.map { ConnectedButtonGroupItem.Label(stringResource(if (it == SheetTab.FILTERS) R.string.filters else R.string.sort_by)) },\n                selectedIndex = SheetTab.entries.indexOf(activeTab),\n                onItemSelected = { activeTab = SheetTab.entries[it] },\n                modifier = Modifier.fillMaxWidth()\n            )\n\n            Spacer(Modifier.height(16.dp))\n\n            when (activeTab) {\n                SheetTab.FILTERS -> {\n                    LazyColumn(\n                        modifier = Modifier.height(200.dp),\n                        verticalArrangement = Arrangement.spacedBy(8.dp)\n                    ) {\n                        items(filterTypes) { type ->\n                            ListItem(\n                                headlineContent = {\n                                    Text(\n                                        type.asGeneralName(),\n                                        maxLines = 1,\n                                        overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis\n                                    )\n                                },\n                                trailingContent = {\n                                    if (filterType == type) Icon(\n                                        painterResource(R.drawable.check_circle),\n                                        null,\n                                        tint = MaterialTheme.colorScheme.primary\n                                    )\n                                },\n                                modifier = Modifier\n                                    .clip(RoundedCornerShape(16.dp))\n                                    .background(\n                                        MaterialTheme.colorScheme.surfaceContainerHigh.copy(\n                                            alpha = 0.5f\n                                        )\n                                    )\n                                    .clickable { onFilterTypeChanged(type) },\n                                colors = androidx.compose.material3.ListItemDefaults.colors(\n                                    containerColor = Color.Transparent\n                                )\n                            )\n                        }\n                    }\n                }\n\n                SheetTab.SORT -> {\n                    Row(verticalAlignment = Alignment.CenterVertically) {\n                        Text(\n                            stringResource(R.string.order),\n                            style = MaterialTheme.typography.titleMedium\n                        )\n                        Spacer(Modifier.width(8.dp))\n                        FilterChip(\n                            selected = sortOrder == SortOrder.ASCENDING,\n                            onClick = { onSortOrderChanged(SortOrder.ASCENDING) },\n                            label = { Text(stringResource(R.string.ascending)) },\n                            leadingIcon = { Icon(painterResource(R.drawable.arrow_upward), null) }\n                        )\n                        Spacer(Modifier.width(8.dp))\n                        FilterChip(\n                            selected = sortOrder == SortOrder.DESCENDING,\n                            onClick = { onSortOrderChanged(SortOrder.DESCENDING) },\n                            label = { Text(stringResource(R.string.descending)) },\n                            leadingIcon = { Icon(painterResource(R.drawable.arrow_downward), null) }\n                        )\n                    }\n                    Spacer(Modifier.height(12.dp))\n                    LazyColumn(\n                        modifier = Modifier.height(200.dp),\n                        verticalArrangement = Arrangement.spacedBy(8.dp)\n                    ) {\n                        items(SortBy.entries) { item ->\n                            ListItem(\n                                headlineContent = {\n                                    Text(\n                                        item.asGeneralName(),\n                                        maxLines = 1,\n                                        overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis\n                                    )\n                                },\n                                trailingContent = {\n                                    if (sortBy == item) Icon(\n                                        painterResource(R.drawable.check_circle),\n                                        null,\n                                        tint = MaterialTheme.colorScheme.primary\n                                    )\n                                },\n                                modifier = Modifier\n                                    .clip(RoundedCornerShape(16.dp))\n                                    .background(\n                                        MaterialTheme.colorScheme.surfaceContainerHigh.copy(\n                                            alpha = 0.5f\n                                        )\n                                    )\n                                    .clickable { onSortByChanged(item) },\n                                colors = androidx.compose.material3.ListItemDefaults.colors(\n                                    containerColor = Color.Transparent\n                                )\n                            )\n                        }\n                    }\n                }\n            }\n            Spacer(Modifier.height(24.dp))\n            Button(\n                onClick = onDismiss,\n                modifier = Modifier.fillMaxWidth()\n            ) { Text(stringResource(R.string.done)) }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/widgets/MultiSelectToolBox.kt",
    "content": "package com.valhalla.thor.presentation.widgets\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.horizontalScroll\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.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.Card\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\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.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.res.painterResource\nimport androidx.compose.ui.unit.dp\nimport com.valhalla.thor.R\nimport com.valhalla.thor.domain.model.AppInfo\nimport com.valhalla.thor.domain.model.MultiAppAction\n\n@Composable\nfun MultiSelectToolBox(\n    modifier: Modifier = Modifier,\n    selected: List<AppInfo> = emptyList(),\n    isRoot: Boolean = false,\n    isShizuku: Boolean = false,\n    onCancel: () -> Unit = {},\n    onMultiAppAction: (MultiAppAction) -> Unit = {}\n) {\n    var hasFrozen by remember { mutableStateOf(selected.any { !it.enabled }) }\n    var hasUnFrozen by remember { mutableStateOf(selected.any { it.enabled }) }\n    var hasSuspended by remember { mutableStateOf(selected.any { it.isSuspended }) }\n    var hasUnSuspended by remember { mutableStateOf(selected.any { !it.isSuspended }) }\n\n    LaunchedEffect(selected) {\n        hasFrozen = selected.any { !it.enabled }\n        hasUnFrozen = selected.any { it.enabled }\n        hasSuspended = selected.any { it.isSuspended }\n        hasUnSuspended = selected.any { !it.isSuspended }\n    }\n\n    Card(\n        modifier = modifier,\n        shape = RoundedCornerShape(32.dp),\n        colors = androidx.compose.material3.CardDefaults.cardColors(\n            containerColor = MaterialTheme.colorScheme.surfaceContainerHighest\n        )\n    ) {\n        Row(\n            modifier = Modifier\n                .padding(12.dp)\n                .horizontalScroll(rememberScrollState()),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.spacedBy(12.dp)\n        ) {\n            // Close Action (Leftmost for easy exit)\n            ToolBoxItem(\n                icon = R.drawable.round_close,\n                label = \"Close\",\n                onClick = onCancel\n            )\n\n            // ReInstall (Root only)\n            if (isRoot) {\n                ToolBoxItem(\n                    icon = R.drawable.apk_install,\n                    label = \"ReInstall\",\n                    onClick = { onMultiAppAction(MultiAppAction.ReInstall(selected)) }\n                )\n            }\n\n            // Freeze/Unfreeze (Root OR Shizuku)\n            if (isRoot || isShizuku) {\n                if (hasUnFrozen) {\n                    ToolBoxItem(\n                        icon = R.drawable.frozen,\n                        label = \"Freeze\",\n                        onClick = { onMultiAppAction(MultiAppAction.Freeze(selected)) }\n                    )\n                }\n                if (hasFrozen) {\n                    ToolBoxItem(\n                        icon = R.drawable.unfreeze,\n                        label = \"UnFreeze\",\n                        onClick = { onMultiAppAction(MultiAppAction.UnFreeze(selected)) }\n                    )\n                }\n                if (hasUnSuspended) {\n                    ToolBoxItem(\n                        icon = R.drawable.warning,\n                        label = \"Suspend\",\n                        onClick = { onMultiAppAction(MultiAppAction.Suspend(selected)) }\n                    )\n                }\n                if (hasSuspended) {\n                    ToolBoxItem(\n                        icon = R.drawable.bolt,\n                        label = \"Unsuspend\",\n                        onClick = { onMultiAppAction(MultiAppAction.UnSuspend(selected)) }\n                    )\n                }\n            }\n\n            // Standard Actions\n            ToolBoxItem(\n                icon = R.drawable.clear_all,\n                label = \"Cache\",\n                onClick = { onMultiAppAction(MultiAppAction.ClearCache(selected)) }\n            )\n            ToolBoxItem(\n                icon = R.drawable.delete_forever,\n                label = \"Uninstall\",\n                onClick = { onMultiAppAction(MultiAppAction.Uninstall(selected)) }\n            )\n            ToolBoxItem(\n                icon = R.drawable.danger,\n                label = \"Kill\",\n                onClick = { onMultiAppAction(MultiAppAction.Kill(selected)) }\n            )\n        }\n    }\n}\n\n@Composable\nprivate fun ToolBoxItem(\n    icon: Int,\n    label: String,\n    onClick: () -> Unit\n) {\n    Column(\n        horizontalAlignment = Alignment.CenterHorizontally,\n        modifier = Modifier\n            .clip(RoundedCornerShape(24.dp))\n            .clickable(onClick = onClick)\n            .padding(8.dp)\n    ) {\n        Box(\n            modifier = Modifier\n                .size(48.dp)\n                .clip(RoundedCornerShape(16.dp))\n                .background(MaterialTheme.colorScheme.surfaceContainerHigh),\n            contentAlignment = Alignment.Center\n        ) {\n            Icon(\n                painter = painterResource(icon),\n                contentDescription = label,\n                modifier = Modifier.size(24.dp),\n                tint = MaterialTheme.colorScheme.primary\n            )\n        }\n        Spacer(Modifier.height(4.dp))\n        Text(\n            text = label,\n            style = MaterialTheme.typography.labelSmall,\n            fontWeight = androidx.compose.ui.text.font.FontWeight.Bold,\n            maxLines = 1\n        )\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/widgets/TermLogger.kt",
    "content": "package com.valhalla.thor.presentation.widgets\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.layout.Arrangement\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.layout.size\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.lazy.rememberLazyListState\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.Button\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.layout.ContentScale\nimport androidx.compose.ui.res.painterResource\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.window.Dialog\nimport androidx.compose.ui.window.DialogProperties\nimport com.valhalla.thor.R\nimport com.valhalla.thor.presentation.theme.firaMonoFontFamily\n\n@Composable\nfun TermLoggerDialog(\n    modifier: Modifier = Modifier,\n    title: String,\n    logs: List<String>,\n    isOperationComplete: Boolean,\n    onDismiss: () -> Unit\n) {\n    Dialog(\n        onDismissRequest = {\n            // Only allow dismiss if operation is done\n            if (isOperationComplete) {\n                onDismiss()\n            }\n        },\n        properties = DialogProperties(\n            dismissOnBackPress = false,\n            dismissOnClickOutside = false,\n            usePlatformDefaultWidth = false\n        )\n    ) {\n        Column(modifier = modifier.fillMaxSize(), verticalArrangement = Arrangement.Bottom) {\n            Column(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .background(\n                        color = MaterialTheme.colorScheme.background,\n                        shape = RoundedCornerShape(topEnd = 20.dp, topStart = 20.dp)\n                    )\n                    .padding(10.dp)\n                    .padding(bottom = 50.dp),\n                horizontalAlignment = Alignment.CenterHorizontally\n            ) {\n                // Header\n                Row(\n                    verticalAlignment = Alignment.CenterVertically,\n                    modifier = Modifier.fillMaxWidth()\n                ) {\n                    if (!isOperationComplete) {\n                        AnimateLottieRaw(\n                            resId = R.raw.rearrange,\n                            shouldLoop = true,\n                            modifier = Modifier.size(50.dp),\n                            contentScale = ContentScale.Crop\n                        )\n                    } else {\n                        Icon(\n                            painterResource(R.drawable.check_circle),\n                            contentDescription = stringResource(R.string.cd_selected),\n                            modifier = Modifier.size(40.dp),\n                            tint = MaterialTheme.colorScheme.primary\n                        )\n                    }\n\n                    Text(\n                        text = if (isOperationComplete) stringResource(R.string.done) else title,\n                        color = MaterialTheme.colorScheme.onBackground,\n                        style = MaterialTheme.typography.titleMedium,\n                        modifier = Modifier\n                            .weight(1f)\n                            .padding(start = 12.dp),\n                        maxLines = 1,\n                        overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis\n                    )\n\n                    if (isOperationComplete) {\n                        IconButton(onClick = onDismiss) {\n                            Icon(\n                                painterResource(R.drawable.round_close),\n                                stringResource(R.string.cd_close),\n                                tint = MaterialTheme.colorScheme.onBackground\n                            )\n                        }\n                    }\n                }\n\n                // Logs List\n                val lazyListState = rememberLazyListState()\n                LaunchedEffect(logs.size) {\n                    if (logs.isNotEmpty()) {\n                        lazyListState.animateScrollToItem(logs.lastIndex)\n                    }\n                }\n\n                LazyColumn(\n                    state = lazyListState,\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(top = 10.dp)\n                ) {\n                    items(logs) { logTxt ->\n                        Text(\n                            text = \"> $logTxt\",\n                            softWrap = false,\n                            modifier = Modifier.fillMaxWidth(),\n                            style = MaterialTheme.typography.bodySmall.copy(\n                                fontFamily = firaMonoFontFamily\n                            ),\n                            maxLines = 1,\n                            textAlign = TextAlign.Start,\n                            color = MaterialTheme.colorScheme.onBackground\n                        )\n                    }\n                }\n\n                if (isOperationComplete) {\n                    Button(\n                        onClick = onDismiss,\n                        modifier = Modifier\n                            .padding(top = 16.dp)\n                            .fillMaxWidth()\n                    ) {\n                        Text(stringResource(R.string.close))\n                    }\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/presentation/widgets/TypeWriterText.kt",
    "content": "package com.valhalla.thor.presentation.widgets\n\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\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.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.text.TextStyle\nimport androidx.compose.ui.text.font.FontStyle\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.tooling.preview.Preview\nimport kotlinx.coroutines.delay\n\n@Composable\nfun TypeWriterText(\n    modifier: Modifier = Modifier,\n    text: String,\n    style: TextStyle = MaterialTheme.typography.bodyLarge,\n    fontWeight: FontWeight = FontWeight.Normal,\n    fontStyle: FontStyle = FontStyle.Normal,\n    maxLines: Int = 1,\n    delay: Long = 100,\n    delayOnLoop: Long = 2000,\n    loop: Boolean = false,\n    reverse: Boolean = true,\n    textAlign: TextAlign = TextAlign.Center,\n    softWrap: Boolean = false,\n    onEnd: () -> Unit = {}\n) {\n\n    var textCharList by remember {\n        mutableStateOf(\n            emptyList<String>()\n        )\n    }\n\n    var textToDisplay by remember { mutableStateOf(\"\") }\n    var currentIndex by remember { mutableIntStateOf(0) }\n    var isReversing by remember { mutableStateOf(false) }\n\n    LaunchedEffect(text) {\n        textCharList = emptyList()\n        text.codePoints().forEach {\n            textCharList += Char(it).toString()\n        }\n        textToDisplay = \"\"\n        currentIndex = 0\n        isReversing = false\n        while (currentIndex < textCharList.size || (isReversing && currentIndex > 0)) {\n            if (!isReversing) {\n                currentIndex++\n                textToDisplay += textCharList[currentIndex - 1]\n            } else {\n                currentIndex--\n                textToDisplay = textCharList.subList(0, currentIndex).joinToString(\"\")\n            }\n            if (currentIndex == textCharList.size || currentIndex == 0) {\n                onEnd()\n                if (loop) {\n                    if (reverse) {\n                        isReversing = !isReversing\n                    } else {\n                        textToDisplay = \"\"\n                        currentIndex = 0\n                    }\n                    delay(delayOnLoop)\n                }\n            } else\n                delay(delay)\n        }\n    }\n\n    Text(\n        text = textToDisplay,\n        style = style,\n        fontWeight = fontWeight,\n        fontStyle = fontStyle,\n        maxLines = maxLines,\n        textAlign = textAlign,\n        modifier = modifier\n    )\n\n}\n\n@Preview(showBackground = true)\n@Composable\nfun TypeWriterTextPreview() {\n    TypeWriterText(text = \"Hello, World! \\uD83D\\uDC9C\\uD83D\\uDC4B\", loop = true, reverse = true)\n}"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/util/LocaleManager.kt",
    "content": "package com.valhalla.thor.util\n\nimport android.app.LocaleManager as AndroidLocaleManager\nimport android.content.Context\nimport android.os.Build\nimport android.os.LocaleList\nimport androidx.appcompat.app.AppCompatDelegate\nimport androidx.core.os.LocaleListCompat\n\n/**\n * Modern Locale Manager that uses the system LocaleManager on Android 13+ (API 33)\n * and falls back to AppCompatDelegate for older versions.\n */\nclass LocaleManager(private val context: Context) {\n\n    /**\n     * Applies the given language code to the application.\n     * `@param` languageCode The language tag (e.g., \"en\", \"zh-CN\"), or null for system default.\n     */\n    fun applyLocale(languageCode: String?) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n            val localeManager = context.getSystemService(Context.LOCALE_SERVICE) as AndroidLocaleManager\n            localeManager.applicationLocales = if (languageCode == null) {\n                LocaleList.getEmptyLocaleList()\n            } else {\n                LocaleList.forLanguageTags(languageCode)\n            }\n        } else {\n            val appLocale: LocaleListCompat = if (languageCode == null) {\n                LocaleListCompat.getEmptyLocaleList()\n            } else {\n                LocaleListCompat.forLanguageTags(languageCode)\n            }\n            AppCompatDelegate.setApplicationLocales(appLocale)\n        }\n    }\n\n    /**\n     * Returns the currently applied application locales.\n     */\n    fun getAppliedLocales(): LocaleListCompat {\n        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n            val localeManager = context.getSystemService(Context.LOCALE_SERVICE) as AndroidLocaleManager\n            LocaleListCompat.wrap(localeManager.applicationLocales)\n        } else {\n            AppCompatDelegate.getApplicationLocales()\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/valhalla/thor/util/Logger.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage com.valhalla.thor.util\n\nimport android.util.Log\nimport com.valhalla.thor.BuildConfig\nimport org.koin.core.logger.Level\n\nobject Logger {\n\n    /**\n     * Koin Log Level Configuration.\n     * Usage in startKoin: androidLogger(Logger.koinLogLevel)\n     */\n    val koinLogLevel: Level = if (BuildConfig.DEBUG) Level.DEBUG else Level.NONE\n\n    fun d(tag: String, message: String) {\n        if (BuildConfig.DEBUG) {\n            Log.d(tag, message)\n        }\n    }\n\n    fun i(tag: String, message: String) {\n        if (BuildConfig.DEBUG) {\n            Log.i(tag, message)\n        }\n    }\n\n    fun w(tag: String, message: String) {\n        if (BuildConfig.DEBUG) {\n            Log.w(tag, message)\n        }\n    }\n\n    fun v(tag: String, message: String) {\n        if (BuildConfig.DEBUG) {\n            Log.v(tag, message)\n        }\n    }\n\n    fun e(tag: String, message: String, throwable: Throwable? = null) {\n        if (BuildConfig.DEBUG) {\n            Log.e(tag, message, throwable)\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/res/drawable/android.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"#000000\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M40,720Q49,613 105.5,523Q162,433 256,380L182,252Q176,243 179,233Q182,223 192,218Q200,213 210,216Q220,219 226,228L300,356Q386,320 480,320Q574,320 660,356L734,228Q740,219 750,216Q760,213 768,218Q778,223 781,233Q784,243 778,252L704,380Q798,433 854.5,523Q911,613 920,720L40,720ZM280,610Q301,610 315.5,595.5Q330,581 330,560Q330,539 315.5,524.5Q301,510 280,510Q259,510 244.5,524.5Q230,539 230,560Q230,581 244.5,595.5Q259,610 280,610ZM680,610Q701,610 715.5,595.5Q730,581 730,560Q730,539 715.5,524.5Q701,510 680,510Q659,510 644.5,524.5Q630,539 630,560Q630,581 644.5,595.5Q659,610 680,610Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/apk_install.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"#000000\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M160,880Q127,880 103.5,856.5Q80,833 80,800L80,160Q80,127 103.5,103.5Q127,80 160,80L447,80Q463,80 477.5,86Q492,92 503,103L697,297Q708,308 714,322.5Q720,337 720,353L720,450Q720,467 708.5,478.5Q697,490 680,490L640,490Q623,490 611.5,501.5Q600,513 600,530L600,840Q600,857 588.5,868.5Q577,880 560,880L160,880ZM440,360L640,360L440,160L440,360ZM200,760L600,760Q596,711 570,670Q544,629 502,605L540,537Q542,533 541,528Q540,523 535,521Q531,519 526.5,520Q522,521 520,525L481,595Q461,587 441,582.5Q421,578 400,578Q379,578 359,582.5Q339,587 319,595L280,525Q278,520 273.5,520Q269,520 264,522Q264,522 260,537L298,605Q256,629 230,670Q204,711 200,760ZM310,700Q302,700 296,694Q290,688 290,680Q290,672 296,666Q302,660 310,660Q318,660 324,666Q330,672 330,680Q330,688 324,694Q318,700 310,700ZM490,700Q482,700 476,694Q470,688 470,680Q470,672 476,666Q482,660 490,660Q498,660 504,666Q510,672 510,680Q510,688 504,694Q498,700 490,700ZM760,726L760,600Q760,583 771.5,571.5Q783,560 800,560Q817,560 828.5,571.5Q840,583 840,600L840,726L876,691Q888,680 904,680Q920,680 932,692Q943,704 943.5,720Q944,736 932,748L828,852Q816,864 800,864Q784,864 772,852L668,748Q657,737 656.5,720.5Q656,704 668,692Q679,680 696,680Q713,680 725,691L760,726Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/apps.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M240,800Q207,800 183.5,776.5Q160,753 160,720Q160,687 183.5,663.5Q207,640 240,640Q273,640 296.5,663.5Q320,687 320,720Q320,753 296.5,776.5Q273,800 240,800ZM480,800Q447,800 423.5,776.5Q400,753 400,720Q400,687 423.5,663.5Q447,640 480,640Q513,640 536.5,663.5Q560,687 560,720Q560,753 536.5,776.5Q513,800 480,800ZM720,800Q687,800 663.5,776.5Q640,753 640,720Q640,687 663.5,663.5Q687,640 720,640Q753,640 776.5,663.5Q800,687 800,720Q800,753 776.5,776.5Q753,800 720,800ZM240,560Q207,560 183.5,536.5Q160,513 160,480Q160,447 183.5,423.5Q207,400 240,400Q273,400 296.5,423.5Q320,447 320,480Q320,513 296.5,536.5Q273,560 240,560ZM480,560Q447,560 423.5,536.5Q400,513 400,480Q400,447 423.5,423.5Q447,400 480,400Q513,400 536.5,423.5Q560,447 560,480Q560,513 536.5,536.5Q513,560 480,560ZM720,560Q687,560 663.5,536.5Q640,513 640,480Q640,447 663.5,423.5Q687,400 720,400Q753,400 776.5,423.5Q800,447 800,480Q800,513 776.5,536.5Q753,560 720,560ZM240,320Q207,320 183.5,296.5Q160,273 160,240Q160,207 183.5,183.5Q207,160 240,160Q273,160 296.5,183.5Q320,207 320,240Q320,273 296.5,296.5Q273,320 240,320ZM480,320Q447,320 423.5,296.5Q400,273 400,240Q400,207 423.5,183.5Q447,160 480,160Q513,160 536.5,183.5Q560,207 560,240Q560,273 536.5,296.5Q513,320 480,320ZM720,320Q687,320 663.5,296.5Q640,273 640,240Q640,207 663.5,183.5Q687,160 720,160Q753,160 776.5,183.5Q800,207 800,240Q800,273 776.5,296.5Q753,320 720,320Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/arrow_downward.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M440,647L440,200Q440,183 451.5,171.5Q463,160 480,160Q497,160 508.5,171.5Q520,183 520,200L520,647L716,451Q728,439 744,439.5Q760,440 772,452Q783,464 783.5,480Q784,496 772,508L508,772Q502,778 495,780.5Q488,783 480,783Q472,783 465,780.5Q458,778 452,772L188,508Q177,497 177,480.5Q177,464 188,452Q200,440 216.5,440Q233,440 245,452L440,647Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/arrow_drop_down.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"#000000\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M459,579L314,434Q311,431 309.5,427.5Q308,424 308,420Q308,412 313.5,406Q319,400 328,400L632,400Q641,400 646.5,406Q652,412 652,420Q652,422 646,434L501,579Q496,584 491,586Q486,588 480,588Q474,588 469,586Q464,584 459,579Z\" />\n\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/arrow_upward.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M440,313L244,509Q232,521 216,520.5Q200,520 188,508Q177,496 176.5,480Q176,464 188,452L452,188Q458,182 465,179.5Q472,177 480,177Q488,177 495,179.5Q502,182 508,188L772,452Q783,463 783,479.5Q783,496 772,508Q760,520 743.5,520Q727,520 715,508L520,313L520,760Q520,777 508.5,788.5Q497,800 480,800Q463,800 451.5,788.5Q440,777 440,760L440,313Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/bolt.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M360,600L236,600Q212,600 200.5,578.5Q189,557 203,537L502,107Q512,93 528,87.5Q544,82 561,88Q578,94 586,109Q594,124 592,141L560,400L715,400Q741,400 751.5,423Q762,446 745,466L416,860Q405,873 389,877Q373,881 358,874Q343,867 334.5,852.5Q326,838 328,821L360,600Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/brand_github.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:fillColor=\"#000000\"\n        android:pathData=\"M5.315,2.1c0.791,-0.113 1.9,0.145 3.333,0.966l0.272,0.161l0.16,0.1l0.397,-0.083a13.3,13.3 0,0 1,4.59 -0.08l0.456,0.08l0.396,0.083l0.161,-0.1c1.385,-0.84 2.487,-1.17 3.322,-1.148l0.164,0.008l0.147,0.017l0.076,0.014l0.05,0.011l0.144,0.047a1,1 0,0 1,0.53 0.514a5.2,5.2 0,0 1,0.397 2.91l-0.047,0.267l-0.046,0.196l0.123,0.163c0.574,0.795 0.93,1.728 1.03,2.707l0.023,0.295l0.007,0.272c0,3.855 -1.659,5.883 -4.644,6.68l-0.245,0.061l-0.132,0.029l0.014,0.161l0.008,0.157l0.004,0.365l-0.002,0.213l-0.003,3.834a1,1 0,0 1,-0.883 0.993l-0.117,0.007h-6a1,1 0,0 1,-0.993 -0.883l-0.007,-0.117v-0.734c-1.818,0.26 -3.03,-0.424 -4.11,-1.878l-0.535,-0.766c-0.28,-0.396 -0.455,-0.579 -0.589,-0.644l-0.048,-0.019a1,1 0,0 1,0.564 -1.918c0.642,0.188 1.074,0.568 1.57,1.239l0.538,0.769c0.76,1.079 1.36,1.459 2.609,1.191l0.001,-0.678l-0.018,-0.168a5.03,5.03 0,0 1,-0.021 -0.824l0.017,-0.185l0.019,-0.12l-0.108,-0.024c-2.976,-0.71 -4.703,-2.573 -4.875,-6.139l-0.01,-0.31l-0.004,-0.292a5.6,5.6 0,0 1,0.908 -3.051l0.152,-0.222l0.122,-0.163l-0.045,-0.196a5.2,5.2 0,0 1,0.145 -2.642l0.1,-0.282l0.106,-0.253a1,1 0,0 1,0.529 -0.514l0.144,-0.047l0.154,-0.03z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/brand_patreon.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:fillColor=\"#000000\"\n        android:pathData=\"M7.462,3.1c2.615,-1.268 6.226,-1.446 9.063,-0.503c2.568,0.853 4.471,3.175 4.475,5.81c0.004,3.061 -1.942,5.492 -4.896,6.243c-1.693,0.43 -2.338,0.75 -2.942,1.582c-0.238,0.328 -0.45,0.745 -0.796,1.533l-0.22,0.5c-1.146,2.601 -2.156,3.762 -4.236,3.735c-2.232,-0.03 -3.603,-1.742 -4.313,-4.48c-0.458,-1.768 -0.617,-3.808 -0.594,-5.876c0.044,-3.993 1.42,-7.072 4.46,-8.545z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/brand_telegram.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:fillColor=\"#000000\"\n        android:pathData=\"M15,10l-4,4l6,6l4,-16l-18,7l4,2l2,6l3,-4\"\n        android:strokeWidth=\"2\"\n        android:strokeColor=\"#000000\"\n        android:strokeLineCap=\"round\"\n        android:strokeLineJoin=\"round\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/cat.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"800dp\"\n    android:height=\"800dp\"\n    android:viewportWidth=\"16\"\n    android:viewportHeight=\"16\">\n    <path\n        android:fillColor=\"#000000\"\n        android:fillType=\"evenOdd\"\n        android:pathData=\"M1,7L4.801,1.439C5.561,0.527 6.686,0 7.873,0H8V4L12,5L15,10L14.188,11.219C13.446,12.332 12.197,13 10.859,13H9L7,16H5L1,7ZM10,9C10.552,9 11,8.552 11,8C11,7.448 10.552,7 10,7C9.448,7 9,7.448 9,8C9,8.552 9.448,9 10,9Z\" />\n    <path\n        android:fillColor=\"#000000\"\n        android:pathData=\"M10,0.466V2.438L12,2.938V0H11.873C11.212,0 10.57,0.164 10,0.466Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/check_circle.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"#4CAF50\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M424,552L338,466Q327,455 310,455Q293,455 282,466Q271,477 271,494Q271,511 282,522L396,636Q408,648 424,648Q440,648 452,636L678,410Q689,399 689,382Q689,365 678,354Q667,343 650,343Q633,343 622,354L424,552ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/clear_all.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:fillColor=\"@android:color/white\"\n        android:pathData=\"M160,680Q143,680 131.5,668.5Q120,657 120,640Q120,623 131.5,611.5Q143,600 160,600L640,600Q657,600 668.5,611.5Q680,623 680,640Q680,657 668.5,668.5Q657,680 640,680L160,680ZM240,520Q223,520 211.5,508.5Q200,497 200,480Q200,463 211.5,451.5Q223,440 240,440L720,440Q737,440 748.5,451.5Q760,463 760,480Q760,497 748.5,508.5Q737,520 720,520L240,520ZM320,360Q303,360 291.5,348.5Q280,337 280,320Q280,303 291.5,291.5Q303,280 320,280L800,280Q817,280 828.5,291.5Q840,303 840,320Q840,337 828.5,348.5Q817,360 800,360L320,360Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/danger.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M240,880L240,710Q201,693 171.5,664.5Q142,636 121.5,600Q101,564 90.5,523Q80,482 80,440Q80,282 192,181Q304,80 480,80Q656,80 768,181Q880,282 880,440Q880,482 869.5,523Q859,564 838.5,600Q818,636 788.5,664.5Q759,693 720,710L720,880L240,880ZM320,800L360,800L360,720L440,720L440,800L520,800L520,720L600,720L600,800L640,800L640,658Q678,649 707.5,628Q737,607 757.5,578Q778,549 789,514Q800,479 800,440Q800,315 711.5,237.5Q623,160 480,160Q337,160 248.5,237.5Q160,315 160,440Q160,479 171,514Q182,549 202.5,578Q223,607 253,628Q283,649 320,658L320,800ZM420,600L540,600L480,480L420,600ZM340,520Q373,520 396.5,496.5Q420,473 420,440Q420,407 396.5,383.5Q373,360 340,360Q307,360 283.5,383.5Q260,407 260,440Q260,473 283.5,496.5Q307,520 340,520ZM620,520Q653,520 676.5,496.5Q700,473 700,440Q700,407 676.5,383.5Q653,360 620,360Q587,360 563.5,383.5Q540,407 540,440Q540,473 563.5,496.5Q587,520 620,520ZM480,800L480,800Q480,800 480,800Q480,800 480,800Q480,800 480,800Q480,800 480,800Q480,800 480,800Q480,800 480,800Q480,800 480,800Q480,800 480,800Q480,800 480,800Q480,800 480,800Q480,800 480,800Q480,800 480,800L480,800L480,800L480,800L480,800L480,800L480,800L480,800L480,800L480,800Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/delete.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M280,840Q247,840 223.5,816.5Q200,793 200,760L200,240L200,240Q183,240 171.5,228.5Q160,217 160,200Q160,183 171.5,171.5Q183,160 200,160L360,160L360,160Q360,143 371.5,131.5Q383,120 400,120L560,120Q577,120 588.5,131.5Q600,143 600,160L600,160L760,160Q777,160 788.5,171.5Q800,183 800,200Q800,217 788.5,228.5Q777,240 760,240L760,240L760,760Q760,793 736.5,816.5Q713,840 680,840L280,840ZM680,240L280,240L280,760Q280,760 280,760Q280,760 280,760L680,760Q680,760 680,760Q680,760 680,760L680,240ZM400,680Q417,680 428.5,668.5Q440,657 440,640L440,360Q440,343 428.5,331.5Q417,320 400,320Q383,320 371.5,331.5Q360,343 360,360L360,640Q360,657 371.5,668.5Q383,680 400,680ZM560,680Q577,680 588.5,668.5Q600,657 600,640L600,360Q600,343 588.5,331.5Q577,320 560,320Q543,320 531.5,331.5Q520,343 520,360L520,640Q520,657 531.5,668.5Q543,680 560,680ZM280,240L280,240L280,760Q280,760 280,760Q280,760 280,760L280,760Q280,760 280,760Q280,760 280,760L280,240Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/delete_forever.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"#000000\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M280,840Q247,840 223.5,816.5Q200,793 200,760L200,240L200,240Q183,240 171.5,228.5Q160,217 160,200Q160,183 171.5,171.5Q183,160 200,160L360,160L360,160Q360,143 371.5,131.5Q383,120 400,120L560,120Q577,120 588.5,131.5Q600,143 600,160L600,160L760,160Q777,160 788.5,171.5Q800,183 800,200Q800,217 788.5,228.5Q777,240 760,240L760,240L760,760Q760,793 736.5,816.5Q713,840 680,840L280,840ZM480,556L556,632Q567,643 584,643Q601,643 612,632Q623,621 623,604Q623,587 612,576L536,500L612,424Q623,413 623,396Q623,379 612,368Q601,357 584,357Q567,357 556,368L480,444L404,368Q393,357 376,357Q359,357 348,368Q337,379 337,396Q337,413 348,424L424,500L348,576Q337,587 337,604Q337,621 348,632Q359,643 376,643Q393,643 404,632L480,556Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/dhizuku.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"240\"\n    android:viewportHeight=\"240\">\n    <path\n        android:fillColor=\"#000000\"\n        android:pathData=\"M82.12,162.96C75.15,162.9 75.13,162.88 75.16,154.99C75.18,148.65 75.78,148.03 81.9,148.01C84.05,148 86.21,148.01 88.77,148.01C88.92,146.65 89.17,145.4 89.17,144.15C89.2,130.99 89.11,117.83 89.25,104.67C89.28,101.84 88.56,100.35 85.8,99.06C72.92,93.05 68.31,78.5 75.32,66.85C82.49,54.9 97.55,51.96 109.04,60.26C122.02,69.65 120.92,90.4 106.57,97.82C101.77,100.3 100.03,102.96 100.75,108C101.09,110.44 100.8,112.96 100.8,116.16C106.3,116.16 111.56,116.22 116.81,116.09C117.62,116.07 118.54,115.32 119.19,114.68C132.32,101.68 145.45,88.68 158.45,75.55C159.51,74.47 159.95,72.4 160.02,70.76C160.21,66.1 159.98,61.43 160.08,56.77C160.2,51.46 163.53,48.16 168.85,48.03C172.68,47.93 176.51,47.96 180.34,48.02C185.82,48.1 189.5,51.42 189.78,56.81C189.99,60.79 189.98,64.81 189.79,68.8C189.52,74.24 185.93,77.7 180.44,77.78C175.62,77.85 170.8,77.56 165.98,77.55C164.98,77.55 163.67,77.79 163.01,78.43C150.63,90.51 138.33,102.66 125.03,115.76C138.87,115.76 151.18,115.76 163.5,115.77C164.94,106.09 167.06,104.57 177.14,104.26C191.64,103.82 194.93,106.65 193.81,121C193.55,124.32 193.03,127.97 191.38,130.71C190.31,132.51 187.07,133.4 184.64,133.87C181.91,134.4 178.97,133.73 176.17,134.04C167.36,135 163.43,129.81 163.87,122.17C151.31,122.17 138.77,122.17 125.28,122.17C128.87,125.99 131.86,129.36 135.07,132.52C144.08,141.4 153.13,150.24 162.31,158.94C163.37,159.95 165.34,160.37 166.91,160.42C171.73,160.57 176.57,160.23 181.39,160.42C186.13,160.61 189.47,163.93 189.75,168.71C190.01,173.03 190.01,177.38 189.76,181.7C189.47,186.8 186.09,189.88 181.01,190C176.85,190.09 172.68,190.1 168.52,189.99C163.46,189.86 160.28,186.59 160.06,181.51C160.04,181.01 159.98,180.5 160.07,180.01C162.26,168.6 157.73,160.44 149.03,153.03C139.17,144.63 130.34,134.99 121.33,125.63C118.54,122.73 115.74,121.42 111.77,121.78C108.33,122.1 104.85,121.85 100.8,121.85C100.8,125.84 100.8,129.45 100.8,133.06C100.81,148.55 100.99,164.05 100.62,179.53C100.56,181.94 98.48,184.95 96.43,186.41C95.42,187.13 92.13,185.27 90.29,183.98C89.3,183.29 89.19,181.34 88.53,179.54C85.68,179.54 82.7,179.49 79.73,179.56C76.65,179.63 75.04,178.36 75.14,175.13C75.23,171.99 76.46,170.05 79.9,170.11C82.38,170.16 84.87,170.12 87.68,170.12C87.68,167.83 87.68,165.74 87.68,162.96C86.08,162.96 84.33,162.96 82.12,162.96M106.01,83.1C106.35,81.66 106.9,80.23 106.98,78.78C107.26,73.68 103.52,68.51 98.7,67.09C93.16,65.46 87.49,67.81 84.65,72.9C82.01,77.64 82.93,83.12 86.98,86.88C91.73,91.29 100.25,92.09 106.01,83.1M184.04,70.14C184.1,68.65 184.21,67.16 184.21,65.66C184.14,51.38 185.83,53.08 171.4,53C168.72,52.99 165.42,52.75 165.43,56.48C165.44,61.62 166.23,66.76 166.7,71.89C166.71,71.97 166.99,72.08 167.14,72.08C172.64,71.72 178.14,71.36 184.04,70.14M167.88,165.89C167.06,166.98 165.7,167.98 165.5,169.18C163.25,182.54 166.39,185.95 179.83,185.02C180,185.01 180.16,185.02 180.33,185.01C182.89,184.94 184.27,183.85 184.2,181.06C183.7,162.42 187.17,166.25 170.3,165.8C169.8,165.79 169.31,165.81 167.88,165.89M186.99,110.37C170.93,107.71 168.72,109.48 169.69,124.2C169.73,124.7 169.54,125.4 169.8,125.66C170.97,126.84 172.17,128.68 173.52,128.84C177.3,129.27 181.19,129.22 184.98,128.83C186.17,128.71 188.09,126.94 188.12,125.88C188.27,120.94 187.87,115.98 186.99,110.37z\" />\n\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/exit_to_app.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:autoMirrored=\"true\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M200,840Q167,840 143.5,816.5Q120,793 120,760L120,640Q120,623 131.5,611.5Q143,600 160,600Q177,600 188.5,611.5Q200,623 200,640L200,760Q200,760 200,760Q200,760 200,760L760,760Q760,760 760,760Q760,760 760,760L760,200Q760,200 760,200Q760,200 760,200L200,200Q200,200 200,200Q200,200 200,200L200,320Q200,337 188.5,348.5Q177,360 160,360Q143,360 131.5,348.5Q120,337 120,320L120,200Q120,167 143.5,143.5Q167,120 200,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM466,520L160,520Q143,520 131.5,508.5Q120,497 120,480Q120,463 131.5,451.5Q143,440 160,440L466,440L392,366Q380,354 380.5,338Q381,322 392,310Q404,298 420.5,297.5Q437,297 449,309L592,452Q598,458 600.5,465Q603,472 603,480Q603,488 600.5,495Q598,502 592,508L449,651Q437,663 420.5,662.5Q404,662 392,650Q381,638 380.5,622Q380,606 392,594L466,520Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/filter_list.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M440,720Q423,720 411.5,708.5Q400,697 400,680Q400,663 411.5,651.5Q423,640 440,640L520,640Q537,640 548.5,651.5Q560,663 560,680Q560,697 548.5,708.5Q537,720 520,720L440,720ZM280,520Q263,520 251.5,508.5Q240,497 240,480Q240,463 251.5,451.5Q263,440 280,440L680,440Q697,440 708.5,451.5Q720,463 720,480Q720,497 708.5,508.5Q697,520 680,520L280,520ZM160,320Q143,320 131.5,308.5Q120,297 120,280Q120,263 131.5,251.5Q143,240 160,240L800,240Q817,240 828.5,251.5Q840,263 840,280Q840,297 828.5,308.5Q817,320 800,320L160,320Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/force_close.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M480,536L596,652Q607,663 624,663Q641,663 652,652Q663,641 663,624Q663,607 652,596L536,480L652,364Q663,353 663,336Q663,319 652,308Q641,297 624,297Q607,297 596,308L480,424L364,308Q353,297 336,297Q319,297 308,308Q297,319 297,336Q297,353 308,364L424,480L308,596Q297,607 297,624Q297,641 308,652Q319,663 336,663Q353,663 364,652L480,536ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/freeze_off.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:fillColor=\"@android:color/white\"\n        android:pathData=\"M520,713L520,840Q520,857 508.5,868.5Q497,880 480,880Q463,880 451.5,868.5Q440,857 440,840L440,714L338,815Q326,826 309.5,825.5Q293,825 282,814Q271,803 271,786Q271,769 282,758L423,616L343,536L202,678Q191,689 174.5,689.5Q158,690 146,678Q135,667 135,650Q135,633 146,622L246,520L120,520Q103,520 91.5,508.5Q80,497 80,480Q80,463 91.5,451.5Q103,440 120,440L247,440L59,252Q47,240 47.5,223.5Q48,207 60,195Q72,183 88.5,183Q105,183 117,195L796,875Q808,887 808,903Q808,919 796,931Q784,943 767.5,943Q751,943 739,931L520,713ZM600,520L555,520L440,405L440,360L282,202Q271,191 270.5,174.5Q270,158 282,146Q293,135 310,135Q327,135 338,146L440,246L440,120Q440,103 451.5,91.5Q463,80 480,80Q497,80 508.5,91.5Q520,103 520,120L520,246L622,145Q634,134 650.5,134.5Q667,135 678,146Q689,157 689,174Q689,191 678,202L520,360L520,440L600,440L758,282Q769,271 785.5,270.5Q802,270 814,282Q825,293 825,310Q825,327 814,338L714,440L840,440Q857,440 868.5,451.5Q880,463 880,480Q880,497 868.5,508.5Q857,520 840,520L714,520L815,622Q821,628 823.5,635.5Q826,643 826,650.5Q826,658 823,665Q820,672 814,678Q803,689 786,689Q769,689 758,678L600,520Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/frozen.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M440,880L440,714L310,842L254,786L440,600L440,520L360,520L174,706L118,650L246,520L80,520L80,440L246,440L118,310L174,254L360,440L440,440L440,360L254,174L310,118L440,246L440,80L520,80L520,246L650,118L706,174L520,360L520,440L600,440L786,254L842,310L714,440L880,440L880,520L714,520L842,650L786,706L600,520L520,520L520,600L706,786L650,842L520,714L520,880L440,880Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/grid_view.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M200,440Q167,440 143.5,416.5Q120,393 120,360L120,200Q120,167 143.5,143.5Q167,120 200,120L360,120Q393,120 416.5,143.5Q440,167 440,200L440,360Q440,393 416.5,416.5Q393,440 360,440L200,440ZM200,840Q167,840 143.5,816.5Q120,793 120,760L120,600Q120,567 143.5,543.5Q167,520 200,520L360,520Q393,520 416.5,543.5Q440,567 440,600L440,760Q440,793 416.5,816.5Q393,840 360,840L200,840ZM600,440Q567,440 543.5,416.5Q520,393 520,360L520,200Q520,167 543.5,143.5Q567,120 600,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,360Q840,393 816.5,416.5Q793,440 760,440L600,440ZM600,840Q567,840 543.5,816.5Q520,793 520,760L520,600Q520,567 543.5,543.5Q567,520 600,520L760,520Q793,520 816.5,543.5Q840,567 840,600L840,760Q840,793 816.5,816.5Q793,840 760,840L600,840Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/home.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"#FFFFFF\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M160,760L160,400Q160,381 168.5,364Q177,347 192,336L432,156Q453,140 480,140Q507,140 528,156L768,336Q783,347 791.5,364Q800,381 800,400L800,760Q800,793 776.5,816.5Q753,840 720,840L600,840Q583,840 571.5,828.5Q560,817 560,800L560,600Q560,583 548.5,571.5Q537,560 520,560L440,560Q423,560 411.5,571.5Q400,583 400,600L400,800Q400,817 388.5,828.5Q377,840 360,840L240,840Q207,840 183.5,816.5Q160,793 160,760Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/home_outline.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"#FFFFFF\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M240,760L360,760L360,560Q360,543 371.5,531.5Q383,520 400,520L560,520Q577,520 588.5,531.5Q600,543 600,560L600,760L720,760L720,400Q720,400 720,400Q720,400 720,400L480,220Q480,220 480,220Q480,220 480,220L240,400Q240,400 240,400Q240,400 240,400L240,760ZM160,760L160,400Q160,381 168.5,364Q177,347 192,336L432,156Q453,140 480,140Q507,140 528,156L768,336Q783,347 791.5,364Q800,381 800,400L800,760Q800,793 776.5,816.5Q753,840 720,840L560,840Q543,840 531.5,828.5Q520,817 520,800L520,600Q520,600 520,600Q520,600 520,600L440,600Q440,600 440,600Q440,600 440,600L440,800Q440,817 428.5,828.5Q417,840 400,840L240,840Q207,840 183.5,816.5Q160,793 160,760ZM480,490L480,490L480,490Q480,490 480,490Q480,490 480,490L480,490L480,490L480,490L480,490Q480,490 480,490Q480,490 480,490L480,490Q480,490 480,490Q480,490 480,490L480,490Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path\n        android:fillColor=\"#3DDC84\"\n        android:pathData=\"M0,0h108v108h-108z\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M9,0L9,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,0L19,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,0L29,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,0L39,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,0L49,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,0L59,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,0L69,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,0L79,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M89,0L89,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M99,0L99,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,9L108,9\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,19L108,19\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,29L108,29\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,39L108,39\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,49L108,49\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,59L108,59\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,69L108,69\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,79L108,79\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,89L108,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,99L108,99\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,29L89,29\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,39L89,39\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,49L89,49\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,59L89,59\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,69L89,69\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,79L89,79\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,19L29,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,19L39,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,19L49,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,19L59,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,19L69,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,19L79,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher_foreground.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path android:pathData=\"M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z\">\n        <aapt:attr name=\"android:fillColor\">\n            <gradient\n                android:endX=\"85.84757\"\n                android:endY=\"92.4963\"\n                android:startX=\"42.9492\"\n                android:startY=\"49.59793\"\n                android:type=\"linear\">\n                <item\n                    android:color=\"#44000000\"\n                    android:offset=\"0.0\" />\n                <item\n                    android:color=\"#00000000\"\n                    android:offset=\"1.0\" />\n            </gradient>\n        </aapt:attr>\n    </path>\n    <path\n        android:fillColor=\"#FFFFFF\"\n        android:fillType=\"nonZero\"\n        android:pathData=\"M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z\"\n        android:strokeWidth=\"1\"\n        android:strokeColor=\"#00000000\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ios_share.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"#000000\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M240,920Q207,920 183.5,896.5Q160,873 160,840L160,400Q160,367 183.5,343.5Q207,320 240,320L320,320Q337,320 348.5,331.5Q360,343 360,360Q360,377 348.5,388.5Q337,400 320,400L240,400Q240,400 240,400Q240,400 240,400L240,840Q240,840 240,840Q240,840 240,840L720,840Q720,840 720,840Q720,840 720,840L720,400Q720,400 720,400Q720,400 720,400L640,400Q623,400 611.5,388.5Q600,377 600,360Q600,343 611.5,331.5Q623,320 640,320L720,320Q753,320 776.5,343.5Q800,367 800,400L800,840Q800,873 776.5,896.5Q753,920 720,920L240,920ZM440,193L404,229Q392,241 376,240.5Q360,240 348,228Q337,216 336.5,200Q336,184 348,172L452,68Q464,56 480,56Q496,56 508,68L612,172Q623,183 623,199.5Q623,216 612,228Q600,240 583.5,240Q567,240 555,228L520,193L520,600Q520,617 508.5,628.5Q497,640 480,640Q463,640 451.5,628.5Q440,617 440,600L440,193Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/key.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M280,600Q230,600 195,565Q160,530 160,480Q160,430 195,395Q230,360 280,360Q330,360 365,395Q400,430 400,480Q400,530 365,565Q330,600 280,600ZM280,720Q357,720 419,676Q481,632 506,560L520,560L572,612Q578,618 585,620.5Q592,623 600,623Q608,623 615,620.5Q622,618 628,612L680,560L750,615Q756,620 763.5,622.5Q771,625 779,624Q787,623 793.5,619.5Q800,616 805,610L895,507Q900,501 902.5,494Q905,487 905,479Q905,471 902,464.5Q899,458 894,453L853,412Q847,406 839.5,403Q832,400 824,400L506,400Q482,332 421.5,286Q361,240 280,240Q180,240 110,310Q40,380 40,480Q40,580 110,650Q180,720 280,720Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/key_outline.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M280,560Q247,560 223.5,536.5Q200,513 200,480Q200,447 223.5,423.5Q247,400 280,400Q313,400 336.5,423.5Q360,447 360,480Q360,513 336.5,536.5Q313,560 280,560ZM280,720Q180,720 110,650Q40,580 40,480Q40,380 110,310Q180,240 280,240Q347,240 401.5,273Q456,306 488,360L823,360Q831,360 838.5,363Q846,366 852,372L932,452Q938,458 940.5,465Q943,472 943,480Q943,488 940.5,495Q938,502 932,508L805,635Q800,640 793,643Q786,646 779,647Q772,648 765,646Q758,644 752,639L700,600L643,643Q638,647 632,649Q626,651 620,651Q614,651 607.5,649Q601,647 596,643L535,600L488,600Q456,654 401.5,687Q347,720 280,720ZM280,640Q336,640 378.5,606Q421,572 435,520L560,520L618,561Q618,561 618,561.5Q618,562 618,561Q618,561 618,561Q618,561 618,561L700,500L771,555Q771,555 771,555Q771,555 771,555Q771,555 771,555Q771,555 771,555L846,480Q845,480 845.5,480Q846,480 846,480Q846,480 846,480Q846,480 846,480L806,440Q806,439 806,439.5Q806,440 806,440L435,440Q421,388 378.5,354Q336,320 280,320Q214,320 167,367Q120,414 120,480Q120,546 167,593Q214,640 280,640Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/list.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:autoMirrored=\"true\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M320,360Q303,360 291.5,348.5Q280,337 280,320Q280,303 291.5,291.5Q303,280 320,280L800,280Q817,280 828.5,291.5Q840,303 840,320Q840,337 828.5,348.5Q817,360 800,360L320,360ZM320,520Q303,520 291.5,508.5Q280,497 280,480Q280,463 291.5,451.5Q303,440 320,440L800,440Q817,440 828.5,451.5Q840,463 840,480Q840,497 828.5,508.5Q817,520 800,520L320,520ZM320,680Q303,680 291.5,668.5Q280,657 280,640Q280,623 291.5,611.5Q303,600 320,600L800,600Q817,600 828.5,611.5Q840,623 840,640Q840,657 828.5,668.5Q817,680 800,680L320,680ZM160,360Q143,360 131.5,348.5Q120,337 120,320Q120,303 131.5,291.5Q143,280 160,280Q177,280 188.5,291.5Q200,303 200,320Q200,337 188.5,348.5Q177,360 160,360ZM160,520Q143,520 131.5,508.5Q120,497 120,480Q120,463 131.5,451.5Q143,440 160,440Q177,440 188.5,451.5Q200,463 200,480Q200,497 188.5,508.5Q177,520 160,520ZM160,680Q143,680 131.5,668.5Q120,657 120,640Q120,623 131.5,611.5Q143,600 160,600Q177,600 188.5,611.5Q200,623 200,640Q200,657 188.5,668.5Q177,680 160,680Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/magisk_icon.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:fillColor=\"#000000\"\n        android:pathData=\"M10.857,14.458s0.155,0.921 -0.034,2.952c-0.236,2.546 0.97,6.59 0.97,6.59s1.645,-4.052 1.358,-6.67c-0.236,-2.152 0.107,-2.904 0.034,-2.803 -1.264,1.746 -2.328,-0.069 -2.328,-0.069zM13.939,16.643c0.206,1.591 -0.023,2.462 -0.32,4.164 -0.15,0.861 3.068,-2.589 4.302,-4.645 0.206,-0.343 -1.18,1.337 -2.55,0.137 -0.952,-0.832 -1.115,-1.085 -1.854,-1.808 -0.249,-0.244 0.277,1.014 0.423,2.151zM10.427,14.618c-0.739,0.723 -0.903,0.976 -1.853,1.808 -1.371,1.2 -2.757,-0.48 -2.551,-0.137 1.234,2.057 4.452,5.506 4.302,4.645 -0.297,-1.703 -0.526,-2.574 -0.32,-4.164 0.147,-1.137 0.673,-2.395 0.423,-2.15zM13.593,11.779c1.504,0.434 2.088,2.523 3.606,2.781 0.314,0.053 0.667,0.148 1.08,0.128 0.77,-0.037 1.743,-0.472 3.044,-2.318 0.385,-0.546 -0.955,3.514 -4.313,3.563 -2.46,0.036 -2.747,-2.408 -4.387,-2.482 -0.592,-0.027 -0.629,-1.156 -0.629,-1.156s0.706,-0.774 1.598,-0.517zM10.407,11.767c-1.504,0.434 -2.088,2.523 -3.606,2.781 -0.314,0.053 -0.667,0.148 -1.08,0.128 -0.77,-0.037 -1.743,-0.472 -3.044,-2.318 -0.385,-0.546 0.955,3.514 4.313,3.563 2.46,0.036 2.747,-2.408 4.387,-2.482 0.592,-0.027 0.629,-1.156 0.629,-1.156s-0.706,-0.774 -1.598,-0.517zM16.033,11.747c1.513,1.146 1.062,2.408 1.911,2.048 2.86,-1.212 2.36,-7.434 2.128,-6.682 -1.303,4.242 -4.143,4.48 -6.876,2.528 -0.534,-0.38 1.985,1.46 2.837,2.105zM10.793,9.641C8.06,11.592 5.22,11.355 3.917,7.113c-0.231,-0.752 -0.731,5.47 2.128,6.682 0.849,0.36 0.398,-0.902 1.91,-2.048 0.853,-0.646 3.372,-2.486 2.838,-2.105zM16.319,10.225c3.3,-0.136 3.91,-5.545 3.65,-4.885 -1.165,2.963 -5.574,1.848 -5.995,3.718 -0.083,0.367 0.747,1.233 2.345,1.167zM10.015,9.058c-0.421,-1.87 -4.831,-0.755 -5.995,-3.718 -0.26,-0.66 0.35,4.75 3.65,4.885 1.599,0.066 2.428,-0.8 2.345,-1.167zM13.768,8.234s1.794,-0.964 3.33,-1.384c1.435,-0.393 2.512,-1.359 2.631,-2.38 0.09,-0.76 -1.11,-2.197 -1.11,-2.197s-0.84,2.334 -1.945,3.501c-1.2,1.27 -0.745,1.1 -2.906,2.46zM7.315,5.774c-1.104,-1.167 -1.945,-3.5 -1.945,-3.5S4.17,3.708 4.26,4.47c0.12,1.021 1.196,1.987 2.63,2.38 1.537,0.421 3.331,1.384 3.331,1.384 -2.162,-1.36 -1.705,-1.19 -2.906,-2.46zM13.55,8.086c1.943,-1.594 2.976,-3.673 4.657,-5.949 0.317,-0.429 -1.419,-1.465 -2.105,-1.533 -0.686,-0.068 -1.262,2.453 -1.327,3.936 -0.059,1.354 -1.486,3.761 -1.224,3.547zM9.214,4.54C9.149,3.056 8.573,0.535 7.887,0.603 7.2,0.671 5.465,1.707 5.782,2.136c1.68,2.276 2.713,4.356 4.657,5.95 0.261,0.213 -1.165,-2.194 -1.224,-3.548zM13.745,2.938c0.137,-1.098 0.631,-1.9 1.613,-2.574 -0.868,-0.29 -1.591,-0.526 -1.968,-0.217 -0.377,0.309 -1.403,1.342 -1.266,3.023s0.007,7.962 0.305,7.846c0.16,-4.302 1.522,-5.538 1.316,-8.077zM11.559,11.015c0.297,0.116 0.167,-6.165 0.305,-7.846 0.138,-1.681 -0.888,-2.714 -1.266,-3.023 -0.377,-0.309 -1.1,-0.073 -1.968,0.217 0.983,0.674 1.476,1.476 1.613,2.574 -0.206,2.54 1.156,3.775 1.316,8.077z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/open_in.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:autoMirrored=\"true\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L440,120Q457,120 468.5,131.5Q480,143 480,160Q480,177 468.5,188.5Q457,200 440,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760L760,760Q760,760 760,760Q760,760 760,760L760,520Q760,503 771.5,491.5Q783,480 800,480Q817,480 828.5,491.5Q840,503 840,520L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM760,256L416,600Q405,611 388,611Q371,611 360,600Q349,589 349,572Q349,555 360,544L704,200L600,200Q583,200 571.5,188.5Q560,177 560,160Q560,143 571.5,131.5Q583,120 600,120L800,120Q817,120 828.5,131.5Q840,143 840,160L840,360Q840,377 828.5,388.5Q817,400 800,400Q783,400 771.5,388.5Q760,377 760,360L760,256Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/open_in_new.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:autoMirrored=\"true\"\n    android:tint=\"#000000\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L440,120Q457,120 468.5,131.5Q480,143 480,160Q480,177 468.5,188.5Q457,200 440,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760L760,760Q760,760 760,760Q760,760 760,760L760,520Q760,503 771.5,491.5Q783,480 800,480Q817,480 828.5,491.5Q840,503 840,520L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM760,256L416,600Q405,611 388,611Q371,611 360,600Q349,589 349,572Q349,555 360,544L704,200L600,200Q583,200 571.5,188.5Q560,177 560,160Q560,143 571.5,131.5Q583,120 600,120L800,120Q817,120 828.5,131.5Q840,143 840,160L840,360Q840,377 828.5,388.5Q817,400 800,400Q783,400 771.5,388.5Q760,377 760,360L760,256Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/privacy_tip.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M480,680Q497,680 508.5,668.5Q520,657 520,640L520,480Q520,463 508.5,451.5Q497,440 480,440Q463,440 451.5,451.5Q440,463 440,480L440,640Q440,657 451.5,668.5Q463,680 480,680ZM480,360Q497,360 508.5,348.5Q520,337 520,320Q520,303 508.5,291.5Q497,280 480,280Q463,280 451.5,291.5Q440,303 440,320Q440,337 451.5,348.5Q463,360 480,360ZM480,876Q473,876 467,875Q461,874 455,872Q320,827 240,705.5Q160,584 160,444L160,255Q160,230 174.5,210Q189,190 212,181L452,91Q466,86 480,86Q494,86 508,91L748,181Q771,190 785.5,210Q800,230 800,255L800,444Q800,584 720,705.5Q640,827 505,872Q499,874 493,875Q487,876 480,876Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/round_close.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"#000000\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M18.3,5.71c-0.39,-0.39 -1.02,-0.39 -1.41,0L12,10.59 7.11,5.7c-0.39,-0.39 -1.02,-0.39 -1.41,0 -0.39,0.39 -0.39,1.02 0,1.41L10.59,12 5.7,16.89c-0.39,0.39 -0.39,1.02 0,1.41 0.39,0.39 1.02,0.39 1.41,0L12,13.41l4.89,4.89c0.39,0.39 1.02,0.39 1.41,0 0.39,-0.39 0.39,-1.02 0,-1.41L13.41,12l4.89,-4.89c0.38,-0.38 0.38,-1.02 0,-1.4z\" />\n\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/round_key.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"#000000\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M20.59,10h-7.94C11.7,7.31 8.89,5.5 5.77,6.12c-2.29,0.46 -4.15,2.3 -4.63,4.58C0.32,14.58 3.26,18 7,18c2.61,0 4.83,-1.67 5.65,-4H13l1.29,1.29c0.39,0.39 1.02,0.39 1.41,0L17,14l1.29,1.29c0.39,0.39 1.03,0.39 1.42,0l2.59,-2.61c0.39,-0.39 0.39,-1.03 -0.01,-1.42l-0.99,-0.97C21.1,10.1 20.85,10 20.59,10zM7,15c-1.65,0 -3,-1.35 -3,-3c0,-1.65 1.35,-3 3,-3s3,1.35 3,3C10,13.65 8.65,15 7,15z\" />\n\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/round_search.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"#000000\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M15.5,14h-0.79l-0.28,-0.27c1.2,-1.4 1.82,-3.31 1.48,-5.34 -0.47,-2.78 -2.79,-5 -5.59,-5.34 -4.23,-0.52 -7.79,3.04 -7.27,7.27 0.34,2.8 2.56,5.12 5.34,5.59 2.03,0.34 3.94,-0.28 5.34,-1.48l0.27,0.28v0.79l4.25,4.25c0.41,0.41 1.08,0.41 1.49,0 0.41,-0.41 0.41,-1.08 0,-1.49L15.5,14zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z\" />\n\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:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M433,880Q406,880 386.5,862Q367,844 363,818L354,752Q341,747 329.5,740Q318,733 307,725L245,751Q220,762 195,753Q170,744 156,721L109,639Q95,616 101,590Q107,564 128,547L181,507Q180,500 180,493.5Q180,487 180,480Q180,473 180,466.5Q180,460 181,453L128,413Q107,396 101,370Q95,344 109,321L156,239Q170,216 195,207Q220,198 245,209L307,235Q318,227 330,220Q342,213 354,208L363,142Q367,116 386.5,98Q406,80 433,80L527,80Q554,80 573.5,98Q593,116 597,142L606,208Q619,213 630.5,220Q642,227 653,235L715,209Q740,198 765,207Q790,216 804,239L851,321Q865,344 859,370Q853,396 832,413L779,453Q780,460 780,466.5Q780,473 780,480Q780,487 780,493.5Q780,500 778,507L831,547Q852,564 858,590Q864,616 850,639L802,721Q788,744 763,753Q738,762 713,751L653,725Q642,733 630,740Q618,747 606,752L597,818Q593,844 573.5,862Q554,880 527,880L433,880ZM482,620Q540,620 581,579Q622,538 622,480Q622,422 581,381Q540,340 482,340Q423,340 382.5,381Q342,422 342,480Q342,538 382.5,579Q423,620 482,620Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/settings_backup_restore.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:autoMirrored=\"true\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M480,840Q354,840 257,763.5Q160,687 131,568Q127,553 137,540.5Q147,528 164,526Q180,524 193,532Q206,540 211,556Q235,646 310,703Q385,760 480,760Q597,760 678.5,678.5Q760,597 760,480Q760,363 678.5,281.5Q597,200 480,200Q411,200 351,232Q291,264 250,320L320,320Q337,320 348.5,331.5Q360,343 360,360Q360,377 348.5,388.5Q337,400 320,400L160,400Q143,400 131.5,388.5Q120,377 120,360L120,200Q120,183 131.5,171.5Q143,160 160,160Q177,160 188.5,171.5Q200,183 200,200L200,254Q251,190 324.5,155Q398,120 480,120Q555,120 620.5,148.5Q686,177 734.5,225.5Q783,274 811.5,339.5Q840,405 840,480Q840,555 811.5,620.5Q783,686 734.5,734.5Q686,783 620.5,811.5Q555,840 480,840ZM480,560Q447,560 423.5,536.5Q400,513 400,480Q400,447 423.5,423.5Q447,400 480,400Q513,400 536.5,423.5Q560,447 560,480Q560,513 536.5,536.5Q513,560 480,560Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/settings_outline.xml",
    "content": "<!--\n  ~ Copyright (C) 2026 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<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"#FFFFFF\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M433,880Q406,880 386.5,862Q367,844 363,818L354,752Q341,747 329.5,740Q318,733 307,725L245,751Q220,762 195,753Q170,744 156,721L109,639Q95,616 101,590Q107,564 128,547L181,507Q180,500 180,493.5Q180,487 180,480Q180,473 180,466.5Q180,460 181,453L128,413Q107,396 101,370Q95,344 109,321L156,239Q170,216 195,207Q220,198 245,209L307,235Q318,227 330,220Q342,213 354,208L363,142Q367,116 386.5,98Q406,80 433,80L527,80Q554,80 573.5,98Q593,116 597,142L606,208Q619,213 630.5,220Q642,227 653,235L715,209Q740,198 765,207Q790,216 804,239L851,321Q865,344 859,370Q853,396 832,413L779,453Q780,460 780,466.5Q780,473 780,480Q780,487 780,493.5Q780,500 778,507L831,547Q852,564 858,590Q864,616 850,639L802,721Q788,744 763,753Q738,762 713,751L653,725Q642,733 630,740Q618,747 606,752L597,818Q593,844 573.5,862Q554,880 527,880L433,880ZM440,800L519,800L533,694Q564,686 590.5,670.5Q617,655 639,633L738,674L777,606L691,541Q696,527 698,511.5Q700,496 700,480Q700,464 698,448.5Q696,433 691,419L777,354L738,286L639,328Q617,305 590.5,289.5Q564,274 533,266L520,160L441,160L427,266Q396,274 369.5,289.5Q343,305 321,327L222,286L183,354L269,418Q264,433 262,448Q260,463 260,480Q260,496 262,511Q264,526 269,541L183,606L222,674L321,632Q343,655 369.5,670.5Q396,686 427,694L440,800ZM482,620Q540,620 581,579Q622,538 622,480Q622,422 581,381Q540,340 482,340Q423,340 382.5,381Q342,422 342,480Q342,538 382.5,579Q423,620 482,620ZM480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Z\" />\n\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/share.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"#000000\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M680,880Q630,880 595,845Q560,810 560,760Q560,754 563,732L282,568Q266,583 245,591.5Q224,600 200,600Q150,600 115,565Q80,530 80,480Q80,430 115,395Q150,360 200,360Q224,360 245,368.5Q266,377 282,392L563,228Q561,221 560.5,214.5Q560,208 560,200Q560,150 595,115Q630,80 680,80Q730,80 765,115Q800,150 800,200Q800,250 765,285Q730,320 680,320Q656,320 635,311.5Q614,303 598,288L317,452Q319,459 319.5,465.5Q320,472 320,480Q320,488 319.5,494.5Q319,501 317,508L598,672Q614,657 635,648.5Q656,640 680,640Q730,640 765,675Q800,710 800,760Q800,810 765,845Q730,880 680,880Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/shield.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M480,876Q473,876 467,875Q461,874 455,872Q320,827 240,705.5Q160,584 160,444L160,255Q160,230 174.5,210Q189,190 212,181L452,91Q466,86 480,86Q494,86 508,91L748,181Q771,190 785.5,210Q800,230 800,255L800,444Q800,584 720,705.5Q640,827 505,872Q499,874 493,875Q487,876 480,876Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/shield_bad.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M480,536L536,592Q547,603 564,603Q581,603 592,592Q603,581 603,564Q603,547 592,536L536,480L592,424Q603,413 603,396Q603,379 592,368Q581,357 564,357Q547,357 536,368L480,424L424,368Q413,357 396,357Q379,357 368,368Q357,379 357,396Q357,413 368,424L424,480L368,536Q357,547 357,564Q357,581 368,592Q379,603 396,603Q413,603 424,592L480,536ZM480,876Q473,876 467,875Q461,874 455,872Q320,827 240,705.5Q160,584 160,444L160,255Q160,230 174.5,210Q189,190 212,181L452,91Q466,86 480,86Q494,86 508,91L748,181Q771,190 785.5,210Q800,230 800,255L800,444Q800,584 720,705.5Q640,827 505,872Q499,874 493,875Q487,876 480,876Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/shield_countdown.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M480,640Q555,640 607.5,587.5Q660,535 660,460Q660,385 607.5,332.5Q555,280 480,280L480,340Q530,340 565,375Q600,410 600,460Q600,510 565,545Q530,580 480,580Q447,580 418.5,563Q390,546 374,517L322,546Q346,590 388,615Q430,640 480,640ZM330,494Q343,494 351.5,485.5Q360,477 360,464Q360,451 351.5,442.5Q343,434 330,434Q317,434 308.5,442.5Q300,451 300,464Q300,477 308.5,485.5Q317,494 330,494ZM352,410Q365,410 373.5,401.5Q382,393 382,380Q382,367 373.5,358.5Q365,350 352,350Q339,350 330.5,358.5Q322,367 322,380Q322,393 330.5,401.5Q339,410 352,410ZM410,355Q423,355 431.5,346.5Q440,338 440,325Q440,312 431.5,303.5Q423,295 410,295Q397,295 388.5,303.5Q380,312 380,325Q380,338 388.5,346.5Q397,355 410,355ZM480,876Q473,876 467,875Q461,874 455,872Q320,827 240,705.5Q160,584 160,444L160,255Q160,230 174.5,210Q189,190 212,181L452,91Q466,86 480,86Q494,86 508,91L748,181Q771,190 785.5,210Q800,230 800,255L800,444Q800,584 720,705.5Q640,827 505,872Q499,874 493,875Q487,876 480,876Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/shield_encrypted.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M444,600L516,600Q525,600 531.5,592.5Q538,585 536,576L517,471Q537,461 548.5,442Q560,423 560,400Q560,367 536.5,343.5Q513,320 480,320Q447,320 423.5,343.5Q400,367 400,400Q400,423 411.5,442Q423,461 443,471L424,576Q422,585 428.5,592.5Q435,600 444,600ZM480,876Q473,876 467,875Q461,874 455,872Q320,827 240,705.5Q160,584 160,444L160,255Q160,230 174.5,210Q189,190 212,181L452,91Q466,86 480,86Q494,86 508,91L748,181Q771,190 785.5,210Q800,230 800,255L800,444Q800,584 720,705.5Q640,827 505,872Q499,874 493,875Q487,876 480,876Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/shield_maybe.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M480,640Q497,640 508.5,628.5Q520,617 520,600Q520,583 508.5,571.5Q497,560 480,560Q463,560 451.5,571.5Q440,583 440,600Q440,617 451.5,628.5Q463,640 480,640ZM480,480Q497,480 508.5,468.5Q520,457 520,440L520,320Q520,303 508.5,291.5Q497,280 480,280Q463,280 451.5,291.5Q440,303 440,320L440,440Q440,457 451.5,468.5Q463,480 480,480ZM480,876Q473,876 467,875Q461,874 455,872Q320,827 240,705.5Q160,584 160,444L160,255Q160,230 174.5,210Q189,190 212,181L452,91Q466,86 480,86Q494,86 508,91L748,181Q771,190 785.5,210Q800,230 800,255L800,444Q800,584 720,705.5Q640,827 505,872Q499,874 493,875Q487,876 480,876Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/shield_search.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M480,876Q473,876 467,875Q461,874 455,872Q320,827 240,705.5Q160,584 160,444L160,255Q160,230 174.5,210Q189,190 212,181L452,91Q466,86 480,86Q494,86 508,91L748,181Q771,190 785.5,210Q800,230 800,255L800,444Q800,507 783.5,566.5Q767,626 736,680L618,562Q629,543 634.5,522.5Q640,502 640,480Q640,414 593,367Q546,320 480,320Q414,320 367,367Q320,414 320,480Q320,546 367,593Q414,640 480,640Q501,640 521.5,634.5Q542,629 560,618L689,746Q651,791 605.5,822.5Q560,854 505,872Q499,874 493,875Q487,876 480,876ZM480,560Q447,560 423.5,536.5Q400,513 400,480Q400,447 423.5,423.5Q447,400 480,400Q513,400 536.5,423.5Q560,447 560,480Q560,513 536.5,536.5Q513,560 480,560Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/shield_verified.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M438,508L382,452Q370,440 354,440Q338,440 326,452Q314,464 314,480.5Q314,497 326,509L410,594Q422,606 438,606Q454,606 466,594L636,424Q648,412 648,395.5Q648,379 636,367Q624,355 607.5,355Q591,355 579,367L438,508ZM480,876Q473,876 467,875Q461,874 455,872Q320,827 240,705.5Q160,584 160,444L160,255Q160,230 174.5,210Q189,190 212,181L452,91Q466,86 480,86Q494,86 508,91L748,181Q771,190 785.5,210Q800,230 800,255L800,444Q800,584 720,705.5Q640,827 505,872Q499,874 493,875Q487,876 480,876Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/shield_with_heart.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M320,436Q320,492 375,543Q430,594 467,628Q473,633 480.5,633Q488,633 494,628Q530,595 585,542.5Q640,490 640,436Q640,400 614,374Q588,348 552,348Q531,348 511.5,356.5Q492,365 480,380Q468,365 449,356.5Q430,348 408,348Q372,348 346,374Q320,400 320,436ZM480,876Q473,876 467,875Q461,874 455,872Q320,827 240,705.5Q160,584 160,444L160,255Q160,230 174.5,210Q189,190 212,181L452,91Q466,86 480,86Q494,86 508,91L748,181Q771,190 785.5,210Q800,230 800,255L800,444Q800,584 720,705.5Q640,827 505,872Q499,874 493,875Q487,876 480,876Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/shizuku.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"192\"\n    android:viewportHeight=\"192\">\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M128.6,162.5c-0.8,0.4 -1.7,0.8 -2.6,1.2 -3,1.3 -6,2.4 -9.1,3.3 -36.1,10.6 -73.8,-7.5 -88.6,-41 3.2,-4.6 7.4,-8.6 11.8,-11.9 -0.8,-7.9 1,-15.9 6.7,-24.3 9.2,2.7 17.1,7.6 23.4,14.8 10.2,-1.9 20,-0.4 29.4,3.5 8.3,-4.2 16.2,-6.5 23.6,-6.2 0.8,-0.1 1.6,0.5 1.8,1.4 1.6,8.7 0.8,16.8 -2.7,24.2 5.3,11.5 8.8,23.7 6.3,35z\"\n        android:strokeWidth=\"12\"\n        android:strokeColor=\"#000000\"\n        android:strokeLineCap=\"round\"\n        android:strokeLineJoin=\"round\" />\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M59.8,130.4m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0\" />\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M100.4,136.1m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M128.6,162.5c-0.8,0.4 -1.7,0.8 -2.6,1.2 -3,1.3 -6,2.4 -9.1,3.3 -36.1,10.6 -73.8,-7.5 -88.6,-41 -0.5,-1.2 -1,-2.4 -1.5,-3.7 -0.6,-1.8 -1.3,-3.6 -1.8,-5.4C13.5,77.7 35.9,36.6 75.1,25c39.2,-11.5 80.3,10.8 91.9,50.1 10.3,35.1 -6.6,71.8 -38.4,87.4z\"\n        android:strokeWidth=\"12\"\n        android:strokeColor=\"#000000\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"m122.7,128.6 l19.5,-11.3V74l-37.5,-21.7L67.2,74v24.6\"\n        android:strokeWidth=\"12\"\n        android:strokeColor=\"#000000\"\n        android:strokeLineJoin=\"round\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/shizuku_outline_icon.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"800dp\"\n    android:height=\"800dp\"\n    android:viewportWidth=\"192\"\n    android:viewportHeight=\"192\">\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M128.6,162.5c-0.8,0.4 -1.7,0.8 -2.6,1.2 -3,1.3 -6,2.4 -9.1,3.3 -36.1,10.6 -73.8,-7.5 -88.6,-41 3.2,-4.6 7.4,-8.6 11.8,-11.9 -0.8,-7.9 1,-15.9 6.7,-24.3 9.2,2.7 17.1,7.6 23.4,14.8 10.2,-1.9 20,-0.4 29.4,3.5 8.3,-4.2 16.2,-6.5 23.6,-6.2 0.8,-0.1 1.6,0.5 1.8,1.4 1.6,8.7 0.8,16.8 -2.7,24.2 5.3,11.5 8.8,23.7 6.3,35z\"\n        android:strokeWidth=\"12\"\n        android:strokeColor=\"#000000\"\n        android:strokeLineCap=\"round\"\n        android:strokeLineJoin=\"round\" />\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M59.8,130.4m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0\" />\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M100.4,136.1m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M128.6,162.5c-0.8,0.4 -1.7,0.8 -2.6,1.2 -3,1.3 -6,2.4 -9.1,3.3 -36.1,10.6 -73.8,-7.5 -88.6,-41 -0.5,-1.2 -1,-2.4 -1.5,-3.7 -0.6,-1.8 -1.3,-3.6 -1.8,-5.4C13.5,77.7 35.9,36.6 75.1,25c39.2,-11.5 80.3,10.8 91.9,50.1 10.3,35.1 -6.6,71.8 -38.4,87.4z\"\n        android:strokeWidth=\"12\"\n        android:strokeColor=\"#000000\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"m122.7,128.6 l19.5,-11.3V74l-37.5,-21.7L67.2,74v24.6\"\n        android:strokeWidth=\"12\"\n        android:strokeColor=\"#000000\"\n        android:strokeLineJoin=\"round\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/snowflake.xml",
    "content": "<!--\n  ~ Copyright (C) 2026 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<vector xmlns:android=\"http://schemas.android.com/apk/res/android\" android:height=\"24dp\" android:tint=\"#FFFFFF\" android:viewportHeight=\"960\" android:viewportWidth=\"960\" android:width=\"24dp\">\n      \n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M440,549L308,625L288,735Q285,752 272,761.5Q259,771 242,768Q225,765 216,751.5Q207,738 210,721L218,678L188,695Q174,703 158,698.5Q142,694 134,680Q126,666 130,649.5Q134,633 148,625L178,608L136,593Q120,588 113,573Q106,558 112,542Q117,526 132,519.5Q147,513 163,518L268,556L400,480L268,404L163,442Q147,447 132.5,440.5Q118,434 112,418Q106,402 113,386.5Q120,371 136,366L178,352L148,335Q134,327 130,310.5Q126,294 134,280Q142,266 158,261.5Q174,257 188,265L218,282L210,239Q207,222 216,208.5Q225,195 242,192Q259,189 272,198.5Q285,208 288,225L308,335L440,411L440,258L355,186Q342,175 340.5,159Q339,143 350,130Q361,117 377,115.5Q393,114 406,125L440,154L440,120Q440,103 451.5,91.5Q463,80 480,80Q497,80 508.5,91.5Q520,103 520,120L520,154L554,125Q567,114 583,115.5Q599,117 610,130Q621,143 619.5,159Q618,175 605,186L520,258L520,411L652,335L672,225Q675,208 688,198.5Q701,189 718,192Q735,195 744,208.5Q753,222 750,239L742,282L772,265Q786,257 802,261.5Q818,266 826,280Q834,294 830,310.5Q826,327 812,335L782,352L824,367Q840,372 847,387Q854,402 848,418Q843,434 828,440.5Q813,447 797,442L692,404L560,480L692,556L797,518Q813,513 827.5,519.5Q842,526 848,542Q854,558 847,573.5Q840,589 824,594L782,608L812,625Q826,633 830,649.5Q834,666 826,680Q818,694 802,698.5Q786,703 772,695L742,678L750,721Q753,738 744,751.5Q735,765 718,768Q701,771 688,761.5Q675,752 672,735L652,625L520,549L520,702L605,774Q618,785 619.5,801Q621,817 610,830Q599,843 583,844.5Q567,846 554,835L520,806L520,840Q520,857 508.5,868.5Q497,880 480,880Q463,880 451.5,868.5Q440,857 440,840L440,806L406,835Q393,846 377,844.5Q361,843 350,830Q339,817 340.5,801Q342,785 355,774L440,702L440,549Z\"/>\n    \n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/sort.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:autoMirrored=\"true\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M160,720Q143,720 131.5,708.5Q120,697 120,680Q120,663 131.5,651.5Q143,640 160,640L320,640Q337,640 348.5,651.5Q360,663 360,680Q360,697 348.5,708.5Q337,720 320,720L160,720ZM160,520Q143,520 131.5,508.5Q120,497 120,480Q120,463 131.5,451.5Q143,440 160,440L560,440Q577,440 588.5,451.5Q600,463 600,480Q600,497 588.5,508.5Q577,520 560,520L160,520ZM160,320Q143,320 131.5,308.5Q120,297 120,280Q120,263 131.5,251.5Q143,240 160,240L800,240Q817,240 828.5,251.5Q840,263 840,280Q840,297 828.5,308.5Q817,320 800,320L160,320Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/sort_by_alpha.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M196,584L173,654Q169,665 159,672.5Q149,680 137,680Q117,680 104.5,663.5Q92,647 100,627L220,306Q225,294 235,287Q245,280 258,280L288,280Q301,280 311,287Q321,294 326,306L447,629Q454,648 442.5,664Q431,680 411,680Q399,680 389,672.5Q379,665 375,654L350,584L196,584ZM220,516L324,516L276,366L270,366L220,516ZM638,608L804,608Q819,608 829.5,618.5Q840,629 840,644Q840,659 829.5,669.5Q819,680 804,680L572,680Q562,680 555,673Q548,666 548,656L548,618Q548,611 550,604.5Q552,598 557,593L750,352L592,352Q577,352 566.5,341.5Q556,331 556,316Q556,301 566.5,290.5Q577,280 592,280L814,280Q824,280 831,287Q838,294 838,304L838,342Q838,349 836,355.5Q834,362 829,367L638,608ZM384,200Q377,200 374.5,194Q372,188 377,183L466,94Q472,88 480,88Q488,88 494,94L583,183Q588,188 585.5,194Q583,200 576,200L384,200ZM466,866L377,777Q372,772 374.5,766Q377,760 384,760L576,760Q583,760 585.5,766Q588,772 583,777L494,866Q488,872 480,872Q472,872 466,866Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/storage.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M200,800Q167,800 143.5,776.5Q120,753 120,720Q120,687 143.5,663.5Q167,640 200,640L760,640Q793,640 816.5,663.5Q840,687 840,720Q840,753 816.5,776.5Q793,800 760,800L200,800ZM200,320Q167,320 143.5,296.5Q120,273 120,240Q120,207 143.5,183.5Q167,160 200,160L760,160Q793,160 816.5,183.5Q840,207 840,240Q840,273 816.5,296.5Q793,320 760,320L200,320ZM200,560Q167,560 143.5,536.5Q120,513 120,480Q120,447 143.5,423.5Q167,400 200,400L760,400Q793,400 816.5,423.5Q840,447 840,480Q840,513 816.5,536.5Q793,560 760,560L200,560ZM240,280Q257,280 268.5,268.5Q280,257 280,240Q280,223 268.5,211.5Q257,200 240,200Q223,200 211.5,211.5Q200,223 200,240Q200,257 211.5,268.5Q223,280 240,280ZM240,520Q257,520 268.5,508.5Q280,497 280,480Q280,463 268.5,451.5Q257,440 240,440Q223,440 211.5,451.5Q200,463 200,480Q200,497 211.5,508.5Q223,520 240,520ZM240,760Q257,760 268.5,748.5Q280,737 280,720Q280,703 268.5,691.5Q257,680 240,680Q223,680 211.5,691.5Q200,703 200,720Q200,737 211.5,748.5Q223,760 240,760Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/theme_panel.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M480,880Q398,880 325,848.5Q252,817 197.5,762.5Q143,708 111.5,635Q80,562 80,480Q80,397 112.5,324Q145,251 200.5,197Q256,143 330,111.5Q404,80 488,80Q568,80 639,107.5Q710,135 763.5,183.5Q817,232 848.5,298.5Q880,365 880,442Q880,557 810,618.5Q740,680 640,680L566,680Q557,680 553.5,685Q550,690 550,696Q550,708 565,730.5Q580,753 580,782Q580,832 552.5,856Q525,880 480,880ZM303,503Q320,486 320,460Q320,434 303,417Q286,400 260,400Q234,400 217,417Q200,434 200,460Q200,486 217,503Q234,520 260,520Q286,520 303,503ZM423,343Q440,326 440,300Q440,274 423,257Q406,240 380,240Q354,240 337,257Q320,274 320,300Q320,326 337,343Q354,360 380,360Q406,360 423,343ZM623,343Q640,326 640,300Q640,274 623,257Q606,240 580,240Q554,240 537,257Q520,274 520,300Q520,326 537,343Q554,360 580,360Q606,360 623,343ZM743,503Q760,486 760,460Q760,434 743,417Q726,400 700,400Q674,400 657,417Q640,434 640,460Q640,486 657,503Q674,520 700,520Q726,520 743,503Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/thor_animated.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\">\n\n    <aapt:attr name=\"android:drawable\">\n        <vector\n            android:width=\"108dp\"\n            android:height=\"108dp\"\n            android:viewportWidth=\"135.47\"\n            android:viewportHeight=\"135.47\">\n            <group\n                android:name=\"main_group\"\n                android:scaleX=\"0.501875\"\n                android:scaleY=\"0.501875\"\n                android:translateX=\"33.740498\"\n                android:translateY=\"33.740498\">\n                <path\n                    android:fillColor=\"#ffffff\"\n                    android:pathData=\"m13.22,8.35 l1.65,17.89c0,0 0.22,1.45 1.11,2.62 0.89,1.17 19.09,22.98 19.09,22.98l3.34,34c0,0 -0.01,0.03 -0.02,0.04l0.02,0.18c0,0 -0.08,0.5 -0.33,0.04 -0.25,-0.46 -4.36,-33.73 -4.36,-33.73 0,0 -0.15,-0.58 -0.83,-1.34C32.21,50.27 17.1,31.82 17.08,31.8l0.5,11.89 10.7,10.55 -1.39,-1.15 1.39,1.37 -8.76,-7.23 0.42,8.42 11.49,10.39 -2.25,-1.81 2.25,2.03 -9.46,-7.62 0.65,7.63 11.92,12.32 -1.5,-1.33 1.5,1.55 -10.54,-9.33 1.18,7.1c0,0 0.51,1.38 2.2,3.03 1.69,1.65 5.51,5.43 5.51,5.43 0,0 0.63,0.94 1.53,3.86 0.91,2.91 3.09,8.6 3.09,8.6 0,0 0.56,1.39 0.75,4.03 0.19,2.64 0.53,5.9 0.53,5.9 0,0 0.5,3.12 1.39,5.09 0.89,1.98 2.7,5.2 5.48,7.62 2.78,2.42 5.65,4.03 7.85,5.04 2.08,0.95 5.04,1.84 5.36,1.94 -0.55,-0.72 -4.32,-5.87 -5.64,-14.6 -1.19,-7.9 -1.17,-9 -1.13,-9.16 0,-0.02 -0.01,-0.08 -0.01,-0.1 -0.07,-0.07 -6.83,-6.95 -8.38,-10.74 -1.56,-3.81 -1.95,-5.15 -2.06,-7.93 -0.11,-2.78 -0.25,-37.78 -0.25,-37.78 0,0 0,-0.01 0,-0.01 -0,-0 -0,-0.21 -0,-0.21 0,0 0.01,-0.11 0.02,-0.2 0,-0.64 -0.15,-1.95 -1.32,-3.53C38.46,40.65 13.27,8.41 13.22,8.35ZM122.25,8.35c-0.05,0.06 -25.24,32.3 -26.88,34.5 -1.17,1.57 -1.33,2.89 -1.32,3.53 0.01,0.09 0.02,0.2 0.02,0.2 0,0 -0,0.21 -0,0.21 0,0 0,0.01 0,0.01 0,0 -0.14,35 -0.25,37.78 -0.11,2.78 -0.5,4.12 -2.06,7.93 -1.55,3.79 -8.31,10.68 -8.38,10.74 0,0.02 -0.01,0.08 -0.01,0.1 0.04,0.17 0.06,1.26 -1.13,9.16 -1.32,8.73 -5.08,13.87 -5.64,14.6 0.32,-0.1 3.28,-0.99 5.36,-1.94 2.2,-1 5.06,-2.62 7.85,-5.04 2.78,-2.42 4.59,-5.65 5.48,-7.62 0.89,-1.98 1.39,-5.09 1.39,-5.09 0,0 0.33,-3.26 0.53,-5.9 0.19,-2.64 0.75,-4.03 0.75,-4.03 0,0 2.18,-5.69 3.09,-8.6 0.91,-2.91 1.53,-3.86 1.53,-3.86 0,0 3.82,-3.78 5.51,-5.43 1.69,-1.65 2.2,-3.03 2.2,-3.03l1.18,-7.1 -10.54,9.33 1.5,-1.55 -1.5,1.33 11.92,-12.32 0.65,-7.63 -9.46,7.62 2.25,-2.03 -2.25,1.81 11.49,-10.39 0.42,-8.42 -8.76,7.23 1.39,-1.37 -1.39,1.15 10.7,-10.55 0.5,-11.89c-0.02,0.02 -15.13,18.47 -15.81,19.23 -0.68,0.76 -0.83,1.34 -0.83,1.34 0,0 -4.11,33.27 -4.36,33.73 -0.25,0.46 -0.33,-0.04 -0.33,-0.04l0.02,-0.18c-0,-0.01 -0.02,-0.04 -0.02,-0.04L100.4,51.84c0,0 18.2,-21.81 19.09,-22.98 0.89,-1.17 1.11,-2.62 1.11,-2.62zM67.73,23.76c-0.21,0.04 -0.75,0.17 -1.44,1.06 -0.85,1.08 -2.69,3.27 -4.62,4.82 -1.93,1.55 -4.25,1.97 -4.39,2.14 -0.14,0.17 -2.72,1 -6.86,2.62 -0.18,0.08 -1.17,0.52 -2.3,1.04 -3.44,1.74 -6.47,4.07 -7.21,4.66 -0.89,0.71 -0.59,1.3 -0.59,1.3 0,0 0.57,0.81 2.16,2.38 1.59,1.57 1.28,2.44 1.28,2.44 0,0 -0.02,27.66 -0.02,28.65 0,0.98 0.59,0.85 2.03,0.89 0.87,0.02 1.96,0.14 3.28,0.31 0.18,0.03 0.29,0.04 0.5,0.08 0.76,0.11 1.44,0.2 2.5,0.38 1.91,0.33 3.81,1.46 5.35,2.54 0.59,0.37 1.16,0.78 1.7,1.24 0.29,0.23 0.62,0.49 0.83,0.64 1.3,0.98 2.01,2.16 2.44,2.77 0.43,0.61 1.34,2.77 2.05,3.32 0.71,0.55 1.96,1.16 3.25,1.26 0.03,-0 0.05,0 0.08,0 0.03,0 0.05,-0 0.08,-0 1.29,-0.1 2.55,-0.71 3.25,-1.26 0.71,-0.55 1.61,-2.71 2.05,-3.32 0.43,-0.61 1.14,-1.79 2.44,-2.77 0.2,-0.15 0.53,-0.41 0.83,-0.64 0.54,-0.46 1.1,-0.87 1.7,-1.24 1.54,-1.08 3.44,-2.22 5.35,-2.54 1.06,-0.18 1.74,-0.27 2.5,-0.38 0.21,-0.04 0.32,-0.05 0.5,-0.08 1.32,-0.18 2.41,-0.29 3.28,-0.31 1.44,-0.04 2.03,0.1 2.03,-0.89 0,-0.98 -0.02,-28.65 -0.02,-28.65 0,0 -0.32,-0.87 1.28,-2.44 1.59,-1.57 2.16,-2.38 2.16,-2.38 0,0 0.3,-0.59 -0.59,-1.3 -0.73,-0.59 -3.77,-2.92 -7.2,-4.66 -1.13,-0.52 -2.12,-0.97 -2.3,-1.04 -4.14,-1.63 -6.73,-2.45 -6.86,-2.62 -0.14,-0.18 -2.46,-0.59 -4.39,-2.14 -1.93,-1.55 -3.78,-3.74 -4.62,-4.82 -0.69,-0.89 -1.23,-1.02 -1.44,-1.06zM52.88,48.44c0.18,0 1.15,0.06 2.39,0.18 0.35,0.03 0.68,0.07 1.02,0.11 0.39,0.05 0.66,0.06 1.11,0.12 2.48,0.35 4.99,1.5 6.03,2.02 1.04,0.52 3.07,1.98 3.92,2.69 0.15,0.12 0.27,0.19 0.39,0.23 0.11,-0.04 0.24,-0.11 0.39,-0.23 0.85,-0.71 2.89,-2.17 3.92,-2.69 1.04,-0.52 3.55,-1.66 6.03,-2.02 0.44,-0.06 0.71,-0.07 1.11,-0.12 0.33,-0.04 0.67,-0.08 1.02,-0.11 1.24,-0.11 2.21,-0.17 2.39,-0.18 0.28,-0.01 2.93,0.1 3.55,0.15 0.62,0.05 1,0.16 1,0.16 0,0 -0.47,0.02 -0.84,0.04 -0.21,0.02 -0.37,0.03 -1.73,0.21 -1.77,0.24 -4.69,0.71 -8.01,1.99 -3.32,1.28 -5.82,4.17 -7.28,6.19 -1.47,2.02 -1.4,2.14 -1.4,2.14 0,0 -0.15,-0.3 -0.16,-0.31 -0.01,0.02 -0.16,0.31 -0.16,0.31 0,0 0.07,-0.12 -1.4,-2.14 -1.47,-2.02 -3.96,-4.91 -7.28,-6.19 -3.32,-1.28 -6.25,-1.75 -8.01,-1.99 -1.35,-0.18 -1.52,-0.19 -1.73,-0.21 -0.37,-0.02 -0.84,-0.04 -0.84,-0.04 0,0 0.38,-0.11 1,-0.16 0.62,-0.05 3.28,-0.16 3.55,-0.15zM51.59,61.12c0.2,0.01 1.28,0.08 2.65,0.22 0.33,0.03 0.65,0.07 0.97,0.12 0.46,0.06 0.77,0.07 1.29,0.15 2.7,0.43 5.43,1.79 6.56,2.42 1.13,0.63 3.34,2.38 4.27,3.22 0.16,0.14 0.29,0.22 0.41,0.27 0.12,-0.05 0.25,-0.13 0.41,-0.27 0.93,-0.85 3.14,-2.6 4.27,-3.22 1.13,-0.63 3.86,-1.99 6.56,-2.42 0.52,-0.08 0.84,-0.1 1.29,-0.15 0.32,-0.05 0.64,-0.09 0.97,-0.12 1.37,-0.14 2.45,-0.21 2.65,-0.22 0.3,-0.01 3.19,0.12 3.87,0.18 0.68,0.06 1.09,0.19 1.09,0.19 0,0 -0.5,0.03 -0.87,0.04 -0.22,0.03 -0.41,0.04 -1.89,0.26 -1.92,0.28 -5.1,0.85 -8.72,2.39 -3.62,1.53 -6.32,5 -7.92,7.42 -1.59,2.42 -1.52,2.57 -1.52,2.57 0,0 -0.18,-0.38 -0.19,-0.41 -0.01,0.02 -0.19,0.41 -0.19,0.41 0,0 0.07,-0.14 -1.52,-2.57 -1.59,-2.42 -4.3,-5.89 -7.92,-7.42 -3.62,-1.53 -6.79,-2.1 -8.72,-2.39 -1.48,-0.22 -1.67,-0.23 -1.89,-0.26 -0.37,-0.02 -0.87,-0.04 -0.87,-0.04 0,0 0.41,-0.13 1.09,-0.19 0.68,-0.06 3.56,-0.19 3.87,-0.18z\"\n                    android:strokeWidth=\"0\"\n                    android:strokeAlpha=\"0\"\n                    android:strokeColor=\"#ffffff\" />\n            </group>\n        </vector>\n    </aapt:attr>\n\n    <target android:name=\"main_group\">\n\n        <aapt:attr name=\"android:animation\">\n            <set>\n                <objectAnimator\n                    android:duration=\"1000\"\n                    android:interpolator=\"@android:anim/overshoot_interpolator\"\n                    android:propertyName=\"scaleX\"\n                    android:valueFrom=\"0.3\"\n                    android:valueTo=\"0.5\" />\n                <objectAnimator\n                    android:duration=\"1000\"\n                    android:interpolator=\"@android:anim/overshoot_interpolator\"\n                    android:propertyName=\"scaleY\"\n                    android:valueFrom=\"0.3\"\n                    android:valueTo=\"0.5\" />\n            </set>\n        </aapt:attr>\n\n    </target>\n\n</animated-vector>"
  },
  {
    "path": "app/src/main/res/drawable/thor_drawn_foreground.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"135.47\"\n    android:viewportHeight=\"135.47\">\n    <group\n        android:scaleX=\"0.501875\"\n        android:scaleY=\"0.501875\"\n        android:translateX=\"33.740498\"\n        android:translateY=\"33.740498\">\n        <path\n            android:fillColor=\"#ffffff\"\n            android:pathData=\"m13.22,8.35 l1.65,17.89c0,0 0.22,1.45 1.11,2.62 0.89,1.17 19.09,22.98 19.09,22.98l3.34,34c0,0 -0.01,0.03 -0.02,0.04l0.02,0.18c0,0 -0.08,0.5 -0.33,0.04 -0.25,-0.46 -4.36,-33.73 -4.36,-33.73 0,0 -0.15,-0.58 -0.83,-1.34C32.21,50.27 17.1,31.82 17.08,31.8l0.5,11.89 10.7,10.55 -1.39,-1.15 1.39,1.37 -8.76,-7.23 0.42,8.42 11.49,10.39 -2.25,-1.81 2.25,2.03 -9.46,-7.62 0.65,7.63 11.92,12.32 -1.5,-1.33 1.5,1.55 -10.54,-9.33 1.18,7.1c0,0 0.51,1.38 2.2,3.03 1.69,1.65 5.51,5.43 5.51,5.43 0,0 0.63,0.94 1.53,3.86 0.91,2.91 3.09,8.6 3.09,8.6 0,0 0.56,1.39 0.75,4.03 0.19,2.64 0.53,5.9 0.53,5.9 0,0 0.5,3.12 1.39,5.09 0.89,1.98 2.7,5.2 5.48,7.62 2.78,2.42 5.65,4.03 7.85,5.04 2.08,0.95 5.04,1.84 5.36,1.94 -0.55,-0.72 -4.32,-5.87 -5.64,-14.6 -1.19,-7.9 -1.17,-9 -1.13,-9.16 0,-0.02 -0.01,-0.08 -0.01,-0.1 -0.07,-0.07 -6.83,-6.95 -8.38,-10.74 -1.56,-3.81 -1.95,-5.15 -2.06,-7.93 -0.11,-2.78 -0.25,-37.78 -0.25,-37.78 0,0 0,-0.01 0,-0.01 -0,-0 -0,-0.21 -0,-0.21 0,0 0.01,-0.11 0.02,-0.2 0,-0.64 -0.15,-1.95 -1.32,-3.53C38.46,40.65 13.27,8.41 13.22,8.35ZM122.25,8.35c-0.05,0.06 -25.24,32.3 -26.88,34.5 -1.17,1.57 -1.33,2.89 -1.32,3.53 0.01,0.09 0.02,0.2 0.02,0.2 0,0 -0,0.21 -0,0.21 0,0 0,0.01 0,0.01 0,0 -0.14,35 -0.25,37.78 -0.11,2.78 -0.5,4.12 -2.06,7.93 -1.55,3.79 -8.31,10.68 -8.38,10.74 0,0.02 -0.01,0.08 -0.01,0.1 0.04,0.17 0.06,1.26 -1.13,9.16 -1.32,8.73 -5.08,13.87 -5.64,14.6 0.32,-0.1 3.28,-0.99 5.36,-1.94 2.2,-1 5.06,-2.62 7.85,-5.04 2.78,-2.42 4.59,-5.65 5.48,-7.62 0.89,-1.98 1.39,-5.09 1.39,-5.09 0,0 0.33,-3.26 0.53,-5.9 0.19,-2.64 0.75,-4.03 0.75,-4.03 0,0 2.18,-5.69 3.09,-8.6 0.91,-2.91 1.53,-3.86 1.53,-3.86 0,0 3.82,-3.78 5.51,-5.43 1.69,-1.65 2.2,-3.03 2.2,-3.03l1.18,-7.1 -10.54,9.33 1.5,-1.55 -1.5,1.33 11.92,-12.32 0.65,-7.63 -9.46,7.62 2.25,-2.03 -2.25,1.81 11.49,-10.39 0.42,-8.42 -8.76,7.23 1.39,-1.37 -1.39,1.15 10.7,-10.55 0.5,-11.89c-0.02,0.02 -15.13,18.47 -15.81,19.23 -0.68,0.76 -0.83,1.34 -0.83,1.34 0,0 -4.11,33.27 -4.36,33.73 -0.25,0.46 -0.33,-0.04 -0.33,-0.04l0.02,-0.18c-0,-0.01 -0.02,-0.04 -0.02,-0.04L100.4,51.84c0,0 18.2,-21.81 19.09,-22.98 0.89,-1.17 1.11,-2.62 1.11,-2.62zM67.73,23.76c-0.21,0.04 -0.75,0.17 -1.44,1.06 -0.85,1.08 -2.69,3.27 -4.62,4.82 -1.93,1.55 -4.25,1.97 -4.39,2.14 -0.14,0.17 -2.72,1 -6.86,2.62 -0.18,0.08 -1.17,0.52 -2.3,1.04 -3.44,1.74 -6.47,4.07 -7.21,4.66 -0.89,0.71 -0.59,1.3 -0.59,1.3 0,0 0.57,0.81 2.16,2.38 1.59,1.57 1.28,2.44 1.28,2.44 0,0 -0.02,27.66 -0.02,28.65 0,0.98 0.59,0.85 2.03,0.89 0.87,0.02 1.96,0.14 3.28,0.31 0.18,0.03 0.29,0.04 0.5,0.08 0.76,0.11 1.44,0.2 2.5,0.38 1.91,0.33 3.81,1.46 5.35,2.54 0.59,0.37 1.16,0.78 1.7,1.24 0.29,0.23 0.62,0.49 0.83,0.64 1.3,0.98 2.01,2.16 2.44,2.77 0.43,0.61 1.34,2.77 2.05,3.32 0.71,0.55 1.96,1.16 3.25,1.26 0.03,-0 0.05,0 0.08,0 0.03,0 0.05,-0 0.08,-0 1.29,-0.1 2.55,-0.71 3.25,-1.26 0.71,-0.55 1.61,-2.71 2.05,-3.32 0.43,-0.61 1.14,-1.79 2.44,-2.77 0.2,-0.15 0.53,-0.41 0.83,-0.64 0.54,-0.46 1.1,-0.87 1.7,-1.24 1.54,-1.08 3.44,-2.22 5.35,-2.54 1.06,-0.18 1.74,-0.27 2.5,-0.38 0.21,-0.04 0.32,-0.05 0.5,-0.08 1.32,-0.18 2.41,-0.29 3.28,-0.31 1.44,-0.04 2.03,0.1 2.03,-0.89 0,-0.98 -0.02,-28.65 -0.02,-28.65 0,0 -0.32,-0.87 1.28,-2.44 1.59,-1.57 2.16,-2.38 2.16,-2.38 0,0 0.3,-0.59 -0.59,-1.3 -0.73,-0.59 -3.77,-2.92 -7.2,-4.66 -1.13,-0.52 -2.12,-0.97 -2.3,-1.04 -4.14,-1.63 -6.73,-2.45 -6.86,-2.62 -0.14,-0.18 -2.46,-0.59 -4.39,-2.14 -1.93,-1.55 -3.78,-3.74 -4.62,-4.82 -0.69,-0.89 -1.23,-1.02 -1.44,-1.06zM52.88,48.44c0.18,0 1.15,0.06 2.39,0.18 0.35,0.03 0.68,0.07 1.02,0.11 0.39,0.05 0.66,0.06 1.11,0.12 2.48,0.35 4.99,1.5 6.03,2.02 1.04,0.52 3.07,1.98 3.92,2.69 0.15,0.12 0.27,0.19 0.39,0.23 0.11,-0.04 0.24,-0.11 0.39,-0.23 0.85,-0.71 2.89,-2.17 3.92,-2.69 1.04,-0.52 3.55,-1.66 6.03,-2.02 0.44,-0.06 0.71,-0.07 1.11,-0.12 0.33,-0.04 0.67,-0.08 1.02,-0.11 1.24,-0.11 2.21,-0.17 2.39,-0.18 0.28,-0.01 2.93,0.1 3.55,0.15 0.62,0.05 1,0.16 1,0.16 0,0 -0.47,0.02 -0.84,0.04 -0.21,0.02 -0.37,0.03 -1.73,0.21 -1.77,0.24 -4.69,0.71 -8.01,1.99 -3.32,1.28 -5.82,4.17 -7.28,6.19 -1.47,2.02 -1.4,2.14 -1.4,2.14 0,0 -0.15,-0.3 -0.16,-0.31 -0.01,0.02 -0.16,0.31 -0.16,0.31 0,0 0.07,-0.12 -1.4,-2.14 -1.47,-2.02 -3.96,-4.91 -7.28,-6.19 -3.32,-1.28 -6.25,-1.75 -8.01,-1.99 -1.35,-0.18 -1.52,-0.19 -1.73,-0.21 -0.37,-0.02 -0.84,-0.04 -0.84,-0.04 0,0 0.38,-0.11 1,-0.16 0.62,-0.05 3.28,-0.16 3.55,-0.15zM51.59,61.12c0.2,0.01 1.28,0.08 2.65,0.22 0.33,0.03 0.65,0.07 0.97,0.12 0.46,0.06 0.77,0.07 1.29,0.15 2.7,0.43 5.43,1.79 6.56,2.42 1.13,0.63 3.34,2.38 4.27,3.22 0.16,0.14 0.29,0.22 0.41,0.27 0.12,-0.05 0.25,-0.13 0.41,-0.27 0.93,-0.85 3.14,-2.6 4.27,-3.22 1.13,-0.63 3.86,-1.99 6.56,-2.42 0.52,-0.08 0.84,-0.1 1.29,-0.15 0.32,-0.05 0.64,-0.09 0.97,-0.12 1.37,-0.14 2.45,-0.21 2.65,-0.22 0.3,-0.01 3.19,0.12 3.87,0.18 0.68,0.06 1.09,0.19 1.09,0.19 0,0 -0.5,0.03 -0.87,0.04 -0.22,0.03 -0.41,0.04 -1.89,0.26 -1.92,0.28 -5.1,0.85 -8.72,2.39 -3.62,1.53 -6.32,5 -7.92,7.42 -1.59,2.42 -1.52,2.57 -1.52,2.57 0,0 -0.18,-0.38 -0.19,-0.41 -0.01,0.02 -0.19,0.41 -0.19,0.41 0,0 0.07,-0.14 -1.52,-2.57 -1.59,-2.42 -4.3,-5.89 -7.92,-7.42 -3.62,-1.53 -6.79,-2.1 -8.72,-2.39 -1.48,-0.22 -1.67,-0.23 -1.89,-0.26 -0.37,-0.02 -0.87,-0.04 -0.87,-0.04 0,0 0.41,-0.13 1.09,-0.19 0.68,-0.06 3.56,-0.19 3.87,-0.18z\"\n            android:strokeWidth=\"0\"\n            android:strokeAlpha=\"0\"\n            android:strokeColor=\"#ffffff\" />\n    </group>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/thor_icon_foreground.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"135.47\"\n    android:viewportHeight=\"135.47\">\n    <group\n        android:scaleX=\"0.501875\"\n        android:scaleY=\"0.501875\"\n        android:translateX=\"33.740498\"\n        android:translateY=\"33.740498\">\n        <path\n            android:fillColor=\"#ffffff\"\n            android:pathData=\"m13.22,8.35 l1.65,17.89c0,0 0.22,1.45 1.11,2.62 0.89,1.17 19.09,22.98 19.09,22.98l3.34,34c0,0 -0.01,0.03 -0.02,0.04l0.02,0.18c0,0 -0.08,0.5 -0.33,0.04 -0.25,-0.46 -4.36,-33.73 -4.36,-33.73 0,0 -0.15,-0.58 -0.83,-1.34C32.21,50.27 17.1,31.82 17.08,31.8l0.5,11.89 10.7,10.55 -1.39,-1.15 1.39,1.37 -8.76,-7.23 0.42,8.42 11.49,10.39 -2.25,-1.81 2.25,2.03 -9.46,-7.62 0.65,7.63 11.92,12.32 -1.5,-1.33 1.5,1.55 -10.54,-9.33 1.18,7.1c0,0 0.51,1.38 2.2,3.03 1.69,1.65 5.51,5.43 5.51,5.43 0,0 0.63,0.94 1.53,3.86 0.91,2.91 3.09,8.6 3.09,8.6 0,0 0.56,1.39 0.75,4.03 0.19,2.64 0.53,5.9 0.53,5.9 0,0 0.5,3.12 1.39,5.09 0.89,1.98 2.7,5.2 5.48,7.62 2.78,2.42 5.65,4.03 7.85,5.04 2.08,0.95 5.04,1.84 5.36,1.94 -0.55,-0.72 -4.32,-5.87 -5.64,-14.6 -1.19,-7.9 -1.17,-9 -1.13,-9.16 0,-0.02 -0.01,-0.08 -0.01,-0.1 -0.07,-0.07 -6.83,-6.95 -8.38,-10.74 -1.56,-3.81 -1.95,-5.15 -2.06,-7.93 -0.11,-2.78 -0.25,-37.78 -0.25,-37.78 0,0 0,-0.01 0,-0.01 -0,-0 -0,-0.21 -0,-0.21 0,0 0.01,-0.11 0.02,-0.2 0,-0.64 -0.15,-1.95 -1.32,-3.53C38.46,40.65 13.27,8.41 13.22,8.35ZM122.25,8.35c-0.05,0.06 -25.24,32.3 -26.88,34.5 -1.17,1.57 -1.33,2.89 -1.32,3.53 0.01,0.09 0.02,0.2 0.02,0.2 0,0 -0,0.21 -0,0.21 0,0 0,0.01 0,0.01 0,0 -0.14,35 -0.25,37.78 -0.11,2.78 -0.5,4.12 -2.06,7.93 -1.55,3.79 -8.31,10.68 -8.38,10.74 0,0.02 -0.01,0.08 -0.01,0.1 0.04,0.17 0.06,1.26 -1.13,9.16 -1.32,8.73 -5.08,13.87 -5.64,14.6 0.32,-0.1 3.28,-0.99 5.36,-1.94 2.2,-1 5.06,-2.62 7.85,-5.04 2.78,-2.42 4.59,-5.65 5.48,-7.62 0.89,-1.98 1.39,-5.09 1.39,-5.09 0,0 0.33,-3.26 0.53,-5.9 0.19,-2.64 0.75,-4.03 0.75,-4.03 0,0 2.18,-5.69 3.09,-8.6 0.91,-2.91 1.53,-3.86 1.53,-3.86 0,0 3.82,-3.78 5.51,-5.43 1.69,-1.65 2.2,-3.03 2.2,-3.03l1.18,-7.1 -10.54,9.33 1.5,-1.55 -1.5,1.33 11.92,-12.32 0.65,-7.63 -9.46,7.62 2.25,-2.03 -2.25,1.81 11.49,-10.39 0.42,-8.42 -8.76,7.23 1.39,-1.37 -1.39,1.15 10.7,-10.55 0.5,-11.89c-0.02,0.02 -15.13,18.47 -15.81,19.23 -0.68,0.76 -0.83,1.34 -0.83,1.34 0,0 -4.11,33.27 -4.36,33.73 -0.25,0.46 -0.33,-0.04 -0.33,-0.04l0.02,-0.18c-0,-0.01 -0.02,-0.04 -0.02,-0.04L100.4,51.84c0,0 18.2,-21.81 19.09,-22.98 0.89,-1.17 1.11,-2.62 1.11,-2.62zM67.73,23.76c-0.21,0.04 -0.75,0.17 -1.44,1.06 -0.85,1.08 -2.69,3.27 -4.62,4.82 -1.93,1.55 -4.25,1.97 -4.39,2.14 -0.14,0.17 -2.72,1 -6.86,2.62 -0.18,0.08 -1.17,0.52 -2.3,1.04 -3.44,1.74 -6.47,4.07 -7.21,4.66 -0.89,0.71 -0.59,1.3 -0.59,1.3 0,0 0.57,0.81 2.16,2.38 1.59,1.57 1.28,2.44 1.28,2.44 0,0 -0.02,27.66 -0.02,28.65 0,0.98 0.59,0.85 2.03,0.89 0.87,0.02 1.96,0.14 3.28,0.31 0.18,0.03 0.29,0.04 0.5,0.08 0.76,0.11 1.44,0.2 2.5,0.38 1.91,0.33 3.81,1.46 5.35,2.54 0.59,0.37 1.16,0.78 1.7,1.24 0.29,0.23 0.62,0.49 0.83,0.64 1.3,0.98 2.01,2.16 2.44,2.77 0.43,0.61 1.34,2.77 2.05,3.32 0.71,0.55 1.96,1.16 3.25,1.26 0.03,-0 0.05,0 0.08,0 0.03,0 0.05,-0 0.08,-0 1.29,-0.1 2.55,-0.71 3.25,-1.26 0.71,-0.55 1.61,-2.71 2.05,-3.32 0.43,-0.61 1.14,-1.79 2.44,-2.77 0.2,-0.15 0.53,-0.41 0.83,-0.64 0.54,-0.46 1.1,-0.87 1.7,-1.24 1.54,-1.08 3.44,-2.22 5.35,-2.54 1.06,-0.18 1.74,-0.27 2.5,-0.38 0.21,-0.04 0.32,-0.05 0.5,-0.08 1.32,-0.18 2.41,-0.29 3.28,-0.31 1.44,-0.04 2.03,0.1 2.03,-0.89 0,-0.98 -0.02,-28.65 -0.02,-28.65 0,0 -0.32,-0.87 1.28,-2.44 1.59,-1.57 2.16,-2.38 2.16,-2.38 0,0 0.3,-0.59 -0.59,-1.3 -0.73,-0.59 -3.77,-2.92 -7.2,-4.66 -1.13,-0.52 -2.12,-0.97 -2.3,-1.04 -4.14,-1.63 -6.73,-2.45 -6.86,-2.62 -0.14,-0.18 -2.46,-0.59 -4.39,-2.14 -1.93,-1.55 -3.78,-3.74 -4.62,-4.82 -0.69,-0.89 -1.23,-1.02 -1.44,-1.06zM52.88,48.44c0.18,0 1.15,0.06 2.39,0.18 0.35,0.03 0.68,0.07 1.02,0.11 0.39,0.05 0.66,0.06 1.11,0.12 2.48,0.35 4.99,1.5 6.03,2.02 1.04,0.52 3.07,1.98 3.92,2.69 0.15,0.12 0.27,0.19 0.39,0.23 0.11,-0.04 0.24,-0.11 0.39,-0.23 0.85,-0.71 2.89,-2.17 3.92,-2.69 1.04,-0.52 3.55,-1.66 6.03,-2.02 0.44,-0.06 0.71,-0.07 1.11,-0.12 0.33,-0.04 0.67,-0.08 1.02,-0.11 1.24,-0.11 2.21,-0.17 2.39,-0.18 0.28,-0.01 2.93,0.1 3.55,0.15 0.62,0.05 1,0.16 1,0.16 0,0 -0.47,0.02 -0.84,0.04 -0.21,0.02 -0.37,0.03 -1.73,0.21 -1.77,0.24 -4.69,0.71 -8.01,1.99 -3.32,1.28 -5.82,4.17 -7.28,6.19 -1.47,2.02 -1.4,2.14 -1.4,2.14 0,0 -0.15,-0.3 -0.16,-0.31 -0.01,0.02 -0.16,0.31 -0.16,0.31 0,0 0.07,-0.12 -1.4,-2.14 -1.47,-2.02 -3.96,-4.91 -7.28,-6.19 -3.32,-1.28 -6.25,-1.75 -8.01,-1.99 -1.35,-0.18 -1.52,-0.19 -1.73,-0.21 -0.37,-0.02 -0.84,-0.04 -0.84,-0.04 0,0 0.38,-0.11 1,-0.16 0.62,-0.05 3.28,-0.16 3.55,-0.15zM51.59,61.12c0.2,0.01 1.28,0.08 2.65,0.22 0.33,0.03 0.65,0.07 0.97,0.12 0.46,0.06 0.77,0.07 1.29,0.15 2.7,0.43 5.43,1.79 6.56,2.42 1.13,0.63 3.34,2.38 4.27,3.22 0.16,0.14 0.29,0.22 0.41,0.27 0.12,-0.05 0.25,-0.13 0.41,-0.27 0.93,-0.85 3.14,-2.6 4.27,-3.22 1.13,-0.63 3.86,-1.99 6.56,-2.42 0.52,-0.08 0.84,-0.1 1.29,-0.15 0.32,-0.05 0.64,-0.09 0.97,-0.12 1.37,-0.14 2.45,-0.21 2.65,-0.22 0.3,-0.01 3.19,0.12 3.87,0.18 0.68,0.06 1.09,0.19 1.09,0.19 0,0 -0.5,0.03 -0.87,0.04 -0.22,0.03 -0.41,0.04 -1.89,0.26 -1.92,0.28 -5.1,0.85 -8.72,2.39 -3.62,1.53 -6.32,5 -7.92,7.42 -1.59,2.42 -1.52,2.57 -1.52,2.57 0,0 -0.18,-0.38 -0.19,-0.41 -0.01,0.02 -0.19,0.41 -0.19,0.41 0,0 0.07,-0.14 -1.52,-2.57 -1.59,-2.42 -4.3,-5.89 -7.92,-7.42 -3.62,-1.53 -6.79,-2.1 -8.72,-2.39 -1.48,-0.22 -1.67,-0.23 -1.89,-0.26 -0.37,-0.02 -0.87,-0.04 -0.87,-0.04 0,0 0.41,-0.13 1.09,-0.19 0.68,-0.06 3.56,-0.19 3.87,-0.18z\"\n            android:strokeWidth=\"0\"\n            android:strokeAlpha=\"0\"\n            android:strokeColor=\"#ffffff\" />\n    </group>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/thor_mono.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"200dp\"\n    android:height=\"200dp\"\n    android:viewportWidth=\"135.47\"\n    android:viewportHeight=\"135.47\">\n    <path\n        android:fillColor=\"#FFFFFF\"\n        android:pathData=\"m45.93,97.32c0.05,0.14 0.1,0.27 0.21,0.5 0.16,0.19 0.27,0.29 0.38,0.39 0,0 0.01,-0.03 0.01,0.05 0.26,0.41 0.52,0.74 0.78,1.08 0,0 0.01,-0.01 0.01,0.06 0.12,0.17 0.24,0.28 0.35,0.38 0.04,0.09 0.09,0.18 0.18,0.36 0.17,0.19 0.28,0.29 0.4,0.39 0.41,0.48 0.82,0.97 1.3,1.54 0.64,0.63 1.21,1.17 1.78,1.71 0,0 0.01,-0.02 0.02,0.04 0.13,0.15 0.25,0.24 0.38,0.33 0,0 0.01,-0.01 0.01,0.05 0.07,0.15 0.14,0.23 0.21,0.32 -0,0.09 -0,0.17 -0,0.41 0.07,0.73 0.14,1.31 0.2,1.89 0,0.15 0.01,0.3 0.02,0.59 0.12,0.67 0.23,1.19 0.35,1.72 -0,0.09 -0,0.17 0,0.39 0.07,0.34 0.13,0.55 0.19,0.76 0.02,0.15 0.03,0.3 0.06,0.58 0.18,0.73 0.35,1.32 0.52,1.92 0.05,0.28 0.11,0.56 0.19,0.97 0.15,0.47 0.27,0.81 0.4,1.14 0.06,0.28 0.12,0.56 0.2,0.97 0.15,0.47 0.28,0.8 0.4,1.13 0.03,0.16 0.07,0.32 0.13,0.6 0.08,0.2 0.14,0.3 0.19,0.39 0.01,0.09 0.02,0.17 0.04,0.37 0.07,0.21 0.12,0.3 0.18,0.39 0.06,0.22 0.12,0.43 0.2,0.78 0.15,0.4 0.27,0.67 0.39,0.95 0.06,0.22 0.12,0.44 0.2,0.78 0.19,0.44 0.35,0.75 0.51,1.06 0.01,0.12 0.02,0.24 0.05,0.49 0.19,0.43 0.35,0.74 0.52,1.06 0.01,0.12 0.02,0.24 0.05,0.48 0.19,0.43 0.36,0.74 0.53,1.05 0.01,0.12 0.02,0.24 0.06,0.47 0.08,0.2 0.14,0.29 0.2,0.38 0.1,0.29 0.2,0.57 0.34,0.97 0.09,0.2 0.15,0.29 0.21,0.38 -0.09,0.12 -0.18,0.24 -0.36,0.33 -0.39,-0.11 -0.69,-0.18 -0.98,-0.26 -0.21,-0.08 -0.43,-0.16 -0.74,-0.3 -0.4,-0.14 -0.69,-0.23 -0.98,-0.32 -0.15,-0.07 -0.3,-0.14 -0.55,-0.26 -0.33,-0.13 -0.56,-0.22 -0.79,-0.3 -0.06,-0.03 -0.13,-0.06 -0.27,-0.13 -0.22,-0.11 -0.36,-0.17 -0.49,-0.23 -0.4,-0.21 -0.8,-0.42 -1.3,-0.7 -0.4,-0.21 -0.7,-0.35 -1.01,-0.5 -0.12,-0.08 -0.24,-0.16 -0.45,-0.3 -0.23,-0.13 -0.37,-0.2 -0.51,-0.28 -0.11,-0.09 -0.23,-0.17 -0.43,-0.32 -0.3,-0.2 -0.51,-0.33 -0.72,-0.47 0,0 -0.04,-0.04 -0.1,-0.09 -0.26,-0.2 -0.46,-0.34 -0.67,-0.49 -0.14,-0.14 -0.28,-0.27 -0.5,-0.48 -0.33,-0.27 -0.59,-0.46 -0.85,-0.65 -0.65,-0.69 -1.31,-1.39 -2.03,-2.17 -0.19,-0.2 -0.31,-0.31 -0.43,-0.42 -0.05,-0.08 -0.11,-0.16 -0.2,-0.33 -0.16,-0.2 -0.27,-0.31 -0.38,-0.41 -0.04,-0.09 -0.09,-0.18 -0.18,-0.36 -0.16,-0.2 -0.28,-0.31 -0.39,-0.42 -0.24,-0.4 -0.47,-0.8 -0.76,-1.3 -0.24,-0.38 -0.42,-0.66 -0.6,-0.94 -0.11,-0.22 -0.22,-0.44 -0.37,-0.78 -0.21,-0.43 -0.39,-0.74 -0.56,-1.05 0,-0.06 0.01,-0.13 -0.02,-0.31 -0.36,-1.09 -0.69,-2.05 -1.01,-3.1 -0.06,-0.41 -0.12,-0.75 -0.18,-1.18 -0.05,-0.54 -0.12,-1 -0.17,-1.53 -0.01,-0.22 -0.03,-0.36 -0.03,-0.58 -0.05,-0.47 -0.12,-0.86 -0.18,-1.34 -0.03,-0.42 -0.08,-0.75 -0.09,-1.15 -0.01,-0.52 -0.06,-0.96 -0.1,-1.4 -0.18,-2.15 -0.5,-4.27 -1.52,-6.28 -0.08,-0.28 -0.16,-0.49 -0.23,-0.77 -0.16,-0.44 -0.33,-0.81 -0.48,-1.23 -0.01,-0.11 -0.02,-0.17 -0.04,-0.31 -0.52,-1.56 -1.04,-3.04 -1.57,-4.59 -0.08,-0.28 -0.16,-0.49 -0.23,-0.77 -0.15,-0.38 -0.31,-0.69 -0.46,-1.07 -0.03,-0.24 -0.06,-0.41 -0.09,-0.65 -0.11,-0.22 -0.22,-0.37 -0.35,-0.57 -0.05,-0.1 -0.09,-0.13 -0.17,-0.21 -0.72,-0.83 -1.43,-1.6 -2.17,-2.42 -0.32,-0.36 -0.62,-0.66 -0.93,-1.02 -0.7,-0.8 -1.4,-1.54 -2.1,-2.32 -0.05,-0.09 -0.09,-0.12 -0.17,-0.19 -0.28,-0.33 -0.54,-0.6 -0.82,-0.92 -0.12,-0.15 -0.23,-0.25 -0.35,-0.41 -0.32,-0.36 -0.63,-0.66 -0.95,-1.01 -0.08,-0.12 -0.15,-0.19 -0.23,-0.32 -0.07,-0.21 -0.15,-0.35 -0.2,-0.58 -0.03,-0.48 -0.07,-0.88 -0.09,-1.34 -0.01,-0.34 -0.04,-0.62 -0.07,-0.96 -0.01,-0.15 -0.03,-0.23 -0.04,-0.4 -0.05,-0.73 -0.11,-1.38 -0.18,-2.03 -0,-0.15 -0,-0.29 0.02,-0.56 0.23,-0.05 0.48,-0.02 0.64,0.11 0.63,0.5 1.23,1.03 1.88,1.58 0.03,0.03 0.1,0.05 0.12,0.11 0.38,0.36 0.75,0.66 1.14,1.02 0.66,0.64 1.29,1.23 1.95,1.87 0.65,0.6 1.27,1.15 1.92,1.75 0.47,0.45 0.92,0.83 1.38,1.25 0.03,0.04 0.04,0.05 0.06,0.06 -0.03,-0.03 -0.05,-0.05 -0.07,-0.15 -0.36,-0.47 -0.72,-0.88 -1.12,-1.34 -0.65,-0.68 -1.26,-1.31 -1.92,-1.98 -0.61,-0.64 -1.17,-1.24 -1.76,-1.88 -0.14,-0.15 -0.25,-0.24 -0.38,-0.39 -0.16,-0.14 -0.3,-0.23 -0.45,-0.39 -0.11,-0.2 -0.22,-0.34 -0.34,-0.54 -1.15,-1.26 -2.28,-2.45 -3.41,-3.65 -0.35,-0.41 -0.7,-0.82 -1.12,-1.32 -0.24,-0.26 -0.42,-0.43 -0.6,-0.6 -0.02,-0.13 -0.04,-0.25 -0.05,-0.53 -0.06,-0.93 -0.13,-1.7 -0.2,-2.48 -0,-0.08 -0,-0.17 0,-0.4 -0.07,-1.05 -0.15,-1.95 -0.23,-2.85 0,-0.15 0.01,-0.31 0.07,-0.54 0.29,-0 0.52,0.07 0.75,0.15 0.07,0.07 0.15,0.13 0.3,0.28 1.4,1.13 2.73,2.18 4.08,3.29 0.61,0.53 1.19,1 1.8,1.52 0.46,0.39 0.89,0.72 1.34,1.1 0.09,0.04 0.16,0.03 0.23,0.03 -0.09,-0.02 -0.17,-0.04 -0.25,-0.13 -0.44,-0.54 -0.89,-1 -1.38,-1.52 -0.49,-0.41 -0.94,-0.77 -1.42,-1.19 -1.2,-1.11 -2.37,-2.16 -3.54,-3.22 -1.1,-1.06 -2.21,-2.12 -3.37,-3.28 -0.25,-0.24 -0.44,-0.39 -0.63,-0.53 0,0 -0.02,0.02 -0.02,-0.05 -0.09,-0.15 -0.17,-0.23 -0.25,-0.3 -0.01,-0.14 -0.01,-0.27 -0,-0.56 -0.1,-1.84 -0.23,-3.52 -0.35,-5.21 -0,-0.14 -0,-0.28 0.01,-0.57 0.18,-0.55 -0.36,-1.19 0.42,-1.38 0.39,0.32 0.78,0.64 1.25,1.06 0.33,0.29 0.6,0.48 0.86,0.68 0.15,0.12 0.29,0.24 0.5,0.46 0.19,0.17 0.32,0.25 0.45,0.33 0.08,0.07 0.16,0.14 0.31,0.29 1.42,1.25 2.77,2.41 4.12,3.57 0.07,-0.07 0.14,-0.15 0.22,-0.22 -0.87,-1.42 -2.32,-2.29 -3.45,-3.46 -2.18,-2.19 -4.35,-4.38 -6.57,-6.72 -0.04,-0.49 -0.02,-0.85 -0.05,-1.19 -0.07,-0.64 -0.17,-1.28 -0.26,-1.92 -0.03,-2.55 -0.07,-5.1 -0.04,-7.73 0.21,-0.01 0.36,0.07 0.51,0.14 0.05,0.09 0.1,0.18 0.2,0.37 0.29,0.32 0.52,0.55 0.76,0.78 0.11,0.16 0.22,0.31 0.38,0.57 0.29,0.33 0.52,0.56 0.76,0.8 0.05,0.09 0.1,0.18 0.2,0.36 0.16,0.19 0.27,0.3 0.39,0.4 0.11,0.16 0.22,0.31 0.38,0.56 0.29,0.33 0.52,0.56 0.76,0.8 0.05,0.09 0.1,0.18 0.2,0.35 0.16,0.19 0.28,0.29 0.39,0.4 0.11,0.15 0.22,0.31 0.38,0.56 0.29,0.33 0.52,0.56 0.76,0.8 0.06,0.08 0.12,0.16 0.22,0.34 0.15,0.2 0.26,0.3 0.37,0.41 0.11,0.16 0.23,0.31 0.39,0.56 0.28,0.33 0.51,0.57 0.74,0.81 0.05,0.09 0.1,0.18 0.2,0.36 0.22,0.26 0.4,0.42 0.58,0.59 0.05,0.09 0.11,0.18 0.21,0.36 0.28,0.33 0.51,0.56 0.75,0.8 0.11,0.16 0.22,0.31 0.38,0.56 0.25,0.3 0.44,0.5 0.63,0.7 0.09,0.12 0.19,0.23 0.33,0.45 0.16,0.2 0.27,0.3 0.39,0.41 0.11,0.15 0.23,0.31 0.39,0.55 0.29,0.33 0.52,0.57 0.75,0.8 0.05,0.09 0.1,0.18 0.2,0.36 0.16,0.19 0.27,0.3 0.39,0.4 0.11,0.15 0.23,0.31 0.39,0.56 0.28,0.33 0.51,0.57 0.75,0.81 0.05,0.09 0.1,0.18 0.2,0.35 0.16,0.19 0.28,0.29 0.39,0.4 0.11,0.16 0.22,0.31 0.39,0.56 0.29,0.32 0.53,0.56 0.76,0.79 0.07,0.14 0.14,0.27 0.23,0.54 0.09,0.53 0.16,0.92 0.24,1.32 0.01,0.14 0.01,0.28 0.01,0.57 0.18,1.32 0.37,2.49 0.56,3.65 0.01,0.2 0.02,0.4 0.03,0.76 0.18,1.38 0.37,2.62 0.56,3.85 0,0.14 0.01,0.28 0.01,0.57 0.11,0.81 0.22,1.47 0.33,2.13 0.01,0.08 0.01,0.16 0.02,0.39 0.1,0.57 0.2,0.99 0.3,1.42 0.01,0.18 0.01,0.37 -0.01,0.68 0.08,0.66 0.18,1.2 0.28,1.74 0,0.14 0.01,0.28 0.01,0.57 0.07,0.48 0.14,0.82 0.21,1.15 0,0.08 0,0.16 -0,0.39 0.41,3.35 0.82,6.56 1.21,9.84 0.2,1.55 0.44,3.03 0.67,4.5 0.09,-0.01 0.18,-0.03 0.28,-0.04 -0.17,-1.51 -0.34,-3.01 -0.5,-4.6 -0.25,-2.93 -0.5,-5.78 -0.77,-8.63 -0.03,-0.36 -0.15,-0.72 -0.23,-1.08 0,-0.14 0,-0.28 0.01,-0.57 -0.11,-1.13 -0.23,-2.1 -0.35,-3.08 -0,-0.14 -0,-0.28 0,-0.58 -0.07,-0.8 -0.15,-1.45 -0.23,-2.1 -0,-0.14 -0,-0.28 0,-0.58 -0.11,-1.13 -0.23,-2.1 -0.35,-3.08 -0.01,-0.2 -0.01,-0.4 -0.01,-0.76 -0.07,-0.68 -0.15,-1.2 -0.22,-1.72 -0.01,-0.26 -0.01,-0.53 -0.01,-0.95 -0.11,-1.14 -0.23,-2.11 -0.34,-3.09 0,-0.08 0,-0.17 0.01,-0.4 -0.07,-0.73 -0.15,-1.32 -0.23,-1.9 -0.01,-0.26 -0.02,-0.53 -0.02,-0.93 -0.21,-0.38 -0.43,-0.63 -0.65,-0.88 -0.05,-0.09 -0.11,-0.17 -0.21,-0.35 -0.16,-0.19 -0.27,-0.29 -0.39,-0.4 -0.12,-0.15 -0.24,-0.29 -0.4,-0.54 -0.23,-0.31 -0.42,-0.51 -0.62,-0.71 -0.1,-0.11 -0.2,-0.23 -0.34,-0.44 -0.22,-0.27 -0.39,-0.44 -0.57,-0.6 -0.05,-0.09 -0.1,-0.18 -0.2,-0.37 -0.28,-0.33 -0.51,-0.56 -0.75,-0.8 -0.06,-0.08 -0.12,-0.16 -0.22,-0.34 -0.21,-0.27 -0.38,-0.44 -0.56,-0.6 -0.05,-0.09 -0.11,-0.18 -0.21,-0.36 -0.28,-0.33 -0.51,-0.56 -0.75,-0.79 -0.17,-0.22 -0.33,-0.44 -0.55,-0.76 -0.23,-0.27 -0.41,-0.43 -0.58,-0.6 -0.05,-0.09 -0.1,-0.18 -0.2,-0.35 -0.16,-0.19 -0.28,-0.29 -0.39,-0.39 -0.11,-0.15 -0.22,-0.31 -0.39,-0.56 -0.29,-0.33 -0.52,-0.56 -0.75,-0.8 -0.06,-0.08 -0.12,-0.17 -0.21,-0.35 -0.47,-0.59 -0.9,-1.07 -1.34,-1.55 -0.12,-0.15 -0.23,-0.3 -0.39,-0.55 -0.16,-0.2 -0.27,-0.3 -0.39,-0.4 -2.5,-3.03 -4.95,-6.11 -7.51,-9.08 -0.85,-0.98 -1.21,-2.01 -1.33,-3.26 -0.46,-4.96 -0.97,-9.92 -1.45,-14.87 -0.05,-0.47 -0.05,-0.94 -0.08,-1.4 0.08,-0.04 0.16,-0.07 0.23,-0.11 6.34,8.09 12.68,16.19 19.1,24.37 0.18,0.19 0.3,0.29 0.42,0.39 0.36,0.47 0.71,0.94 1.12,1.52 0.24,0.32 0.43,0.53 0.62,0.74 0,0.07 0.03,0.12 0.13,0.25 0.37,0.44 0.68,0.8 0.99,1.16 0.35,0.47 0.7,0.95 1.11,1.51 0.24,0.26 0.42,0.42 0.6,0.58 0,0 0.01,-0.05 0.01,0.04 0.67,0.97 1.31,1.89 2.02,2.74 0.85,1.02 1.37,2.06 1.37,3.5 -0.07,12.37 -0.05,24.75 -0.02,37.12 0,1.13 0.21,2.26 0.33,3.4 0,0.15 0.01,0.29 0.02,0.57 0.08,0.34 0.15,0.55 0.22,0.75 0.03,0.16 0.05,0.33 0.11,0.62 0.4,1.07 0.78,2.01 1.15,2.95 0,0.07 0.01,0.13 0.03,0.31 0.13,0.23 0.23,0.36 0.34,0.49 0.06,0.19 0.12,0.37 0.22,0.66 0.1,0.19 0.16,0.28 0.22,0.37 0.04,0.11 0.07,0.22 0.16,0.41 0.16,0.21 0.26,0.33 0.37,0.46 0.17,0.32 0.34,0.64 0.56,1.07 0.17,0.23 0.28,0.35 0.39,0.47z\"\n        android:strokeWidth=\"0.19\"\n        android:strokeColor=\"#00000000\" />\n    <path\n        android:fillColor=\"#FFFFFF\"\n        android:pathData=\"m80.34,119.46c0.06,-0.15 0.13,-0.31 0.24,-0.56 0.14,-0.46 0.24,-0.81 0.33,-1.17 0.06,-0.16 0.12,-0.31 0.23,-0.57 0.18,-0.65 0.32,-1.19 0.45,-1.74 0.03,-0.08 0.06,-0.17 0.14,-0.35 0.06,-0.2 0.07,-0.31 0.08,-0.42 0.03,-0.08 0.05,-0.17 0.12,-0.35 0.06,-0.21 0.08,-0.31 0.09,-0.42 0.08,-0.27 0.16,-0.55 0.29,-0.94 0.13,-0.6 0.22,-1.08 0.31,-1.56 0.07,-0.27 0.15,-0.55 0.27,-0.94 0.06,-0.34 0.08,-0.57 0.1,-0.79 0.02,-0.08 0.04,-0.17 0.1,-0.36 0.05,-0.27 0.07,-0.44 0.08,-0.6 0.02,-0.08 0.05,-0.17 0.1,-0.36 0.02,-0.21 0.01,-0.31 0.01,-0.41 0.05,-0.27 0.09,-0.54 0.18,-0.94 0.05,-1.5 0.15,-2.82 1.47,-3.66 0.04,-0.03 0.04,-0.11 0.06,-0.17 0,0 -0.02,-0.02 0.04,-0.02 0.16,-0.13 0.26,-0.25 0.35,-0.37 0.06,-0.01 0.11,-0.04 0.21,-0.15 0.16,-0.19 0.25,-0.31 0.35,-0.44 0.29,-0.29 0.59,-0.58 0.97,-0.95 0.24,-0.31 0.4,-0.55 0.56,-0.78 0.05,-0.04 0.1,-0.08 0.22,-0.19 0.23,-0.3 0.39,-0.54 0.55,-0.77 0.07,-0.07 0.13,-0.14 0.26,-0.3 0.14,-0.21 0.23,-0.34 0.31,-0.48 0.07,-0.07 0.14,-0.14 0.27,-0.3 0.21,-0.34 0.36,-0.6 0.51,-0.86 0,0 0.03,-0.04 0.09,-0.1 0.13,-0.2 0.22,-0.34 0.3,-0.48 0,0 0.04,-0.04 0.09,-0.1 0.19,-0.33 0.33,-0.6 0.47,-0.86 0.08,-0.12 0.16,-0.24 0.3,-0.44 0.19,-0.36 0.32,-0.63 0.45,-0.9 0.09,-0.18 0.19,-0.35 0.33,-0.62 0.12,-0.24 0.18,-0.39 0.25,-0.53 0.02,-0.06 0.05,-0.12 0.12,-0.27 0.18,-0.42 0.31,-0.74 0.44,-1.07 0.03,-0.12 0.06,-0.23 0.14,-0.44 0.1,-0.24 0.15,-0.38 0.2,-0.52 0.03,-0.08 0.05,-0.17 0.13,-0.36 0.18,-0.72 0.32,-1.33 0.45,-1.94 0.03,-0.14 0.05,-0.29 0.12,-0.56 0.06,-0.48 0.09,-0.82 0.11,-1.17 0.01,-0.09 0.03,-0.17 0.08,-0.39 0.05,-0.61 0.09,-1.08 0.09,-1.56 0,-12.47 0.01,-24.94 -0.01,-37.4 -0,-0.97 -0.01,-1.9 0.64,-2.7 0.13,-0.14 0.25,-0.28 0.45,-0.49 0.15,-0.21 0.23,-0.34 0.31,-0.47 0,0 0.04,-0.04 0.1,-0.1 0.4,-0.51 0.74,-0.97 1.08,-1.42 0.07,-0.07 0.13,-0.14 0.26,-0.28 0.21,-0.28 0.36,-0.48 0.5,-0.69 0.57,-0.7 1.14,-1.41 1.78,-2.2 0.15,-0.22 0.22,-0.36 0.3,-0.49 0.07,-0.07 0.15,-0.14 0.28,-0.29 0.15,-0.21 0.24,-0.34 0.33,-0.47 0.11,-0.11 0.22,-0.21 0.4,-0.39 0.68,-0.88 1.28,-1.69 1.88,-2.5 0,0 0.01,0.01 0.07,-0.02 0.21,-0.26 0.37,-0.5 0.53,-0.73 0.07,-0.07 0.13,-0.14 0.26,-0.29 0.15,-0.21 0.24,-0.34 0.33,-0.47 0.11,-0.1 0.21,-0.21 0.39,-0.38 0.17,-0.24 0.28,-0.41 0.38,-0.58 0.23,-0.28 0.47,-0.56 0.78,-0.93 0.29,-0.39 0.5,-0.7 0.71,-1 0.07,-0.07 0.14,-0.14 0.28,-0.28 0.16,-0.21 0.24,-0.34 0.33,-0.47 0.25,-0.31 0.51,-0.63 0.83,-1.03 0.53,-0.68 0.98,-1.27 1.44,-1.86 0.63,-0.78 1.26,-1.56 1.98,-2.42 0.16,-0.21 0.24,-0.34 0.32,-0.46 0.26,-0.32 0.52,-0.65 0.86,-1.05 0.27,-0.35 0.46,-0.62 0.65,-0.88 0.18,-0.22 0.37,-0.43 0.64,-0.73 0.3,-0.38 0.51,-0.69 0.72,-0.99 0.07,-0.08 0.14,-0.16 0.28,-0.31 0.15,-0.2 0.23,-0.33 0.3,-0.46 0.07,-0.08 0.14,-0.15 0.28,-0.3 0.15,-0.2 0.23,-0.33 0.31,-0.46 0.07,-0.08 0.13,-0.15 0.27,-0.3 0.15,-0.2 0.23,-0.33 0.31,-0.46 0.07,-0.08 0.13,-0.15 0.28,-0.3 0.22,-0.27 0.36,-0.46 0.5,-0.65 0.37,-0.46 0.74,-0.93 1.19,-1.47 0.17,-0.21 0.24,-0.34 0.32,-0.47 0.07,-0.08 0.14,-0.15 0.28,-0.3 0.15,-0.2 0.23,-0.33 0.31,-0.46 0.38,-0.46 0.75,-0.92 1.21,-1.46 0.16,-0.21 0.24,-0.34 0.32,-0.47 0.13,-0.14 0.25,-0.29 0.45,-0.5 0.15,-0.2 0.23,-0.33 0.3,-0.46 0.07,-0.08 0.13,-0.16 0.28,-0.3 0.15,-0.2 0.23,-0.33 0.3,-0.46 0,0 0.01,0 0.07,0.04 0.12,0.34 0.18,0.65 0.24,0.95 -0.03,0.2 -0.05,0.4 -0.13,0.73 -0.08,0.73 -0.1,1.34 -0.13,1.95 -0.01,0.08 -0.02,0.17 -0.08,0.37 -0.12,1.11 -0.18,2.11 -0.25,3.1 -0.02,0.2 -0.05,0.4 -0.12,0.73 -0.08,0.73 -0.1,1.34 -0.12,1.95 -0.02,0.08 -0.03,0.16 -0.09,0.36 -0.1,0.99 -0.16,1.85 -0.22,2.72 -0.03,0.2 -0.06,0.4 -0.14,0.73 -0.08,0.73 -0.1,1.34 -0.13,1.95 -0.01,0.08 -0.02,0.17 -0.08,0.37 -0.06,0.47 -0.07,0.82 -0.08,1.18 -0.01,0.08 -0.03,0.16 -0.09,0.35 -0.23,0.52 -0.41,0.93 -0.58,1.34 -0.18,0.21 -0.35,0.42 -0.62,0.7 -0.28,0.34 -0.48,0.6 -0.67,0.86 -0.32,0.39 -0.64,0.78 -1.05,1.25 -0.17,0.2 -0.25,0.33 -0.34,0.46 -0.1,0.11 -0.21,0.21 -0.39,0.39 -0.3,0.37 -0.52,0.67 -0.74,0.97 -0.07,0.08 -0.14,0.15 -0.28,0.3 -0.15,0.2 -0.23,0.33 -0.31,0.46 -0.24,0.27 -0.49,0.55 -0.81,0.89 -0.16,0.2 -0.24,0.33 -0.32,0.46 -0.11,0.1 -0.22,0.21 -0.41,0.38 -0.24,0.3 -0.4,0.54 -0.55,0.77 -0.13,0.14 -0.26,0.28 -0.46,0.49 -0.16,0.2 -0.24,0.33 -0.32,0.46 -0.24,0.28 -0.48,0.55 -0.81,0.9 -0.17,0.2 -0.25,0.32 -0.33,0.45 -0.06,0.08 -0.13,0.16 -0.28,0.3 -0.16,0.2 -0.24,0.32 -0.32,0.45 -0.25,0.27 -0.49,0.55 -0.82,0.89 -0.16,0.2 -0.24,0.33 -0.32,0.46 -0.06,0.08 -0.13,0.16 -0.28,0.3 -0.28,0.33 -0.48,0.59 -0.68,0.86 -0.12,0.14 -0.25,0.28 -0.46,0.49 -0.16,0.2 -0.24,0.33 -0.32,0.46 -0.07,0.08 -0.13,0.15 -0.28,0.3 -0.22,0.26 -0.37,0.46 -0.51,0.65 -0.18,0.21 -0.35,0.42 -0.61,0.71 -0.17,0.2 -0.25,0.33 -0.33,0.46 -0.18,0.21 -0.37,0.42 -0.63,0.7 -0.16,0.2 -0.24,0.33 -0.31,0.46 -0.12,0.14 -0.24,0.29 -0.46,0.5 -0.43,0.51 -0.77,0.95 -1.11,1.39 0,0 0.02,0.02 -0.05,0.03 -0.15,0.13 -0.23,0.26 -0.31,0.38 -0.19,0.21 -0.37,0.42 -0.63,0.7 -0.16,0.2 -0.24,0.32 -0.32,0.45 -0.44,0.52 -0.89,1.05 -1.42,1.65 -0.13,0.28 -0.16,0.48 -0.2,0.67 -0.03,0.26 -0.06,0.52 -0.14,0.91 -0.08,0.67 -0.1,1.22 -0.12,1.76 -0.01,0.14 -0.02,0.28 -0.08,0.55 -0.06,0.54 -0.07,0.96 -0.09,1.38 -0.01,0.08 -0.03,0.16 -0.09,0.36 -0.05,0.47 -0.05,0.83 -0.05,1.18 -0.03,0.26 -0.06,0.52 -0.15,0.91 -0.07,0.74 -0.1,1.35 -0.12,1.96 -0.01,0.14 -0.03,0.28 -0.09,0.55 -0.07,0.61 -0.09,1.08 -0.11,1.56 -0.01,0.08 -0.02,0.17 -0.08,0.36 -0.05,0.47 -0.05,0.83 -0.06,1.18 -0.02,0.2 -0.05,0.4 -0.12,0.73 -0.07,0.74 -0.1,1.34 -0.12,1.95 -0.01,0.14 -0.03,0.28 -0.08,0.55 -0.06,0.48 -0.07,0.83 -0.08,1.19 -0.02,0.08 -0.03,0.16 -0.1,0.36 -0.06,0.6 -0.06,1.08 -0.07,1.56 -0.02,0.2 -0.05,0.4 -0.12,0.73 -0.13,0.95 -0.2,1.77 -0.28,2.6 -0.21,2.27 -0.42,4.55 -0.7,6.87 -0.17,1.6 -0.29,3.16 -0.4,4.71 0.11,0.01 0.23,0.02 0.34,0.03 0.13,-1.21 0.26,-2.42 0.39,-3.63 0.03,-0.32 0.04,-0.65 0.07,-1.06 0.02,-0.34 0,-0.59 0.04,-0.85 0.32,-2.08 0.65,-4.16 0.96,-6.24 0.12,-0.81 0.17,-1.63 0.26,-2.45 0.03,-0.14 0.05,-0.28 0.13,-0.54 0.07,-0.47 0.1,-0.83 0.12,-1.19 0.02,-0.14 0.03,-0.28 0.1,-0.54 0.1,-0.67 0.15,-1.22 0.2,-1.77 0.04,-0.26 0.07,-0.52 0.16,-0.91 0.13,-0.93 0.21,-1.74 0.28,-2.54 0.02,-0.08 0.03,-0.16 0.1,-0.35 0.05,-0.34 0.05,-0.57 0.05,-0.81 0.03,-0.2 0.06,-0.4 0.14,-0.72 0.08,-0.48 0.1,-0.83 0.12,-1.19 0.02,-0.14 0.04,-0.28 0.1,-0.54 0.06,-0.35 0.07,-0.58 0.08,-0.81 0.02,-0.08 0.03,-0.16 0.1,-0.36 0.06,-0.41 0.06,-0.7 0.07,-0.99 0.02,-0.08 0.04,-0.16 0.11,-0.36 0.19,-1.36 0.32,-2.62 0.46,-3.87 0.02,-0.14 0.04,-0.28 0.11,-0.54 0.08,-0.53 0.11,-0.95 0.15,-1.37 0.08,-0.13 0.16,-0.27 0.31,-0.47 0.27,-0.34 0.47,-0.6 0.66,-0.87 0.13,-0.14 0.25,-0.29 0.46,-0.5 0.16,-0.2 0.24,-0.33 0.32,-0.45 0.07,-0.08 0.13,-0.15 0.28,-0.3 0.28,-0.33 0.48,-0.59 0.68,-0.86 0.12,-0.14 0.25,-0.28 0.46,-0.49 0.16,-0.2 0.24,-0.33 0.32,-0.46 0.44,-0.53 0.88,-1.05 1.4,-1.65 0.17,-0.2 0.25,-0.33 0.33,-0.46 0.18,-0.21 0.36,-0.42 0.63,-0.7 0.16,-0.2 0.24,-0.33 0.32,-0.46 0.12,-0.14 0.25,-0.28 0.46,-0.49 0.28,-0.33 0.47,-0.6 0.67,-0.86 0.07,-0.08 0.14,-0.15 0.29,-0.3 0.22,-0.26 0.36,-0.46 0.5,-0.65 0.07,-0.07 0.14,-0.15 0.29,-0.29 0.27,-0.34 0.46,-0.6 0.65,-0.87 0.13,-0.14 0.25,-0.28 0.47,-0.5 0.23,-0.26 0.36,-0.46 0.5,-0.65 0.13,-0.14 0.25,-0.29 0.46,-0.5 0.15,-0.2 0.23,-0.33 0.31,-0.46 0.13,-0.14 0.25,-0.28 0.46,-0.49 0.16,-0.2 0.24,-0.33 0.32,-0.46 0.07,-0.08 0.13,-0.16 0.28,-0.3 0.23,-0.26 0.37,-0.45 0.52,-0.64 0.18,-0.21 0.35,-0.42 0.61,-0.71 0.16,-0.2 0.24,-0.33 0.32,-0.46 0.44,-0.52 0.88,-1.05 1.41,-1.64 0.17,-0.21 0.25,-0.33 0.33,-0.46 0.18,-0.21 0.37,-0.42 0.64,-0.7 0.82,-0.34 0.35,0.37 0.56,0.52 -0.02,0.83 -0.03,1.67 -0.09,2.65 -0.06,2.04 -0.07,3.92 -0.08,5.81 0.2,1.32 -0.55,2.16 -1.44,2.99 -1.3,1.21 -2.52,2.5 -3.87,3.83 -0.25,0.25 -0.41,0.44 -0.56,0.62 -0.71,0.7 -1.42,1.41 -2.14,2.11 0.08,0.1 0.16,0.19 0.24,0.29 1.85,-1.46 3.69,-2.93 5.72,-4.53 0,0.47 0.01,0.76 -0,1.05 -0.12,2.1 -0.22,4.2 -0.39,6.29 -0.04,0.45 -0.27,0.99 -0.59,1.31 -1.56,1.57 -3.19,3.08 -4.9,4.65 -1.25,1.15 -2.39,2.26 -3.58,3.38 -0.12,0.03 -0.19,0.05 -0.32,0.1 -0.59,0.5 -1.13,0.99 -1.66,1.47 0.07,0.08 0.15,0.16 0.22,0.25 0.57,-0.5 1.15,-1.01 1.76,-1.54 0.09,-0.06 0.13,-0.11 0.21,-0.2 1.23,-0.99 2.66,-1.68 3.37,-3.14 0.74,-0.46 1.48,-0.91 2.33,-1.43 0.27,-0.11 0.43,-0.16 0.58,-0.21 0,1.19 0,2.31 0,3.44 -0.01,0.14 -0.03,0.28 -0.08,0.54 -0.02,0.34 -0,0.57 0.02,0.8 -0.05,0.46 -0.09,0.92 -0.18,1.5 -0.04,0.22 -0.04,0.34 -0.03,0.45 -0.93,1.03 -1.86,2.06 -2.88,3.16 -0.18,0.2 -0.27,0.32 -0.35,0.44 -0.24,0.27 -0.48,0.53 -0.81,0.88 -1.41,1.53 -2.74,2.98 -4.12,4.45 -0.77,0.73 -1.48,1.43 -2.19,2.14 0.11,0.1 0.21,0.21 0.32,0.31 0.74,-0.73 1.48,-1.46 2.26,-2.24 1.49,-1.36 2.94,-2.67 4.39,-3.98 0.29,-0.24 0.57,-0.48 0.97,-0.77 0.2,-0.04 0.28,-0.03 0.37,-0.02 -0.01,0.15 -0.02,0.29 -0.06,0.58 -0.11,1.5 -0.18,2.87 -0.28,4.31 -0.03,0.28 -0.02,0.5 -0.07,0.74 -0.09,0.2 -0.13,0.36 -0.22,0.55 -0.09,0.11 -0.14,0.2 -0.24,0.31 -0.34,0.35 -0.61,0.67 -0.94,1.02 -0.14,0.14 -0.23,0.26 -0.37,0.4 -0.93,1.02 -1.79,2.02 -2.65,3.03 0.01,0.01 -0,-0.01 -0.06,0 -0.33,0.33 -0.6,0.65 -0.91,1.01 -0.28,0.32 -0.52,0.6 -0.82,0.9 -0.24,0.26 -0.42,0.51 -0.65,0.77 -0.44,0.46 -0.82,0.91 -1.25,1.38 -0.12,0.2 -0.19,0.38 -0.29,0.62 -0.09,0.28 -0.15,0.5 -0.26,0.75 -0.13,0.33 -0.21,0.62 -0.33,0.98 -0.09,0.28 -0.14,0.5 -0.25,0.76 -0.16,0.39 -0.26,0.75 -0.38,1.15 -0.02,0.11 -0.01,0.17 -0.07,0.28 -0.38,1.01 -0.7,1.97 -1.04,3 -0.04,0.18 -0.04,0.3 -0.1,0.45 -0.11,0.17 -0.16,0.32 -0.24,0.52 -0.04,0.15 -0.04,0.23 -0.09,0.36 -0.06,0.14 -0.08,0.25 -0.12,0.41 -0.04,0.18 -0.06,0.3 -0.13,0.47 -0.51,1.59 -1.13,3.12 -1.38,4.7 -0.64,4.02 -0.51,8.17 -1.97,12.05 -0.02,0.06 -0.04,0.12 -0.11,0.26 -0.11,0.22 -0.17,0.37 -0.23,0.51 -0.1,0.24 -0.2,0.47 -0.35,0.81 -0.13,0.24 -0.21,0.39 -0.28,0.53 -0.07,0.12 -0.15,0.24 -0.28,0.45 -0.13,0.23 -0.2,0.37 -0.28,0.51 -0.26,0.38 -0.52,0.77 -0.85,1.24 -0.15,0.22 -0.23,0.35 -0.31,0.49 -0.07,0.07 -0.14,0.15 -0.27,0.3 -0.14,0.21 -0.22,0.34 -0.3,0.48 -0.37,0.41 -0.73,0.83 -1.19,1.31 -0.56,0.57 -1.04,1.07 -1.51,1.56 -0.43,0.34 -0.86,0.69 -1.37,1.1 -0.23,0.19 -0.38,0.32 -0.52,0.45 -0.42,0.29 -0.85,0.59 -1.38,0.93 -0.23,0.15 -0.34,0.26 -0.46,0.37 -0.14,0.05 -0.28,0.1 -0.52,0.19 -0.6,0.35 -1.09,0.66 -1.59,0.96 -0.19,0.07 -0.38,0.14 -0.69,0.25 -0.23,0.13 -0.35,0.23 -0.47,0.33 -0.07,0.01 -0.13,0.02 -0.3,0.05 -0.23,0.12 -0.36,0.22 -0.48,0.32 -0.24,0.07 -0.48,0.14 -0.86,0.23 -0.98,0.35 -1.82,0.66 -2.67,0.98 -0.09,-0.01 -0.17,-0.02 -0.31,-0.09 -0.01,-0.27 0.04,-0.49 0.08,-0.7 0.07,-0.15 0.15,-0.3 0.27,-0.55 0.33,-0.84 0.6,-1.59 0.87,-2.33 0.07,-0.16 0.13,-0.31 0.25,-0.57 0.15,-0.45 0.26,-0.81 0.36,-1.16 0.02,-0.06 0.04,-0.12 0.11,-0.27 0.38,-1.06 0.7,-2.02 1.03,-2.99m28.16,-64.68c0.35,-0.32 0.7,-0.64 1.2,-1.08 -0.97,-0.01 -1.83,0.67 -2.42,1.93 0.37,-0.26 0.74,-0.51 1.22,-0.85z\"\n        android:strokeWidth=\"0.19\"\n        android:strokeColor=\"#00000000\" />\n    <path\n        android:fillColor=\"#FFFFFF\"\n        android:pathData=\"m85.73,77.09c-0.34,0.05 -0.69,0.1 -1.17,0.16 -0.53,0.15 -0.93,0.28 -1.32,0.41 -0.16,0.03 -0.32,0.07 -0.61,0.13 -0.5,0.21 -0.87,0.38 -1.24,0.56 -0.07,0 -0.13,0.01 -0.31,0.04 -0.91,0.41 -1.8,0.67 -2.48,1.2 -1.46,1.13 -2.81,2.39 -4.21,3.6 -0.26,0.32 -0.52,0.64 -0.85,1.05 -0.52,1.06 -0.97,2.03 -1.42,2.99 -0.19,0.21 -0.39,0.41 -0.67,0.7 -0.39,0.31 -0.7,0.55 -1,0.79 -0.25,0.08 -0.5,0.16 -0.87,0.26 -0.27,0.08 -0.41,0.15 -0.55,0.22 -0.15,0.01 -0.31,0.01 -0.61,0.02 -0.55,0.02 -0.94,0.04 -1.34,0.06 -0.15,-0.04 -0.29,-0.08 -0.56,-0.16 -1.04,-0.54 -1.96,-1.04 -2.88,-1.54 0,0 -0.01,0.02 -0.01,-0.05 -0.18,-0.31 -0.36,-0.54 -0.54,-0.78 0,0 0.01,-0.06 -0.01,-0.16 -0.4,-0.84 -0.77,-1.58 -1.15,-2.32 -0.04,-0.09 -0.08,-0.18 -0.18,-0.35 -0.17,-0.2 -0.28,-0.31 -0.4,-0.42 -0.05,-0.09 -0.09,-0.18 -0.21,-0.35 -1.29,-1.63 -2.78,-2.85 -4.59,-3.65 0,0 -0.03,0.02 -0.03,-0.05 -0.26,-0.2 -0.51,-0.33 -0.77,-0.47 -0.06,-0.03 -0.12,-0.05 -0.27,-0.12 -0.22,-0.11 -0.36,-0.17 -0.5,-0.23 -0.24,-0.1 -0.48,-0.21 -0.83,-0.36 -0.5,-0.18 -0.89,-0.31 -1.28,-0.45 -0.16,-0.06 -0.31,-0.13 -0.57,-0.24 -0.78,-0.18 -1.45,-0.3 -2.12,-0.43 -0.14,-0.04 -0.29,-0.08 -0.56,-0.16 -0.54,-0.07 -0.95,-0.09 -1.36,-0.12 -0.09,-0.02 -0.17,-0.03 -0.38,-0.09 -1.18,-0.07 -2.24,-0.17 -3.28,-0.11 -0.85,0.05 -1,-0.26 -1,-1.03 0.02,-8.44 -0.1,-16.89 0.08,-25.33 0.06,-2.94 -0.35,-5.39 -2.87,-7.17 -0.71,-0.5 -0.87,-1.13 -0.18,-1.81 0.29,-0.22 0.58,-0.44 0.96,-0.73 0.2,-0.18 0.31,-0.29 0.43,-0.41 0.09,-0.04 0.17,-0.09 0.34,-0.19 0.2,-0.16 0.31,-0.28 0.43,-0.39 0.39,-0.24 0.78,-0.49 1.28,-0.78 0.23,-0.15 0.34,-0.26 0.46,-0.37 0,0 0.06,0.01 0.14,-0.02 0.33,-0.2 0.57,-0.38 0.81,-0.56 0,0 0.06,0.01 0.14,-0.01 0.26,-0.14 0.43,-0.26 0.6,-0.38 0.26,-0.13 0.53,-0.26 0.9,-0.42 0.24,-0.13 0.36,-0.23 0.48,-0.34 0.19,-0.07 0.38,-0.13 0.68,-0.23 0.23,-0.14 0.36,-0.24 0.48,-0.34 0.06,-0 0.13,-0 0.3,-0.03 1.21,-0.54 2.32,-1.05 3.42,-1.57 0.16,-0.04 0.32,-0.09 0.6,-0.16 0.39,-0.16 0.66,-0.29 0.94,-0.41 0.16,-0.04 0.32,-0.08 0.6,-0.14 0.5,-0.21 0.87,-0.38 1.24,-0.56 0.06,0 0.13,0 0.31,-0.02 0.8,-0.33 1.48,-0.64 2.16,-0.95 0.14,-0.05 0.27,-0.1 0.51,-0.18 0.19,-0.1 0.28,-0.16 0.37,-0.22 0.49,-0.23 0.97,-0.47 1.55,-0.76 0.21,-0.16 0.32,-0.27 0.44,-0.37 0,0 0.07,-0.01 0.16,-0.03 0.2,-0.13 0.32,-0.24 0.43,-0.35 0,0 0.06,0 0.15,-0.03 0.58,-0.46 1.07,-0.89 1.57,-1.32 0.62,-0.71 1.24,-1.42 1.92,-2.22 0.15,-0.22 0.23,-0.36 0.31,-0.49 0,0 0.04,-0.04 0.1,-0.1 0.2,-0.26 0.34,-0.46 0.48,-0.67 0.07,-0.01 0.12,-0.05 0.23,-0.16 0.23,-0.17 0.38,-0.29 0.54,-0.41 0.09,-0.01 0.17,-0.02 0.39,-0.04 0.34,-0.01 0.55,-0.01 0.76,-0.01 0.28,0.19 0.56,0.38 0.91,0.66 0.37,0.46 0.68,0.82 0.98,1.19 0,0 -0,0.06 0.02,0.15 0.15,0.22 0.28,0.36 0.41,0.5 0.65,0.71 1.31,1.43 2.05,2.21 0.59,0.41 1.08,0.75 1.58,1.08 0.12,0.08 0.24,0.16 0.45,0.3 0.36,0.2 0.62,0.34 0.89,0.48 0.25,0.14 0.5,0.28 0.84,0.48 1,0.44 1.9,0.82 2.81,1.2 0.12,0.04 0.23,0.07 0.45,0.16 0.49,0.19 0.88,0.33 1.28,0.46 0.15,0.06 0.31,0.12 0.56,0.23 0.39,0.14 0.68,0.23 0.98,0.32 0.15,0.07 0.3,0.14 0.55,0.27 0.39,0.15 0.69,0.25 0.98,0.34 0.06,0.02 0.12,0.04 0.26,0.11 0.35,0.17 0.62,0.29 0.89,0.41 0.11,0.04 0.23,0.08 0.44,0.17 1.13,0.56 2.17,1.08 3.21,1.59 0.06,0.03 0.12,0.06 0.26,0.13 0.23,0.12 0.37,0.19 0.52,0.26 0.12,0.08 0.24,0.16 0.46,0.3 0.36,0.2 0.62,0.34 0.88,0.49 0,0 0.05,0.04 0.11,0.09 0.39,0.25 0.72,0.45 1.05,0.64 0.19,0.14 0.38,0.28 0.65,0.49 0.22,0.15 0.36,0.23 0.49,0.31 0.07,0.07 0.14,0.14 0.29,0.27 0.21,0.15 0.34,0.23 0.47,0.32 0.07,0.07 0.14,0.14 0.28,0.27 0.27,0.22 0.47,0.37 0.67,0.52 0,0 0.01,-0.02 0.02,0.05 0.58,0.59 0.52,1.08 -0.04,1.58 -0.21,0.18 -0.36,0.43 -0.53,0.65 -0.37,0.36 -0.73,0.72 -1.18,1.16 -1.02,0.75 -1.3,1.63 -1.3,2.8 0.04,9.37 0.01,18.75 0.03,28.12 0,0.75 -0.19,1 -0.96,0.98 -1.17,-0.02 -2.34,0.1 -3.51,0.16 -0.21,0 -0.41,0.01 -0.77,0.02 -0.41,0.07 -0.68,0.14 -0.95,0.21M47.93,64.19c-0.36,0.03 -0.73,0.06 -1.09,0.09 0.01,0.1 0.02,0.2 0.03,0.3 0.4,0 0.81,0 1.33,0.09 0.48,0.02 0.96,0.03 1.6,0.07 0.84,0.13 1.67,0.25 2.57,0.48 0.11,0.01 0.23,0.01 0.49,0.07 6.79,1.59 12.11,5.04 14.88,11.97 0.19,-0.42 0.27,-0.58 0.34,-0.75 1.13,-2.55 2.64,-4.83 4.8,-6.61 3.82,-3.14 8.32,-4.63 13.35,-5.16 0.68,-0.07 1.37,-0.13 2.05,-0.2 -0.01,-0.11 -0.01,-0.21 -0.02,-0.32 -0.73,0 -1.46,0 -2.37,-0.01 -0.69,0.03 -1.38,0.05 -2.21,0.02 -0.28,0.06 -0.57,0.12 -1,0.17 -0.4,0.05 -0.8,0.1 -1.28,0.06 -0.19,0.07 -0.37,0.14 -0.72,0.24 -0.44,0.06 -0.89,0.08 -1.33,0.19 -4.21,1 -8.03,2.74 -11.01,6 -0.47,0.52 -0.77,0.43 -1.23,-0.03 -0.84,-0.83 -1.69,-1.67 -2.64,-2.35 -3.02,-2.16 -6.41,-3.41 -10.12,-4 -0.11,0.01 -0.22,0.02 -0.49,0.01 -0.46,-0.04 -0.93,-0.08 -1.49,-0.23 -0.42,-0.01 -0.84,-0.02 -1.42,-0.07 -0.44,-0 -0.89,-0 -1.5,-0.04 -0.46,0 -0.91,0.01 -1.54,-0.01m14.58,-5.32c2.24,1.65 4,3.7 5.23,6.35 0.16,-0.27 0.24,-0.43 0.34,-0.57 1.15,-1.52 2.13,-3.21 3.48,-4.52 3.54,-3.41 8.1,-4.54 12.81,-5.19 0.58,-0.08 1.16,-0.15 1.74,-0.23 -1.65,-0.13 -3.27,-0.21 -4.89,-0.12 -4.81,0.27 -9.21,1.69 -12.88,4.96 -0.43,0.39 -0.71,0.36 -1.21,-0 -1.35,-1.01 -2.71,-2.04 -4.2,-2.81 -3.12,-1.6 -6.53,-2.15 -10,-2.29 -1.18,-0.05 -2.36,-0.01 -3.54,-0.01 -0.01,0.1 -0.02,0.21 -0.03,0.31 4.66,0.35 9.09,1.4 13.13,4.1z\"\n        android:strokeWidth=\"0.19\"\n        android:strokeColor=\"#00000000\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/unfreeze.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M480,932L346,800L160,800L160,614L28,480L160,346L160,160L346,160L480,28L614,160L800,160L800,346L932,480L800,614L800,800L614,800L480,932ZM480,680Q563,680 621.5,621.5Q680,563 680,480Q680,397 621.5,338.5Q563,280 480,280Q397,280 338.5,338.5Q280,397 280,480Q280,563 338.5,621.5Q397,680 480,680ZM480,820L580,720L720,720L720,580L820,480L720,380L720,240L580,240L480,140L380,240L240,240L240,380L140,480L240,580L240,720L380,720L480,820Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/view_stream.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M200,760Q167,760 143.5,736.5Q120,713 120,680L120,600Q120,567 143.5,543.5Q167,520 200,520L760,520Q793,520 816.5,543.5Q840,567 840,600L840,680Q840,713 816.5,736.5Q793,760 760,760L200,760ZM200,440Q167,440 143.5,416.5Q120,393 120,360L120,280Q120,247 143.5,223.5Q167,200 200,200L760,200Q793,200 816.5,223.5Q840,247 840,280L840,360Q840,393 816.5,416.5Q793,440 760,440L200,440Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/warning.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M109,840Q98,840 89,834.5Q80,829 75,820Q70,811 69.5,800.5Q69,790 75,780L445,140Q451,130 460.5,125Q470,120 480,120Q490,120 499.5,125Q509,130 515,140L885,780Q891,790 890.5,800.5Q890,811 885,820Q880,829 871,834.5Q862,840 851,840L109,840ZM480,720Q497,720 508.5,708.5Q520,697 520,680Q520,663 508.5,651.5Q497,640 480,640Q463,640 451.5,651.5Q440,663 440,680Q440,697 451.5,708.5Q463,720 480,720ZM480,600Q497,600 508.5,588.5Q520,577 520,560L520,440Q520,423 508.5,411.5Q497,400 480,400Q463,400 451.5,411.5Q440,423 440,440L440,560Q440,577 451.5,588.5Q463,600 480,600Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/thor_drawn.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/thor_drawn_background\" />\n    <foreground android:drawable=\"@drawable/thor_drawn_foreground\" />\n    <monochrome android:drawable=\"@drawable/thor_drawn_foreground\" />\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/thor_drawn_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/thor_drawn_background\" />\n    <foreground android:drawable=\"@drawable/thor_drawn_foreground\" />\n    <monochrome android:drawable=\"@drawable/thor_drawn_foreground\" />\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/raw/rearrange.json",
    "content": "{\n  \"nm\": \"BOXS\",\n  \"ddd\": 0,\n  \"h\": 1080,\n  \"w\": 1080,\n  \"meta\": {\n    \"g\": \"@lottiefiles/toolkit-js 0.33.2\"\n  },\n  \"layers\": [\n    {\n      \"ty\": 3,\n      \"nm\": \"Adjustment Layer 7\",\n      \"sr\": 1,\n      \"st\": 0,\n      \"op\": 301,\n      \"ip\": 0,\n      \"hd\": false,\n      \"ddd\": 0,\n      \"bm\": 0,\n      \"hasMask\": false,\n      \"ao\": 0,\n      \"ks\": {\n        \"a\": {\n          \"a\": 0,\n          \"k\": [\n            540,\n            540,\n            0\n          ],\n          \"ix\": 1\n        },\n        \"s\": {\n          \"a\": 0,\n          \"k\": [\n            100,\n            100,\n            100\n          ],\n          \"ix\": 6\n        },\n        \"sk\": {\n          \"a\": 0,\n          \"k\": 0\n        },\n        \"p\": {\n          \"a\": 0,\n          \"k\": [\n            540,\n            540,\n            0\n          ],\n          \"ix\": 2\n        },\n        \"r\": {\n          \"a\": 0,\n          \"k\": 0,\n          \"ix\": 10\n        },\n        \"sa\": {\n          \"a\": 0,\n          \"k\": 0\n        },\n        \"o\": {\n          \"a\": 0,\n          \"k\": 100,\n          \"ix\": 11\n        }\n      },\n      \"ef\": [\n        {\n          \"ty\": 20,\n          \"mn\": \"ADBE Tint\",\n          \"nm\": \"Tint\",\n          \"ix\": 1,\n          \"en\": 1,\n          \"ef\": [\n            {\n              \"ty\": 2,\n              \"mn\": \"ADBE Tint-0001\",\n              \"nm\": \"Map Black To\",\n              \"ix\": 1,\n              \"v\": {\n                \"a\": 0,\n                \"k\": [\n                  0,\n                  0,\n                  0,\n                  0\n                ],\n                \"ix\": 1\n              }\n            },\n            {\n              \"ty\": 2,\n              \"mn\": \"ADBE Tint-0002\",\n              \"nm\": \"Map White To\",\n              \"ix\": 2,\n              \"v\": {\n                \"a\": 0,\n                \"k\": [\n                  1,\n                  1,\n                  1,\n                  0\n                ],\n                \"ix\": 2\n              }\n            },\n            {\n              \"ty\": 0,\n              \"mn\": \"ADBE Tint-0003\",\n              \"nm\": \"Amount to Tint\",\n              \"ix\": 3,\n              \"v\": {\n                \"a\": 0,\n                \"k\": 100,\n                \"ix\": 3\n              }\n            },\n            {\n              \"ty\": 6,\n              \"mn\": \"ADBE Tint-0004\",\n              \"nm\": \"\",\n              \"ix\": 4,\n              \"v\": 0\n            }\n          ]\n        }\n      ],\n      \"ind\": 1\n    },\n    {\n      \"ty\": 0,\n      \"nm\": \"Boxs Animation\",\n      \"sr\": 1,\n      \"st\": 0,\n      \"op\": 301,\n      \"ip\": 0,\n      \"hd\": false,\n      \"ddd\": 0,\n      \"bm\": 0,\n      \"hasMask\": false,\n      \"ao\": 0,\n      \"ks\": {\n        \"a\": {\n          \"a\": 0,\n          \"k\": [\n            540,\n            540,\n            0\n          ],\n          \"ix\": 1\n        },\n        \"s\": {\n          \"a\": 0,\n          \"k\": [\n            42,\n            42,\n            100\n          ],\n          \"ix\": 6\n        },\n        \"sk\": {\n          \"a\": 0,\n          \"k\": 0\n        },\n        \"p\": {\n          \"a\": 0,\n          \"k\": [\n            540,\n            540,\n            0\n          ],\n          \"ix\": 2\n        },\n        \"r\": {\n          \"a\": 0,\n          \"k\": 225.1,\n          \"ix\": 10\n        },\n        \"sa\": {\n          \"a\": 0,\n          \"k\": 0\n        },\n        \"o\": {\n          \"a\": 0,\n          \"k\": 100,\n          \"ix\": 11\n        }\n      },\n      \"ef\": [],\n      \"w\": 1080,\n      \"h\": 1080,\n      \"refId\": \"comp_0\",\n      \"tm\": {\n        \"a\": 1,\n        \"k\": [\n          {\n            \"o\": {\n              \"x\": 0.167,\n              \"y\": 0.167\n            },\n            \"i\": {\n              \"x\": 0.833,\n              \"y\": 0.833\n            },\n            \"s\": [\n              0\n            ],\n            \"t\": 0\n          },\n          {\n            \"o\": {\n              \"x\": 0.167,\n              \"y\": 0.167\n            },\n            \"i\": {\n              \"x\": 0.833,\n              \"y\": 0.833\n            },\n            \"s\": [\n              0.217\n            ],\n            \"t\": 13\n          },\n          {\n            \"o\": {\n              \"x\": 0.167,\n              \"y\": 0.167\n            },\n            \"i\": {\n              \"x\": 0.833,\n              \"y\": 0.833\n            },\n            \"s\": [\n              1.15\n            ],\n            \"t\": 109\n          },\n          {\n            \"s\": [\n              5.017\n            ],\n            \"t\": 301\n          }\n        ],\n        \"ix\": 2\n      },\n      \"ind\": 2\n    }\n  ],\n  \"v\": \"5.7.3\",\n  \"fr\": 60,\n  \"op\": 110,\n  \"ip\": 13,\n  \"assets\": [\n    {\n      \"nm\": \"\",\n      \"id\": \"comp_0\",\n      \"layers\": [\n        {\n          \"ty\": 4,\n          \"nm\": \"7\",\n          \"sr\": 1,\n          \"st\": 0,\n          \"op\": 301,\n          \"ip\": 0,\n          \"hd\": false,\n          \"ddd\": 0,\n          \"bm\": 0,\n          \"hasMask\": false,\n          \"ao\": 0,\n          \"ks\": {\n            \"a\": {\n              \"a\": 0,\n              \"k\": [\n                0,\n                0,\n                0\n              ],\n              \"ix\": 1\n            },\n            \"s\": {\n              \"a\": 0,\n              \"k\": [\n                100,\n                100,\n                100\n              ],\n              \"ix\": 6\n            },\n            \"sk\": {\n              \"a\": 0,\n              \"k\": 0\n            },\n            \"p\": {\n              \"a\": 1,\n              \"k\": [\n                {\n                  \"o\": {\n                    \"x\": 0.714,\n                    \"y\": 0\n                  },\n                  \"i\": {\n                    \"x\": 0.218,\n                    \"y\": 1\n                  },\n                  \"s\": [\n                    239.25,\n                    840.25,\n                    0\n                  ],\n                  \"t\": 41.236,\n                  \"ti\": [\n                    0,\n                    50.042,\n                    0\n                  ],\n                  \"to\": [\n                    0,\n                    -50.042,\n                    0\n                  ]\n                },\n                {\n                  \"s\": [\n                    239.25,\n                    540,\n                    0\n                  ],\n                  \"t\": 75.599609375\n                }\n              ],\n              \"ix\": 2\n            },\n            \"r\": {\n              \"a\": 0,\n              \"k\": 0,\n              \"ix\": 10\n            },\n            \"sa\": {\n              \"a\": 0,\n              \"k\": 0\n            },\n            \"o\": {\n              \"a\": 0,\n              \"k\": 100,\n              \"ix\": 11\n            }\n          },\n          \"ef\": [],\n          \"shapes\": [\n            {\n              \"ty\": \"gr\",\n              \"bm\": 0,\n              \"hd\": false,\n              \"mn\": \"ADBE Vector Group\",\n              \"nm\": \"Rectangle 1\",\n              \"ix\": 1,\n              \"cix\": 2,\n              \"np\": 3,\n              \"it\": [\n                {\n                  \"ty\": \"rc\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Shape - Rect\",\n                  \"nm\": \"Rectangle Path 1\",\n                  \"d\": 1,\n                  \"p\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 3\n                  },\n                  \"r\": {\n                    \"a\": 0,\n                    \"k\": 150,\n                    \"ix\": 4\n                  },\n                  \"s\": {\n                    \"a\": 0,\n                    \"k\": [\n                      1080,\n                      1080\n                    ],\n                    \"ix\": 2\n                  }\n                },\n                {\n                  \"ty\": \"st\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Graphic - Stroke\",\n                  \"nm\": \"Stroke 1\",\n                  \"lc\": 1,\n                  \"lj\": 1,\n                  \"ml\": 4,\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 4\n                  },\n                  \"w\": {\n                    \"a\": 0,\n                    \"k\": 74,\n                    \"ix\": 5\n                  },\n                  \"c\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0,\n                      0\n                    ],\n                    \"ix\": 3\n                  }\n                },\n                {\n                  \"ty\": \"fl\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Graphic - Fill\",\n                  \"nm\": \"Fill 1\",\n                  \"c\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0,\n                      0\n                    ],\n                    \"ix\": 4\n                  },\n                  \"r\": 1,\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 5\n                  }\n                },\n                {\n                  \"ty\": \"tr\",\n                  \"a\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 1\n                  },\n                  \"s\": {\n                    \"a\": 0,\n                    \"k\": [\n                      18.493,\n                      18.493\n                    ],\n                    \"ix\": 3\n                  },\n                  \"sk\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 4\n                  },\n                  \"p\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 2\n                  },\n                  \"r\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 6\n                  },\n                  \"sa\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 5\n                  },\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 7\n                  }\n                }\n              ]\n            }\n          ],\n          \"ind\": 1\n        },\n        {\n          \"ty\": 4,\n          \"nm\": \"6\",\n          \"sr\": 1,\n          \"st\": 0,\n          \"op\": 301,\n          \"ip\": 0,\n          \"hd\": false,\n          \"ddd\": 0,\n          \"bm\": 0,\n          \"hasMask\": false,\n          \"ao\": 0,\n          \"ks\": {\n            \"a\": {\n              \"a\": 0,\n              \"k\": [\n                0,\n                0,\n                0\n              ],\n              \"ix\": 1\n            },\n            \"s\": {\n              \"a\": 0,\n              \"k\": [\n                100,\n                100,\n                100\n              ],\n              \"ix\": 6\n            },\n            \"sk\": {\n              \"a\": 0,\n              \"k\": 0\n            },\n            \"p\": {\n              \"a\": 1,\n              \"k\": [\n                {\n                  \"o\": {\n                    \"x\": 0.714,\n                    \"y\": 0\n                  },\n                  \"i\": {\n                    \"x\": 0.218,\n                    \"y\": 1\n                  },\n                  \"s\": [\n                    239.25,\n                    540,\n                    0\n                  ],\n                  \"t\": 34.363,\n                  \"ti\": [\n                    0,\n                    50.083,\n                    0\n                  ],\n                  \"to\": [\n                    0,\n                    -50.083,\n                    0\n                  ]\n                },\n                {\n                  \"s\": [\n                    239.25,\n                    239.5,\n                    0\n                  ],\n                  \"t\": 68.7265625\n                }\n              ],\n              \"ix\": 2\n            },\n            \"r\": {\n              \"a\": 0,\n              \"k\": 0,\n              \"ix\": 10\n            },\n            \"sa\": {\n              \"a\": 0,\n              \"k\": 0\n            },\n            \"o\": {\n              \"a\": 0,\n              \"k\": 100,\n              \"ix\": 11\n            }\n          },\n          \"ef\": [],\n          \"shapes\": [\n            {\n              \"ty\": \"gr\",\n              \"bm\": 0,\n              \"hd\": false,\n              \"mn\": \"ADBE Vector Group\",\n              \"nm\": \"Rectangle 1\",\n              \"ix\": 1,\n              \"cix\": 2,\n              \"np\": 3,\n              \"it\": [\n                {\n                  \"ty\": \"rc\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Shape - Rect\",\n                  \"nm\": \"Rectangle Path 1\",\n                  \"d\": 1,\n                  \"p\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 3\n                  },\n                  \"r\": {\n                    \"a\": 0,\n                    \"k\": 150,\n                    \"ix\": 4\n                  },\n                  \"s\": {\n                    \"a\": 0,\n                    \"k\": [\n                      1080,\n                      1080\n                    ],\n                    \"ix\": 2\n                  }\n                },\n                {\n                  \"ty\": \"st\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Graphic - Stroke\",\n                  \"nm\": \"Stroke 1\",\n                  \"lc\": 1,\n                  \"lj\": 1,\n                  \"ml\": 4,\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 4\n                  },\n                  \"w\": {\n                    \"a\": 0,\n                    \"k\": 74,\n                    \"ix\": 5\n                  },\n                  \"c\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0,\n                      0\n                    ],\n                    \"ix\": 3\n                  }\n                },\n                {\n                  \"ty\": \"fl\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Graphic - Fill\",\n                  \"nm\": \"Fill 1\",\n                  \"c\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0,\n                      0\n                    ],\n                    \"ix\": 4\n                  },\n                  \"r\": 1,\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 5\n                  }\n                },\n                {\n                  \"ty\": \"tr\",\n                  \"a\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 1\n                  },\n                  \"s\": {\n                    \"a\": 0,\n                    \"k\": [\n                      18.493,\n                      18.493\n                    ],\n                    \"ix\": 3\n                  },\n                  \"sk\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 4\n                  },\n                  \"p\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 2\n                  },\n                  \"r\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 6\n                  },\n                  \"sa\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 5\n                  },\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 7\n                  }\n                }\n              ]\n            }\n          ],\n          \"ind\": 2\n        },\n        {\n          \"ty\": 4,\n          \"nm\": \"5\",\n          \"sr\": 1,\n          \"st\": 0,\n          \"op\": 301,\n          \"ip\": 0,\n          \"hd\": false,\n          \"ddd\": 0,\n          \"bm\": 0,\n          \"hasMask\": false,\n          \"ao\": 0,\n          \"ks\": {\n            \"a\": {\n              \"a\": 0,\n              \"k\": [\n                0,\n                0,\n                0\n              ],\n              \"ix\": 1\n            },\n            \"s\": {\n              \"a\": 0,\n              \"k\": [\n                100,\n                100,\n                100\n              ],\n              \"ix\": 6\n            },\n            \"sk\": {\n              \"a\": 0,\n              \"k\": 0\n            },\n            \"p\": {\n              \"a\": 1,\n              \"k\": [\n                {\n                  \"o\": {\n                    \"x\": 0.714,\n                    \"y\": 0\n                  },\n                  \"i\": {\n                    \"x\": 0.218,\n                    \"y\": 1\n                  },\n                  \"s\": [\n                    239.25,\n                    239.5,\n                    0\n                  ],\n                  \"t\": 27.49,\n                  \"ti\": [\n                    -50.125,\n                    0,\n                    0\n                  ],\n                  \"to\": [\n                    50.125,\n                    0,\n                    0\n                  ]\n                },\n                {\n                  \"s\": [\n                    540,\n                    239.5,\n                    0\n                  ],\n                  \"t\": 61.853515625\n                }\n              ],\n              \"ix\": 2\n            },\n            \"r\": {\n              \"a\": 0,\n              \"k\": 0,\n              \"ix\": 10\n            },\n            \"sa\": {\n              \"a\": 0,\n              \"k\": 0\n            },\n            \"o\": {\n              \"a\": 0,\n              \"k\": 100,\n              \"ix\": 11\n            }\n          },\n          \"ef\": [],\n          \"shapes\": [\n            {\n              \"ty\": \"gr\",\n              \"bm\": 0,\n              \"hd\": false,\n              \"mn\": \"ADBE Vector Group\",\n              \"nm\": \"Rectangle 1\",\n              \"ix\": 1,\n              \"cix\": 2,\n              \"np\": 3,\n              \"it\": [\n                {\n                  \"ty\": \"rc\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Shape - Rect\",\n                  \"nm\": \"Rectangle Path 1\",\n                  \"d\": 1,\n                  \"p\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 3\n                  },\n                  \"r\": {\n                    \"a\": 0,\n                    \"k\": 150,\n                    \"ix\": 4\n                  },\n                  \"s\": {\n                    \"a\": 0,\n                    \"k\": [\n                      1080,\n                      1080\n                    ],\n                    \"ix\": 2\n                  }\n                },\n                {\n                  \"ty\": \"st\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Graphic - Stroke\",\n                  \"nm\": \"Stroke 1\",\n                  \"lc\": 1,\n                  \"lj\": 1,\n                  \"ml\": 4,\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 4\n                  },\n                  \"w\": {\n                    \"a\": 0,\n                    \"k\": 74,\n                    \"ix\": 5\n                  },\n                  \"c\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0,\n                      0\n                    ],\n                    \"ix\": 3\n                  }\n                },\n                {\n                  \"ty\": \"fl\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Graphic - Fill\",\n                  \"nm\": \"Fill 1\",\n                  \"c\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0,\n                      0\n                    ],\n                    \"ix\": 4\n                  },\n                  \"r\": 1,\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 5\n                  }\n                },\n                {\n                  \"ty\": \"tr\",\n                  \"a\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 1\n                  },\n                  \"s\": {\n                    \"a\": 0,\n                    \"k\": [\n                      18.493,\n                      18.493\n                    ],\n                    \"ix\": 3\n                  },\n                  \"sk\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 4\n                  },\n                  \"p\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 2\n                  },\n                  \"r\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 6\n                  },\n                  \"sa\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 5\n                  },\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 7\n                  }\n                }\n              ]\n            }\n          ],\n          \"ind\": 3\n        },\n        {\n          \"ty\": 4,\n          \"nm\": \"4\",\n          \"sr\": 1,\n          \"st\": 0,\n          \"op\": 301,\n          \"ip\": 0,\n          \"hd\": false,\n          \"ddd\": 0,\n          \"bm\": 0,\n          \"hasMask\": false,\n          \"ao\": 0,\n          \"ks\": {\n            \"a\": {\n              \"a\": 0,\n              \"k\": [\n                0,\n                0,\n                0\n              ],\n              \"ix\": 1\n            },\n            \"s\": {\n              \"a\": 0,\n              \"k\": [\n                100,\n                100,\n                100\n              ],\n              \"ix\": 6\n            },\n            \"sk\": {\n              \"a\": 0,\n              \"k\": 0\n            },\n            \"p\": {\n              \"a\": 1,\n              \"k\": [\n                {\n                  \"o\": {\n                    \"x\": 0.714,\n                    \"y\": 0\n                  },\n                  \"i\": {\n                    \"x\": 0.218,\n                    \"y\": 1\n                  },\n                  \"s\": [\n                    540,\n                    239.5,\n                    0\n                  ],\n                  \"t\": 20.617,\n                  \"ti\": [\n                    -49.917,\n                    0,\n                    0\n                  ],\n                  \"to\": [\n                    49.917,\n                    0,\n                    0\n                  ]\n                },\n                {\n                  \"s\": [\n                    839.5,\n                    239.5,\n                    0\n                  ],\n                  \"t\": 54.98046875\n                }\n              ],\n              \"ix\": 2\n            },\n            \"r\": {\n              \"a\": 0,\n              \"k\": 0,\n              \"ix\": 10\n            },\n            \"sa\": {\n              \"a\": 0,\n              \"k\": 0\n            },\n            \"o\": {\n              \"a\": 0,\n              \"k\": 100,\n              \"ix\": 11\n            }\n          },\n          \"ef\": [],\n          \"shapes\": [\n            {\n              \"ty\": \"gr\",\n              \"bm\": 0,\n              \"hd\": false,\n              \"mn\": \"ADBE Vector Group\",\n              \"nm\": \"Rectangle 1\",\n              \"ix\": 1,\n              \"cix\": 2,\n              \"np\": 3,\n              \"it\": [\n                {\n                  \"ty\": \"rc\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Shape - Rect\",\n                  \"nm\": \"Rectangle Path 1\",\n                  \"d\": 1,\n                  \"p\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 3\n                  },\n                  \"r\": {\n                    \"a\": 0,\n                    \"k\": 150,\n                    \"ix\": 4\n                  },\n                  \"s\": {\n                    \"a\": 0,\n                    \"k\": [\n                      1080,\n                      1080\n                    ],\n                    \"ix\": 2\n                  }\n                },\n                {\n                  \"ty\": \"st\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Graphic - Stroke\",\n                  \"nm\": \"Stroke 1\",\n                  \"lc\": 1,\n                  \"lj\": 1,\n                  \"ml\": 4,\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 4\n                  },\n                  \"w\": {\n                    \"a\": 0,\n                    \"k\": 74,\n                    \"ix\": 5\n                  },\n                  \"c\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0,\n                      0\n                    ],\n                    \"ix\": 3\n                  }\n                },\n                {\n                  \"ty\": \"fl\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Graphic - Fill\",\n                  \"nm\": \"Fill 1\",\n                  \"c\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0,\n                      0\n                    ],\n                    \"ix\": 4\n                  },\n                  \"r\": 1,\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 5\n                  }\n                },\n                {\n                  \"ty\": \"tr\",\n                  \"a\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 1\n                  },\n                  \"s\": {\n                    \"a\": 0,\n                    \"k\": [\n                      18.493,\n                      18.493\n                    ],\n                    \"ix\": 3\n                  },\n                  \"sk\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 4\n                  },\n                  \"p\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 2\n                  },\n                  \"r\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 6\n                  },\n                  \"sa\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 5\n                  },\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 7\n                  }\n                }\n              ]\n            }\n          ],\n          \"ind\": 4\n        },\n        {\n          \"ty\": 4,\n          \"nm\": \"3\",\n          \"sr\": 1,\n          \"st\": 0,\n          \"op\": 301,\n          \"ip\": 0,\n          \"hd\": false,\n          \"ddd\": 0,\n          \"bm\": 0,\n          \"hasMask\": false,\n          \"ao\": 0,\n          \"ks\": {\n            \"a\": {\n              \"a\": 0,\n              \"k\": [\n                0,\n                0,\n                0\n              ],\n              \"ix\": 1\n            },\n            \"s\": {\n              \"a\": 0,\n              \"k\": [\n                100,\n                100,\n                100\n              ],\n              \"ix\": 6\n            },\n            \"sk\": {\n              \"a\": 0,\n              \"k\": 0\n            },\n            \"p\": {\n              \"a\": 1,\n              \"k\": [\n                {\n                  \"o\": {\n                    \"x\": 0.714,\n                    \"y\": 0\n                  },\n                  \"i\": {\n                    \"x\": 0.218,\n                    \"y\": 1\n                  },\n                  \"s\": [\n                    839.5,\n                    239.5,\n                    0\n                  ],\n                  \"t\": 13.746,\n                  \"ti\": [\n                    0,\n                    -50.083,\n                    0\n                  ],\n                  \"to\": [\n                    0,\n                    50.083,\n                    0\n                  ]\n                },\n                {\n                  \"o\": {\n                    \"x\": 0.167,\n                    \"y\": 0.167\n                  },\n                  \"i\": {\n                    \"x\": 0.833,\n                    \"y\": 0.833\n                  },\n                  \"s\": [\n                    839.5,\n                    540,\n                    0\n                  ],\n                  \"t\": 48.109,\n                  \"ti\": [\n                    0,\n                    0,\n                    0\n                  ],\n                  \"to\": [\n                    0,\n                    0,\n                    0\n                  ]\n                },\n                {\n                  \"o\": {\n                    \"x\": 0.546,\n                    \"y\": 0\n                  },\n                  \"i\": {\n                    \"x\": 0.218,\n                    \"y\": 1\n                  },\n                  \"s\": [\n                    839.5,\n                    540,\n                    0\n                  ],\n                  \"t\": 64.908,\n                  \"ti\": [\n                    0,\n                    0,\n                    0\n                  ],\n                  \"to\": [\n                    -49.917,\n                    0,\n                    0\n                  ]\n                },\n                {\n                  \"s\": [\n                    540,\n                    540,\n                    0\n                  ],\n                  \"t\": 98\n                }\n              ],\n              \"ix\": 2\n            },\n            \"r\": {\n              \"a\": 0,\n              \"k\": 0,\n              \"ix\": 10\n            },\n            \"sa\": {\n              \"a\": 0,\n              \"k\": 0\n            },\n            \"o\": {\n              \"a\": 0,\n              \"k\": 100,\n              \"ix\": 11\n            }\n          },\n          \"ef\": [],\n          \"shapes\": [\n            {\n              \"ty\": \"gr\",\n              \"bm\": 0,\n              \"hd\": false,\n              \"mn\": \"ADBE Vector Group\",\n              \"nm\": \"Rectangle 1\",\n              \"ix\": 1,\n              \"cix\": 2,\n              \"np\": 3,\n              \"it\": [\n                {\n                  \"ty\": \"rc\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Shape - Rect\",\n                  \"nm\": \"Rectangle Path 1\",\n                  \"d\": 1,\n                  \"p\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 3\n                  },\n                  \"r\": {\n                    \"a\": 0,\n                    \"k\": 150,\n                    \"ix\": 4\n                  },\n                  \"s\": {\n                    \"a\": 0,\n                    \"k\": [\n                      1080,\n                      1080\n                    ],\n                    \"ix\": 2\n                  }\n                },\n                {\n                  \"ty\": \"st\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Graphic - Stroke\",\n                  \"nm\": \"Stroke 1\",\n                  \"lc\": 1,\n                  \"lj\": 1,\n                  \"ml\": 4,\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 4\n                  },\n                  \"w\": {\n                    \"a\": 0,\n                    \"k\": 74,\n                    \"ix\": 5\n                  },\n                  \"c\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0,\n                      0\n                    ],\n                    \"ix\": 3\n                  }\n                },\n                {\n                  \"ty\": \"fl\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Graphic - Fill\",\n                  \"nm\": \"Fill 1\",\n                  \"c\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0,\n                      0\n                    ],\n                    \"ix\": 4\n                  },\n                  \"r\": 1,\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 5\n                  }\n                },\n                {\n                  \"ty\": \"tr\",\n                  \"a\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 1\n                  },\n                  \"s\": {\n                    \"a\": 0,\n                    \"k\": [\n                      18.493,\n                      18.493\n                    ],\n                    \"ix\": 3\n                  },\n                  \"sk\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 4\n                  },\n                  \"p\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 2\n                  },\n                  \"r\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 6\n                  },\n                  \"sa\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 5\n                  },\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 7\n                  }\n                }\n              ]\n            }\n          ],\n          \"ind\": 5\n        },\n        {\n          \"ty\": 4,\n          \"nm\": \"2\",\n          \"sr\": 1,\n          \"st\": 0,\n          \"op\": 301,\n          \"ip\": 0,\n          \"hd\": false,\n          \"ddd\": 0,\n          \"bm\": 0,\n          \"hasMask\": false,\n          \"ao\": 0,\n          \"ks\": {\n            \"a\": {\n              \"a\": 0,\n              \"k\": [\n                0,\n                0,\n                0\n              ],\n              \"ix\": 1\n            },\n            \"s\": {\n              \"a\": 0,\n              \"k\": [\n                100,\n                100,\n                100\n              ],\n              \"ix\": 6\n            },\n            \"sk\": {\n              \"a\": 0,\n              \"k\": 0\n            },\n            \"p\": {\n              \"a\": 1,\n              \"k\": [\n                {\n                  \"o\": {\n                    \"x\": 0.714,\n                    \"y\": 0\n                  },\n                  \"i\": {\n                    \"x\": 0.218,\n                    \"y\": 1\n                  },\n                  \"s\": [\n                    839.5,\n                    540,\n                    0\n                  ],\n                  \"t\": 6.873,\n                  \"ti\": [\n                    0,\n                    0,\n                    0\n                  ],\n                  \"to\": [\n                    0,\n                    0,\n                    0\n                  ]\n                },\n                {\n                  \"o\": {\n                    \"x\": 0.167,\n                    \"y\": 0.167\n                  },\n                  \"i\": {\n                    \"x\": 0.833,\n                    \"y\": 0.833\n                  },\n                  \"s\": [\n                    540,\n                    540,\n                    0\n                  ],\n                  \"t\": 41.236,\n                  \"ti\": [\n                    0,\n                    0,\n                    0\n                  ],\n                  \"to\": [\n                    0,\n                    0,\n                    0\n                  ]\n                },\n                {\n                  \"o\": {\n                    \"x\": 0.756,\n                    \"y\": 0\n                  },\n                  \"i\": {\n                    \"x\": 0.189,\n                    \"y\": 1\n                  },\n                  \"s\": [\n                    540,\n                    540,\n                    0\n                  ],\n                  \"t\": 56,\n                  \"ti\": [\n                    0.083,\n                    -50.042,\n                    0\n                  ],\n                  \"to\": [\n                    0,\n                    0,\n                    0\n                  ]\n                },\n                {\n                  \"s\": [\n                    539.5,\n                    840.25,\n                    0\n                  ],\n                  \"t\": 89.091796875\n                }\n              ],\n              \"ix\": 2\n            },\n            \"r\": {\n              \"a\": 0,\n              \"k\": 0,\n              \"ix\": 10\n            },\n            \"sa\": {\n              \"a\": 0,\n              \"k\": 0\n            },\n            \"o\": {\n              \"a\": 0,\n              \"k\": 100,\n              \"ix\": 11\n            }\n          },\n          \"ef\": [],\n          \"shapes\": [\n            {\n              \"ty\": \"gr\",\n              \"bm\": 0,\n              \"hd\": false,\n              \"mn\": \"ADBE Vector Group\",\n              \"nm\": \"Rectangle 1\",\n              \"ix\": 1,\n              \"cix\": 2,\n              \"np\": 3,\n              \"it\": [\n                {\n                  \"ty\": \"rc\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Shape - Rect\",\n                  \"nm\": \"Rectangle Path 1\",\n                  \"d\": 1,\n                  \"p\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 3\n                  },\n                  \"r\": {\n                    \"a\": 0,\n                    \"k\": 150,\n                    \"ix\": 4\n                  },\n                  \"s\": {\n                    \"a\": 0,\n                    \"k\": [\n                      1080,\n                      1080\n                    ],\n                    \"ix\": 2\n                  }\n                },\n                {\n                  \"ty\": \"st\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Graphic - Stroke\",\n                  \"nm\": \"Stroke 1\",\n                  \"lc\": 1,\n                  \"lj\": 1,\n                  \"ml\": 4,\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 4\n                  },\n                  \"w\": {\n                    \"a\": 0,\n                    \"k\": 74,\n                    \"ix\": 5\n                  },\n                  \"c\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0,\n                      0\n                    ],\n                    \"ix\": 3\n                  }\n                },\n                {\n                  \"ty\": \"fl\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Graphic - Fill\",\n                  \"nm\": \"Fill 1\",\n                  \"c\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0,\n                      0\n                    ],\n                    \"ix\": 4\n                  },\n                  \"r\": 1,\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 5\n                  }\n                },\n                {\n                  \"ty\": \"tr\",\n                  \"a\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 1\n                  },\n                  \"s\": {\n                    \"a\": 0,\n                    \"k\": [\n                      18.493,\n                      18.493\n                    ],\n                    \"ix\": 3\n                  },\n                  \"sk\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 4\n                  },\n                  \"p\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 2\n                  },\n                  \"r\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 6\n                  },\n                  \"sa\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 5\n                  },\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 7\n                  }\n                }\n              ]\n            }\n          ],\n          \"ind\": 6\n        },\n        {\n          \"ty\": 4,\n          \"nm\": \"1\",\n          \"sr\": 1,\n          \"st\": 0,\n          \"op\": 301,\n          \"ip\": 0,\n          \"hd\": false,\n          \"ddd\": 0,\n          \"bm\": 0,\n          \"hasMask\": false,\n          \"ao\": 0,\n          \"ks\": {\n            \"a\": {\n              \"a\": 0,\n              \"k\": [\n                0,\n                0,\n                0\n              ],\n              \"ix\": 1\n            },\n            \"s\": {\n              \"a\": 0,\n              \"k\": [\n                100,\n                100,\n                100\n              ],\n              \"ix\": 6\n            },\n            \"sk\": {\n              \"a\": 0,\n              \"k\": 0\n            },\n            \"p\": {\n              \"a\": 1,\n              \"k\": [\n                {\n                  \"o\": {\n                    \"x\": 0.714,\n                    \"y\": 0\n                  },\n                  \"i\": {\n                    \"x\": 0.218,\n                    \"y\": 1\n                  },\n                  \"s\": [\n                    540,\n                    540,\n                    0\n                  ],\n                  \"t\": 0,\n                  \"ti\": [\n                    0.083,\n                    -50.042,\n                    0\n                  ],\n                  \"to\": [\n                    -0.083,\n                    50.042,\n                    0\n                  ]\n                },\n                {\n                  \"o\": {\n                    \"x\": 0.167,\n                    \"y\": 0.167\n                  },\n                  \"i\": {\n                    \"x\": 0.833,\n                    \"y\": 0.833\n                  },\n                  \"s\": [\n                    539.5,\n                    840.25,\n                    0\n                  ],\n                  \"t\": 34.363,\n                  \"ti\": [\n                    0,\n                    0,\n                    0\n                  ],\n                  \"to\": [\n                    0,\n                    0,\n                    0\n                  ]\n                },\n                {\n                  \"o\": {\n                    \"x\": 0.742,\n                    \"y\": 0\n                  },\n                  \"i\": {\n                    \"x\": 0.186,\n                    \"y\": 1\n                  },\n                  \"s\": [\n                    539.5,\n                    840.25,\n                    0\n                  ],\n                  \"t\": 48.109,\n                  \"ti\": [\n                    50.042,\n                    0,\n                    0\n                  ],\n                  \"to\": [\n                    -50.042,\n                    0,\n                    0\n                  ]\n                },\n                {\n                  \"s\": [\n                    239.25,\n                    840.25,\n                    0\n                  ],\n                  \"t\": 80.181640625\n                }\n              ],\n              \"ix\": 2\n            },\n            \"r\": {\n              \"a\": 0,\n              \"k\": 0,\n              \"ix\": 10\n            },\n            \"sa\": {\n              \"a\": 0,\n              \"k\": 0\n            },\n            \"o\": {\n              \"a\": 0,\n              \"k\": 100,\n              \"ix\": 11\n            }\n          },\n          \"ef\": [],\n          \"shapes\": [\n            {\n              \"ty\": \"gr\",\n              \"bm\": 0,\n              \"hd\": false,\n              \"mn\": \"ADBE Vector Group\",\n              \"nm\": \"Rectangle 1\",\n              \"ix\": 1,\n              \"cix\": 2,\n              \"np\": 3,\n              \"it\": [\n                {\n                  \"ty\": \"rc\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Shape - Rect\",\n                  \"nm\": \"Rectangle Path 1\",\n                  \"d\": 1,\n                  \"p\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 3\n                  },\n                  \"r\": {\n                    \"a\": 0,\n                    \"k\": 150,\n                    \"ix\": 4\n                  },\n                  \"s\": {\n                    \"a\": 0,\n                    \"k\": [\n                      1080,\n                      1080\n                    ],\n                    \"ix\": 2\n                  }\n                },\n                {\n                  \"ty\": \"st\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Graphic - Stroke\",\n                  \"nm\": \"Stroke 1\",\n                  \"lc\": 1,\n                  \"lj\": 1,\n                  \"ml\": 4,\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 4\n                  },\n                  \"w\": {\n                    \"a\": 0,\n                    \"k\": 74,\n                    \"ix\": 5\n                  },\n                  \"c\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0,\n                      0\n                    ],\n                    \"ix\": 3\n                  }\n                },\n                {\n                  \"ty\": \"fl\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Graphic - Fill\",\n                  \"nm\": \"Fill 1\",\n                  \"c\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0,\n                      0\n                    ],\n                    \"ix\": 4\n                  },\n                  \"r\": 1,\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 5\n                  }\n                },\n                {\n                  \"ty\": \"tr\",\n                  \"a\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 1\n                  },\n                  \"s\": {\n                    \"a\": 0,\n                    \"k\": [\n                      18.493,\n                      18.493\n                    ],\n                    \"ix\": 3\n                  },\n                  \"sk\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 4\n                  },\n                  \"p\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 2\n                  },\n                  \"r\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 6\n                  },\n                  \"sa\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 5\n                  },\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 7\n                  }\n                }\n              ]\n            }\n          ],\n          \"ind\": 7\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "app/src/main/res/raw-night/rearrange.json",
    "content": "{\n  \"nm\": \"BOXS\",\n  \"ddd\": 0,\n  \"h\": 1080,\n  \"w\": 1080,\n  \"meta\": {\n    \"g\": \"@lottiefiles/toolkit-js 0.33.2\"\n  },\n  \"layers\": [\n    {\n      \"ty\": 3,\n      \"nm\": \"Adjustment Layer 7\",\n      \"sr\": 1,\n      \"st\": 0,\n      \"op\": 301,\n      \"ip\": 0,\n      \"hd\": false,\n      \"ddd\": 0,\n      \"bm\": 0,\n      \"hasMask\": false,\n      \"ao\": 0,\n      \"ks\": {\n        \"a\": {\n          \"a\": 0,\n          \"k\": [\n            540,\n            540,\n            0\n          ],\n          \"ix\": 1\n        },\n        \"s\": {\n          \"a\": 0,\n          \"k\": [\n            100,\n            100,\n            100\n          ],\n          \"ix\": 6\n        },\n        \"sk\": {\n          \"a\": 0,\n          \"k\": 0\n        },\n        \"p\": {\n          \"a\": 0,\n          \"k\": [\n            540,\n            540,\n            0\n          ],\n          \"ix\": 2\n        },\n        \"r\": {\n          \"a\": 0,\n          \"k\": 0,\n          \"ix\": 10\n        },\n        \"sa\": {\n          \"a\": 0,\n          \"k\": 0\n        },\n        \"o\": {\n          \"a\": 0,\n          \"k\": 100,\n          \"ix\": 11\n        }\n      },\n      \"ef\": [\n        {\n          \"ty\": 20,\n          \"mn\": \"ADBE Tint\",\n          \"nm\": \"Tint\",\n          \"ix\": 1,\n          \"en\": 1,\n          \"ef\": [\n            {\n              \"ty\": 2,\n              \"mn\": \"ADBE Tint-0001\",\n              \"nm\": \"Map Black To\",\n              \"ix\": 1,\n              \"v\": {\n                \"a\": 0,\n                \"k\": [\n                  0,\n                  0,\n                  0,\n                  0\n                ],\n                \"ix\": 1\n              }\n            },\n            {\n              \"ty\": 2,\n              \"mn\": \"ADBE Tint-0002\",\n              \"nm\": \"Map White To\",\n              \"ix\": 2,\n              \"v\": {\n                \"a\": 0,\n                \"k\": [\n                  1,\n                  1,\n                  1,\n                  0\n                ],\n                \"ix\": 2\n              }\n            },\n            {\n              \"ty\": 0,\n              \"mn\": \"ADBE Tint-0003\",\n              \"nm\": \"Amount to Tint\",\n              \"ix\": 3,\n              \"v\": {\n                \"a\": 0,\n                \"k\": 100,\n                \"ix\": 3\n              }\n            },\n            {\n              \"ty\": 6,\n              \"mn\": \"ADBE Tint-0004\",\n              \"nm\": \"\",\n              \"ix\": 4,\n              \"v\": 0\n            }\n          ]\n        }\n      ],\n      \"ind\": 1\n    },\n    {\n      \"ty\": 0,\n      \"nm\": \"Boxs Animation\",\n      \"sr\": 1,\n      \"st\": 0,\n      \"op\": 301,\n      \"ip\": 0,\n      \"hd\": false,\n      \"ddd\": 0,\n      \"bm\": 0,\n      \"hasMask\": false,\n      \"ao\": 0,\n      \"ks\": {\n        \"a\": {\n          \"a\": 0,\n          \"k\": [\n            540,\n            540,\n            0\n          ],\n          \"ix\": 1\n        },\n        \"s\": {\n          \"a\": 0,\n          \"k\": [\n            42,\n            42,\n            100\n          ],\n          \"ix\": 6\n        },\n        \"sk\": {\n          \"a\": 0,\n          \"k\": 0\n        },\n        \"p\": {\n          \"a\": 0,\n          \"k\": [\n            540,\n            540,\n            0\n          ],\n          \"ix\": 2\n        },\n        \"r\": {\n          \"a\": 0,\n          \"k\": 225.1,\n          \"ix\": 10\n        },\n        \"sa\": {\n          \"a\": 0,\n          \"k\": 0\n        },\n        \"o\": {\n          \"a\": 0,\n          \"k\": 100,\n          \"ix\": 11\n        }\n      },\n      \"ef\": [],\n      \"w\": 1080,\n      \"h\": 1080,\n      \"refId\": \"comp_0\",\n      \"tm\": {\n        \"a\": 1,\n        \"k\": [\n          {\n            \"o\": {\n              \"x\": 0.167,\n              \"y\": 0.167\n            },\n            \"i\": {\n              \"x\": 0.833,\n              \"y\": 0.833\n            },\n            \"s\": [\n              0\n            ],\n            \"t\": 0\n          },\n          {\n            \"o\": {\n              \"x\": 0.167,\n              \"y\": 0.167\n            },\n            \"i\": {\n              \"x\": 0.833,\n              \"y\": 0.833\n            },\n            \"s\": [\n              0.217\n            ],\n            \"t\": 13\n          },\n          {\n            \"o\": {\n              \"x\": 0.167,\n              \"y\": 0.167\n            },\n            \"i\": {\n              \"x\": 0.833,\n              \"y\": 0.833\n            },\n            \"s\": [\n              1.15\n            ],\n            \"t\": 109\n          },\n          {\n            \"s\": [\n              5.017\n            ],\n            \"t\": 301\n          }\n        ],\n        \"ix\": 2\n      },\n      \"ind\": 2\n    }\n  ],\n  \"v\": \"5.7.3\",\n  \"fr\": 60,\n  \"op\": 110,\n  \"ip\": 13,\n  \"assets\": [\n    {\n      \"nm\": \"\",\n      \"id\": \"comp_0\",\n      \"layers\": [\n        {\n          \"ty\": 4,\n          \"nm\": \"7\",\n          \"sr\": 1,\n          \"st\": 0,\n          \"op\": 301,\n          \"ip\": 0,\n          \"hd\": false,\n          \"ddd\": 0,\n          \"bm\": 0,\n          \"hasMask\": false,\n          \"ao\": 0,\n          \"ks\": {\n            \"a\": {\n              \"a\": 0,\n              \"k\": [\n                0,\n                0,\n                0\n              ],\n              \"ix\": 1\n            },\n            \"s\": {\n              \"a\": 0,\n              \"k\": [\n                100,\n                100,\n                100\n              ],\n              \"ix\": 6\n            },\n            \"sk\": {\n              \"a\": 0,\n              \"k\": 0\n            },\n            \"p\": {\n              \"a\": 1,\n              \"k\": [\n                {\n                  \"o\": {\n                    \"x\": 0.714,\n                    \"y\": 0\n                  },\n                  \"i\": {\n                    \"x\": 0.218,\n                    \"y\": 1\n                  },\n                  \"s\": [\n                    239.25,\n                    840.25,\n                    0\n                  ],\n                  \"t\": 41.236,\n                  \"ti\": [\n                    0,\n                    50.042,\n                    0\n                  ],\n                  \"to\": [\n                    0,\n                    -50.042,\n                    0\n                  ]\n                },\n                {\n                  \"s\": [\n                    239.25,\n                    540,\n                    0\n                  ],\n                  \"t\": 75.599609375\n                }\n              ],\n              \"ix\": 2\n            },\n            \"r\": {\n              \"a\": 0,\n              \"k\": 0,\n              \"ix\": 10\n            },\n            \"sa\": {\n              \"a\": 0,\n              \"k\": 0\n            },\n            \"o\": {\n              \"a\": 0,\n              \"k\": 100,\n              \"ix\": 11\n            }\n          },\n          \"ef\": [],\n          \"shapes\": [\n            {\n              \"ty\": \"gr\",\n              \"bm\": 0,\n              \"hd\": false,\n              \"mn\": \"ADBE Vector Group\",\n              \"nm\": \"Rectangle 1\",\n              \"ix\": 1,\n              \"cix\": 2,\n              \"np\": 3,\n              \"it\": [\n                {\n                  \"ty\": \"rc\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Shape - Rect\",\n                  \"nm\": \"Rectangle Path 1\",\n                  \"d\": 1,\n                  \"p\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 3\n                  },\n                  \"r\": {\n                    \"a\": 0,\n                    \"k\": 150,\n                    \"ix\": 4\n                  },\n                  \"s\": {\n                    \"a\": 0,\n                    \"k\": [\n                      1080,\n                      1080\n                    ],\n                    \"ix\": 2\n                  }\n                },\n                {\n                  \"ty\": \"st\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Graphic - Stroke\",\n                  \"nm\": \"Stroke 1\",\n                  \"lc\": 1,\n                  \"lj\": 1,\n                  \"ml\": 4,\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 4\n                  },\n                  \"w\": {\n                    \"a\": 0,\n                    \"k\": 74,\n                    \"ix\": 5\n                  },\n                  \"c\": {\n                    \"a\": 0,\n                    \"k\": [\n                      1,\n                      1,\n                      1\n                    ],\n                    \"ix\": 3\n                  }\n                },\n                {\n                  \"ty\": \"fl\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Graphic - Fill\",\n                  \"nm\": \"Fill 1\",\n                  \"c\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0.9961,\n                      0.9961,\n                      0.9961\n                    ],\n                    \"ix\": 4\n                  },\n                  \"r\": 1,\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 5\n                  }\n                },\n                {\n                  \"ty\": \"tr\",\n                  \"a\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 1\n                  },\n                  \"s\": {\n                    \"a\": 0,\n                    \"k\": [\n                      18.493,\n                      18.493\n                    ],\n                    \"ix\": 3\n                  },\n                  \"sk\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 4\n                  },\n                  \"p\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 2\n                  },\n                  \"r\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 6\n                  },\n                  \"sa\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 5\n                  },\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 7\n                  }\n                }\n              ]\n            }\n          ],\n          \"ind\": 1\n        },\n        {\n          \"ty\": 4,\n          \"nm\": \"6\",\n          \"sr\": 1,\n          \"st\": 0,\n          \"op\": 301,\n          \"ip\": 0,\n          \"hd\": false,\n          \"ddd\": 0,\n          \"bm\": 0,\n          \"hasMask\": false,\n          \"ao\": 0,\n          \"ks\": {\n            \"a\": {\n              \"a\": 0,\n              \"k\": [\n                0,\n                0,\n                0\n              ],\n              \"ix\": 1\n            },\n            \"s\": {\n              \"a\": 0,\n              \"k\": [\n                100,\n                100,\n                100\n              ],\n              \"ix\": 6\n            },\n            \"sk\": {\n              \"a\": 0,\n              \"k\": 0\n            },\n            \"p\": {\n              \"a\": 1,\n              \"k\": [\n                {\n                  \"o\": {\n                    \"x\": 0.714,\n                    \"y\": 0\n                  },\n                  \"i\": {\n                    \"x\": 0.218,\n                    \"y\": 1\n                  },\n                  \"s\": [\n                    239.25,\n                    540,\n                    0\n                  ],\n                  \"t\": 34.363,\n                  \"ti\": [\n                    0,\n                    50.083,\n                    0\n                  ],\n                  \"to\": [\n                    0,\n                    -50.083,\n                    0\n                  ]\n                },\n                {\n                  \"s\": [\n                    239.25,\n                    239.5,\n                    0\n                  ],\n                  \"t\": 68.7265625\n                }\n              ],\n              \"ix\": 2\n            },\n            \"r\": {\n              \"a\": 0,\n              \"k\": 0,\n              \"ix\": 10\n            },\n            \"sa\": {\n              \"a\": 0,\n              \"k\": 0\n            },\n            \"o\": {\n              \"a\": 0,\n              \"k\": 100,\n              \"ix\": 11\n            }\n          },\n          \"ef\": [],\n          \"shapes\": [\n            {\n              \"ty\": \"gr\",\n              \"bm\": 0,\n              \"hd\": false,\n              \"mn\": \"ADBE Vector Group\",\n              \"nm\": \"Rectangle 1\",\n              \"ix\": 1,\n              \"cix\": 2,\n              \"np\": 3,\n              \"it\": [\n                {\n                  \"ty\": \"rc\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Shape - Rect\",\n                  \"nm\": \"Rectangle Path 1\",\n                  \"d\": 1,\n                  \"p\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 3\n                  },\n                  \"r\": {\n                    \"a\": 0,\n                    \"k\": 150,\n                    \"ix\": 4\n                  },\n                  \"s\": {\n                    \"a\": 0,\n                    \"k\": [\n                      1080,\n                      1080\n                    ],\n                    \"ix\": 2\n                  }\n                },\n                {\n                  \"ty\": \"st\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Graphic - Stroke\",\n                  \"nm\": \"Stroke 1\",\n                  \"lc\": 1,\n                  \"lj\": 1,\n                  \"ml\": 4,\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 4\n                  },\n                  \"w\": {\n                    \"a\": 0,\n                    \"k\": 74,\n                    \"ix\": 5\n                  },\n                  \"c\": {\n                    \"a\": 0,\n                    \"k\": [\n                      1,\n                      1,\n                      1\n                    ],\n                    \"ix\": 3\n                  }\n                },\n                {\n                  \"ty\": \"fl\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Graphic - Fill\",\n                  \"nm\": \"Fill 1\",\n                  \"c\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0.9961,\n                      0.9961,\n                      0.9961\n                    ],\n                    \"ix\": 4\n                  },\n                  \"r\": 1,\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 5\n                  }\n                },\n                {\n                  \"ty\": \"tr\",\n                  \"a\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 1\n                  },\n                  \"s\": {\n                    \"a\": 0,\n                    \"k\": [\n                      18.493,\n                      18.493\n                    ],\n                    \"ix\": 3\n                  },\n                  \"sk\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 4\n                  },\n                  \"p\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 2\n                  },\n                  \"r\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 6\n                  },\n                  \"sa\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 5\n                  },\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 7\n                  }\n                }\n              ]\n            }\n          ],\n          \"ind\": 2\n        },\n        {\n          \"ty\": 4,\n          \"nm\": \"5\",\n          \"sr\": 1,\n          \"st\": 0,\n          \"op\": 301,\n          \"ip\": 0,\n          \"hd\": false,\n          \"ddd\": 0,\n          \"bm\": 0,\n          \"hasMask\": false,\n          \"ao\": 0,\n          \"ks\": {\n            \"a\": {\n              \"a\": 0,\n              \"k\": [\n                0,\n                0,\n                0\n              ],\n              \"ix\": 1\n            },\n            \"s\": {\n              \"a\": 0,\n              \"k\": [\n                100,\n                100,\n                100\n              ],\n              \"ix\": 6\n            },\n            \"sk\": {\n              \"a\": 0,\n              \"k\": 0\n            },\n            \"p\": {\n              \"a\": 1,\n              \"k\": [\n                {\n                  \"o\": {\n                    \"x\": 0.714,\n                    \"y\": 0\n                  },\n                  \"i\": {\n                    \"x\": 0.218,\n                    \"y\": 1\n                  },\n                  \"s\": [\n                    239.25,\n                    239.5,\n                    0\n                  ],\n                  \"t\": 27.49,\n                  \"ti\": [\n                    -50.125,\n                    0,\n                    0\n                  ],\n                  \"to\": [\n                    50.125,\n                    0,\n                    0\n                  ]\n                },\n                {\n                  \"s\": [\n                    540,\n                    239.5,\n                    0\n                  ],\n                  \"t\": 61.853515625\n                }\n              ],\n              \"ix\": 2\n            },\n            \"r\": {\n              \"a\": 0,\n              \"k\": 0,\n              \"ix\": 10\n            },\n            \"sa\": {\n              \"a\": 0,\n              \"k\": 0\n            },\n            \"o\": {\n              \"a\": 0,\n              \"k\": 100,\n              \"ix\": 11\n            }\n          },\n          \"ef\": [],\n          \"shapes\": [\n            {\n              \"ty\": \"gr\",\n              \"bm\": 0,\n              \"hd\": false,\n              \"mn\": \"ADBE Vector Group\",\n              \"nm\": \"Rectangle 1\",\n              \"ix\": 1,\n              \"cix\": 2,\n              \"np\": 3,\n              \"it\": [\n                {\n                  \"ty\": \"rc\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Shape - Rect\",\n                  \"nm\": \"Rectangle Path 1\",\n                  \"d\": 1,\n                  \"p\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 3\n                  },\n                  \"r\": {\n                    \"a\": 0,\n                    \"k\": 150,\n                    \"ix\": 4\n                  },\n                  \"s\": {\n                    \"a\": 0,\n                    \"k\": [\n                      1080,\n                      1080\n                    ],\n                    \"ix\": 2\n                  }\n                },\n                {\n                  \"ty\": \"st\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Graphic - Stroke\",\n                  \"nm\": \"Stroke 1\",\n                  \"lc\": 1,\n                  \"lj\": 1,\n                  \"ml\": 4,\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 4\n                  },\n                  \"w\": {\n                    \"a\": 0,\n                    \"k\": 74,\n                    \"ix\": 5\n                  },\n                  \"c\": {\n                    \"a\": 0,\n                    \"k\": [\n                      1,\n                      1,\n                      1\n                    ],\n                    \"ix\": 3\n                  }\n                },\n                {\n                  \"ty\": \"fl\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Graphic - Fill\",\n                  \"nm\": \"Fill 1\",\n                  \"c\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0.9961,\n                      0.9961,\n                      0.9961\n                    ],\n                    \"ix\": 4\n                  },\n                  \"r\": 1,\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 5\n                  }\n                },\n                {\n                  \"ty\": \"tr\",\n                  \"a\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 1\n                  },\n                  \"s\": {\n                    \"a\": 0,\n                    \"k\": [\n                      18.493,\n                      18.493\n                    ],\n                    \"ix\": 3\n                  },\n                  \"sk\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 4\n                  },\n                  \"p\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 2\n                  },\n                  \"r\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 6\n                  },\n                  \"sa\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 5\n                  },\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 7\n                  }\n                }\n              ]\n            }\n          ],\n          \"ind\": 3\n        },\n        {\n          \"ty\": 4,\n          \"nm\": \"4\",\n          \"sr\": 1,\n          \"st\": 0,\n          \"op\": 301,\n          \"ip\": 0,\n          \"hd\": false,\n          \"ddd\": 0,\n          \"bm\": 0,\n          \"hasMask\": false,\n          \"ao\": 0,\n          \"ks\": {\n            \"a\": {\n              \"a\": 0,\n              \"k\": [\n                0,\n                0,\n                0\n              ],\n              \"ix\": 1\n            },\n            \"s\": {\n              \"a\": 0,\n              \"k\": [\n                100,\n                100,\n                100\n              ],\n              \"ix\": 6\n            },\n            \"sk\": {\n              \"a\": 0,\n              \"k\": 0\n            },\n            \"p\": {\n              \"a\": 1,\n              \"k\": [\n                {\n                  \"o\": {\n                    \"x\": 0.714,\n                    \"y\": 0\n                  },\n                  \"i\": {\n                    \"x\": 0.218,\n                    \"y\": 1\n                  },\n                  \"s\": [\n                    540,\n                    239.5,\n                    0\n                  ],\n                  \"t\": 20.617,\n                  \"ti\": [\n                    -49.917,\n                    0,\n                    0\n                  ],\n                  \"to\": [\n                    49.917,\n                    0,\n                    0\n                  ]\n                },\n                {\n                  \"s\": [\n                    839.5,\n                    239.5,\n                    0\n                  ],\n                  \"t\": 54.98046875\n                }\n              ],\n              \"ix\": 2\n            },\n            \"r\": {\n              \"a\": 0,\n              \"k\": 0,\n              \"ix\": 10\n            },\n            \"sa\": {\n              \"a\": 0,\n              \"k\": 0\n            },\n            \"o\": {\n              \"a\": 0,\n              \"k\": 100,\n              \"ix\": 11\n            }\n          },\n          \"ef\": [],\n          \"shapes\": [\n            {\n              \"ty\": \"gr\",\n              \"bm\": 0,\n              \"hd\": false,\n              \"mn\": \"ADBE Vector Group\",\n              \"nm\": \"Rectangle 1\",\n              \"ix\": 1,\n              \"cix\": 2,\n              \"np\": 3,\n              \"it\": [\n                {\n                  \"ty\": \"rc\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Shape - Rect\",\n                  \"nm\": \"Rectangle Path 1\",\n                  \"d\": 1,\n                  \"p\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 3\n                  },\n                  \"r\": {\n                    \"a\": 0,\n                    \"k\": 150,\n                    \"ix\": 4\n                  },\n                  \"s\": {\n                    \"a\": 0,\n                    \"k\": [\n                      1080,\n                      1080\n                    ],\n                    \"ix\": 2\n                  }\n                },\n                {\n                  \"ty\": \"st\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Graphic - Stroke\",\n                  \"nm\": \"Stroke 1\",\n                  \"lc\": 1,\n                  \"lj\": 1,\n                  \"ml\": 4,\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 4\n                  },\n                  \"w\": {\n                    \"a\": 0,\n                    \"k\": 74,\n                    \"ix\": 5\n                  },\n                  \"c\": {\n                    \"a\": 0,\n                    \"k\": [\n                      1,\n                      1,\n                      1\n                    ],\n                    \"ix\": 3\n                  }\n                },\n                {\n                  \"ty\": \"fl\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Graphic - Fill\",\n                  \"nm\": \"Fill 1\",\n                  \"c\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0.9961,\n                      0.9961,\n                      0.9961\n                    ],\n                    \"ix\": 4\n                  },\n                  \"r\": 1,\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 5\n                  }\n                },\n                {\n                  \"ty\": \"tr\",\n                  \"a\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 1\n                  },\n                  \"s\": {\n                    \"a\": 0,\n                    \"k\": [\n                      18.493,\n                      18.493\n                    ],\n                    \"ix\": 3\n                  },\n                  \"sk\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 4\n                  },\n                  \"p\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 2\n                  },\n                  \"r\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 6\n                  },\n                  \"sa\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 5\n                  },\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 7\n                  }\n                }\n              ]\n            }\n          ],\n          \"ind\": 4\n        },\n        {\n          \"ty\": 4,\n          \"nm\": \"3\",\n          \"sr\": 1,\n          \"st\": 0,\n          \"op\": 301,\n          \"ip\": 0,\n          \"hd\": false,\n          \"ddd\": 0,\n          \"bm\": 0,\n          \"hasMask\": false,\n          \"ao\": 0,\n          \"ks\": {\n            \"a\": {\n              \"a\": 0,\n              \"k\": [\n                0,\n                0,\n                0\n              ],\n              \"ix\": 1\n            },\n            \"s\": {\n              \"a\": 0,\n              \"k\": [\n                100,\n                100,\n                100\n              ],\n              \"ix\": 6\n            },\n            \"sk\": {\n              \"a\": 0,\n              \"k\": 0\n            },\n            \"p\": {\n              \"a\": 1,\n              \"k\": [\n                {\n                  \"o\": {\n                    \"x\": 0.714,\n                    \"y\": 0\n                  },\n                  \"i\": {\n                    \"x\": 0.218,\n                    \"y\": 1\n                  },\n                  \"s\": [\n                    839.5,\n                    239.5,\n                    0\n                  ],\n                  \"t\": 13.746,\n                  \"ti\": [\n                    0,\n                    -50.083,\n                    0\n                  ],\n                  \"to\": [\n                    0,\n                    50.083,\n                    0\n                  ]\n                },\n                {\n                  \"o\": {\n                    \"x\": 0.167,\n                    \"y\": 0.167\n                  },\n                  \"i\": {\n                    \"x\": 0.833,\n                    \"y\": 0.833\n                  },\n                  \"s\": [\n                    839.5,\n                    540,\n                    0\n                  ],\n                  \"t\": 48.109,\n                  \"ti\": [\n                    0,\n                    0,\n                    0\n                  ],\n                  \"to\": [\n                    0,\n                    0,\n                    0\n                  ]\n                },\n                {\n                  \"o\": {\n                    \"x\": 0.546,\n                    \"y\": 0\n                  },\n                  \"i\": {\n                    \"x\": 0.218,\n                    \"y\": 1\n                  },\n                  \"s\": [\n                    839.5,\n                    540,\n                    0\n                  ],\n                  \"t\": 64.908,\n                  \"ti\": [\n                    0,\n                    0,\n                    0\n                  ],\n                  \"to\": [\n                    -49.917,\n                    0,\n                    0\n                  ]\n                },\n                {\n                  \"s\": [\n                    540,\n                    540,\n                    0\n                  ],\n                  \"t\": 98\n                }\n              ],\n              \"ix\": 2\n            },\n            \"r\": {\n              \"a\": 0,\n              \"k\": 0,\n              \"ix\": 10\n            },\n            \"sa\": {\n              \"a\": 0,\n              \"k\": 0\n            },\n            \"o\": {\n              \"a\": 0,\n              \"k\": 100,\n              \"ix\": 11\n            }\n          },\n          \"ef\": [],\n          \"shapes\": [\n            {\n              \"ty\": \"gr\",\n              \"bm\": 0,\n              \"hd\": false,\n              \"mn\": \"ADBE Vector Group\",\n              \"nm\": \"Rectangle 1\",\n              \"ix\": 1,\n              \"cix\": 2,\n              \"np\": 3,\n              \"it\": [\n                {\n                  \"ty\": \"rc\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Shape - Rect\",\n                  \"nm\": \"Rectangle Path 1\",\n                  \"d\": 1,\n                  \"p\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 3\n                  },\n                  \"r\": {\n                    \"a\": 0,\n                    \"k\": 150,\n                    \"ix\": 4\n                  },\n                  \"s\": {\n                    \"a\": 0,\n                    \"k\": [\n                      1080,\n                      1080\n                    ],\n                    \"ix\": 2\n                  }\n                },\n                {\n                  \"ty\": \"st\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Graphic - Stroke\",\n                  \"nm\": \"Stroke 1\",\n                  \"lc\": 1,\n                  \"lj\": 1,\n                  \"ml\": 4,\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 4\n                  },\n                  \"w\": {\n                    \"a\": 0,\n                    \"k\": 74,\n                    \"ix\": 5\n                  },\n                  \"c\": {\n                    \"a\": 0,\n                    \"k\": [\n                      1,\n                      1,\n                      1\n                    ],\n                    \"ix\": 3\n                  }\n                },\n                {\n                  \"ty\": \"fl\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Graphic - Fill\",\n                  \"nm\": \"Fill 1\",\n                  \"c\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0.9961,\n                      0.9961,\n                      0.9961\n                    ],\n                    \"ix\": 4\n                  },\n                  \"r\": 1,\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 5\n                  }\n                },\n                {\n                  \"ty\": \"tr\",\n                  \"a\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 1\n                  },\n                  \"s\": {\n                    \"a\": 0,\n                    \"k\": [\n                      18.493,\n                      18.493\n                    ],\n                    \"ix\": 3\n                  },\n                  \"sk\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 4\n                  },\n                  \"p\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 2\n                  },\n                  \"r\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 6\n                  },\n                  \"sa\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 5\n                  },\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 7\n                  }\n                }\n              ]\n            }\n          ],\n          \"ind\": 5\n        },\n        {\n          \"ty\": 4,\n          \"nm\": \"2\",\n          \"sr\": 1,\n          \"st\": 0,\n          \"op\": 301,\n          \"ip\": 0,\n          \"hd\": false,\n          \"ddd\": 0,\n          \"bm\": 0,\n          \"hasMask\": false,\n          \"ao\": 0,\n          \"ks\": {\n            \"a\": {\n              \"a\": 0,\n              \"k\": [\n                0,\n                0,\n                0\n              ],\n              \"ix\": 1\n            },\n            \"s\": {\n              \"a\": 0,\n              \"k\": [\n                100,\n                100,\n                100\n              ],\n              \"ix\": 6\n            },\n            \"sk\": {\n              \"a\": 0,\n              \"k\": 0\n            },\n            \"p\": {\n              \"a\": 1,\n              \"k\": [\n                {\n                  \"o\": {\n                    \"x\": 0.714,\n                    \"y\": 0\n                  },\n                  \"i\": {\n                    \"x\": 0.218,\n                    \"y\": 1\n                  },\n                  \"s\": [\n                    839.5,\n                    540,\n                    0\n                  ],\n                  \"t\": 6.873,\n                  \"ti\": [\n                    0,\n                    0,\n                    0\n                  ],\n                  \"to\": [\n                    0,\n                    0,\n                    0\n                  ]\n                },\n                {\n                  \"o\": {\n                    \"x\": 0.167,\n                    \"y\": 0.167\n                  },\n                  \"i\": {\n                    \"x\": 0.833,\n                    \"y\": 0.833\n                  },\n                  \"s\": [\n                    540,\n                    540,\n                    0\n                  ],\n                  \"t\": 41.236,\n                  \"ti\": [\n                    0,\n                    0,\n                    0\n                  ],\n                  \"to\": [\n                    0,\n                    0,\n                    0\n                  ]\n                },\n                {\n                  \"o\": {\n                    \"x\": 0.756,\n                    \"y\": 0\n                  },\n                  \"i\": {\n                    \"x\": 0.189,\n                    \"y\": 1\n                  },\n                  \"s\": [\n                    540,\n                    540,\n                    0\n                  ],\n                  \"t\": 56,\n                  \"ti\": [\n                    0.083,\n                    -50.042,\n                    0\n                  ],\n                  \"to\": [\n                    0,\n                    0,\n                    0\n                  ]\n                },\n                {\n                  \"s\": [\n                    539.5,\n                    840.25,\n                    0\n                  ],\n                  \"t\": 89.091796875\n                }\n              ],\n              \"ix\": 2\n            },\n            \"r\": {\n              \"a\": 0,\n              \"k\": 0,\n              \"ix\": 10\n            },\n            \"sa\": {\n              \"a\": 0,\n              \"k\": 0\n            },\n            \"o\": {\n              \"a\": 0,\n              \"k\": 100,\n              \"ix\": 11\n            }\n          },\n          \"ef\": [],\n          \"shapes\": [\n            {\n              \"ty\": \"gr\",\n              \"bm\": 0,\n              \"hd\": false,\n              \"mn\": \"ADBE Vector Group\",\n              \"nm\": \"Rectangle 1\",\n              \"ix\": 1,\n              \"cix\": 2,\n              \"np\": 3,\n              \"it\": [\n                {\n                  \"ty\": \"rc\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Shape - Rect\",\n                  \"nm\": \"Rectangle Path 1\",\n                  \"d\": 1,\n                  \"p\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 3\n                  },\n                  \"r\": {\n                    \"a\": 0,\n                    \"k\": 150,\n                    \"ix\": 4\n                  },\n                  \"s\": {\n                    \"a\": 0,\n                    \"k\": [\n                      1080,\n                      1080\n                    ],\n                    \"ix\": 2\n                  }\n                },\n                {\n                  \"ty\": \"st\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Graphic - Stroke\",\n                  \"nm\": \"Stroke 1\",\n                  \"lc\": 1,\n                  \"lj\": 1,\n                  \"ml\": 4,\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 4\n                  },\n                  \"w\": {\n                    \"a\": 0,\n                    \"k\": 74,\n                    \"ix\": 5\n                  },\n                  \"c\": {\n                    \"a\": 0,\n                    \"k\": [\n                      1,\n                      1,\n                      1\n                    ],\n                    \"ix\": 3\n                  }\n                },\n                {\n                  \"ty\": \"fl\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Graphic - Fill\",\n                  \"nm\": \"Fill 1\",\n                  \"c\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0.9961,\n                      0.9961,\n                      0.9961\n                    ],\n                    \"ix\": 4\n                  },\n                  \"r\": 1,\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 5\n                  }\n                },\n                {\n                  \"ty\": \"tr\",\n                  \"a\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 1\n                  },\n                  \"s\": {\n                    \"a\": 0,\n                    \"k\": [\n                      18.493,\n                      18.493\n                    ],\n                    \"ix\": 3\n                  },\n                  \"sk\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 4\n                  },\n                  \"p\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 2\n                  },\n                  \"r\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 6\n                  },\n                  \"sa\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 5\n                  },\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 7\n                  }\n                }\n              ]\n            }\n          ],\n          \"ind\": 6\n        },\n        {\n          \"ty\": 4,\n          \"nm\": \"1\",\n          \"sr\": 1,\n          \"st\": 0,\n          \"op\": 301,\n          \"ip\": 0,\n          \"hd\": false,\n          \"ddd\": 0,\n          \"bm\": 0,\n          \"hasMask\": false,\n          \"ao\": 0,\n          \"ks\": {\n            \"a\": {\n              \"a\": 0,\n              \"k\": [\n                0,\n                0,\n                0\n              ],\n              \"ix\": 1\n            },\n            \"s\": {\n              \"a\": 0,\n              \"k\": [\n                100,\n                100,\n                100\n              ],\n              \"ix\": 6\n            },\n            \"sk\": {\n              \"a\": 0,\n              \"k\": 0\n            },\n            \"p\": {\n              \"a\": 1,\n              \"k\": [\n                {\n                  \"o\": {\n                    \"x\": 0.714,\n                    \"y\": 0\n                  },\n                  \"i\": {\n                    \"x\": 0.218,\n                    \"y\": 1\n                  },\n                  \"s\": [\n                    540,\n                    540,\n                    0\n                  ],\n                  \"t\": 0,\n                  \"ti\": [\n                    0.083,\n                    -50.042,\n                    0\n                  ],\n                  \"to\": [\n                    -0.083,\n                    50.042,\n                    0\n                  ]\n                },\n                {\n                  \"o\": {\n                    \"x\": 0.167,\n                    \"y\": 0.167\n                  },\n                  \"i\": {\n                    \"x\": 0.833,\n                    \"y\": 0.833\n                  },\n                  \"s\": [\n                    539.5,\n                    840.25,\n                    0\n                  ],\n                  \"t\": 34.363,\n                  \"ti\": [\n                    0,\n                    0,\n                    0\n                  ],\n                  \"to\": [\n                    0,\n                    0,\n                    0\n                  ]\n                },\n                {\n                  \"o\": {\n                    \"x\": 0.742,\n                    \"y\": 0\n                  },\n                  \"i\": {\n                    \"x\": 0.186,\n                    \"y\": 1\n                  },\n                  \"s\": [\n                    539.5,\n                    840.25,\n                    0\n                  ],\n                  \"t\": 48.109,\n                  \"ti\": [\n                    50.042,\n                    0,\n                    0\n                  ],\n                  \"to\": [\n                    -50.042,\n                    0,\n                    0\n                  ]\n                },\n                {\n                  \"s\": [\n                    239.25,\n                    840.25,\n                    0\n                  ],\n                  \"t\": 80.181640625\n                }\n              ],\n              \"ix\": 2\n            },\n            \"r\": {\n              \"a\": 0,\n              \"k\": 0,\n              \"ix\": 10\n            },\n            \"sa\": {\n              \"a\": 0,\n              \"k\": 0\n            },\n            \"o\": {\n              \"a\": 0,\n              \"k\": 100,\n              \"ix\": 11\n            }\n          },\n          \"ef\": [],\n          \"shapes\": [\n            {\n              \"ty\": \"gr\",\n              \"bm\": 0,\n              \"hd\": false,\n              \"mn\": \"ADBE Vector Group\",\n              \"nm\": \"Rectangle 1\",\n              \"ix\": 1,\n              \"cix\": 2,\n              \"np\": 3,\n              \"it\": [\n                {\n                  \"ty\": \"rc\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Shape - Rect\",\n                  \"nm\": \"Rectangle Path 1\",\n                  \"d\": 1,\n                  \"p\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 3\n                  },\n                  \"r\": {\n                    \"a\": 0,\n                    \"k\": 150,\n                    \"ix\": 4\n                  },\n                  \"s\": {\n                    \"a\": 0,\n                    \"k\": [\n                      1080,\n                      1080\n                    ],\n                    \"ix\": 2\n                  }\n                },\n                {\n                  \"ty\": \"st\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Graphic - Stroke\",\n                  \"nm\": \"Stroke 1\",\n                  \"lc\": 1,\n                  \"lj\": 1,\n                  \"ml\": 4,\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 4\n                  },\n                  \"w\": {\n                    \"a\": 0,\n                    \"k\": 74,\n                    \"ix\": 5\n                  },\n                  \"c\": {\n                    \"a\": 0,\n                    \"k\": [\n                      1,\n                      1,\n                      1\n                    ],\n                    \"ix\": 3\n                  }\n                },\n                {\n                  \"ty\": \"fl\",\n                  \"bm\": 0,\n                  \"hd\": false,\n                  \"mn\": \"ADBE Vector Graphic - Fill\",\n                  \"nm\": \"Fill 1\",\n                  \"c\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0.9961,\n                      0.9961,\n                      0.9961\n                    ],\n                    \"ix\": 4\n                  },\n                  \"r\": 1,\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 5\n                  }\n                },\n                {\n                  \"ty\": \"tr\",\n                  \"a\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 1\n                  },\n                  \"s\": {\n                    \"a\": 0,\n                    \"k\": [\n                      18.493,\n                      18.493\n                    ],\n                    \"ix\": 3\n                  },\n                  \"sk\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 4\n                  },\n                  \"p\": {\n                    \"a\": 0,\n                    \"k\": [\n                      0,\n                      0\n                    ],\n                    \"ix\": 2\n                  },\n                  \"r\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 6\n                  },\n                  \"sa\": {\n                    \"a\": 0,\n                    \"k\": 0,\n                    \"ix\": 5\n                  },\n                  \"o\": {\n                    \"a\": 0,\n                    \"k\": 100,\n                    \"ix\": 7\n                  }\n                }\n              ]\n            }\n          ],\n          \"ind\": 7\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"purple_200\">#FFBB86FC</color>\n    <color name=\"purple_500\">#FF6200EE</color>\n    <color name=\"purple_700\">#FF3700B3</color>\n    <color name=\"teal_200\">#FF03DAC5</color>\n    <color name=\"teal_700\">#FF018786</color>\n    <color name=\"black\">#FF000000</color>\n    <color name=\"white\">#FFFFFFFF</color>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/font_certs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  Copyright 2022 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<resources>\n    <array name=\"com_google_android_gms_fonts_certs\">\n        <item>@array/com_google_android_gms_fonts_certs_dev</item>\n        <item>@array/com_google_android_gms_fonts_certs_prod</item>\n    </array>\n    <string-array name=\"com_google_android_gms_fonts_certs_dev\">\n        <item>\n            MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAeFw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVyxW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8XW8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexAcKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkwHQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0cxb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrPzgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXclaXjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05aIskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+aayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUWEv9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs=\n        </item>\n    </string-array>\n    <string-array name=\"com_google_android_gms_fonts_certs_prod\">\n        <item>\n            MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK\n        </item>\n    </string-array>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/non-translatable.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\" tools:ignore=\"MissingTranslation\">\n    <string name=\"app_name\" translatable=\"false\">Thor</string>\n    <string name=\"play_store\" translatable=\"false\">Play Store</string>\n    <string name=\"f_droid\" translatable=\"false\">F-Droid</string>\n    <string name=\"sideloaded\" translatable=\"false\">Sideloaded</string>\n    <string name=\"github\" translatable=\"false\">GitHub</string>\n    <string name=\"telegram\" translatable=\"false\">Telegram</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"title_activity_script_runner\">ScriptRunner</string>\n    <string name=\"title_activity_home\">HomeActivity</string>\n\n    <!-- Navigation -->\n    <string name=\"home\">Home</string>\n    <string name=\"home_desc\">Home Page</string>\n    <string name=\"apps\">App List</string>\n    <string name=\"apps_desc\">Shows A list of all apps available</string>\n    <string name=\"freezer\">Freezer</string>\n    <string name=\"freezer_desc\">Freeze/Unfreeze apps from here</string>\n    <string name=\"settings\">Settings</string>\n    <string name=\"settings_desc\">Settings Page to change app settings</string>\n    <string name=\"config_engine_v\">Configuration Engine • v%1$s</string>\n    <string name=\"general\">GENERAL</string>\n    <string name=\"show_reinstall_card\">Show Reinstall Card</string>\n    <string name=\"show_reinstall_card_desc\">Show Fix Store reminder on home screen</string>\n    <string name=\"appearance\">APPEARANCE</string>\n    <string name=\"theme\">Theme</string>\n    <string name=\"theme_desc\">Visual interface style</string>\n    <string name=\"amoled_mode\">AMOLED Mode</string>\n    <string name=\"amoled_desc\">Pure black background</string>\n    <string name=\"dynamic_colors\">Dynamic Colors</string>\n    <string name=\"dynamic_colors_desc\">Material You integration</string>\n    <string name=\"security\">SECURITY</string>\n    <string name=\"biometric_lock\">Biometric Lock</string>\n    <string name=\"biometric_lock_desc\">Require auth on launch</string>\n    <string name=\"biometric_not_available\">Biometrics not enrolled or available</string>\n    <string name=\"work_mode\">WORK MODE</string>\n    <string name=\"active_engine\">Active Engine</string>\n    <string name=\"active_engine_desc\">Switch between available providers</string>\n    <string name=\"about\">ABOUT</string>\n    <string name=\"version\">Version</string>\n    <string name=\"release_candidate\">Release candidate</string>\n    <string name=\"source_code\">SOURCE CODE</string>\n    <string name=\"community\">COMMUNITY</string>\n    <string name=\"kernel_status\">KERNEL_STATUS: OPTIMIZED</string>\n    <string name=\"built_with_precision\">BUILT WITH PRECISION FOR POWER USERS</string>\n    <string name=\"app_language\">App Language</string>\n    <string name=\"select_language\">Select Language</string>\n    <string name=\"system_default\">System Default</string>\n    <string name=\"english\">English</string>\n    <string name=\"chinese\">Chinese</string>\n    <string name=\"french\">French</string>\n    <string name=\"spanish\">Spanish</string>\n    <string name=\"arabic\">Arabic</string>\n\n    <!-- Common Actions -->\n    <string name=\"cancel\">Cancel</string>\n    <string name=\"confirm\">Confirm</string>\n    <string name=\"proceed\">Proceed</string>\n    <string name=\"yes\">Yes</string>\n    <string name=\"no\">No</string>\n    <string name=\"done\">Done</string>\n    <string name=\"close\">Close</string>\n    <string name=\"refresh\">Refresh</string>\n    <string name=\"dismiss\">Dismiss</string>\n    <string name=\"search_apps\">Search apps&#8230;</string>\n    <string name=\"unknown\">Unknown</string>\n\n    <!-- Home Screen -->\n    <string name=\"reinstall_all\">Reinstall All</string>\n    <string name=\"reinstall_all_subtitle\">%1$d %2$s apps not from Play Store. Fix them?</string>\n    <string name=\"install_from_file\">Install from File</string>\n    <string name=\"install_from_file_subtitle\">Install APK, XAPK, APKS or Split bundles</string>\n    <string name=\"app_distribution\">App Distribution</string>\n    <string name=\"total_apps\">TOTAL: %1$d</string>\n    <string name=\"others\">Others</string>\n    <string name=\"clear_all_cache\">Clear All Cache</string>\n    <string name=\"clear_cache_prompt\">Which apps would you like to clear?</string>\n    <string name=\"user_apps\">User Apps</string>\n    <string name=\"system_apps\">System Apps</string>\n    <string name=\"active\">Active</string>\n    <string name=\"frozen\">Frozen</string>\n    <string name=\"suspended\">Suspended</string>\n    <string name=\"privilege_check\">Privilege Check</string>\n    <string name=\"privilege_check_desc\">Thor requires Root or Shizuku access to function correctly.\\n\\nPlease grant access in your manager app and click Refresh.</string>\n\n    <!-- App List / Freezer -->\n    <string name=\"system_apps_warning\">⚠ System Apps</string>\n    <string name=\"selected_count\">%1$d Selected</string>\n    <string name=\"no_matching_apps\">No matching apps found</string>\n    <string name=\"no_apps_display\">No apps to display</string>\n    <string name=\"adjust_filters_hint\">Try adjusting your search or filters</string>\n    <string name=\"configuration\">Configuration</string>\n    <string name=\"app_source\">App Source</string>\n    <string name=\"view_mode\">View Mode</string>\n    <string name=\"grid\">Grid</string>\n    <string name=\"list\">List</string>\n    <string name=\"order\">Order:</string>\n    <string name=\"ascending\">Ascending</string>\n    <string name=\"descending\">Descending</string>\n    <string name=\"filters\">Filters</string>\n    <string name=\"sort_by\">Sort By</string>\n\n    <!-- App Info Dialog -->\n    <string name=\"clear_app_data_title\">Clear App Data?</string>\n    <string name=\"clear_app_data_desc\">This will permanently delete all data for %1$s. This action cannot be undone.</string>\n    <string name=\"clear_all_data\">Clear All Data</string>\n    <string name=\"uninstall_system_app_title\">Uninstall System App?</string>\n    <string name=\"uninstall_system_app_desc\">This allows you to uninstall updates or factory reset this system app. Proceed?</string>\n    <string name=\"risk_warning_title\">Risk Warning</string>\n    <string name=\"risk_warning_desc\">This forces the installer record to \\'Google Play Store\\'.\\n\\nUpdates may fail if the signature doesn\\'t match the official store version.</string>\n    <string name=\"reinstall_play_store_title\">Reinstall with Play Store?</string>\n    <string name=\"reinstall_play_store_desc\">This will attempt to reinstall %1$s using the Google Play Store.</string>\n    <string name=\"fix_store\">Fix Store</string>\n\n    <!-- App Status -->\n    <string name=\"status_split\">SPLIT</string>\n    <string name=\"status_frozen\">FROZEN</string>\n    <string name=\"status_suspended\">SUSPENDED</string>\n\n    <!-- App Actions -->\n    <string name=\"action_launch\">Launch</string>\n    <string name=\"action_share\">Share</string>\n    <string name=\"action_freeze\">Freeze</string>\n    <string name=\"action_unfreeze\">Unfreeze</string>\n    <string name=\"action_suspend\">Suspend</string>\n    <string name=\"action_unsuspend\">Unsuspend</string>\n    <string name=\"action_kill\">Kill</string>\n    <string name=\"action_cache\">Cache</string>\n    <string name=\"action_data\">Data</string>\n    <string name=\"action_uninstall\">Uninstall</string>\n    <string name=\"action_reinstall\">Reinstall</string>\n\n    <!-- Main Screen / Global -->\n    <string name=\"cannot_launch_app\">Cannot launch app</string>\n    <string name=\"share_app\">Share App</string>\n    <string name=\"kill_app_title\">Kill App?</string>\n    <string name=\"kill_app_desc\">Force stop %1$s? This may cause data loss.</string>\n    <string name=\"exit_thor_title\">Exit Thor?</string>\n    <string name=\"exit_thor_desc\">Are you sure you want to close the application?</string>\n    <string name=\"are_you_sure\">Are you sure?</string>\n\n    <!-- Accessibility / Content Descriptions -->\n    <string name=\"cd_config\">Config</string>\n    <string name=\"cd_close\">Close</string>\n    <string name=\"cd_frozen\">Frozen</string>\n    <string name=\"cd_suspended\">Suspended</string>\n    <string name=\"cd_selected\">Selected</string>\n    <string name=\"cd_search\">Search</string>\n    <string name=\"cd_clear\">Clear</string>\n    <string name=\"cd_settings\">Settings</string>\n\n</resources>"
  },
  {
    "path": "app/src/main/res/values/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"Theme.Thor\" parent=\"android:Theme.Material.Light.NoActionBar\" />\n\n    <style name=\"Theme.Thor.SplashScreen\" parent=\"Theme.SplashScreen\">\n        <item name=\"windowSplashScreenAnimatedIcon\">@drawable/thor_animated</item>\n        <item name=\"postSplashScreenTheme\">@style/Theme.Thor</item>\n    </style>\n\n</resources>"
  },
  {
    "path": "app/src/main/res/values/thor_drawn_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"thor_drawn_background\">#000000</color>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/thor_icon_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"thor_icon_background\">#000000</color>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-ar/strings.xml",
    "content": "<resources>\n    <string name=\"title_activity_script_runner\">ScriptRunner</string>\n    <string name=\"title_activity_home\">HomeActivity</string>\n\n    <!-- Navigation -->\n    <string name=\"home\">الرئيسية</string>\n    <string name=\"home_desc\">الصفحة الرئيسية</string>\n    <string name=\"apps\">قائمة التطبيقات</string>\n    <string name=\"apps_desc\">يعرض قائمة بجميع التطبيقات المتاحة</string>\n    <string name=\"freezer\">المجمد</string>\n    <string name=\"freezer_desc\">تجميد/إلغاء تجميد التطبيقات من هنا</string>\n    <string name=\"settings\">الإعدادات</string>\n    <string name=\"settings_desc\">صفحة الإعدادات لتغيير إعدادات التطبيق</string>\n    <string name=\"config_engine_v\">محرك التكوين • إصدار %1$s</string>\n    <string name=\"general\">عام</string>\n    <string name=\"show_reinstall_card\">إظهار بطاقة إعادة التثبيت</string>\n    <string name=\"show_reinstall_card_desc\">إظهار تذكير إصلاح المتجر على الشاشة الرئيسية</string>\n    <string name=\"appearance\">المظهر</string>\n    <string name=\"theme\">المظهر</string>\n    <string name=\"theme_desc\">نمط الواجهة المرئية</string>\n    <string name=\"amoled_mode\">وضع AMOLED</string>\n    <string name=\"amoled_desc\">خلفية سوداء نقية</string>\n    <string name=\"dynamic_colors\">ألوان ديناميكية</string>\n    <string name=\"dynamic_colors_desc\">تكامل Material You</string>\n    <string name=\"security\">الأمان</string>\n    <string name=\"biometric_lock\">قفل القياسات الحيوية</string>\n    <string name=\"biometric_lock_desc\">يتطلب المصادقة عند التشغيل</string>\n    <string name=\"biometric_not_available\">المقاييس الحيوية غير مسجلة أو غير متوفرة</string>\n    <string name=\"work_mode\">وضع العمل</string>\n    <string name=\"active_engine\">المحرك النشط</string>\n    <string name=\"active_engine_desc\">التبديل بين الموفرين المتاحين</string>\n    <string name=\"about\">حول</string>\n    <string name=\"version\">الإصدار</string>\n    <string name=\"release_candidate\">نسخة مرشحة</string>\n    <string name=\"source_code\">كود المصدر</string>\n    <string name=\"community\">المجتمع</string>\n    <string name=\"kernel_status\">حالة النواة: محسنة</string>\n    <string name=\"built_with_precision\">بني بدقة للمستخدمين المحترفين</string>\n    <string name=\"app_language\">لغة التطبيق</string>\n    <string name=\"select_language\">اختر اللغة</string>\n    <string name=\"system_default\">افتراضي النظام</string>\n    <string name=\"english\">الإنجليزية</string>\n    <string name=\"chinese\">الصينية</string>\n    <string name=\"french\">الفرنسية</string>\n    <string name=\"spanish\">الإسبانية</string>\n    <string name=\"arabic\">العربية</string>\n\n    <!-- Common Actions -->\n    <string name=\"cancel\">إلغاء</string>\n    <string name=\"confirm\">تأكيد</string>\n    <string name=\"proceed\">متابعة</string>\n    <string name=\"yes\">نعم</string>\n    <string name=\"no\">لا</string>\n    <string name=\"done\">تم</string>\n    <string name=\"close\">إغلاق</string>\n    <string name=\"refresh\">تحديث</string>\n    <string name=\"dismiss\">تجاهل</string>\n    <string name=\"search_apps\">بحث عن تطبيقات&#8230;</string>\n    <string name=\"unknown\">غير معروف</string>\n\n    <!-- Home Screen -->\n    <string name=\"reinstall_all\">إعادة تثبيت الكل</string>\n    <string name=\"reinstall_all_subtitle\">%1$d تطبيق %2$s ليس من متجر Play. هل تريد إصلاحها؟</string>\n    <string name=\"install_from_file\">تثبيت من ملف</string>\n    <string name=\"install_from_file_subtitle\">تثبيت APK أو XAPK أو APKS أو حزم مقسمة</string>\n    <string name=\"app_distribution\">توزيع التطبيقات</string>\n    <string name=\"total_apps\">الإجمالي: %1$d</string>\n    <string name=\"others\">أخرى</string>\n    <string name=\"clear_all_cache\">مسح جميع الذاكرة المؤقتة</string>\n    <string name=\"clear_cache_prompt\">أي التطبيقات ترغب في مسحها؟</string>\n    <string name=\"user_apps\">تطبيقات المستخدم</string>\n    <string name=\"system_apps\">تطبيقات النظام</string>\n    <string name=\"active\">نشط</string>\n    <string name=\"frozen\">مجمد</string>\n    <string name=\"suspended\">معلق</string>\n    <string name=\"privilege_check\">فحص الصلاحيات</string>\n    <string name=\"privilege_check_desc\">يتطلب Thor صلاحيات Root أو Shizuku ليعمل بشكل صحيح.\\n\\nيرجى منح الصلاحيات في تطبيق الإدارة والنقر فوق تحديث.</string>\n\n    <!-- App List / Freezer -->\n    <string name=\"system_apps_warning\">⚠ تطبيقات النظام</string>\n    <string name=\"selected_count\">%1$d مختار</string>\n    <string name=\"no_matching_apps\">لم يتم العثور على تطبيقات مطابقة</string>\n    <string name=\"no_apps_display\">لا توجد تطبيقات لعرضها</string>\n    <string name=\"adjust_filters_hint\">حاول ضبط البحث أو الفلاتر</string>\n    <string name=\"configuration\">التكوين</string>\n    <string name=\"app_source\">مصدر التطبيق</string>\n    <string name=\"view_mode\">وضع العرض</string>\n    <string name=\"grid\">شبكة</string>\n    <string name=\"list\">قائمة</string>\n    <string name=\"order\">الترتيب:</string>\n    <string name=\"ascending\">تصاعدي</string>\n    <string name=\"descending\">تنازلي</string>\n    <string name=\"filters\">الفلاتر</string>\n    <string name=\"sort_by\">فرز حسب</string>\n\n    <!-- App Info Dialog -->\n    <string name=\"clear_app_data_title\">مسح بيانات التطبيق؟</string>\n    <string name=\"clear_app_data_desc\">سيؤدي هذا إلى حذف جميع بيانات %1$s نهائياً. لا يمكن التراجع عن هذا الإجراء.</string>\n    <string name=\"clear_all_data\">مسح جميع البيانات</string>\n    <string name=\"uninstall_system_app_title\">إلغاء تثبيت تطبيق النظام؟</string>\n    <string name=\"uninstall_system_app_desc\">يتيح لك هذا إلغاء تثبيت التحديثات أو استعادة ضبط المصنع لتطبيق النظام هذا. متابعة؟</string>\n    <string name=\"risk_warning_title\">تحذير من المخاطر</string>\n    <string name=\"risk_warning_desc\">هذا يجبر سجل المثبت على \\'Google Play Store\\'.\\n\\nقد تفشل التحديثات إذا لم يتطابق التوقيع مع نسخة المتجر الرسمية.</string>\n    <string name=\"reinstall_play_store_title\">إعادة التثبيت باستخدام متجر Play؟</string>\n    <string name=\"reinstall_play_store_desc\">سيحاول هذا إعادة تثبيت %1$s باستخدام متجر Google Play.</string>\n    <string name=\"fix_store\">إصلاح المتجر</string>\n\n    <!-- App Status -->\n    <string name=\"status_split\">مقسم</string>\n    <string name=\"status_frozen\">مجمد</string>\n    <string name=\"status_suspended\">معلق</string>\n\n    <!-- App Actions -->\n    <string name=\"action_launch\">تشغيل</string>\n    <string name=\"action_share\">مشاركة</string>\n    <string name=\"action_freeze\">تجميد</string>\n    <string name=\"action_unfreeze\">إلغاء التجميد</string>\n    <string name=\"action_suspend\">تعليق</string>\n    <string name=\"action_unsuspend\">إلغاء التعليق</string>\n    <string name=\"action_kill\">إنهاء</string>\n    <string name=\"action_cache\">ذاكرة مؤقتة</string>\n    <string name=\"action_data\">بيانات</string>\n    <string name=\"action_uninstall\">إلغاء التثبيت</string>\n    <string name=\"action_reinstall\">إعادة تثبيت</string>\n\n    <!-- Main Screen / Global -->\n    <string name=\"cannot_launch_app\">لا يمكن تشغيل التطبيق</string>\n    <string name=\"share_app\">مشاركة التطبيق</string>\n    <string name=\"kill_app_title\">إنهاء التطبيق؟</string>\n    <string name=\"kill_app_desc\">إيقاف إجباري لـ %1$s؟ قد يؤدي هذا إلى فقدان البيانات.</string>\n    <string name=\"exit_thor_title\">الخروج من Thor؟</string>\n    <string name=\"exit_thor_desc\">هل أنت متأكد أنك تريد إغلاق التطبيق؟</string>\n    <string name=\"are_you_sure\">هل أنت متأكد؟</string>\n\n    <!-- Accessibility / Content Descriptions -->\n    <string name=\"cd_config\">تكوين</string>\n    <string name=\"cd_close\">إغلاق</string>\n    <string name=\"cd_frozen\">مجمد</string>\n    <string name=\"cd_suspended\">معلق</string>\n    <string name=\"cd_selected\">مختار</string>\n    <string name=\"cd_search\">بحث</string>\n    <string name=\"cd_clear\">مسح</string>\n    <string name=\"cd_settings\">الإعدادات</string>\n\n</resources>"
  },
  {
    "path": "app/src/main/res/values-es/strings.xml",
    "content": "<resources>\n    <string name=\"title_activity_script_runner\">ScriptRunner</string>\n    <string name=\"title_activity_home\">HomeActivity</string>\n\n    <!-- Navigation -->\n    <string name=\"home\">Inicio</string>\n    <string name=\"home_desc\">Página de inicio</string>\n    <string name=\"apps\">Lista de aplicaciones</string>\n    <string name=\"apps_desc\">Muestra una lista de todas las aplicaciones disponibles</string>\n    <string name=\"freezer\">Congelador</string>\n    <string name=\"freezer_desc\">Congelar/Descongelar aplicaciones desde aquí</string>\n    <string name=\"settings\">Ajustes</string>\n    <string name=\"settings_desc\">Página de ajustes para cambiar las opciones de la aplicación</string>\n    <string name=\"config_engine_v\">Motor de configuración • v%1$s</string>\n    <string name=\"general\">GENERAL</string>\n    <string name=\"show_reinstall_card\">Mostrar tarjeta de reinstalación</string>\n    <string name=\"show_reinstall_card_desc\">Mostrar recordatorio de Fix Store en la pantalla de inicio</string>\n    <string name=\"appearance\">APARIENCIA</string>\n    <string name=\"theme\">Tema</string>\n    <string name=\"theme_desc\">Estilo de interfaz visual</string>\n    <string name=\"amoled_mode\">Modo AMOLED</string>\n    <string name=\"amoled_desc\">Fondo negro puro</string>\n    <string name=\"dynamic_colors\">Colores dinámicos</string>\n    <string name=\"dynamic_colors_desc\">Integración con Material You</string>\n    <string name=\"security\">SEGURIDAD</string>\n    <string name=\"biometric_lock\">Bloqueo biométrico</string>\n    <string name=\"biometric_lock_desc\">Requerir autenticación al iniciar</string>\n    <string name=\"biometric_not_available\">Biometría no registrada o disponible</string>\n    <string name=\"work_mode\">MODO DE TRABAJO</string>\n    <string name=\"active_engine\">Motor activo</string>\n    <string name=\"active_engine_desc\">Cambiar entre proveedores disponibles</string>\n    <string name=\"about\">ACERCA DE</string>\n    <string name=\"version\">Versión</string>\n    <string name=\"release_candidate\">Versión candidata</string>\n    <string name=\"source_code\">CÓDIGO FUENTE</string>\n    <string name=\"community\">COMUNIDAD</string>\n    <string name=\"kernel_status\">ESTADO_DEL_KERNEL: OPTIMIZADO</string>\n    <string name=\"built_with_precision\">CONSTRUIDO CON PRECISIÓN PARA USUARIOS AVANZADOS</string>\n    <string name=\"app_language\">Idioma de la aplicación</string>\n    <string name=\"select_language\">Seleccionar idioma</string>\n    <string name=\"system_default\">Predeterminado del sistema</string>\n    <string name=\"english\">Inglés</string>\n    <string name=\"chinese\">Chino</string>\n    <string name=\"french\">Francés</string>\n    <string name=\"spanish\">Español</string>\n    <string name=\"arabic\">Árabe</string>\n\n    <!-- Common Actions -->\n    <string name=\"cancel\">Cancelar</string>\n    <string name=\"confirm\">Confirmar</string>\n    <string name=\"proceed\">Continuar</string>\n    <string name=\"yes\">Sí</string>\n    <string name=\"no\">No</string>\n    <string name=\"done\">Hecho</string>\n    <string name=\"close\">Cerrar</string>\n    <string name=\"refresh\">Actualizar</string>\n    <string name=\"dismiss\">Descartar</string>\n    <string name=\"search_apps\">Buscar aplicaciones&#8230;</string>\n    <string name=\"unknown\">Desconocido</string>\n\n    <!-- Home Screen -->\n    <string name=\"reinstall_all\">Reinstalar todo</string>\n    <string name=\"reinstall_all_subtitle\">%1$d aplicaciones %2$s no son de Play Store. ¿Repararlas?</string>\n    <string name=\"install_from_file\">Instalar desde archivo</string>\n    <string name=\"install_from_file_subtitle\">Instalar APK, XAPK, APKS o paquetes divididos</string>\n    <string name=\"app_distribution\">Distribución de aplicaciones</string>\n    <string name=\"total_apps\">TOTAL: %1$d</string>\n    <string name=\"others\">Otros</string>\n    <string name=\"clear_all_cache\">Limpiar todos los cachés</string>\n    <string name=\"clear_cache_prompt\">¿Qué aplicaciones te gustaría limpiar?</string>\n    <string name=\"user_apps\">Aplicaciones de usuario</string>\n    <string name=\"system_apps\">Aplicaciones del sistema</string>\n    <string name=\"active\">Activo</string>\n    <string name=\"frozen\">Congelado</string>\n    <string name=\"suspended\">Suspendido</string>\n    <string name=\"privilege_check\">Verificación de privilegios</string>\n    <string name=\"privilege_check_desc\">Thor requiere acceso Root o Shizuku para funcionar correctamente.\\n\\nPor favor, concede acceso en tu aplicación de gestión y pulsa Actualizar.</string>\n\n    <!-- App List / Freezer -->\n    <string name=\"system_apps_warning\">⚠ Aplicaciones del sistema</string>\n    <string name=\"selected_count\">%1$d Seleccionado(s)</string>\n    <string name=\"no_matching_apps\">No se encontraron aplicaciones</string>\n    <string name=\"no_apps_display\">No hay aplicaciones para mostrar</string>\n    <string name=\"adjust_filters_hint\">Intenta ajustar tu búsqueda o filtros</string>\n    <string name=\"configuration\">Configuración</string>\n    <string name=\"app_source\">Fuente de la aplicación</string>\n    <string name=\"view_mode\">Modo de vista</string>\n    <string name=\"grid\">Cuadrícula</string>\n    <string name=\"list\">Lista</string>\n    <string name=\"order\">Orden:</string>\n    <string name=\"ascending\">Ascendente</string>\n    <string name=\"descending\">Descendente</string>\n    <string name=\"filters\">Filtros</string>\n    <string name=\"sort_by\">Ordenar por</string>\n\n    <!-- App Info Dialog -->\n    <string name=\"clear_app_data_title\">¿Borrar datos de la aplicación?</string>\n    <string name=\"clear_app_data_desc\">Esto borrará permanentemente todos los datos de %1$s. Esta acción no se puede deshacer.</string>\n    <string name=\"clear_all_data\">Borrar todos los datos</string>\n    <string name=\"uninstall_system_app_title\">¿Desinstalar aplicación del sistema?</string>\n    <string name=\"uninstall_system_app_desc\">Esto te permite desinstalar actualizaciones o restablecer de fábrica esta aplicación del sistema. ¿Continuar?</string>\n    <string name=\"risk_warning_title\">Advertencia de riesgo</string>\n    <string name=\"risk_warning_desc\">Esto fuerza el registro del instalador a \\'Google Play Store\\'.\\n\\nLas actualizaciones pueden fallar si la firma no coincide con la versión oficial de la tienda.</string>\n    <string name=\"reinstall_play_store_title\">¿Reinstalar con Play Store?</string>\n    <string name=\"reinstall_play_store_desc\">Esto intentará reinstalar %1$s usando Google Play Store.</string>\n    <string name=\"fix_store\">Reparar tienda</string>\n\n    <!-- App Status -->\n    <string name=\"status_split\">DIVIDIDO</string>\n    <string name=\"status_frozen\">CONGELADO</string>\n    <string name=\"status_suspended\">SUSPENDIDO</string>\n\n    <!-- App Actions -->\n    <string name=\"action_launch\">Abrir</string>\n    <string name=\"action_share\">Compartir</string>\n    <string name=\"action_freeze\">Congelar</string>\n    <string name=\"action_unfreeze\">Descongelar</string>\n    <string name=\"action_suspend\">Suspender</string>\n    <string name=\"action_unsuspend\">Habilitar</string>\n    <string name=\"action_kill\">Detener</string>\n    <string name=\"action_cache\">Caché</string>\n    <string name=\"action_data\">Datos</string>\n    <string name=\"action_uninstall\">Desinstalar</string>\n    <string name=\"action_reinstall\">Reinstalar</string>\n\n    <!-- Main Screen / Global -->\n    <string name=\"cannot_launch_app\">No se puede abrir la aplicación</string>\n    <string name=\"share_app\">Compartir aplicación</string>\n    <string name=\"kill_app_title\">¿Detener aplicación?</string>\n    <string name=\"kill_app_desc\">¿Forzar detención de %1$s? Esto puede causar pérdida de datos.</string>\n    <string name=\"exit_thor_title\">¿Salir de Thor?</string>\n    <string name=\"exit_thor_desc\">¿Estás seguro de que quieres cerrar la aplicación?</string>\n    <string name=\"are_you_sure\">¿Estás seguro?</string>\n\n    <!-- Accessibility / Content Descriptions -->\n    <string name=\"cd_config\">Configuración</string>\n    <string name=\"cd_close\">Cerrar</string>\n    <string name=\"cd_frozen\">Congelado</string>\n    <string name=\"cd_suspended\">Suspendido</string>\n    <string name=\"cd_selected\">Seleccionado</string>\n    <string name=\"cd_search\">Buscar</string>\n    <string name=\"cd_clear\">Limpiar</string>\n    <string name=\"cd_settings\">Ajustes</string>\n\n</resources>"
  },
  {
    "path": "app/src/main/res/values-fr/strings.xml",
    "content": "<resources>\n    <string name=\"title_activity_script_runner\">ScriptRunner</string>\n    <string name=\"title_activity_home\">HomeActivity</string>\n\n    <!-- Navigation -->\n    <string name=\"home\">Accueil</string>\n    <string name=\"home_desc\">Page d\\'accueil</string>\n    <string name=\"apps\">Liste des applis</string>\n    <string name=\"apps_desc\">Affiche une liste de toutes les applications installées</string>\n    <string name=\"freezer\">Congélateur</string>\n    <string name=\"freezer_desc\">Geler/Dégeler les applications ici</string>\n    <string name=\"settings\">Paramètres</string>\n    <string name=\"settings_desc\">Page de paramètres pour modifier les options de l\\'application</string>\n    <string name=\"config_engine_v\">Moteur de configuration • v%1$s</string>\n    <string name=\"general\">GÉNÉRAL</string>\n    <string name=\"show_reinstall_card\">Afficher la carte de réinstallation</string>\n    <string name=\"show_reinstall_card_desc\">Afficher le rappel Fix Store sur l\\'écran d\\'accueil</string>\n    <string name=\"appearance\">APPARENCE</string>\n    <string name=\"theme\">Thème</string>\n    <string name=\"theme_desc\">Style d\\'interface visuelle</string>\n    <string name=\"amoled_mode\">Mode AMOLED</string>\n    <string name=\"amoled_desc\">Arrière-plan noir pur</string>\n    <string name=\"dynamic_colors\">Couleurs dynamiques</string>\n    <string name=\"dynamic_colors_desc\">Intégration Material You</string>\n    <string name=\"security\">SÉCURITÉ</string>\n    <string name=\"biometric_lock\">Verrouillage biométrique</string>\n    <string name=\"biometric_lock_desc\">Exiger une authentification au lancement</string>\n    <string name=\"biometric_not_available\">Biométrie non enregistrée ou indisponible</string>\n    <string name=\"work_mode\">MODE DE TRAVAIL</string>\n    <string name=\"active_engine\">Moteur actif</string>\n    <string name=\"active_engine_desc\">Passer d\\'un fournisseur à l\\'autre</string>\n    <string name=\"about\">À PROPOS</string>\n    <string name=\"version\">Version</string>\n    <string name=\"release_candidate\">Version candidate</string>\n    <string name=\"source_code\">CODE SOURCE</string>\n    <string name=\"community\">COMMUNAUTÉ</string>\n    <string name=\"kernel_status\">ÉTAT_DU_NOYAU : OPTIMISÉ</string>\n    <string name=\"built_with_precision\">CONÇU AVEC PRÉCISION POUR LES UTILISATEURS AVANCÉS</string>\n    <string name=\"app_language\">Langue de l\\'application</string>\n    <string name=\"select_language\">Choisir la langue</string>\n    <string name=\"system_default\">Par défaut du système</string>\n    <string name=\"english\">Anglais</string>\n    <string name=\"chinese\">Chinois</string>\n    <string name=\"french\">Français</string>\n    <string name=\"spanish\">Espagnol</string>\n    <string name=\"arabic\">Arabe</string>\n\n    <!-- Common Actions -->\n    <string name=\"cancel\">Annuler</string>\n    <string name=\"confirm\">Confirmer</string>\n    <string name=\"proceed\">Continuer</string>\n    <string name=\"yes\">Oui</string>\n    <string name=\"no\">Non</string>\n    <string name=\"done\">Terminé</string>\n    <string name=\"close\">Fermer</string>\n    <string name=\"refresh\">Actualiser</string>\n    <string name=\"dismiss\">Ignorer</string>\n    <string name=\"search_apps\">Rechercher des applis&#8230;</string>\n    <string name=\"unknown\">Inconnu</string>\n\n    <!-- Home Screen -->\n    <string name=\"reinstall_all\">Tout réinstaller</string>\n    <string name=\"reinstall_all_subtitle\">%1$d applications %2$s ne provenant pas du Play Store. Voulez-vous les réparer ?</string>\n    <string name=\"install_from_file\">Installer depuis un fichier</string>\n    <string name=\"install_from_file_subtitle\">Installer APK, XAPK, APKS ou lots de fichiers</string>\n    <string name=\"app_distribution\">Distribution des applis</string>\n    <string name=\"total_apps\">TOTAL : %1$d</string>\n    <string name=\"others\">Autres</string>\n    <string name=\"clear_all_cache\">Vider tous les caches</string>\n    <string name=\"clear_cache_prompt\">Quelles applications souhaitez-vous vider ?</string>\n    <string name=\"user_apps\">Applis utilisateur</string>\n    <string name=\"system_apps\">Applis système</string>\n    <string name=\"active\">Actif</string>\n    <string name=\"frozen\">Gelé</string>\n    <string name=\"suspended\">Suspendu</string>\n    <string name=\"privilege_check\">Vérification des privilèges</string>\n    <string name=\"privilege_check_desc\">Thor nécessite un accès Root ou Shizuku pour fonctionner correctement.\\n\\nVeuillez accorder l\\'accès dans votre application de gestion et cliquer sur Actualiser.</string>\n\n    <!-- App List / Freezer -->\n    <string name=\"system_apps_warning\">⚠ Applis Système</string>\n    <string name=\"selected_count\">%1$d Sélectionné(s)</string>\n    <string name=\"no_matching_apps\">Aucune application correspondante</string>\n    <string name=\"no_apps_display\">Aucune application à afficher</string>\n    <string name=\"adjust_filters_hint\">Essayez d\\'ajuster votre recherche ou vos filtres</string>\n    <string name=\"configuration\">Configuration</string>\n    <string name=\"app_source\">Source des applis</string>\n    <string name=\"view_mode\">Mode d\\'affichage</string>\n    <string name=\"grid\">Grille</string>\n    <string name=\"list\">Liste</string>\n    <string name=\"order\">Ordre :</string>\n    <string name=\"ascending\">Croissant</string>\n    <string name=\"descending\">Décroissant</string>\n    <string name=\"filters\">Filtres</string>\n    <string name=\"sort_by\">Trier par</string>\n\n    <!-- App Info Dialog -->\n    <string name=\"clear_app_data_title\">Effacer les données ?</string>\n    <string name=\"clear_app_data_desc\">Cela supprimera définitivement toutes les données de %1$s. Cette action est irréversible.</string>\n    <string name=\"clear_all_data\">Effacer toutes les données</string>\n    <string name=\"uninstall_system_app_title\">Désinstaller l\\'appli système ?</string>\n    <string name=\"uninstall_system_app_desc\">Cela vous permet de désinstaller les mises à jour ou de réinitialiser cette application système. Continuer ?</string>\n    <string name=\"risk_warning_title\">Avertissement de risque</string>\n    <string name=\"risk_warning_desc\">Ceci force l\\'enregistrement de l\\'installateur sur \\'Google Play Store\\'.\\n\\nLes mises à jour peuvent échouer si la signature ne correspond pas à la version officielle du store.</string>\n    <string name=\"reinstall_play_store_title\">Réinstaller avec Play Store ?</string>\n    <string name=\"reinstall_play_store_desc\">Cela tentera de réinstaller %1$s en utilisant le Google Play Store.</string>\n    <string name=\"fix_store\">Réparer Store</string>\n\n    <!-- App Status -->\n    <string name=\"status_split\">DIVISÉ</string>\n    <string name=\"status_frozen\">GELÉ</string>\n    <string name=\"status_suspended\">SUSPENDU</string>\n\n    <!-- App Actions -->\n    <string name=\"action_launch\">Lancer</string>\n    <string name=\"action_share\">Partager</string>\n    <string name=\"action_freeze\">Geler</string>\n    <string name=\"action_unfreeze\">Dégeler</string>\n    <string name=\"action_suspend\">Suspendre</string>\n    <string name=\"action_unsuspend\">Rétablir</string>\n    <string name=\"action_kill\">Arrêter</string>\n    <string name=\"action_cache\">Cache</string>\n    <string name=\"action_data\">Données</string>\n    <string name=\"action_uninstall\">Désinstaller</string>\n    <string name=\"action_reinstall\">Réinstaller</string>\n\n    <!-- Main Screen / Global -->\n    <string name=\"cannot_launch_app\">Impossible de lancer l\\'appli</string>\n    <string name=\"share_app\">Partager l\\'appli</string>\n    <string name=\"kill_app_title\">Arrêter l\\'appli ?</string>\n    <string name=\"kill_app_desc\">Arrêt forcé de %1$s ? Cela peut entraîner une perte de données.</string>\n    <string name=\"exit_thor_title\">Quitter Thor ?</string>\n    <string name=\"exit_thor_desc\">Voulez-vous vraiment fermer l\\'application ?</string>\n    <string name=\"are_you_sure\">Êtes-vous sûr ?</string>\n\n    <!-- Accessibility / Content Descriptions -->\n    <string name=\"cd_config\">Configuration</string>\n    <string name=\"cd_close\">Fermer</string>\n    <string name=\"cd_frozen\">Gelé</string>\n    <string name=\"cd_suspended\">Suspendu</string>\n    <string name=\"cd_selected\">Sélectionné</string>\n    <string name=\"cd_search\">Rechercher</string>\n    <string name=\"cd_clear\">Effacer</string>\n    <string name=\"cd_settings\">Paramètres</string>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-v31/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"Theme.Thor.SplashScreen\" parent=\"Theme.SplashScreen\">\n        <item name=\"windowSplashScreenAnimatedIcon\">@drawable/thor_animated</item>\n        <item name=\"android:windowSplashScreenBackground\">#000</item>\n        <item name=\"postSplashScreenTheme\">@style/Theme.Thor</item>\n    </style>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-zh-rCN/strings.xml",
    "content": "<resources>\n    <string name=\"title_activity_script_runner\">脚本运行器</string>\n    <string name=\"title_activity_home\">主页</string>\n\n    <!-- Navigation -->\n    <string name=\"home\">主页</string>\n    <string name=\"home_desc\">主页</string>\n    <string name=\"apps\">应用列表</string>\n    <string name=\"apps_desc\">显示所有可用应用程序的列表</string>\n    <string name=\"freezer\">应用冻结</string>\n    <string name=\"freezer_desc\">从这里冻结/解冻应用程序</string>\n    <string name=\"settings\">设置</string>\n    <string name=\"settings_desc\">设置页面以更改应用设置</string>\n    <string name=\"config_engine_v\">配置引擎 • v%1$s</string>\n    <string name=\"general\">通用</string>\n    <string name=\"show_reinstall_card\">显示重新安装卡片</string>\n    <string name=\"show_reinstall_card_desc\">在主屏幕上显示修复商店提醒</string>\n    <string name=\"appearance\">外观</string>\n    <string name=\"theme\">主题</string>\n    <string name=\"theme_desc\">视觉界面风格</string>\n    <string name=\"amoled_mode\">AMOLED 模式</string>\n    <string name=\"amoled_desc\">纯黑背景</string>\n    <string name=\"dynamic_colors\">动态色彩</string>\n    <string name=\"dynamic_colors_desc\">Material You 集成</string>\n    <string name=\"security\">安全</string>\n    <string name=\"biometric_lock\">生物识别锁定</string>\n    <string name=\"biometric_lock_desc\">启动时需要验证</string>\n    <string name=\"biometric_not_available\">生物识别未注册或不可用</string>\n    <string name=\"work_mode\">工作模式</string>\n    <string name=\"active_engine\">活动引擎</string>\n    <string name=\"active_engine_desc\">在可用提供者之间切换</string>\n    <string name=\"about\">关于</string>\n    <string name=\"version\">版本</string>\n    <string name=\"release_candidate\">发布候选版</string>\n    <string name=\"source_code\">源代码</string>\n    <string name=\"community\">社区</string>\n    <string name=\"kernel_status\">内核状态: 已优化</string>\n    <string name=\"built_with_precision\">为高级用户精准打造</string>\n    <string name=\"app_language\">应用语言</string>\n    <string name=\"select_language\">选择语言</string>\n    <string name=\"system_default\">系统默认</string>\n    <string name=\"english\">英语</string>\n    <string name=\"chinese\">中文</string>\n    <string name=\"french\">法语</string>\n    <string name=\"spanish\">西班牙语</string>\n    <string name=\"arabic\">阿拉伯语</string>\n\n    <!-- Common Actions -->\n    <string name=\"cancel\">取消</string>\n    <string name=\"confirm\">确认</string>\n    <string name=\"proceed\">继续</string>\n    <string name=\"yes\">是</string>\n    <string name=\"no\">否</string>\n    <string name=\"done\">完成</string>\n    <string name=\"close\">关闭</string>\n    <string name=\"refresh\">刷新</string>\n    <string name=\"dismiss\">关闭</string>\n    <string name=\"search_apps\">搜索应用&#8230;</string>\n    <string name=\"unknown\">未知</string>\n\n    <!-- Home Screen -->\n    <string name=\"reinstall_all\">全部重新安装</string>\n    <string name=\"reinstall_all_subtitle\">%1$d 个非来自 Play 商店的 %2$s 应用。要修复它们吗？</string>\n    <string name=\"install_from_file\">从文件安装</string>\n    <string name=\"install_from_file_subtitle\">安装 APK、XAPK、APKS 或分割包</string>\n    <string name=\"app_distribution\">应用分布</string>\n    <string name=\"total_apps\">总计: %1$d</string>\n    <string name=\"others\">其他</string>\n    <string name=\"clear_all_cache\">清除所有缓存</string>\n    <string name=\"clear_cache_prompt\">您想清除哪些应用？</string>\n    <string name=\"user_apps\">用户应用</string>\n    <string name=\"system_apps\">系统应用</string>\n    <string name=\"privilege_check\">权限检查</string>\n    <string name=\"active\">活跃</string>\n    <string name=\"frozen\">已冻结</string>\n    <string name=\"suspended\">已暂停</string>\n    <string name=\"privilege_check_desc\">Thor 需要 Root 或 Shizuku 权限才能正常运行。\\n\\n请在管理应用中授予权限并点击刷新。</string>\n\n    <!-- App List / Freezer -->\n    <string name=\"system_apps_warning\">⚠ 系统应用</string>\n    <string name=\"selected_count\">已选择 %1$d 个</string>\n    <string name=\"no_matching_apps\">未找到匹配的应用</string>\n    <string name=\"no_apps_display\">无应用可显示</string>\n    <string name=\"adjust_filters_hint\">尝试调整搜索或过滤器</string>\n    <string name=\"configuration\">配置</string>\n    <string name=\"app_source\">应用来源</string>\n    <string name=\"view_mode\">视图模式</string>\n    <string name=\"grid\">网格</string>\n    <string name=\"list\">列表</string>\n    <string name=\"order\">排序:</string>\n    <string name=\"ascending\">升序</string>\n    <string name=\"descending\">降序</string>\n    <string name=\"filters\">过滤器</string>\n    <string name=\"sort_by\">排序方式</string>\n\n    <!-- App Info Dialog -->\n    <string name=\"clear_app_data_title\">清除应用数据？</string>\n    <string name=\"clear_app_data_desc\">这将永久删除 %1$s 的所有数据。此操作无法撤销。</string>\n    <string name=\"clear_all_data\">清除所有数据</string>\n    <string name=\"uninstall_system_app_title\">卸载系统应用？</string>\n    <string name=\"uninstall_system_app_desc\">这允许您卸载更新或恢复此系统应用的出厂设置。继续吗？</string>\n    <string name=\"risk_warning_title\">风险警告</string>\n    <string name=\"risk_warning_desc\">这会强制将安装程序记录更改为“Google Play 商店”。\\n\\n如果签名与官方商店版本不匹配，更新可能会失败。</string>\n    <string name=\"reinstall_play_store_title\">使用 Play 商店重新安装？</string>\n    <string name=\"reinstall_play_store_desc\">这将尝试使用 Google Play 商店重新安装 %1$s。</string>\n    <string name=\"fix_store\">修复商店</string>\n\n    <!-- App Status -->\n    <string name=\"status_split\">分割</string>\n    <string name=\"status_frozen\">已冻结</string>\n    <string name=\"status_suspended\">已暂停</string>\n\n    <!-- App Actions -->\n    <string name=\"action_launch\">启动</string>\n    <string name=\"action_share\">分享</string>\n    <string name=\"action_freeze\">冻结</string>\n    <string name=\"action_unfreeze\">解冻</string>\n    <string name=\"action_suspend\">暂停</string>\n    <string name=\"action_unsuspend\">取消暂停</string>\n    <string name=\"action_kill\">结束</string>\n    <string name=\"action_cache\">缓存</string>\n    <string name=\"action_data\">数据</string>\n    <string name=\"action_uninstall\">卸载</string>\n    <string name=\"action_reinstall\">重新安装</string>\n\n    <!-- Main Screen / Global -->\n    <string name=\"cannot_launch_app\">无法启动应用</string>\n    <string name=\"share_app\">分享应用</string>\n    <string name=\"kill_app_title\">结束应用？</string>\n    <string name=\"kill_app_desc\">强制停止 %1$s？这可能会导致数据丢失。</string>\n    <string name=\"exit_thor_title\">退出 Thor？</string>\n    <string name=\"exit_thor_desc\">您确定要关闭应用程序吗？</string>\n    <string name=\"are_you_sure\">您确定吗？</string>\n\n    <!-- Accessibility / Content Descriptions -->\n    <string name=\"cd_config\">配置</string>\n    <string name=\"cd_close\">关闭</string>\n    <string name=\"cd_frozen\">已冻结</string>\n    <string name=\"cd_suspended\">已暂停</string>\n    <string name=\"cd_selected\">已选择</string>\n    <string name=\"cd_search\">搜索</string>\n    <string name=\"cd_clear\">清除</string>\n    <string name=\"cd_settings\">设置</string>\n\n</resources>"
  },
  {
    "path": "app/src/main/res/xml/backup_rules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n   Sample backup rules file; uncomment and customize as necessary.\n   See https://developer.android.com/guide/topics/data/autobackup\n   for details.\n   Note: This file is ignored for devices older than 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/locales_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<locale-config xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <locale android:name=\"en\" />\n    <locale android:name=\"zh-CN\" />\n    <locale android:name=\"fr\" />\n    <locale android:name=\"es\" />\n    <locale android:name=\"ar\" />\n</locale-config>"
  },
  {
    "path": "app/src/main/res/xml/provider_paths.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<paths>\n    <external-path\n        name=\"external_files\"\n        path=\".\" />\n    <files-path\n        name=\"files\"\n        path=\".\" />\n    <cache-path\n        name=\"cached_files\"\n        path=\".\" />\n    <root-path\n        name=\"root\"\n        path=\".\" />\n</paths>"
  },
  {
    "path": "app/src/test/java/com/valhalla/thor/ExampleUnitTest.kt",
    "content": "package com.valhalla.thor\n\nimport org.junit.Assert.assertEquals\nimport org.junit.Test\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\nclass ExampleUnitTest {\n    @Test\n    fun addition_isCorrect() {\n        assertEquals(4, 2 + 2)\n    }\n}"
  },
  {
    "path": "build.gradle.kts",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\nplugins {\n    alias(libs.plugins.android.application) apply false\n    alias(libs.plugins.kotlin.compose) apply false\n    alias(libs.plugins.kotlinSerialization) apply false\n    alias(libs.plugins.android.test) apply false\n    alias(libs.plugins.android.library) apply false\n    alias(libs.plugins.kotlinJvm) apply false\n}"
  },
  {
    "path": "bypass/.gitignore",
    "content": "/build"
  },
  {
    "path": "bypass/README.md",
    "content": "# bypass\n\nAn internal module that replaces\nthe [AndroidHiddenApiBypass](https://github.com/LSPosed/AndroidHiddenApiBypass) dependency for\naccessing Android's restricted (hidden) APIs at runtime.\n\n## Why this exists\n\nAndroid enforces hidden API restrictions via a denylist checked in `java.lang.reflect` and in the\nnative linker. The standard bypass technique calls `VMRuntime.setHiddenApiExemptions()` — a hidden\nmethod itself — before any restricted calls are made. Rather than pulling in an external AAR\ndependency for this single responsibility, `bypass` implements it directly with full control over\nthe exemption signatures.\n\n## Module structure\n\n```\n:bypass         — the runtime implementation (Bypass.kt)\n:vm-runtime     — compileOnly Java stubs for dalvik.system.VMRuntime\n```\n\n`:vm-runtime` is a plain `java-library` that provides stub classes so `:bypass` can reference\n`VMRuntime.setHiddenApiExemptions()` at compile time without those classes being on the normal\nclasspath. The real implementations are always present on-device.\n\n## API reference\n\nAll functionality lives in the `com.valhalla.bypass.Bypass` singleton.\n\n### Setup\n\n```kotlin\n// Call once, early in Application.onCreate()\nBypass.prepareThor()\n\n// Optional: wire in your own logger\nBypass.setLogger { message, throwable ->\n    Log.e(\"Bypass\", message, throwable)\n}\n```\n\n`prepareThor()` exempts the package prefixes Thor uses most:\n\n| Exempted prefix             | Covers                                    |\n|-----------------------------|-------------------------------------------|\n| `Landroid/app`              | ActivityManager, hidden app ops, etc.     |\n| `Landroid/content/pm`       | PackageManager internals, IPackageManager |\n| `Landroid/hardware/input`   | Input manager internals                   |\n| `Lcom/android/internal/app` | Internal app utilities                    |\n\n### Exemption methods\n\n```kotlin\n// Exempt specific signatures (Dalvik descriptor prefix format)\nBypass.addExemptions(\"Landroid/content/pm\", \"Lcom/android/internal\")\n\n// Exempt every hidden API (equivalent to HiddenApiBypass.addHiddenApiExemptions(\"L\"))\nBypass.exemptAll()\n```\n\nExemptions are additive and permanent for the process lifetime. Prefer `addExemptions()` with tight\nprefixes over `exemptAll()` in production builds.\n\n### Reflection helpers\n\nThese helpers provide a unified API for reflection on hidden members. Ensure `prepareThor()` or\n`exemptAll()` is called first to allow hidden API access.\n\n```kotlin\n// Call a hidden method\nval result = Bypass.invoke(\n    clazz    = ActivityManager::class.java,\n    instance = activityManagerInstance,\n    methodName = \"getRunningServiceControlPanel\",\n    intent\n)\n\n// Get a declared Method object (already set accessible)\nval method: Method? = Bypass.getDeclaredMethod(\n    SomeHiddenClass::class.java,\n    \"hiddenMethodName\",\n    String::class.java, Int::class.java   // parameter types\n)\n\n// Read a field value (bypasses access checks)\nval value: Any? = Bypass.getField(instance, \"mHiddenField\")\n\n// Instantiate a class with a hidden constructor\nval obj = Bypass.newInstance(HiddenClass::class.java, arg1, arg2)\n```\n\n## Migration from AndroidHiddenApiBypass\n\n| AndroidHiddenApiBypass                                   | bypass equivalent                               |\n|----------------------------------------------------------|-------------------------------------------------|\n| `HiddenApiBypass.addHiddenApiExemptions(\"L\")`            | `Bypass.exemptAll()`                            |\n| `HiddenApiBypass.addHiddenApiExemptions(\"Landroid/app\")` | `Bypass.addExemptions(\"Landroid/app\")`          |\n| `HiddenApiBypass.invoke(clazz, obj, method, args)`       | `Bypass.invoke(clazz, obj, method, args)`       |\n| `HiddenApiBypass.getDeclaredMethod(clazz, name, params)` | `Bypass.getDeclaredMethod(clazz, name, params)` |\n| `HiddenApiBypass.newInstance(clazz, args)`               | `Bypass.newInstance(clazz, args)`               |\n\n## Usage in the project\n\nAdd the module dependency:\n\n```kotlin\n// in your module's build.gradle.kts\nimplementation(project(\":bypass\"))\n```\n\n`:vm-runtime` must **not** be added as a runtime dependency — it is only needed as `compileOnly`\ninside `:bypass` itself and is already declared there.\n\n## How it works\n\n1. **`VMRuntime.setHiddenApiExemptions()`** — the primary path. `VMRuntime` is itself a hidden\n   class; `:vm-runtime` provides a compile-time stub in the `dalvik.system` package so the call\n   compiles. At runtime the real `dalvik.system.VMRuntime` on the device is used, and calling\n   `setHiddenApiExemptions` with a set of Dalvik descriptor prefixes whitelists all matching members\n   for the current process.\n\n2. **Reflection-based access** — once exemptions are added, standard reflection (\n   `getDeclaredMethod`, `getDeclaredField`, etc.) works even for hidden members.\n"
  },
  {
    "path": "bypass/build.gradle.kts",
    "content": "import org.jetbrains.kotlin.gradle.dsl.JvmTarget\n\nplugins {\n    alias(libs.plugins.android.library)\n}\n\nkotlin {\n    compilerOptions {\n        jvmTarget.set(JvmTarget.JVM_21)\n    }\n}\n\n\nandroid {\n    namespace = \"com.valhalla.bypass\"\n    compileSdk = 37\n\n    defaultConfig {\n        minSdk = 28\n\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n        consumerProguardFiles(\"consumer-rules.pro\")\n    }\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_21\n        targetCompatibility = JavaVersion.VERSION_21\n    }\n\n    buildFeatures {\n        buildConfig = true\n    }\n}\n\ndependencies {\n    implementation(libs.androidx.core.ktx)\n    compileOnly(project(\":vm-runtime\"))\n}"
  },
  {
    "path": "bypass/consumer-rules.pro",
    "content": ""
  },
  {
    "path": "bypass/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile"
  },
  {
    "path": "bypass/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest />"
  },
  {
    "path": "bypass/src/main/java/com/valhalla/bypass/Bypass.kt",
    "content": "package com.valhalla.bypass\n\nimport android.annotation.SuppressLint\nimport com.valhalla.bypass.Bypass.exemptAll\nimport com.valhalla.bypass.Bypass.prepareThor\nimport dalvik.system.VMRuntime\nimport java.lang.reflect.Method\n\n/**\n * Modern implementation of Hidden API Bypass for Thor.\n * Integrated directly as a core module to reduce external dependencies.\n */\n@SuppressLint(\"DiscouragedPrivateApi\")\nobject Bypass {\n\n    private var logger: ((String, Throwable?) -> Unit)? = null\n\n    /**\n     * Set a custom logger to trace bypass operations without creating a direct\n     * dependency on the app module's logger.\n     */\n    fun setLogger(logger: (String, Throwable?) -> Unit) {\n        this.logger = logger\n    }\n\n    private val runtime: VMRuntime by lazy { VMRuntime.getRuntime() }\n\n    /**\n     * Consolidates common exemptions used across the Thor project.\n     * This avoids having to call addExemptions in multiple places.\n     */\n    fun prepareThor() {\n        addExemptions(\n            \"Landroid/app\",\n            \"Landroid/content/pm\",\n            \"Landroid/hardware/input\",\n            \"Lcom/android/internal/app\"\n        )\n    }\n\n    /**\n     * Exempts specific signatures or the entire app from hidden API restrictions.\n     * Use \"L\" to exempt everything (default).\n     */\n    fun addExemptions(vararg signatures: String) {\n        runCatching {\n            runtime.setHiddenApiExemptions(*signatures)\n        }.onFailure {\n            logger?.invoke(\"Failed to add exemptions for: ${signatures.joinToString()}\", it)\n        }\n    }\n\n    /**\n     * Convenience to exempt everything (replicates original HiddenApiBypass.addHiddenApiExemptions(\"L\"))\n     */\n    fun exemptAll() {\n        addExemptions(\"L\")\n    }\n\n    /**\n     * Advanced: Reflection-based invocation of hidden methods without globally exempting.\n     * Ensure [prepareThor] or [exemptAll] is called first to allow hidden API access.\n     */\n    fun <T> invoke(\n        clazz: Class<*>,\n        instance: Any?,\n        methodName: String,\n        vararg args: Any?\n    ): T {\n        val paramTypes = args.map { it?.javaClass ?: Any::class.java }.toTypedArray()\n        return invoke(clazz, instance, methodName, paramTypes, *args)\n    }\n\n    /**\n     * Advanced: Reflection-based invocation with explicit parameter types.\n     * @throws NoSuchMethodException if the method cannot be resolved.\n     */\n    fun <T> invoke(\n        clazz: Class<*>,\n        instance: Any?,\n        methodName: String,\n        paramTypes: Array<out Class<*>>,\n        vararg args: Any?\n    ): T {\n        val method = getDeclaredMethod(clazz, methodName, *paramTypes)\n        @Suppress(\"UNCHECKED_CAST\")\n        return method.invoke(instance, *args) as T\n    }\n\n    /**\n     * Instantiates a class using reflection.\n     */\n    fun <T> newInstance(clazz: Class<*>, vararg args: Any?): T {\n        val paramTypes = args.map { it?.javaClass ?: Any::class.java }.toTypedArray()\n        return newInstance(clazz, paramTypes, *args)\n    }\n\n    /**\n     * Instantiates a class using reflection with explicit parameter types.\n     * @throws NoSuchMethodException if the constructor cannot be resolved.\n     */\n    fun <T> newInstance(clazz: Class<*>, paramTypes: Array<out Class<*>>, vararg args: Any?): T {\n        val constructor = getDeclaredConstructor(clazz, *paramTypes)\n        @Suppress(\"UNCHECKED_CAST\")\n        return constructor.newInstance(*args) as T\n    }\n\n    private fun getDeclaredConstructor(\n        clazz: Class<*>,\n        vararg parameterTypes: Class<*>\n    ): java.lang.reflect.Constructor<*> {\n        val exactConstructor = runCatching {\n            clazz.getDeclaredConstructor(*parameterTypes).apply {\n                isAccessible = true\n            }\n        }.getOrNull()\n        if (exactConstructor != null) return exactConstructor\n\n        // Fallback for compatible types (primitives, subtypes, nulls)\n        return runCatching {\n            clazz.declaredConstructors.find { constructor ->\n                constructor.parameterCount == parameterTypes.size &&\n                        constructor.parameterTypes.zip(parameterTypes).all { (declared, provided) ->\n                            isCompatible(declared, provided)\n                        }\n            }?.apply { isAccessible = true }\n        }.getOrNull() ?: throw NoSuchMethodException(\"Constructor not found on ${clazz.name}\")\n    }\n\n    /**\n     * Finds a method and ensures it is accessible.\n     * Traverses the class hierarchy to find methods in superclasses.\n     * @throws NoSuchMethodException if the method cannot be resolved.\n     */\n    fun getDeclaredMethod(\n        clazz: Class<*>,\n        name: String,\n        vararg parameterTypes: Class<*>\n    ): Method {\n        var current: Class<*>? = clazz\n        while (current != null) {\n            val exactMethod = runCatching {\n                current.getDeclaredMethod(name, *parameterTypes).apply {\n                    isAccessible = true\n                }\n            }.getOrNull()\n            if (exactMethod != null) return exactMethod\n\n            // Fallback for compatible types (primitives, subtypes, nulls)\n            val fallbackMethod = runCatching {\n                current.declaredMethods.find { method ->\n                    method.name == name &&\n                            method.parameterCount == parameterTypes.size &&\n                            method.parameterTypes.zip(parameterTypes).all { (declared, provided) ->\n                                isCompatible(declared, provided)\n                            }\n                }?.apply { isAccessible = true }\n            }.getOrNull()\n            if (fallbackMethod != null) return fallbackMethod\n\n            current = current.superclass\n        }\n        throw NoSuchMethodException(\"Method $name not found on ${clazz.name}\")\n    }\n\n    private fun isCompatible(declared: Class<*>, provided: Class<*>): Boolean {\n        if (provided == Any::class.java) return !declared.isPrimitive\n        if (declared.isAssignableFrom(provided)) return true\n        if (declared.isPrimitive) {\n            return when (provided) {\n                Int::class.javaObjectType -> declared == Int::class.javaPrimitiveType\n                Boolean::class.javaObjectType -> declared == Boolean::class.javaPrimitiveType\n                Long::class.javaObjectType -> declared == Long::class.javaPrimitiveType\n                Double::class.javaObjectType -> declared == Double::class.javaPrimitiveType\n                Float::class.javaObjectType -> declared == Float::class.javaPrimitiveType\n                Byte::class.javaObjectType -> declared == Byte::class.javaPrimitiveType\n                Char::class.javaObjectType -> declared == Char::class.javaPrimitiveType\n                Short::class.javaObjectType -> declared == Short::class.javaPrimitiveType\n                else -> false\n            }\n        }\n        return false\n    }\n\n    /**\n     * Directly get a field bypassing access checks.\n     * Supports both instance and static fields (by passing the Class as instance).\n     * Traverses the class hierarchy to find fields in superclasses.\n     * @throws NoSuchFieldException if the field cannot be found.\n     */\n    fun <T> getField(instance: Any, fieldName: String): T {\n        val target = if (instance is Class<*>) null else instance\n        var clazz: Class<*>? = instance as? Class<*> ?: instance.javaClass\n\n        while (clazz != null) {\n            try {\n                val field = clazz.getDeclaredField(fieldName)\n                field.isAccessible = true\n                @Suppress(\"UNCHECKED_CAST\")\n                return field.get(target) as T\n            } catch (e: NoSuchFieldException) {\n                clazz = clazz.superclass\n            }\n        }\n        throw NoSuchFieldException(\"Field $fieldName not found on ${if (instance is Class<*>) instance.name else instance.javaClass.name}\")\n    }\n}\n"
  },
  {
    "path": "fastlane/Appfile",
    "content": "package_name(\"com.valhalla.thor\")\n\n# DYNAMIC KEY PATH\n# 1. Tries to use ENV[\"JSON_KEY_FILE\"] (CI/CD)\n# 2. Falls back to path relative to this file (Local)\njson_key_file(\n  ENV[\"JSON_KEY_FILE\"] ||\n  File.expand_path(\"../app/google-play-api.json\", __dir__)\n)"
  },
  {
    "path": "fastlane/Fastfile",
    "content": "default_platform(:android)\n\nplatform :android do\n\n  # --- HELPER: Common Build Logic ---\n  # UPDATED: Now accepts 'track' to distinguish between Internal (Dev) and Alpha/Closed (Prod)\n  def prepare_release_artifacts(upload_to_store: false, track: 'internal', version_code: nil)\n    # 1. PREPARATION\n    fastlane_dir = Dir.pwd\n    project_root = File.expand_path(\"..\", fastlane_dir)\n    gradle_properties_path = File.join(project_root, \"gradle.properties\")\n\n    # 2. CONFIGURATION & CHECKS\n    UI.message(\"📝 Reading configuration...\")\n    unless File.exist?(gradle_properties_path)\n      UI.user_error!(\"❌ gradle.properties not found at #{gradle_properties_path}\")\n    end\n\n    # Parse properties\n    properties = File.readlines(gradle_properties_path)\n                     .map(&:strip)\n                     .reject { |l| l.empty? || l.start_with?('#') }\n                     .map { |l| l.split('=', 2).map(&:strip) }\n                     .to_h\n\n    # 3. DETERMINE VERSION CODE\n    final_version_code = nil\n\n    if version_code\n      final_version_code = version_code.to_i\n      UI.important(\"⚠️  Using MANUAL version code override: #{final_version_code}\")\n    else\n      final_version_code = properties[\"versionCode\"]&.to_i\n      UI.user_error!(\"❌ 'versionCode' missing in gradle.properties\") unless final_version_code\n      UI.message(\"✅ Using defined version code from properties: #{final_version_code}\")\n    end\n\n    # 4. GET VERSION NAME\n    new_version_name = \"\"\n    Dir.chdir(project_root) do\n      new_version_name = sh(\"./gradlew -q app:printVersionName -PversionCode=#{final_version_code}\").strip\n    end\n\n    UI.message(\"📦 Build Target: v#{new_version_name} (#{final_version_code}) - Track: #{track}\")\n\n    # Save for GitHub Actions steps\n    File.write(File.join(project_root, \"version_name.txt\"), new_version_name)\n    File.write(File.join(project_root, \"version_code.txt\"), final_version_code.to_s)\n\n    # 5. BUILD ARTIFACTS\n    # We build APKs (for GitHub/Telegram) AND Bundle (for Play Store)\n    build_tasks = \"clean copyStoreReleaseApk copyFossReleaseApk\"\n    build_tasks += \" bundleStoreRelease\" if upload_to_store\n\n    gradle(\n      task: build_tasks,\n      project_dir: project_root,\n      properties: {\n        \"versionCode\" => final_version_code,\n        \"android.injected.version.code\" => final_version_code\n      }\n    )\n\n    # 6. DEPLOY (Conditional)\n    if upload_to_store\n      generated_aab_path = lane_context[SharedValues::GRADLE_AAB_OUTPUT_PATH]\n      UI.user_error!(\"❌ No AAB generated\") unless generated_aab_path\n\n      # Upload to specific track (internal or alpha/closed)\n      upload_to_play_store(\n        track: track,\n        release_status: 'completed', # Draft or completed? usually completed for internal/alpha\n        aab: generated_aab_path,\n        skip_upload_metadata: true,\n        skip_upload_images: true,\n        skip_upload_screenshots: true,\n        skip_upload_apk: true\n      )\n      UI.success(\"🚀 Deployed to Play Store [#{track}]!\")\n    else\n      UI.success(\"📦 Artifacts built successfully (Upload skipped)\")\n    end\n  end\n\n  # --- LANES ---\n\n  desc \"Dev: Build -> Play Store (Internal) -> Telegram -> GitHub Pre-release\"\n  lane :distribute_dev do\n    prepare_release_artifacts(upload_to_store: true, track: 'internal')\n  end\n\n  desc \"Prod: Build -> Play Store (Closed/Alpha) -> GitHub Release\"\n  lane :distribute_production do\n    # 'alpha' is the standard track key for \"Closed Testing\" in Google Play Console\n    prepare_release_artifacts(upload_to_store: true, track: 'alpha')\n  end\n\n  # Helper for manual triggering\n  desc \"Build Release Candidates Only (No Store)\"\n  lane :build_release_candidates do |options|\n    prepare_release_artifacts(\n        upload_to_store: false,\n        version_code: options[:version_code]\n    )\n  end\nend"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/1600.txt",
    "content": "Major backend improvements for root operations\nInitial support for Android 16\nNew filters: filter apps by state or installation source\nSorting options: sort apps by name, install date, last updated, version, etc.,\nRemoved integrity checks, downloadable fonts, and unused dependencies\nAdded new grid view for app lists\nSwitched to Material Expressive Theme\nFrozen app icons are now greyed out\n\nBug Fixes:\nFixed system app uninstaller bug; system apps can now be uninstalled individually"
  },
  {
    "path": "fastlane/metadata/android/en-US/full_description.txt",
    "content": "<p><b>Thor App Manager</b> is a modern, open-source Android app manager and installer, designed for power users and enthusiasts who value control, privacy, and efficiency.</p>\n\n<p>Thor is built 100% in Kotlin, following Material 3 design principles and leveraging Jetpack Compose for a smooth, responsive user experience. With a focus on minimalism, Thor delivers a full-featured app management suite in an APK smaller than 2.20 MB.</p>\n\n<p><b>Key Features:</b></p>\n<ul>\n  <li>Fully reproducible, copylefted libre software (GPLv3.0)</li>\n  <li>Material 3 UI with dynamic color support</li>\n  <li>Displays installed apps with advanced sorting and filtering (including by installation source)</li>\n  <li>Launch app activities directly</li>\n  <li>Install, uninstall, freeze, and unfreeze apps (including system apps)</li>\n  <li>Reinstall APKs or apps via Google Play</li>\n  <li>Share APK files easily</li>\n  <li>Batch operations: reinstall, uninstall, or kill multiple apps at once</li>\n  <li>Split APK indicator for apps using multiple APKs</li>\n  <li>App state indicator (e.g., frozen = disabled)</li>\n  <li>Uninstall and freeze/unfreeze system apps</li>\n  <li>Lightweight and fast, with a focus on privacy and no analytics or trackers</li>\n</ul>\n\n<p><b>Features in Testing:</b></p>\n<ul>\n  <li>New settings UI</li>\n  <li>Packages.xml editor (based on community scripts)</li>\n</ul>\n\n<p><b>Upcoming Features:</b></p>\n<ul>\n  <li>Overall application overview (new home UI)</li>\n  <li>Advanced Packages.xml editing</li>\n  <li>App installer and batch install</li>\n  <li>App data backup</li>\n  <li>Option to choose installers when reinstalling</li>\n  <li>And many more enhancements</li>\n</ul>\n\n<p><b>Technical Highlights:</b></p>\n<ul>\n  <li>100% Kotlin codebase for modern Android development</li>\n  <li>Smallest possible APK size for a full-featured app manager</li>\n  <li>Uses a modularized, fully Kotlin-converted version of <a href=\"https://github.com/topjohnwu/libsu\">libsu</a> (as <i>suCore</i>) for root operations</li>\n</ul>\n\n<p><b>Credits:</b></p>\n<ul>\n  <li>Portions of this app use code from <a href=\"https://github.com/topjohnwu/libsu\">libsu</a> by topjohnwu, adapted and integrated as the <a href=\"https://github.com/trinadhthatakula/Thor/tree/master/suCore\">suCore</a> module.</li>\n  <li>libsu was fully converted from Java to Kotlin and modularized for Thor, with significant code size reduction and optimizations.</li>\n</ul>\n\n<p><b>License:</b></p>\n<ul>\n  <li>Thor App Manager is licensed under the GNU General Public License v3.0 (GPL-3.0).</li>\n  <li>libsu is licensed under the Apache License 2.0. All modifications and usage comply with Apache-2.0 requirements.</li>\n</ul>\n\n<p>No analytics, no trackers, no nonsense. Thor is free and open source—forever.</p>"
  },
  {
    "path": "fastlane/metadata/android/en-US/short_description.txt",
    "content": "Android App Manager and App Installer utility"
  },
  {
    "path": "fastlane/metadata/android/en-US/title.txt",
    "content": "Thor - App Manager"
  },
  {
    "path": "gradle/gradle-daemon-jvm.properties",
    "content": "#This file is generated by updateDaemonJvm\ntoolchainUrl.FREE_BSD.AARCH64=https\\://api.foojay.io/disco/v3.0/ids/c5760d82d08e6c26884debb23736ea57/redirect\ntoolchainUrl.FREE_BSD.X86_64=https\\://api.foojay.io/disco/v3.0/ids/879378f84c64b2c76003b97a32968399/redirect\ntoolchainUrl.LINUX.AARCH64=https\\://api.foojay.io/disco/v3.0/ids/ff1d4fc92bcfc9d3799beabb4e70cfa3/redirect\ntoolchainUrl.LINUX.X86_64=https\\://api.foojay.io/disco/v3.0/ids/08ce182188ada0b93565cd9ca4a4ab32/redirect\ntoolchainUrl.MAC_OS.AARCH64=https\\://api.foojay.io/disco/v3.0/ids/021e528cbed860c875a9016f29ee13c1/redirect\ntoolchainUrl.MAC_OS.X86_64=https\\://api.foojay.io/disco/v3.0/ids/ee5178090598fb4291558827b9f00e0d/redirect\ntoolchainUrl.UNIX.AARCH64=https\\://api.foojay.io/disco/v3.0/ids/c5760d82d08e6c26884debb23736ea57/redirect\ntoolchainUrl.UNIX.X86_64=https\\://api.foojay.io/disco/v3.0/ids/879378f84c64b2c76003b97a32968399/redirect\ntoolchainUrl.WINDOWS.AARCH64=https\\://api.foojay.io/disco/v3.0/ids/3dc48436acf46a9c2958682158988183/redirect\ntoolchainUrl.WINDOWS.X86_64=https\\://api.foojay.io/disco/v3.0/ids/cd15c9dc71cc4176d084ef04cfa97a5e/redirect\ntoolchainVersion=21\n"
  },
  {
    "path": "gradle/libs.versions.toml",
    "content": "[versions]\naccompanistDrawablepainter = \"0.37.3\"\nagp = \"9.2.0-rc01\"\nbiometric = \"1.4.0-alpha02\"\ndatastorePreferences = \"1.2.1\"\nkotlin = \"2.3.20\"\ncoreKtx = \"1.18.0\"\njunit = \"4.13.2\"\nroom = \"2.8.4\"\nksp = \"2.3.6\"\njunitVersion = \"1.3.0\"\nespressoCore = \"3.7.0\"\nlifecycleRuntimeKtx = \"2.10.0\"\nactivityCompose = \"1.13.0\"\ncomposeBom = \"2026.03.01\"\nlottieCompose = \"6.7.1\"\nmaterial3 = \"1.5.0-alpha17\"\nkotlinxSerializationJson = \"1.11.0\"\nsplashscreen = \"1.2.0\"\nshizuku = \"13.1.5\"\ndhizuku = \"2.5.4\"\nkoin = \"4.2.1\"\ncoil3 = \"3.4.0\"\nnavigationCompose = \"2.9.7\"\n\n[libraries]\naccompanist-drawablepainter = { module = \"com.google.accompanist:accompanist-drawablepainter\", version.ref = \"accompanistDrawablepainter\" }\n#Core\nandroidx-core-ktx = { group = \"androidx.core\", name = \"core-ktx\", version.ref = \"coreKtx\" }\nandroidx-datastore-preferences = { module = \"androidx.datastore:datastore-preferences\", version.ref = \"datastorePreferences\" }\nandroidx-splashscreen = { group = \"androidx.core\", name = \"core-splashscreen\", version.ref = \"splashscreen\" }\njunit = { group = \"junit\", name = \"junit\", version.ref = \"junit\" }\nandroidx-junit = { group = \"androidx.test.ext\", name = \"junit\", version.ref = \"junitVersion\" }\nandroidx-espresso-core = { group = \"androidx.test.espresso\", name = \"espresso-core\", version.ref = \"espressoCore\" }\nandroidx-biometric = { module = \"androidx.biometric:biometric-ktx\", version.ref = \"biometric\" }\n#Compose\nandroidx-activity-compose = { group = \"androidx.activity\", name = \"activity-compose\", version.ref = \"activityCompose\" }\nandroidx-compose-bom = { group = \"androidx.compose\", name = \"compose-bom\", version.ref = \"composeBom\" }\nandroidx-ui = { group = \"androidx.compose.ui\", name = \"ui\" }\nandroidx-ui-graphics = { group = \"androidx.compose.ui\", name = \"ui-graphics\" }\nandroidx-ui-tooling = { group = \"androidx.compose.ui\", name = \"ui-tooling\" }\nandroidx-ui-tooling-preview = { group = \"androidx.compose.ui\", name = \"ui-tooling-preview\" }\nandroidx-ui-test-manifest = { group = \"androidx.compose.ui\", name = \"ui-test-manifest\" }\nandroidx-ui-test-junit4 = { group = \"androidx.compose.ui\", name = \"ui-test-junit4\" }\nandroidx-material3 = { group = \"androidx.compose.material3\", name = \"material3\", version.ref = \"material3\" }\n#Navigation\nandroidx-navigation-compose = { group = \"androidx.navigation\", name = \"navigation-compose\", version.ref = \"navigationCompose\" }\n#Lifecycle\nandroidx-lifecycle-runtime-ktx = { group = \"androidx.lifecycle\", name = \"lifecycle-runtime-ktx\", version.ref = \"lifecycleRuntimeKtx\" }\nandroidx-lifecycle-runtime-compose = { group = \"androidx.lifecycle\", name = \"lifecycle-runtime-compose\", version.ref = \"lifecycleRuntimeKtx\" }\nandroidx-lifecycle-viewmodel-compose = { group = \"androidx.lifecycle\", name = \"lifecycle-viewmodel-compose\", version.ref = \"lifecycleRuntimeKtx\" }\n#Room DB\nroom-runtime = { module = \"androidx.room:room-runtime\", version.ref = \"room\" }\nroom-ktx = { module = \"androidx.room:room-ktx\", version.ref = \"room\" }\nroom-compiler = { module = \"androidx.room:room-compiler\", version.ref = \"room\" }\n#Kotlin\nkotlinx-serialization-json = { module = \"org.jetbrains.kotlinx:kotlinx-serialization-json\", version.ref = \"kotlinxSerializationJson\" }\n#Lottie\nlottie-compose = { module = \"com.airbnb.android:lottie-compose\", version.ref = \"lottieCompose\" }\n#Shizuku\nshizuku-api = { module = \"dev.rikka.shizuku:api\", version.ref = \"shizuku\" }\nshizuku-provider = { module = \"dev.rikka.shizuku:provider\", version.ref = \"shizuku\" }\ndhizuku-api = { module = \"io.github.iamr0s:Dhizuku-API\", version.ref = \"dhizuku\" }\n#Koin\nkoin-android = { module = \"io.insert-koin:koin-android\", version.ref = \"koin\" }\nkoin-androidx-compose = { module = \"io.insert-koin:koin-androidx-compose\", version.ref = \"koin\" }\nkoin-androidx-startup = { module = \"io.insert-koin:koin-androidx-startup\", version.ref = \"koin\" }\n#Coil\ncoil-compose = { module = \"io.coil-kt.coil3:coil-compose\", version.ref = \"coil3\" }\n\n[plugins]\nandroid-application = { id = \"com.android.application\", version.ref = \"agp\" }\nkotlin-compose = { id = \"org.jetbrains.kotlin.plugin.compose\", version.ref = \"kotlin\" }\nkotlinJvm = { id = \"org.jetbrains.kotlin.jvm\", version.ref = \"kotlin\" }\nkotlinSerialization = { id = \"org.jetbrains.kotlin.plugin.serialization\", version.ref = \"kotlin\" }\nandroid-test = { id = \"com.android.test\", version.ref = \"agp\" }\nroom = { id = \"androidx.room\", version.ref = \"room\" }\nksp = { id = \"com.google.devtools.ksp\", version.ref = \"ksp\" }\nandroid-library = { id = \"com.android.library\", version.ref = \"agp\" }\n\n[bundles]\nkoin = [\n    \"koin-android\",\n    \"koin-androidx-startup\",\n    \"koin-androidx-compose\",\n]\ncoil = [\n    \"coil-compose\",\n]\nroom = [\n    \"room-runtime\",\n    \"room-ktx\",\n    \"room-compiler\"\n]"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Fri Feb 27 20:39:07 IST 2026\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.4.1-bin.zip\nnetworkTimeout=10000\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties",
    "content": "# -----------------------------------------------------------------------------\n# PERFORMANCE (THE BASICS)\n# -----------------------------------------------------------------------------\norg.gradle.configuration-cache=true\norg.gradle.caching=true\norg.gradle.parallel=true\norg.gradle.vfs.watch=true\n# -----------------------------------------------------------------------------\n# MEMORY TUNING (UPDATED FOR RESPONSIVENESS)\n# -----------------------------------------------------------------------------\n# Switched to G1GC based on your preference for system responsiveness.\n# G1GC handles large heaps better and avoids long \"stop-the-world\" pauses.\n# Note: G1GC performs best with more breathing room.\n# If you have 32GB RAM, bump this to -Xmx6g or -Xmx8g.\norg.gradle.jvmargs=-Xmx4g -XX:+UseG1GC -XX:MaxMetaspaceSize=1g\n# Kotlin Daemon: Separate from Gradle daemon.\nkotlin.daemon.jvmargs=-Xmx2g -XX:+UseG1GC\n# -----------------------------------------------------------------------------\n# R8 / SHRINKER OPTIMIZATIONS (RUTHLESS)\n# -----------------------------------------------------------------------------\nandroid.r8.strictFullModeForKeepRules=true\nandroid.r8.optimizedResourceShrinking=true\nandroid.enableR8.fullMode=true\n# -----------------------------------------------------------------------------\n# KOTLIN & COMPOSE (KOTLIN 2.0 ERA)\n# -----------------------------------------------------------------------------\nkotlin.code.style=official\nkotlin.incremental=true\nkotlin.parallel.tasks.in.project=true\n# -----------------------------------------------------------------------------\n# KSP (SINCE YOU USE KOIN)\n# -----------------------------------------------------------------------------\nksp.incremental=true\n# ksp.use.k2=true\n# -----------------------------------------------------------------------------\n# ANDROID BUILD FEATURES\n# -----------------------------------------------------------------------------\nandroid.nonTransitiveRClass=true\nandroid.useAndroidX=true\n# -----------------------------------------------------------------------------\n# LOGGING\n# -----------------------------------------------------------------------------\norg.gradle.warning.mode=all\norg.gradle.welcome=never\n# -----------------------------------------------------------------------------\n# APPLICATION VERSIONING (REPRODUCIBLE BUILD SETUP)\n# -----------------------------------------------------------------------------\n# Kept as fallback for clean builds\ninitialVersionCode=1806\n# REQUIRED: The active source of truth for your release manager\n# Logic: 1801 -> 1.80.1 (based on math: code/1000 . code%1000/10 . code%10)\nversionCode=1806\nversionName=1.80.6\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": "settings.gradle.kts",
    "content": "pluginManagement {\n    repositories {\n        google {\n            content {\n                includeGroupByRegex(\"com\\\\.android.*\")\n                includeGroupByRegex(\"com\\\\.google.*\")\n                includeGroupByRegex(\"androidx.*\")\n            }\n        }\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\nplugins {\n    id(\"org.gradle.toolchains.foojay-resolver-convention\") version \"1.0.0\"\n}\ndependencyResolutionManagement {\n    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)\n    repositories {\n        google()\n        mavenCentral()\n        maven(\"https://jitpack.io\")\n    }\n}\n\nbuildCache {\n    local {\n        isEnabled = true\n    }\n}\n\nrootProject.name = \"Thor\"\ninclude(\":app\")\ninclude(\":suCore\")\ninclude(\":bypass\")\ninclude(\":vm-runtime\")\n"
  },
  {
    "path": "suCore/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "suCore/README.md",
    "content": "# suCore\n\nThis module is based on the `core` module from the [libsu](https://github.com/topjohnwu/libsu)\nopen-source library by [topjohnwu](https://github.com/topjohnwu).  \nOriginal project: https://github.com/topjohnwu/libsu\n\n## Credits\n\n- The original implementation and design are credited to\n  the [libsu](https://github.com/topjohnwu/libsu) project and its contributors.\n\n## Changes Made\n\n- Entire codebase has been converted from Java to Kotlin for improved readability and\n  maintainability.\n- Removed usage of Android `Context` as a static field to prevent potential memory leaks.\n- Refactored code to follow Kotlin best practices and idioms.\n\n## ⚠️ Caution\n\nThe changes made here are specifically to support the Thor project and may not be compatible with\nother use cases. please use the original [libsu](https://github.com/topjohnwu/libsu) project for all\nyour purposes.\n\n## License\n\n- The original [libsu](https://github.com/topjohnwu/libsu) project is licensed under the Apache\n  License 2.0. All modifications and usage in this module comply with the Apache-2.0 requirements.\n- This module, as part of the Thor project, is distributed under the GNU General Public License\n  v3.0 (GPL-3.0).\n- See the [LICENSE](../LICENSE) file for the full license text."
  },
  {
    "path": "suCore/build.gradle.kts",
    "content": "import org.jetbrains.kotlin.gradle.dsl.JvmTarget\n\nplugins {\n    id(\"com.android.library\")\n}\n\nkotlin {\n    compilerOptions {\n        jvmTarget.set(JvmTarget.JVM_21)\n    }\n}\n\nandroid {\n    namespace = \"com.valhalla.superuser\"\n    compileSdk = 36\n    defaultConfig {\n        minSdk = 24\n        consumerProguardFiles(\"proguard-rules.pro\")\n    }\n    buildTypes {\n        create(\"foss_release\") {\n        }\n    }\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_21\n        targetCompatibility = JavaVersion.VERSION_21\n    }\n    buildFeatures {\n        buildConfig = true\n        aidl = true\n    }\n}\n\ndependencies {\n    implementation(libs.androidx.core.ktx)\n}\n"
  },
  {
    "path": "suCore/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n\n# Ignore missing service definitions that are not relevant for Android runtime\n-dontwarn javax.annotation.processing.Processor\n-dontwarn javax.annotation.Nullable\n\n# Make sure R8/Proguard don't break things\n-keep,allowobfuscation class * extends com.valhalla.superuser.Shell$Initializer { *; }\n"
  },
  {
    "path": "suCore/src/main/AndroidManifest.xml",
    "content": "<manifest />\n"
  },
  {
    "path": "suCore/src/main/aidl/com/valhalla/superuser/ipc/IIPC.aidl",
    "content": "package com.valhalla.superuser.ipc;\n\ninterface IIPC {\n    IBinder getService(String name);\n}\n"
  },
  {
    "path": "suCore/src/main/java/com/valhalla/superuser/CallbackList.kt",
    "content": "package com.valhalla.superuser\n\nimport com.valhalla.superuser.internal.UiThreadHandler\nimport java.util.AbstractList\nimport java.util.concurrent.Executor\n\n/**\n * An [AbstractList] that calls `onAddElement` when a new element is added to the list.\n *\n *\n * To simplify the API of [Shell], both STDOUT and STDERR will output to [List]s.\n * This class is useful if you want to trigger a callback every time [Shell]\n * outputs a new line.\n *\n *\n * The `CallbackList` itself does not have a data store. If you need one, you can provide a\n * base [List], and this class will delegate its calls to it.\n */\nabstract class CallbackList<E>\n/**\n * [.onAddElement] runs with the executor; no backing list.\n */ protected constructor(\n    protected var mExecutor: Executor = UiThreadHandler.executor,\n    protected var mBase: MutableList<E?>? = null\n) : AbstractList<E?>() {\n    /**\n     * [.onAddElement] runs on the main thread; sets a backing list.\n     */\n    protected constructor(base: MutableList<E?>?) : this(UiThreadHandler.executor, base)\n\n    /**\n     * [.onAddElement] runs with the executor; sets a backing list.\n     */\n    /**\n     * [.onAddElement] runs on the main thread; no backing list.\n     */\n\n    /**\n     * The callback when a new element is added.\n     *\n     *\n     * This method will be called after `add` is called.\n     * Which thread it runs on depends on which constructor is used to construct the instance.\n     * @param e the new element added to the list.\n     */\n    abstract fun onAddElement(e: E?)\n\n    /**\n     * @see List.get\n     */\n    override fun get(i: Int): E? {\n        return if (mBase == null) null else mBase!![i]\n    }\n\n    override fun set(i: Int, s: E?): E? {\n        return if (mBase == null) null else mBase!!.set(i, s)\n    }\n\n    override fun add(i: Int, s: E?) {\n        if (mBase != null) mBase!!.add(i, s)\n        mExecutor.execute(Runnable { onAddElement(s) })\n    }\n\n    override fun remove(o: E?): Boolean {\n        return if (mBase == null) false else mBase!!.remove(o)\n    }\n\n    override fun removeAt(index: Int): E? {\n        return if (mBase == null) null else mBase!!.removeAt(index)\n    }\n\n    override fun removeFirst(): E? {\n        return if (mBase == null || mBase!!.isEmpty()) null else mBase!!.removeAt(0)\n    }\n\n    override var size: Int\n        get() = if (mBase == null) 0 else mBase!!.size\n        set(value) {\n            if (mBase == null) mBase = ArrayList(value)\n            else mBase!!.clear()\n            for (i in 0 until value) {\n                mBase!!.add(null)\n            }\n        }\n\n\n}\n"
  },
  {
    "path": "suCore/src/main/java/com/valhalla/superuser/NoShellException.kt",
    "content": "package com.valhalla.superuser\n\n/**\n * Thrown when it is impossible to construct `Shell`.\n * This is a runtime exception, and should happen very rarely.\n */\nclass NoShellException : RuntimeException {\n    constructor(msg: String?) : super(msg)\n\n    constructor(message: String?, cause: Throwable?) : super(message, cause)\n}\n"
  },
  {
    "path": "suCore/src/main/java/com/valhalla/superuser/Shell.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage com.valhalla.superuser\n\nimport androidx.annotation.IntDef\nimport com.valhalla.superuser.internal.BuilderImpl\nimport com.valhalla.superuser.internal.MainShell\nimport com.valhalla.superuser.internal.UiThreadHandler\nimport com.valhalla.superuser.internal.Utils\nimport java.io.Closeable\nimport java.io.IOException\nimport java.io.InputStream\nimport java.io.OutputStream\nimport java.util.concurrent.Executor\nimport java.util.concurrent.Executors\nimport java.util.concurrent.Future\nimport java.util.concurrent.TimeUnit\n\n/**\n * A class providing APIs to an interactive Unix shell.\n *\n *\n * Similar to threads where there is a special \"main thread\", `libsu` also has the\n * concept of the \"main shell\". For each process, there is a single globally shared\n * \"main shell\" that is constructed on-demand and cached.\n *\n *\n * To obtain/create the main shell, use the static `Shell.getShell(...)` methods.\n * Developers can use these high level APIs to access the main shell:\n *\n *  * [.cmd]\n *  * [.cmd]\n *\n */\nabstract class Shell : Closeable {\n\n    /* Preserve 2 due to historical reasons */\n    @Retention(AnnotationRetention.SOURCE)\n    @IntDef(UNKNOWN, NON_ROOT_SHELL, ROOT_SHELL)\n    internal annotation class Status\n\n    /* Preserve (1 << 2) due to historical reasons */ /* Preserve (1 << 3) due to historical reasons */ /* Preserve (1 << 4) due to historical reasons */\n    @Suppress(\"DEPRECATION\")\n    @Retention(AnnotationRetention.SOURCE)\n    @IntDef(flag = true, value = [FLAG_NON_ROOT_SHELL, FLAG_MOUNT_MASTER, FLAG_REDIRECT_STDERR])\n    internal annotation class ConfigFlags\n\n    /* ***************\n     * Non-static APIs\n     * ****************/\n    /**\n     * Return whether the shell is still alive.\n     * @return `true` if the shell is still alive.\n     */\n    abstract val isAlive: Boolean\n\n    /**\n     * Execute a low-level [Task] using the shell. USE THIS METHOD WITH CAUTION!\n     *\n     *\n     * This method exposes raw STDIN/STDOUT/STDERR directly to the developer. This is meant for\n     * implementing low-level operations. The shell may stall if the buffer of STDOUT/STDERR\n     * is full. It is recommended to use additional threads to consume STDOUT/STDERR in parallel.\n     *\n     *\n     * STDOUT/STDERR is cleared before executing the task. No output from any previous tasks should\n     * be left over. It is the developer's responsibility to make sure all operations are done;\n     * the shell should be in idle and waiting for further input when the task returns.\n     * @param task the desired task.\n     * @throws IOException I/O errors when doing operations with STDIN/STDOUT/STDERR\n     */\n    @Throws(IOException::class)\n    abstract fun execTask(task: Task)\n\n    /**\n     * Submits a low-level [Task] for execution in a queue of the shell.\n     * @param task the desired task.\n     * @see .execTask\n     */\n    abstract fun submitTask(task: Task)\n\n    /**\n     * Construct a new [Job] that uses the shell for execution.\n     *\n     *\n     * Unlike [.cmd] and [.cmd], **NO**\n     * output will be collected if the developer did not set the output destination with\n     * [Job.to] or [Job.to].\n     * @return a job that the developer can execute or submit later.\n     */\n    abstract fun newJob(): Job\n\n    @get:Status\n    abstract val status: Int\n\n    val isRoot: Boolean\n        /**\n         * Return whether the shell has root access.\n         * @return `true` if the shell has root access.\n         */\n        get() = this.status >= ROOT_SHELL\n\n    /**\n     * Wait for any current/pending tasks to finish before closing this shell\n     * and release any system resources associated with the shell.\n     *\n     *\n     * Blocks until all current/pending tasks have completed execution, or\n     * the timeout occurs, or the current thread is interrupted,\n     * whichever happens first.\n     * @param timeout the maximum time to wait\n     * @param unit the time unit of the timeout argument\n     * @return `true` if this shell is terminated and\n     * `false` if the timeout elapsed before termination, in which\n     * the shell can still to be used afterwards.\n     * @throws IOException if an I/O error occurs.\n     * @throws InterruptedException if interrupted while waiting.\n     */\n    @Throws(IOException::class, InterruptedException::class)\n    abstract fun waitAndClose(timeout: Long, unit: TimeUnit): Boolean\n\n    /**\n     * Wait indefinitely for any current/pending tasks to finish before closing this shell\n     * and release any system resources associated with the shell.\n     * @throws IOException if an I/O error occurs.\n     */\n    @Throws(IOException::class)\n    fun waitAndClose() {\n        while (true) {\n            try {\n                if (waitAndClose(Long.MAX_VALUE, TimeUnit.NANOSECONDS)) break\n            } catch (ignored: InterruptedException) {\n            }\n        }\n    }\n\n    /* **************\n     * Nested classes\n     * ***************/\n    /**\n     * Builder class for [Shell] instances.\n     *\n     *\n     * Set the default builder for the main shell instance with\n     * [.setDefaultBuilder], or directly use a builder object to create new\n     * [Shell] instances.\n     *\n     *\n     * Do not subclass this class! Use [.create] to get a new Builder object.\n     */\n    abstract class Builder {\n        var utils: Utils? = null\n\n        /**\n         * Set the desired [Initializer]s.\n         * @see Initializer\n         *\n         * @param classes the classes of desired initializers.\n         * @return this Builder object for chaining of calls.\n         */\n        @SafeVarargs\n        fun setInitializers(vararg classes: Class<out Initializer?>): Builder {\n            (this as BuilderImpl).setInitializersImpl(classes)\n            return this\n        }\n\n        /**\n         * Set flags to control how a new `Shell` will be constructed.\n         * @param flags the desired flags.\n         * Value is either 0 or bitwise-or'd value of\n         * [.FLAG_NON_ROOT_SHELL] or [.FLAG_MOUNT_MASTER]\n         * @return this Builder object for chaining of calls.\n         */\n        abstract fun setFlags(@ConfigFlags flags: Int): Builder\n\n        /**\n         * Set the maximum time to wait for shell verification.\n         *\n         *\n         * After the timeout occurs and the shell still has no response,\n         * the shell process will be force-closed and throw [NoShellException].\n         * @param timeout the maximum time to wait in seconds.\n         * The default timeout is 20 seconds.\n         * @return this Builder object for chaining of calls.\n         */\n        abstract fun setTimeout(timeout: Long): Builder\n\n        /**\n         * Set the commands that will be used to create a new `Shell`.\n         * @param commands commands that will be passed to [Runtime.exec] to create\n         * a new [Process].\n         * @return this Builder object for chaining of calls.\n         */\n        abstract fun setCommands(vararg commands: String?): Builder\n\n        /**\n         * Combine all of the options that have been set and build a new `Shell` instance.\n         *\n         *\n         * If not [.setCommands], there are 3 methods to construct a Unix shell;\n         * if any method fails, it will fallback to the next method:\n         *\n         *  1. If [.FLAG_NON_ROOT_SHELL] is not set and [.FLAG_MOUNT_MASTER]\n         * is set, construct a Unix shell by calling `su --mount-master`.\n         * It may fail if the root implementation does not support mount master.\n         *  1. If [.FLAG_NON_ROOT_SHELL] is not set, construct a Unix shell by calling\n         * `su`. It may fail if the device is not rooted, or root permission is\n         * not granted.\n         *  1. Construct a Unix shell by calling `sh`. This would never fail in normal\n         * conditions, but should it fail, it will throw [NoShellException]\n         *\n         * The developer should check the status of the returned `Shell` with\n         * [.getStatus] since it may be constructed with calling `sh`.\n         *\n         *\n         * If [.setCommands] is called, the provided commands will be used to\n         * create a new [Process] directly. If the process fails to create, or the process\n         * is not a valid shell, it will throw [NoShellException].\n         * @return the created `Shell` instance.\n         * @throws NoShellException impossible to construct a [Shell] instance, or\n         * initialization failed when using the configured [Initializer]s.\n         */\n        abstract fun build(): Shell\n\n        /**\n         * Combine all of the options that have been set and build a new `Shell` instance\n         * with the provided commands.\n         * @param commands commands that will be passed to [Runtime.exec] to create\n         * a new [Process].\n         * @return the built `Shell` instance.\n         * @throws NoShellException the provided command cannot create a [Shell] instance, or\n         * initialization failed when using the configured [Initializer]s.\n         */\n        fun build(vararg commands: String?): Shell {\n            return setCommands(*commands).build()\n        }\n\n        /**\n         * Combine all of the options that have been set and build a new `Shell` instance\n         * with the provided process.\n         * @param process a shell [Process] that has already been created.\n         * @return the built `Shell` instance.\n         * @throws NoShellException the provided process is not a valid shell, or\n         * initialization failed when using the configured [Initializer]s.\n         */\n        abstract fun build(process: Process?): Shell\n\n        companion object {\n            /**\n             * Create a new [Builder].\n             * @return a new Builder object.\n             */\n            fun create(): Builder {\n                return BuilderImpl()\n            }\n        }\n    }\n\n    /**\n     * The result of a [Job].\n     */\n    abstract class Result {\n        /**\n         * Get the output of STDOUT.\n         * @return a list of strings that stores the output of STDOUT. Empty list if no output\n         * is available.\n         */\n        abstract val out: MutableList<String?>\n\n        /**\n         * Get the output of STDERR.\n         * @return a list of strings that stores the output of STDERR. Empty list if no output\n         * is available.\n         */\n        abstract val err: MutableList<String?>\n\n        /**\n         * Get the return code of the job.\n         * @return the return code of the last operation in the shell. If the job is executed\n         * properly, the code should range from 0-255. If the job fails to execute, it will return\n         * [.JOB_NOT_EXECUTED].\n         */\n        abstract val code: Int\n\n        val isSuccess: Boolean\n            /**\n             * Whether the job succeeded.\n             * `getCode() == 0`.\n             * @return `true` if the return code is 0.\n             */\n            get() = this.code == 0\n\n        companion object {\n            /**\n             * This code indicates that the job was not executed, and the outputs are all empty.\n             * Constant value: {@value}.\n             */\n            const val JOB_NOT_EXECUTED: Int = -1\n        }\n    }\n\n    /**\n     * Represents a shell Job that could later be executed or submitted to background threads.\n     *\n     *\n     * All operations added in [.add] and [.add] will be\n     * executed in the order of addition.\n     */\n    abstract class Job {\n        /**\n         * Store output of STDOUT to a specific list.\n         * @param stdout the list to store STDOUT. Pass `null` to omit all outputs.\n         * @return this Job object for chaining of calls.\n         */\n        abstract fun to(stdout: MutableList<String?>?): Job\n\n        /**\n         * Store output of STDOUT and STDERR to specific lists.\n         * @param stdout the list to store STDOUT. Pass `null` to omit STDOUT.\n         * @param stderr the list to store STDERR. Pass `null` to omit STDERR.\n         * @return this Job object for chaining of calls.\n         */\n        abstract fun to(stdout: MutableList<String?>?, stderr: MutableList<String?>?): Job\n\n        /**\n         * Add a new operation running commands.\n         * @param cmds the commands to run.\n         * @return this Job object for chaining of calls.\n         */\n        abstract fun add(vararg cmds: String): Job\n\n        /**\n         * Add a new operation serving an InputStream to STDIN.\n         *\n         *\n         * This is NOT executing the script like `sh script.sh`.\n         * This is similar to sourcing the script (`. script.sh`) as the\n         * raw content of the script is directly fed into STDIN. If you call\n         * `exit` in the script, **the shell will be killed and this\n         * shell instance will no longer be alive!**\n         * @param inputStream the InputStream to serve to STDIN.\n         * The stream will be closed after consumption.\n         * @return this Job object for chaining of calls.\n         */\n        abstract fun add(inputStream: InputStream): Job\n\n        /**\n         * Execute the job immediately and returns the result.\n         * @return the result of the job.\n         */\n        abstract fun exec(): Result\n\n        /**\n         * Submit the job to an internal queue to run in the background.\n         * The result will be returned with a callback running on the main thread.\n         * @param cb the callback to receive the result of the job.\n         */\n        @JvmOverloads\n        fun submit(cb: ResultCallback? = null) {\n            submit(UiThreadHandler.executor, cb)\n        }\n\n        /**\n         * Submit the job to an internal queue to run in the background.\n         * The result will be returned with a callback executed by the provided executor.\n         * @param executor the executor used to handle the result callback event.\n         * Pass `null` to run the callback on the same thread executing the job.\n         * @param cb the callback to receive the result of the job.\n         */\n        abstract fun submit(executor: Executor?, cb: ResultCallback?)\n\n        /**\n         * Submit the job to an internal queue to run in the background.\n         * @return a [Future] to get the result of the job later.\n         */\n        abstract fun enqueue(): Future<Result?>\n    }\n\n    /**\n     * The initializer when a new `Shell` is constructed.\n     *\n     *\n     * This is an advanced feature. If you need to run specific operations when a new `Shell`\n     * is constructed, extend this class, add your own implementation, and register it with\n     * [Builder.setInitializers].\n     * The concept is similar to `.bashrc`: run specific scripts/commands when the shell\n     * starts up. [.onInit] will be called as soon as the shell is\n     * constructed and tested as a valid shell.\n     *\n     *\n     * An initializer will be constructed and the callbacks will be invoked each time a new\n     * shell is created.\n     */\n    class Initializer {\n        /**\n         * Called when a new shell is constructed.\n         * @param shell the newly constructed shell.\n         * @return `false` when initialization fails, otherwise `true`.\n         */\n        fun onInit(shell: Shell): Boolean {\n            return true\n        }\n    }\n\n    /* **********\n     * Interfaces\n     * **********/\n    /**\n     * A task that can be executed by a shell with the method [.execTask].\n     */\n    interface Task {\n        /**\n         * This method will be called when a task is executed by a shell.\n         * Calling [Closeable.close] on any stream is NOP (does nothing).\n         * @param stdin the STDIN of the shell.\n         * @param stdout the STDOUT of the shell.\n         * @param stderr the STDERR of the shell.\n         * @throws IOException I/O errors when doing operations with STDIN/STDOUT/STDERR\n         */\n        @Throws(IOException::class)\n        fun run(\n            stdin: OutputStream,\n            stdout: InputStream,\n            stderr: InputStream\n        )\n\n        /**\n         * This method will be called when a shell is unable to execute this task.\n         */\n        fun shellDied() {}\n    }\n\n    /**\n     * The callback used in [.getShell].\n     */\n    interface GetShellCallback {\n        /**\n         * @param shell the `Shell` obtained in the asynchronous operation.\n         */\n        fun onShell(shell: Shell)\n    }\n\n    /**\n     * The callback to receive a result in [Job.submit].\n     */\n    interface ResultCallback {\n        /**\n         * @param out the result of the job.\n         */\n        fun onResult(out: Result)\n    }\n\n    companion object {\n        /**\n         * Shell status: Unknown. One possible result of [.getStatus].\n         *\n         *\n         * Constant value {@value}.\n         */\n        const val UNKNOWN: Int = -1\n\n        /**\n         * Shell status: Non-root shell. One possible result of [.getStatus].\n         *\n         *\n         * Constant value {@value}.\n         */\n        const val NON_ROOT_SHELL: Int = 0\n\n        /**\n         * Shell status: Root shell. One possible result of [.getStatus].\n         *\n         *\n         * Constant value {@value}.\n         */\n        const val ROOT_SHELL: Int = 1\n\n        /**\n         * If set, create a non-root shell.\n         *\n         *\n         * Constant value {@value}.\n         */\n        const val FLAG_NON_ROOT_SHELL: Int = (1 shl 0)\n\n        /**\n         * If set, create a root shell with the `--mount-master` option.\n         *\n         *\n         * Constant value {@value}.\n         */\n        const val FLAG_MOUNT_MASTER: Int = (1 shl 1)\n\n        /**\n         * The [Executor] that manages all worker threads used in `libsu`.\n         *\n         *\n         * Note: If the developer decides to replace the default Executor, keep in mind that\n         * each `Shell` instance requires at least 3 threads to operate properly.\n         */\n        @JvmField\n        var EXECUTOR: Executor = Executors.newCachedThreadPool()\n\n        /**\n         * Set to `true` to enable verbose logging throughout the library.\n         */\n        @JvmField\n        var enableVerboseLogging: Boolean = false\n\n        /**\n         * This flag exists for compatibility reasons. DO NOT use unless necessary.\n         *\n         *\n         * If enabled, STDERR outputs will be redirected to the STDOUT output list\n         * when a [Job] is configured with [Job.to].\n         * Since the `Shell.cmd(...)` methods are functionally equivalent to\n         * `Shell.getShell().newJob().add(...).to(new ArrayList<>())`, this variable\n         * also affects the behavior of those methods.\n         *\n         *\n         * Note: The recommended way to redirect STDERR output to STDOUT is to assign the\n         * same list to both STDOUT and STDERR with [Job.to].\n         * The behavior of this flag is unintuitive and error prone.\n         */\n        @JvmField\n        var enableLegacyStderrRedirection: Boolean = false\n\n        /**\n         * Override the default [Builder].\n         *\n         *\n         * This shell builder will be used to construct the main shell.\n         * Set this before the main shell is created anywhere in the program.\n         */\n        fun setDefaultBuilder(builder: Builder?) {\n            MainShell.setBuilder(builder)\n        }\n\n        val shell: Shell\n            /**\n             * Get the main shell instance.\n             *\n             *\n             * If [.getCachedShell] returns null, the default [Builder] will be used to\n             * construct a new `Shell`.\n             *\n             *\n             * Unless already cached, this method blocks until the main shell is created.\n             * The process could take a very long time (e.g. root permission request prompt),\n             * so be extra careful when calling this method from the main thread!\n             *\n             *\n             * A good practice is to \"preheat\" the main shell during app initialization\n             * (e.g. the splash screen) by either calling this method in a background thread or\n             * calling [.getShell] so subsequent calls to this function\n             * returns immediately.\n             * @return the cached/created main shell instance.\n             * @see Builder.build\n             */\n            get() = MainShell.get()\n\n        /**\n         * Get the main shell instance asynchronously via a callback.\n         *\n         *\n         * If [.getCachedShell] returns null, the default [Builder] will be used to\n         * construct a new `Shell` in a background thread.\n         * The cached/created shell instance is returned to the callback on the main thread.\n         * @param callback invoked when a shell is acquired.\n         */\n        fun getShell(callback: GetShellCallback) {\n            MainShell.get(UiThreadHandler.executor, callback)\n        }\n\n        /**\n         * Get the main shell instance asynchronously via a callback.\n         *\n         *\n         * If [.getCachedShell] returns null, the default [Builder] will be used to\n         * construct a new `Shell` in a background thread.\n         * The cached/created shell instance is returned to the callback executed by provided executor.\n         * @param executor the executor used to handle the result callback event.\n         * If `null` is passed, the callback can run on any thread.\n         * @param callback invoked when a shell is acquired.\n         */\n        fun getShell(executor: Executor?, callback: GetShellCallback) {\n            MainShell.get(executor, callback)\n        }\n\n        val cachedShell: Shell?\n            /**\n             * Get the cached main shell.\n             * @return a `Shell` instance. `null` can be returned either when\n             * no main shell has been cached, or the cached shell is no longer active.\n             */\n            get() = MainShell.cached\n\n        val isAppGrantedRoot: Boolean?\n            /**\n             * Whether the application has access to root.\n             *\n             *\n             * This method returns `null` when it is currently unable to determine whether\n             * root access has been granted to the application. A non-null value meant that the root\n             * permission grant state has been accurately determined and finalized. The application\n             * must have at least 1 root shell created to have this method return `true`.\n             * This method will not block the calling thread; results will be returned immediately.\n             * @return whether the application has access to root, or `null` when undetermined.\n             */\n            get() = Utils.isAppGrantedRoot\n\n        /* ************\n    * Static APIs\n    * ************/\n        /**\n         * Create a pending [Job] of the main shell with commands.\n         *\n         *\n         * This method can be treated as functionally equivalent to\n         * `Shell.getShell().newJob().add(commands).to(new ArrayList<>())`, but the internal\n         * implementation is specialized for this use case and does not run this exact code.\n         * The developer can manually override output destination(s) with either\n         * [Job.to] or [Job.to].\n         *\n         *\n         * The main shell will NOT be requested until the developer invokes either\n         * [Job.exec], [Job.enqueue], or `Job.submit(...)`. This makes it\n         * possible to construct [Job]s before the program has created any root shell.\n         * @return a job that the developer can execute or submit later.\n         * @see Job.add\n         */\n        fun cmd(vararg commands: String): Job {\n            return MainShell.newJob(*commands)\n        }\n\n        /**\n         * Create a pending [Job] of the main shell with an [InputStream].\n         *\n         *\n         * This method can be treated as functionally equivalent to\n         * `Shell.getShell().newJob().add(in).to(new ArrayList<>())`, but the internal\n         * implementation is specialized for this use case and does not run this exact code.\n         * The developer can manually override output destination(s) with either\n         * [Job.to] or [Job.to].\n         *\n         *\n         * The main shell will NOT be requested until the developer invokes either\n         * [Job.exec], [Job.enqueue], or `Job.submit(...)`. This makes it\n         * possible to construct [Job]s before the program has created any root shell.\n         * @see Job.add\n         */\n        fun cmd(`in`: InputStream): Job {\n            return MainShell.newJob(`in`)\n        }\n\n        /* ***********\n     * Deprecated\n     * ***********/\n\n        @Deprecated(\"Not used anymore\")\n        const val ROOT_MOUNT_MASTER: Int = 2\n\n        /**\n         * For compatibility, setting this flag will set [.enableLegacyStderrRedirection]\n         * @see .enableLegacyStderrRedirection\n         */\n        @Deprecated(\n            \"\"\"not used anymore\"\"\"\n        )\n        const val FLAG_REDIRECT_STDERR: Int = (1 shl 3)\n\n        /**\n         * Whether the application has access to root.\n         *\n         *\n         * This method would NEVER produce false negatives, but false positives can be returned before\n         * actually constructing a root shell. A `false` returned is guaranteed to be\n         * 100% accurate, while `true` may be returned if the device is rooted, but the user\n         * did not grant root access to your application. However, after any root shell is constructed,\n         * this method will accurately return `true`.\n         * @return whether the application has access to root.\n         */\n        @Deprecated(\"please switch to {@link #isAppGrantedRoot()}\")\n        fun rootAccess(): Boolean {\n            return isAppGrantedRoot == java.lang.Boolean.TRUE\n        }\n    }\n}\n"
  },
  {
    "path": "suCore/src/main/java/com/valhalla/superuser/ShellUtils.kt",
    "content": "package com.valhalla.superuser\n\nimport android.os.Looper\nimport java.io.IOException\nimport java.io.InputStream\n\n/**\n * Some handy utility methods that are used in `libSu`.\n *\n *\n * These methods are for internal use. I personally find them pretty handy, so I gathered them here.\n * However, since these are meant to be used internally, they are not stable APIs.\n * I would change them without too much consideration if needed. Also, these methods are not well\n * tested for public usage, many might not handle some edge cases correctly.\n * **You have been warned!!**\n */\n@Suppress(\"unused\")\nobject ShellUtils {\n    /**\n     * Test whether the list is `null` or empty or all elements are empty strings.\n     * @param out the output of a shell command.\n     * @return `false` if the list is `null` or empty or all elements are empty strings.\n     */\n    fun isValidOutput(out: List<String?>): Boolean {\n        return out.any { !it.isNullOrEmpty() }\n    }\n\n    /**\n     * Run commands with the main shell and get a single line output.\n     * @param commands the commands.\n     * @return the last line of the output of the command, empty string if no output is available.\n     */\n    fun fastCmd(vararg commands: String?): String {\n        return fastCmd(Shell.shell, *commands)\n    }\n\n    /**\n     * Run commands and get a single line output.\n     * @param shell a shell instance.\n     * @param commands the commands.\n     * @return the last line of the output of t\n     * the command, empty string if no output is available.\n     */\n    fun fastCmd(shell: Shell, vararg commands: String?): String {\n        val out = shell.newJob().apply {\n            commands.forEach { cmd ->\n                if (cmd != null) add(cmd)\n            }\n            to(ArrayList(), null)\n        }.exec().out\n        return (if (isValidOutput(out)) out.last() else \"\") ?: \"\"\n    }\n\n    /**\n     * Run commands with the main shell and return whether exits with 0 (success).\n     * @param commands the commands.\n     * @return `true` if the commands succeed.\n     */\n    fun fastCmdResult(vararg commands: String?): Boolean {\n        return fastCmdResult(Shell.shell, *commands)\n    }\n\n    /**\n     * Run commands and return whether exits with 0 (success).\n     * @param shell a shell instance.\n     * @param commands the commands.\n     * @return `true` if the commands succeed.\n     */\n    fun fastCmdResult(shell: Shell, vararg commands: String?): Boolean {\n        return shell.newJob().apply {\n            commands.forEach { cmd ->\n                if (cmd != null) add(cmd)\n            }\n            to(ArrayList(), null)\n        }.exec().isSuccess\n    }\n\n    /**\n     * Check if current thread is main thread.\n     * @return `true` if the current thread is the main thread.\n     */\n    fun onMainThread(): Boolean {\n        return Looper.getMainLooper().thread === Thread.currentThread()\n    }\n\n    /**\n     * Discard all data currently available in an [InputStream].\n     * @param inputStream the [InputStream] to be cleaned.\n     */\n    fun cleanInputStream(inputStream: InputStream) {\n        try {\n            while (inputStream.available() != 0) inputStream.skip(inputStream.available().toLong())\n        } catch (_: IOException) {\n        }\n    }\n\n    private const val SINGLE_QUOTE = '\\''\n\n    /**\n     * Format string to quoted and escaped string suitable for shell commands.\n     * @param s the string to be formatted.\n     * @return the formatted string.\n     */\n\n    fun escapedString(s: String): String {\n        val sb = StringBuilder()\n        sb.append(SINGLE_QUOTE)\n        val len = s.length\n        for (i in 0..<len) {\n            val c = s[i]\n            if (c == SINGLE_QUOTE) {\n                sb.append(\"'\\\\''\")\n                continue\n            }\n            sb.append(c)\n        }\n        sb.append(SINGLE_QUOTE)\n        return sb.toString()\n    }\n\n    /**\n     * Get the greatest common divisor of 2 integers with binary algorithm.\n     * @param u an integer.\n     * @param v an integer.\n     * @return the greatest common divisor.\n     */\n    fun gcd(u: Long, v: Long): Long {\n        var u = u\n        var v = v\n        if (u == 0L) return v\n        if (v == 0L) return u\n        var shift = 0\n        while (((u or v) and 1L) == 0L) {\n            u = u shr 1\n            v = v shr 1\n            ++shift\n        }\n        while ((u and 1L) == 0L) u = u shr 1\n        do {\n            while ((v and 1L) == 0L) v = v shr 1\n\n            if (u > v) {\n                val t = v\n                v = u\n                u = t\n            }\n            v -= u\n        } while (v != 0L)\n\n        return u shl shift\n    }\n}\n"
  },
  {
    "path": "suCore/src/main/java/com/valhalla/superuser/internal/BuilderImpl.kt",
    "content": "package com.valhalla.superuser.internal\n\nimport android.text.TextUtils\nimport androidx.annotation.RestrictTo\nimport com.valhalla.superuser.NoShellException\nimport com.valhalla.superuser.Shell\nimport java.io.IOException\nimport java.lang.reflect.Constructor\n\n@RestrictTo(RestrictTo.Scope.LIBRARY)\nclass BuilderImpl : Shell.Builder() {\n\n    var timeout: Long = 20\n    private var flags = 0\n    private var initializers: Array<Shell.Initializer?>? = null\n    private var command: Array<String?>? = null\n\n    fun hasFlags(mask: Int): Boolean {\n        return (flags and mask) == mask\n    }\n\n    override fun setFlags(flags: Int): Shell.Builder {\n        this@BuilderImpl.flags = flags\n        return this\n    }\n\n    override fun setTimeout(timeout: Long): Shell.Builder {\n        this@BuilderImpl.timeout = timeout\n        return this\n    }\n\n    override fun setCommands(vararg commands: String?): Shell.Builder {\n        command = arrayOf(*commands)\n        return this\n    }\n\n    fun setInitializersImpl(clz: Array<out Class<out Shell.Initializer?>>) {\n        initializers = arrayOfNulls(clz.size)\n        for (i in clz.indices) {\n            try {\n                val c: Constructor<out Shell.Initializer?> = clz[i].getDeclaredConstructor()\n                c.isAccessible = true\n                initializers!![i] = c.newInstance()\n            } catch (e: ReflectiveOperationException) {\n                Utils.err(e)\n            } catch (e: ClassCastException) {\n                Utils.err(e)\n            }\n        }\n    }\n\n    private fun start(): ShellImpl {\n        var shell: ShellImpl? = null\n\n        // Root mount master\n        if (!hasFlags(Shell.FLAG_NON_ROOT_SHELL) && hasFlags(Shell.FLAG_MOUNT_MASTER)) {\n            try {\n                shell = exec(\"su\", \"--mount-master\")\n                if (!shell.isRoot) shell = null\n            } catch (_: NoShellException) {\n            }\n        }\n\n        // Normal root shell\n        if (shell == null && !hasFlags(Shell.FLAG_NON_ROOT_SHELL)) {\n            try {\n                shell = exec(\"su\")\n                if (!shell.isRoot) {\n                    shell = null\n                }\n            } catch (_: NoShellException) {\n            }\n        }\n\n        // Try normal non-root shell\n        if (shell == null) {\n            if (!hasFlags(Shell.FLAG_NON_ROOT_SHELL)) {\n                Utils.setConfirmedRootState(false)\n            }\n            shell = exec(\"sh\")\n        }\n\n        return shell\n    }\n\n    private fun exec(vararg commands: String?): ShellImpl {\n        try {\n            Utils.log(TAG, \"exec \" + TextUtils.join(\" \", commands))\n            val process = Runtime.getRuntime().exec(commands)\n            return build(process)\n        } catch (e: IOException) {\n            Utils.ex(e)\n            throw NoShellException(\"Unable to create a shell!\", e)\n        }\n    }\n\n    override fun build(process: Process?): ShellImpl {\n        if (process == null) {\n            throw NoShellException(\"Process cannot be null!, Unable to create a shell!\")\n        }\n        val shell: ShellImpl\n        try {\n            shell = ShellImpl(this, process)\n        } catch (e: Exception) {\n            Utils.ex(e)\n            throw NoShellException(\"Unable to create a shell!\", e)\n        }\n        @Suppress(\"DEPRECATION\")\n        if (hasFlags(Shell.FLAG_REDIRECT_STDERR)) {\n            Shell.enableLegacyStderrRedirection = true\n        }\n        MainShell.cached = (shell)\n        if (initializers != null) {\n            for (init in initializers) {\n                if (init != null && !init.onInit(shell)) {\n                    MainShell.cached = (null)\n                    throw NoShellException(\"Unable to init shell\")\n                }\n            }\n        }\n        return shell\n    }\n\n    override fun build(): ShellImpl {\n        return if (command != null) {\n            exec(*command!!)\n        } else {\n            start()\n        }\n    }\n\n    companion object {\n        private const val TAG = \"BUILDER\"\n    }\n}\n"
  },
  {
    "path": "suCore/src/main/java/com/valhalla/superuser/internal/CoroutineStreamGobbler.kt",
    "content": "package com.valhalla.superuser.internal\n\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport java.io.BufferedReader\nimport java.io.InputStream\nimport java.io.InputStreamReader\nimport java.nio.charset.StandardCharsets\n\n/**\n * Reads streams using Coroutines on the IO dispatcher.\n * Replaces the legacy 'StreamGobbler' class.\n * Under development.\n */\ninternal abstract class CoroutineStreamGobbler(\n    private val inputStream: InputStream,\n    private val list: MutableList<String?>?\n) {\n\n    suspend fun process(): String? = withContext(Dispatchers.IO) {\n        val br = BufferedReader(InputStreamReader(inputStream, StandardCharsets.UTF_8))\n        var lastLine: String? = null\n\n        while (true) {\n            val line = br.readLine() ?: break\n\n            // Logic ported from legacy StreamGobbler to handle UUID markers\n            val len = line.length\n            val isEnd = line.startsWith(JobTask.END_UUID, len - JobTask.UUID_LEN)\n\n            var content = line\n            if (isEnd) {\n                if (len == JobTask.UUID_LEN) {\n                    // Just the UUID, means we are done\n                    lastLine = br.readLine() // Read the return code line\n                    break\n                }\n                content = line.substring(0, len - JobTask.UUID_LEN)\n            }\n\n            if (list != null) {\n                // Warning: list is likely not thread-safe, ensure the caller handles synchronization\n                // or use a thread-safe list.\n                synchronized(list) {\n                    list.add(content)\n                }\n                Utils.log(\"SHELL_OUT\", content)\n            }\n\n            if (isEnd) {\n                lastLine = br.readLine()\n                break\n            }\n        }\n        lastLine\n    }\n\n    class Out(inputStream: InputStream, list: MutableList<String?>?) :\n        CoroutineStreamGobbler(inputStream, list) {\n\n        suspend fun readCode(): Int {\n            val codeStr = process()\n            return try {\n                codeStr?.toInt() ?: 1\n            } catch (_: NumberFormatException) {\n                1\n            }\n        }\n    }\n\n    class Err(inputStream: InputStream, list: MutableList<String?>?) :\n        CoroutineStreamGobbler(inputStream, list) {\n\n        suspend fun drain() {\n            process()\n        }\n    }\n}"
  },
  {
    "path": "suCore/src/main/java/com/valhalla/superuser/internal/JobTask.kt",
    "content": "package com.valhalla.superuser.internal\n\nimport com.valhalla.superuser.Shell\nimport com.valhalla.superuser.internal.StreamGobbler.ERR\nimport com.valhalla.superuser.internal.StreamGobbler.OUT\nimport java.io.IOException\nimport java.io.InputStream\nimport java.io.OutputStream\nimport java.nio.charset.StandardCharsets\nimport java.util.Collections\nimport java.util.UUID\nimport java.util.concurrent.ExecutionException\nimport java.util.concurrent.Executor\nimport java.util.concurrent.FutureTask\n\ninternal abstract class JobTask : Shell.Job(), Shell.Task {\n    private val sources: MutableList<ShellInputSource> = ArrayList()\n    private var out: MutableList<String?>? = null\n    private var err: MutableList<String?>? = UNSET_LIST\n\n    @JvmField\n    protected var callbackExecutor: Executor? = null\n\n    @JvmField\n    protected var callback: Shell.ResultCallback? = null\n\n    private fun setResult(result: ResultImpl) {\n        if (callback != null) {\n            if (callbackExecutor == null) callback!!.onResult(result)\n            else callbackExecutor!!.execute { callback!!.onResult(result) }\n        }\n    }\n\n    private fun close() {\n        for (src in sources) src.close()\n    }\n\n    override fun run(\n        stdin: OutputStream,\n        stdout: InputStream,\n        stderr: InputStream\n    ) {\n        val noOut = out === UNSET_LIST\n        val noErr = err === UNSET_LIST\n\n        var outList = if (noOut) (if (callback == null) null else ArrayList()) else out\n        var errList =\n            if (noErr) (if (Shell.enableLegacyStderrRedirection) outList else null) else err\n\n        if (outList != null && outList === errList && !Utils.isSynchronized(outList)) {\n            // Synchronize the list internally only if both lists are the same and are not\n            // already synchronized by the user\n            val list = Collections.synchronizedList(outList)\n            outList = list\n            errList = list\n        }\n\n        val outGobbler = FutureTask(OUT(stdout, outList))\n        val errGobbler = FutureTask(ERR(stderr, errList))\n        Shell.EXECUTOR.execute(outGobbler)\n        Shell.EXECUTOR.execute(errGobbler)\n\n        val result = ResultImpl()\n        try {\n            for (src in sources) src.serve(stdin)\n            stdin.write(END_CMD)\n            stdin.flush()\n\n            val code: Int = outGobbler.get()!!\n            errGobbler.get()\n\n            result.code = code\n            result.out = outList ?: mutableListOf()\n            result.err = (if (noErr) null else err) ?: mutableListOf()\n        } catch (e: IOException) {\n            Utils.err(e)\n        } catch (e: ExecutionException) {\n            Utils.err(e)\n        } catch (e: InterruptedException) {\n            Utils.err(e)\n        }\n\n        close()\n        setResult(result)\n    }\n\n    override fun shellDied() {\n        close()\n        setResult(ResultImpl())\n    }\n\n    override fun to(stdout: MutableList<String?>?): Shell.Job {\n        out = stdout\n        err = UNSET_LIST\n        return this\n    }\n\n    override fun to(\n        stdout: MutableList<String?>?,\n        stderr: MutableList<String?>?\n    ): Shell.Job {\n        out = stdout\n        err = stderr\n        return this\n    }\n\n    override fun add(inputStream: InputStream): Shell.Job {\n        sources.add(InputStreamSource(inputStream))\n        return this\n    }\n\n    override fun add(vararg cmds: String): Shell.Job {\n        if (cmds.isNotEmpty()) sources.add(CommandSource(cmds))\n        return this\n    }\n\n    companion object {\n        @JvmField\n        val UNSET_LIST: MutableList<String?> = ArrayList<String?>(0)\n\n        @JvmField\n        val END_UUID: String = UUID.randomUUID().toString()\n        const val UUID_LEN: Int = 36\n        private val END_CMD: ByteArray =\n            String.format($$\"__RET=$?;echo %1$s;echo %1$s >&2;echo $__RET;unset __RET\\n\", END_UUID)\n                .toByteArray(\n                    StandardCharsets.UTF_8\n                )\n\n        //private static final byte[] END_CMD = String\n        //            .format(\"__RET=$?;echo %1$s;echo %1$s >&2;echo $__RET;unset __RET\\n\", END_UUID)\n        //            .getBytes(UTF_8);\n    }\n}\n"
  },
  {
    "path": "suCore/src/main/java/com/valhalla/superuser/internal/MainShell.kt",
    "content": "package com.valhalla.superuser.internal\n\nimport androidx.annotation.GuardedBy\nimport androidx.annotation.RestrictTo\nimport com.valhalla.superuser.NoShellException\nimport com.valhalla.superuser.Shell\nimport com.valhalla.superuser.Shell.GetShellCallback\nimport java.io.InputStream\nimport java.util.concurrent.Executor\n\n@RestrictTo(RestrictTo.Scope.LIBRARY)\nobject MainShell {\n    @GuardedBy(\"self\")\n    private val mainShell = arrayOfNulls<ShellImpl>(1)\n\n    @GuardedBy(\"class\")\n    private var isInitMain = false\n\n    @GuardedBy(\"class\")\n    private var mainBuilder: BuilderImpl? = null\n\n    @JvmStatic\n    @Synchronized\n    fun get(): ShellImpl {\n        var shell: ShellImpl? = cached\n        if (shell == null) {\n            if (isInitMain) {\n                throw NoShellException(\"The main shell died during initialization\")\n            }\n            isInitMain = true\n            if (mainBuilder == null) mainBuilder = BuilderImpl()\n            shell = mainBuilder!!.build()\n            isInitMain = false\n        }\n        return shell\n    }\n\n    private fun returnShell(s: Shell, e: Executor?, cb: GetShellCallback) {\n        if (e == null) cb.onShell(s)\n        else e.execute { cb.onShell(s) }\n    }\n\n    @JvmStatic\n    fun get(executor: Executor?, callback: GetShellCallback) {\n        val shell: Shell? = cached\n        if (shell != null) {\n            returnShell(shell, executor, callback)\n        } else {\n            // Else we get shell in worker thread and call the callback when we get a Shell\n            Shell.EXECUTOR.execute {\n                try {\n                    returnShell(get(), executor, callback)\n                } catch (e: NoShellException) {\n                    Utils.ex(e)\n                }\n            }\n        }\n    }\n\n    @set:Synchronized\n    var cached: ShellImpl?\n        get() {\n            synchronized(mainShell) {\n                var s = mainShell[0]\n                if (s != null && s.status < 0) {\n                    s = null\n                    mainShell[0] = null\n                }\n                return s\n            }\n        }\n        set(shell) {\n            if (isInitMain) {\n                synchronized(mainShell) {\n                    mainShell[0] = shell\n                }\n            }\n        }\n\n    @Synchronized\n    fun setBuilder(builder: Shell.Builder?) {\n        check(!(isInitMain || cached != null)) { \"The main shell was already created\" }\n        mainBuilder = builder as BuilderImpl?\n    }\n\n    fun newJob(`in`: InputStream): Shell.Job {\n        return PendingJob().add(`in`)\n    }\n\n    fun newJob(vararg commands: String?): Shell.Job {\n        return PendingJob().apply {\n            commands.forEach { cmd ->\n                if (cmd != null) add(cmd)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "suCore/src/main/java/com/valhalla/superuser/internal/PendingJob.kt",
    "content": "package com.valhalla.superuser.internal\n\nimport com.valhalla.superuser.NoShellException\nimport com.valhalla.superuser.Shell\nimport com.valhalla.superuser.Shell.GetShellCallback\nimport com.valhalla.superuser.internal.MainShell.get\nimport java.io.IOException\nimport java.util.concurrent.Executor\nimport java.util.concurrent.Future\n\ninternal class PendingJob : JobTask() {\n    private var retryTask: Runnable? = null\n\n    init {\n        to(UNSET_LIST)\n    }\n\n    override fun shellDied() {\n        if (retryTask != null) {\n            val r = retryTask\n            retryTask = null\n            r!!.run()\n        } else {\n            super.shellDied()\n        }\n    }\n\n    private fun exec0() {\n        val shell: ShellImpl?\n        try {\n            shell = get()\n        } catch (_: NoShellException) {\n            super.shellDied()\n            return\n        }\n        try {\n            shell.execTask(this)\n        } catch (_: IOException) { /* JobTask does not throw */\n        }\n    }\n\n    override fun exec(): Shell.Result {\n        retryTask = Runnable {\n            this.exec0()\n        }\n        val holder = ResultHolder()\n        callback = holder\n        callbackExecutor = null\n        exec0()\n        return holder.result\n    }\n\n    private fun submit0() {\n        get(null, object : GetShellCallback {\n            override fun onShell(shell: Shell) {\n                (shell as ShellImpl).submitTask(this@PendingJob)\n            }\n        })\n    }\n\n    override fun enqueue(): Future<Shell.Result?> {\n        retryTask = Runnable { this.submit0() }\n        val future = ResultFuture()\n        callback = future\n        callbackExecutor = null\n        submit0()\n        return future\n    }\n\n    override fun submit(executor: Executor?, cb: Shell.ResultCallback?) {\n        retryTask = Runnable { this.submit0() }\n        callbackExecutor = executor\n        callback = cb\n        submit0()\n    }\n}\n"
  },
  {
    "path": "suCore/src/main/java/com/valhalla/superuser/internal/ResultFuture.kt",
    "content": "package com.valhalla.superuser.internal\n\nimport com.valhalla.superuser.Shell\nimport java.util.concurrent.CountDownLatch\nimport java.util.concurrent.Future\nimport java.util.concurrent.TimeUnit\nimport java.util.concurrent.TimeoutException\n\ninternal class ResultFuture : ResultHolder(), Future<Shell.Result?> {\n    private val latch = CountDownLatch(1)\n\n    override fun onResult(out: Shell.Result) {\n        super.onResult(out)\n        latch.countDown()\n    }\n\n    override fun cancel(mayInterruptIfRunning: Boolean): Boolean {\n        return latch.count != 0L\n    }\n\n    override fun isCancelled(): Boolean {\n        return false\n    }\n\n    override fun isDone(): Boolean {\n        return latch.count == 0L\n    }\n\n    @Throws(InterruptedException::class)\n    override fun get(): Shell.Result {\n        latch.await()\n        return result\n    }\n\n    @Throws(InterruptedException::class, TimeoutException::class)\n    override fun get(timeout: Long, unit: TimeUnit?): Shell.Result {\n        if (!latch.await(timeout, unit)) {\n            throw TimeoutException()\n        }\n        return result\n    }\n}\n"
  },
  {
    "path": "suCore/src/main/java/com/valhalla/superuser/internal/ResultHolder.kt",
    "content": "package com.valhalla.superuser.internal\n\nimport com.valhalla.superuser.Shell\n\ninternal open class ResultHolder : Shell.ResultCallback {\n\n    var result: Shell.Result = ResultImpl()\n\n    override fun onResult(out: Shell.Result) {\n        result = out\n    }\n\n}\n"
  },
  {
    "path": "suCore/src/main/java/com/valhalla/superuser/internal/ResultImpl.kt",
    "content": "package com.valhalla.superuser.internal\n\nimport com.valhalla.superuser.Shell\n\ninternal class ResultImpl : Shell.Result() {\n\n    override var out: MutableList<String?> = mutableListOf()\n    override var err: MutableList<String?> = mutableListOf()\n    override var code: Int = JOB_NOT_EXECUTED\n\n}\n"
  },
  {
    "path": "suCore/src/main/java/com/valhalla/superuser/internal/ShellImpl.kt",
    "content": "package com.valhalla.superuser.internal\n\nimport android.text.TextUtils\nimport com.valhalla.superuser.Shell\nimport com.valhalla.superuser.ShellUtils.cleanInputStream\nimport com.valhalla.superuser.ShellUtils.escapedString\nimport java.io.BufferedOutputStream\nimport java.io.BufferedReader\nimport java.io.FilterInputStream\nimport java.io.FilterOutputStream\nimport java.io.IOException\nimport java.io.InputStream\nimport java.io.InputStreamReader\nimport java.io.OutputStream\nimport java.nio.charset.StandardCharsets\nimport java.util.ArrayDeque\nimport java.util.concurrent.ExecutionException\nimport java.util.concurrent.FutureTask\nimport java.util.concurrent.TimeUnit\nimport java.util.concurrent.TimeoutException\nimport java.util.concurrent.locks.Condition\nimport java.util.concurrent.locks.ReentrantLock\nimport kotlin.concurrent.Volatile\n\nclass ShellImpl(builder: BuilderImpl, private val process: Process) : Shell() {\n    @Volatile\n    override var status: Int\n        private set\n\n    private val stdIn: NoCloseOutputStream\n    private val stdOut: NoCloseInputStream\n    private val stdErr: NoCloseInputStream\n\n    // Guarded by scheduleLock\n    private val scheduleLock = ReentrantLock()\n    private val idle: Condition = scheduleLock.newCondition()\n    private val tasks = ArrayDeque<Task?>()\n    private var isRunningTask = false\n\n    private class SyncTask(private val condition: Condition) : Task {\n        private var set = false\n\n        fun signal() {\n            set = true\n            condition.signal()\n        }\n\n        fun await() {\n            while (!set) {\n                try {\n                    condition.await()\n                } catch (_: InterruptedException) {\n                }\n            }\n        }\n\n        override fun run(stdin: OutputStream, stdout: InputStream, stderr: InputStream) {}\n    }\n\n    private class NoCloseInputStream(`in`: InputStream?) : FilterInputStream(`in`) {\n        override fun close() {}\n\n        @Throws(IOException::class)\n        fun close0() {\n            `in`.close()\n        }\n    }\n\n    private class NoCloseOutputStream(out: OutputStream) :\n        FilterOutputStream(out as? BufferedOutputStream ?: BufferedOutputStream(out)) {\n        @Throws(IOException::class)\n        override fun write(b: ByteArray, off: Int, len: Int) {\n            out.write(b, off, len)\n        }\n\n        @Throws(IOException::class)\n        override fun close() {\n            out.flush()\n        }\n\n        @Throws(IOException::class)\n        fun close0() {\n            super.close()\n        }\n    }\n\n    init {\n        status = UNKNOWN\n        stdIn = NoCloseOutputStream(process.outputStream)\n        stdOut = NoCloseInputStream(process.inputStream)\n        stdErr = NoCloseInputStream(process.errorStream)\n\n        // Shell checks might get stuck indefinitely\n        val check = FutureTask<Int?> { this.shellCheck() }\n        EXECUTOR.execute(check)\n        try {\n            try {\n                status = check.get(builder.timeout, TimeUnit.SECONDS)!!\n            } catch (e: ExecutionException) {\n                val cause = e.cause\n                if (cause is IOException) {\n                    throw cause\n                } else {\n                    throw IOException(\"Unknown ExecutionException\", cause)\n                }\n            } catch (e: TimeoutException) {\n                throw IOException(\"Shell check timeout\", e)\n            } catch (e: InterruptedException) {\n                throw IOException(\"Shell check interrupted\", e)\n            }\n        } catch (e: IOException) {\n            release()\n            throw e\n        }\n    }\n\n    @Throws(IOException::class)\n    private fun shellCheck(): Int {\n        try {\n            process.exitValue()\n            throw IOException(\"Created process has terminated\")\n        } catch (_: IllegalThreadStateException) {\n            // Process is alive\n        }\n\n        // Clean up potential garbage from InputStreams\n        cleanInputStream(stdOut)\n        cleanInputStream(stdErr)\n\n        var status = NON_ROOT_SHELL\n        BufferedReader(InputStreamReader(stdOut)).use { br ->\n            stdIn.write((\"echo SHELL_TEST\\n\").toByteArray(StandardCharsets.UTF_8))\n            stdIn.flush()\n            var s = br.readLine()\n            if (TextUtils.isEmpty(s) || !s!!.contains(\"SHELL_TEST\")) throw IOException(\"Created process is not a shell\")\n\n            stdIn.write((\"id\\n\").toByteArray(StandardCharsets.UTF_8))\n            stdIn.flush()\n            s = br.readLine()\n            if (!TextUtils.isEmpty(s) && s.contains(\"uid=0\")) {\n                status = ROOT_SHELL\n                Utils.setConfirmedRootState(true)\n                // noinspection ConstantConditions\n                val cwd = escapedString(System.getProperty(\"user.dir\") ?: \"/\")\n                stdIn.write((\"cd $cwd\\n\").toByteArray(StandardCharsets.UTF_8))\n                stdIn.flush()\n            }\n        }\n        return status\n    }\n\n    private fun release() {\n        status = UNKNOWN\n        try {\n            stdIn.close0()\n        } catch (_: IOException) {\n        }\n        try {\n            stdErr.close0()\n        } catch (_: IOException) {\n        }\n        try {\n            stdOut.close0()\n        } catch (_: IOException) {\n        }\n        process.destroy()\n    }\n\n    @Throws(InterruptedException::class)\n    override fun waitAndClose(timeout: Long, unit: TimeUnit): Boolean {\n        if (status < 0) return true\n\n        scheduleLock.lock()\n        try {\n            if (isRunningTask && !idle.await(timeout, unit)) return false\n            close()\n        } finally {\n            scheduleLock.unlock()\n        }\n\n        return true\n    }\n\n    override fun close() {\n        if (status < 0) return\n        release()\n    }\n\n    override val isAlive: Boolean\n        get() {\n            // If status is unknown, it is not alive\n            if (status < 0) return false\n\n            try {\n                process.exitValue()\n                // Process is dead, shell is not alive\n                release()\n                return false\n            } catch (_: IllegalThreadStateException) {\n                // Process is still running\n                return true\n            }\n        }\n\n    @Synchronized\n    @Throws(IOException::class)\n    private fun exec0(task: Task) {\n        if (status < 0) {\n            task.shellDied()\n            return\n        }\n\n        cleanInputStream(stdOut)\n        cleanInputStream(stdErr)\n        try {\n            stdIn.write('\\n'.code)\n            stdIn.flush()\n        } catch (_: IOException) {\n            release()\n            task.shellDied()\n            return\n        }\n\n        task.run(stdIn, stdOut, stdErr)\n    }\n\n    private fun processTasks() {\n        var task: Task?\n        while ((processNextTask(false).also { task = it }) != null) {\n            try {\n                exec0(task!!)\n            } catch (_: IOException) {\n            }\n        }\n    }\n\n    private fun processNextTask(fromExec: Boolean): Task? {\n        scheduleLock.lock()\n        try {\n            val task = tasks.poll()\n            if (task == null) {\n                isRunningTask = false\n                idle.signalAll()\n                return null\n            }\n            if (task is SyncTask) {\n                task.signal()\n                return null\n            }\n            if (fromExec) {\n                // Put the task back in front of the queue\n                tasks.offerFirst(task)\n            } else {\n                return task\n            }\n        } finally {\n            scheduleLock.unlock()\n        }\n        EXECUTOR.execute { this.processTasks() }\n        return null\n    }\n\n    override fun submitTask(task: Task) {\n        scheduleLock.lock()\n        try {\n            tasks.offer(task)\n            if (!isRunningTask) {\n                isRunningTask = true\n                EXECUTOR.execute { this.processTasks() }\n            }\n        } finally {\n            scheduleLock.unlock()\n        }\n    }\n\n    @Throws(IOException::class)\n    override fun execTask(task: Task) {\n        scheduleLock.lock()\n        try {\n            if (isRunningTask) {\n                val sync = SyncTask(scheduleLock.newCondition())\n                tasks.offer(sync)\n                // Wait until it's our turn\n                sync.await()\n            }\n            isRunningTask = true\n        } finally {\n            scheduleLock.unlock()\n        }\n        exec0(task)\n        processNextTask(true)\n    }\n\n    override fun newJob(): Job {\n        return ShellJob(this)\n    }\n}\n"
  },
  {
    "path": "suCore/src/main/java/com/valhalla/superuser/internal/ShellInputSource.kt",
    "content": "package com.valhalla.superuser.internal\n\nimport java.io.Closeable\nimport java.io.IOException\nimport java.io.InputStream\nimport java.io.OutputStream\nimport java.nio.charset.StandardCharsets\n\ninternal interface ShellInputSource : Closeable {\n    @Throws(IOException::class)\n    fun serve(out: OutputStream)\n\n    override fun close() {}\n\n    companion object {\n        const val TAG: String = \"SHELL_IN\"\n    }\n}\n\ninternal class InputStreamSource(private val `in`: InputStream) : ShellInputSource {\n    @Throws(IOException::class)\n    override fun serve(out: OutputStream) {\n        Utils.pump(`in`, out)\n        `in`.close()\n        out.write('\\n'.code)\n        Utils.log(ShellInputSource.TAG, \"<InputStream>\")\n    }\n\n    override fun close() {\n        try {\n            `in`.close()\n        } catch (_: IOException) {\n        }\n    }\n}\n\ninternal class CommandSource(private val cmd: Array<out String>) : ShellInputSource {\n    @Throws(IOException::class)\n    override fun serve(out: OutputStream) {\n        for (command in cmd) {\n            out.write(command.toByteArray(StandardCharsets.UTF_8))\n            out.write('\\n'.code)\n            Utils.log(ShellInputSource.TAG, command)\n        }\n    }\n}\n"
  },
  {
    "path": "suCore/src/main/java/com/valhalla/superuser/internal/ShellJob.kt",
    "content": "package com.valhalla.superuser.internal\n\nimport com.valhalla.superuser.Shell\nimport java.io.IOException\nimport java.util.concurrent.Executor\nimport java.util.concurrent.Future\n\ninternal class ShellJob(private val shell: ShellImpl) : JobTask() {\n    override fun exec(): Shell.Result {\n        val holder = ResultHolder()\n        callback = holder\n        callbackExecutor = null\n        try {\n            shell.execTask(this)\n        } catch (_: IOException) { /* JobTask does not throw */\n        }\n        return holder.result\n    }\n\n    override fun submit(executor: Executor?, cb: Shell.ResultCallback?) {\n        callbackExecutor = executor\n        callback = cb\n        shell.submitTask(this)\n    }\n\n    override fun enqueue(): Future<Shell.Result?> {\n        val future = ResultFuture()\n        callback = future\n        callbackExecutor = null\n        shell.submitTask(this)\n        return future\n    }\n}\n"
  },
  {
    "path": "suCore/src/main/java/com/valhalla/superuser/internal/StreamGobbler.kt",
    "content": "package com.valhalla.superuser.internal\n\nimport java.io.BufferedReader\nimport java.io.IOException\nimport java.io.InputStream\nimport java.io.InputStreamReader\nimport java.nio.charset.StandardCharsets\nimport java.util.concurrent.Callable\n\ninternal abstract class StreamGobbler<T>(\n    protected val `in`: InputStream?,\n    protected val list: MutableList<String?>?\n) : Callable<T?> {\n    private fun outputAndCheck(line: String?): Boolean {\n        var line = line ?: return false\n\n        val len = line.length\n        val end = line.startsWith(JobTask.END_UUID, len - JobTask.UUID_LEN)\n        if (end) {\n            if (len == JobTask.UUID_LEN) return false\n            line = line.take(len - JobTask.UUID_LEN)\n        }\n        if (list != null) {\n            list.add(line)\n            Utils.log(TAG, line)\n        }\n        return !end\n    }\n\n    @Throws(IOException::class)\n    protected fun process(res: Boolean): String? {\n        val br = BufferedReader(InputStreamReader(`in`, StandardCharsets.UTF_8))\n        var line: String?\n        do {\n            line = br.readLine()\n        } while (outputAndCheck(line))\n        return if (res) br.readLine() else null\n    }\n\n    internal class OUT(`in`: InputStream?, list: MutableList<String?>?) :\n        StreamGobbler<Int?>(`in`, list) {\n        @Throws(Exception::class)\n        override fun call(): Int {\n            val codeStr = process(true)\n            try {\n                val code = codeStr?.toInt() ?: NO_RESULT_CODE\n                Utils.log(TAG, \"(exit code: $code)\")\n                return code\n            } catch (_: NumberFormatException) {\n                return NO_RESULT_CODE\n            }\n        }\n\n        companion object {\n            private const val NO_RESULT_CODE = 1\n        }\n    }\n\n    internal class ERR(`in`: InputStream?, list: MutableList<String?>?) :\n        StreamGobbler<Void?>(`in`, list) {\n        @Throws(Exception::class)\n        override fun call(): Void? {\n            process(false)\n            return null\n        }\n    }\n\n    companion object {\n        private const val TAG = \"SHELL_OUT\"\n    }\n}\n"
  },
  {
    "path": "suCore/src/main/java/com/valhalla/superuser/internal/UiThreadHandler.kt",
    "content": "package com.valhalla.superuser.internal\n\nimport android.os.Handler\nimport android.os.Looper\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.SupervisorJob\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.runBlocking\nimport kotlinx.coroutines.withContext\nimport java.util.concurrent.Executor\n\n/**\n * A modern replacement for the legacy Handler wrapper.\n * Uses Coroutines to dispatch to the Main thread.\n */\n@Suppress(\"unused\")\nobject UiThreadHandler {\n\n    // We keep the Handler for edge cases where strict Looper access is needed,\n    // but prefer Dispatchers.Main\n    val handler: Handler by lazy { Handler(Looper.getMainLooper()) }\n\n    private val mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)\n\n    /**\n     * An executor that dispatches to the Main Thread via Coroutines.\n     * Used by [com.valhalla.superuser.CallbackList].\n     */\n    val executor: Executor = Executor { command ->\n        if (Looper.myLooper() == Looper.getMainLooper()) {\n            command.run()\n        } else {\n            mainScope.launch { command.run() }\n        }\n    }\n\n    /**\n     * Runs the runnable on the UI thread asynchronously.\n     */\n    fun run(r: Runnable) {\n        if (Looper.myLooper() == Looper.getMainLooper()) {\n            r.run()\n        } else {\n            mainScope.launch { r.run() }\n        }\n    }\n\n    /**\n     * Runs the runnable on the UI thread and BLOCKS the caller until it is finished.\n     * Replaces the archaic WaitRunnable.\n     */\n    fun runAndWait(r: Runnable) {\n        if (Looper.myLooper() == Looper.getMainLooper()) {\n            r.run()\n        } else {\n            // Bridge blocking code to Coroutines\n            runBlocking {\n                withContext(Dispatchers.Main) {\n                    r.run()\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "suCore/src/main/java/com/valhalla/superuser/internal/Utils.kt",
    "content": "package com.valhalla.superuser.internal\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.os.Build\nimport android.os.Process\nimport android.util.ArraySet\nimport androidx.annotation.RestrictTo\nimport com.valhalla.superuser.Shell\nimport com.valhalla.superuser.internal.MainShell.get\nimport com.valhalla.superuser.utils.Logger\nimport java.io.File\nimport java.io.IOException\nimport java.io.InputStream\nimport java.io.OutputStream\nimport java.util.Collections\n\n@Suppress(\"unused\")\n@RestrictTo(RestrictTo.Scope.LIBRARY)\nobject Utils {\n\n    private const val TAG = \"LIB_SU\"\n    private var synchronizedCollectionClass: Class<*>? = null\n\n    // -1: uninitialized\n    //  0: checked, no root\n    //  1: checked, undetermined\n    //  2: checked, root access\n    private var currentRootState = -1\n\n    fun log(log: Any) {\n        log(TAG, log)\n    }\n\n    fun log(tag: String?, log: Any) {\n        if (vLog()) Logger.d(tag, log.toString())\n    }\n\n    fun ex(t: Throwable?) {\n        if (vLog()) Logger.e(TAG, \"\", t)\n    }\n\n    fun err(t: Throwable?) {\n        err(TAG, t)\n    }\n\n    fun err(tag: String?, t: Throwable?) {\n        Logger.e(tag, \"\", t)\n    }\n\n    fun vLog(): Boolean {\n        return Shell.enableVerboseLogging\n    }\n\n    fun hasStartupAgents(context: Context): Boolean {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) return false\n        val agents = File(context.codeCacheDir, \"startup_agents\")\n        return agents.isDirectory()\n    }\n\n    fun isSynchronized(collection: MutableCollection<*>?): Boolean {\n        if (synchronizedCollectionClass == null) {\n            synchronizedCollectionClass =\n                Collections.synchronizedCollection<Any?>(mutableListOf<Any?>()).javaClass\n        }\n        return synchronizedCollectionClass!!.isInstance(collection)\n    }\n\n    @Throws(IOException::class)\n    fun pump(`in`: InputStream, out: OutputStream): Long {\n        var read: Int\n        var total: Long = 0\n        val buf = ByteArray(64 * 1024) /* 64K buffer */\n        while ((`in`.read(buf).also { read = it }) > 0) {\n            out.write(buf, 0, read)\n            total += read.toLong()\n        }\n        return total\n    }\n\n    fun <E> newArraySet(): MutableSet<E?> {\n        return ArraySet<E?>()\n    }\n\n    @get:Synchronized\n    val isAppGrantedRoot: Boolean?\n        get() {\n            if (currentRootState < 0) {\n                if (Process.myUid() == 0) {\n                    // The current process is a root service\n                    currentRootState = 2\n                    return true\n                }\n                // noinspection ConstantConditions\n                for (path in System.getenv(\"PATH\")!!.split(\":\".toRegex())\n                    .dropLastWhile { it.isEmpty() }.toTypedArray()) {\n                    val su = File(path, \"su\")\n                    if (su.canExecute()) {\n                        // We don't actually know whether the app has been granted root access.\n                        // Do NOT set the value as a confirmed state.\n                        currentRootState = 1\n                        return null\n                    }\n                }\n                currentRootState = 0\n                return false\n            }\n            return when (currentRootState) {\n                0 -> false\n                2 -> true\n                else -> null\n            }\n        }\n\n    @Synchronized\n    fun setConfirmedRootState(value: Boolean) {\n        currentRootState = if (value) 2 else 0\n    }\n\n    val isRootImpossible: Boolean\n        get() = isAppGrantedRoot == java.lang.Boolean.FALSE\n\n    val isMainShellRoot: Boolean\n        get() = get().isRoot\n\n    @get:SuppressLint(\"DiscouragedPrivateApi\")\n    val isProcess64Bit: Boolean\n        get() {\n            return Process.is64Bit()\n        }\n}\n"
  },
  {
    "path": "suCore/src/main/java/com/valhalla/superuser/ktx/ShellExtensions.kt",
    "content": "package com.valhalla.superuser.ktx\n\nimport com.valhalla.superuser.Shell\nimport kotlinx.coroutines.channels.awaitClose\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.callbackFlow\nimport kotlinx.coroutines.suspendCancellableCoroutine\nimport kotlin.coroutines.resume\n\n/**\n * Suspends until the Shell Job is complete and returns the result.\n * Replaces the archaic [Shell.Job.submit] callback hell.\n */\nsuspend fun Shell.Job.await(): Shell.Result = suspendCancellableCoroutine { cont ->\n    // Fix: ResultCallback is not a 'fun interface', so we must use an object expression.\n    submit(object : Shell.ResultCallback {\n        override fun onResult(out: Shell.Result) {\n            if (cont.isActive) {\n                cont.resume(out)\n            }\n        }\n    })\n}\n\n/**\n * Converts a Shell Job's output into a reactive Flow.\n * Replaces the \"CallbackList\".\n *\n * Usage:\n * Shell.cmd(\"logcat\").asFlow().collect { line -> ... }\n */\nfun Shell.Job.asFlow(): Flow<String> = callbackFlow {\n    // We create a custom list that emits to the flow when items are added.\n    val flowList = object : java.util.ArrayList<String?>() {\n        override fun add(element: String?): Boolean {\n            element?.let { trySend(it) }\n            return super.add(element)\n        }\n    }\n\n    // Direct output to our flow-emitting list\n    to(flowList)\n\n    // Execute asynchronously using object expression for the callback\n    submit(object : Shell.ResultCallback {\n        override fun onResult(out: Shell.Result) {\n            close() // Close the flow when the job is done\n        }\n    })\n\n    awaitClose {\n        // Handle cancellation if necessary, though libsu jobs might not support explicit cancel\n    }\n}\n\n/**\n * Gets the main shell instance purely via Coroutines, removing the need for\n * blocking [Shell.getShell] calls on the main thread.\n */\nsuspend fun getShellAwait(): Shell = suspendCancellableCoroutine { cont ->\n    Shell.getShell(object : Shell.GetShellCallback {\n        override fun onShell(shell: Shell) {\n            if (cont.isActive) {\n                cont.resume(shell)\n            }\n        }\n    })\n}"
  },
  {
    "path": "suCore/src/main/java/com/valhalla/superuser/ktx/ShellRepository.kt",
    "content": "package com.valhalla.superuser.ktx\n\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport java.io.IOException\n\n/**\n * Interface for interacting with the Root Shell.\n * Inject this into your ViewModels via Koin.\n * DO NOT use static Shell.shell calls in your UI layer.\n */\ninterface ShellRepository {\n    suspend fun isRootGranted(): Boolean\n    suspend fun runCommand(command: String): Result<List<String>>\n    suspend fun runCommands(vararg commands: String): Result<List<String>>\n}\n\nclass RealShellRepository : ShellRepository {\n\n    // Lazy check for root status that doesn't block the UI thread\n    override suspend fun isRootGranted(): Boolean = withContext(Dispatchers.IO) {\n        // Ensure we have a shell first\n        getShellAwait().isRoot\n    }\n\n    override suspend fun runCommand(command: String): Result<List<String>> {\n        return runInternal(command)\n    }\n\n    override suspend fun runCommands(vararg commands: String): Result<List<String>> {\n        return runInternal(*commands)\n    }\n\n    private suspend fun runInternal(vararg commands: String): Result<List<String>> =\n        withContext(Dispatchers.IO) {\n            try {\n                val shell = getShellAwait()\n                val jobResult = shell.newJob().add(*commands).to(ArrayList()).await()\n\n                if (jobResult.isSuccess) {\n                    // Filter out nulls which legacy lib-su might produce\n                    Result.success(jobResult.out.filterNotNull())\n                } else {\n                    val errorMsg = jobResult.err.filterNotNull().joinToString(\"\\n\")\n                    Result.failure(IOException(\"Command failed with code ${jobResult.code}: $errorMsg\"))\n                }\n            } catch (e: Exception) {\n                Result.failure(e)\n            }\n        }\n}"
  },
  {
    "path": "suCore/src/main/java/com/valhalla/superuser/utils/Logger.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage com.valhalla.superuser.utils\n\nimport android.util.Log\nimport com.valhalla.superuser.BuildConfig\n\nobject Logger {\n\n    fun d(tag: String?, message: String) {\n        if (BuildConfig.DEBUG) {\n            Log.d(tag ?: \"\", message)\n        }\n    }\n\n    fun i(tag: String?, message: String) {\n        if (BuildConfig.DEBUG) {\n            Log.i(tag ?: \"\", message)\n        }\n    }\n\n    fun w(tag: String?, message: String) {\n        if (BuildConfig.DEBUG) {\n            Log.w(tag ?: \"\", message)\n        }\n    }\n\n    fun v(tag: String?, message: String) {\n        if (BuildConfig.DEBUG) {\n            Log.v(tag ?: \"\", message)\n        }\n    }\n\n    fun e(tag: String?, message: String, throwable: Throwable? = null) {\n        if (BuildConfig.DEBUG) {\n            Log.e(tag ?: \"\", message, throwable)\n        }\n    }\n}"
  },
  {
    "path": "suCore/src/main/java/com/valhalla/superuser/utils/ShellUtils.kt",
    "content": "package com.valhalla.superuser.utils\n\nimport com.valhalla.superuser.Shell\nimport com.valhalla.superuser.ktx.await\n\n/**\n * Modern replacement for the legacy ShellUtils object.\n * Uses extension functions for cleaner syntax.\n */\n\n/**\n * Checks if the output list contains valid data.\n */\nfun List<String?>?.isValidOutput(): Boolean {\n    return this?.any { !it.isNullOrEmpty() } == true\n}\n\n/**\n * Escapes a string for use in a shell command.\n * Replaces `ShellUtils.escapedString(str)`.\n */\nfun String.escapeForShell(): String {\n    return \"'\" + this.replace(\"'\", \"'\\\\''\") + \"'\"\n}\n\n/**\n * Quickly runs a command and returns the first line of output or empty string.\n * Now a suspend function to prevent blocking the Main Thread.\n */\nsuspend fun Shell.fastCmd(vararg commands: String): String {\n    val result = this.newJob().add(*commands).to(ArrayList()).await()\n    val out = result.out\n    return if (out.isValidOutput()) out.lastOrNull() ?: \"\" else \"\"\n}\n\n/**\n * Quickly checks if a command succeeds (exit code 0).\n */\nsuspend fun Shell.fastCmdResult(vararg commands: String): Boolean {\n    return this.newJob().add(*commands).to(ArrayList(), null).await().isSuccess\n}"
  },
  {
    "path": "vm-runtime/README.md",
    "content": "# vm-runtime\n\nCompile-only Java stubs that allow `:bypass` to reference hidden Android runtime classes at compile\ntime.\n\n## Purpose\n\nThe following class is used by `:bypass` but is not present on the standard Android SDK classpath:\n\n| Stub class                | Real runtime class                    | Used for                           |\n|---------------------------|---------------------------------------|------------------------------------|\n| `dalvik.system.VMRuntime` | `dalvik.system.VMRuntime` (on-device) | Calling `setHiddenApiExemptions()` |\n\nWithout these stubs the `:bypass` module would fail to compile. At runtime the stubs are **not\npresent** — the real classes from the device's ART runtime are used instead.\n\n## Important: never add this as a runtime dependency\n\nThis module is declared `compileOnly` inside `:bypass`:\n\n```kotlin\n// bypass/build.gradle.kts\ncompileOnly(project(\":vm-runtime\"))\n```\n\nDo **not** add `:vm-runtime` as `implementation` or `api` anywhere. Shipping the stub classes in the\nAPK would shadow the real `dalvik.system.VMRuntime` and break the bypass entirely.\n\n## Why stubs instead of implementation\n\nThe real classes like `dalvik.system.VMRuntime` live on the device. Placing the stub in a separate\nmodule avoids conflicts during compilation. The module is used at compile time only.\n\n## Stub surface\n\n**`dalvik.system.VMRuntime`**\n\n```java\npublic static VMRuntime getRuntime();\npublic void setHiddenApiExemptions(String... signatures);\n```\n\nOnly the methods actually called by `:bypass` are stubbed. There is no need to mirror the full\n`VMRuntime` API.\n"
  },
  {
    "path": "vm-runtime/build.gradle.kts",
    "content": "plugins {\n    `java-library`\n}\n\njava {\n    sourceCompatibility = JavaVersion.VERSION_21\n    targetCompatibility = JavaVersion.VERSION_21\n}\n"
  },
  {
    "path": "vm-runtime/src/main/java/dalvik/system/VMRuntime.java",
    "content": "package dalvik.system;\n\npublic class VMRuntime {\n    public static VMRuntime getRuntime() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public void setHiddenApiExemptions(String... signatures) {\n    }\n}\n"
  }
]