diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..2d6d258f47 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,3 @@ +[*.{kt,kts}] +ktlint_code_style = intellij_idea +ktlint_standard_no-wildcard-imports = disabled \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 98b14a097d..f623d8a579 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -72,7 +72,6 @@ body: - **Do not submit a duplicate bug report**: Search for existing bug reports [here](https://github.com/ReVanced/revanced-patches/issues?q=label%3A%22Bug+report%22). - **Review the contribution guidelines**: Make sure your bug report adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-patches/blob/main/CONTRIBUTING.md). - - **Check the troubleshooting guide**: A solution to your issue might be found in the [FAQ](https://github.com/ReVanced/revanced-documentation/blob/main/docs/revanced-resources/questions.md) or the [troubleshooting guide](https://github.com/ReVanced/revanced-documentation/blob/main/docs/revanced-resources/troubleshooting.md). - **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app). - type: textarea attributes: diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 13d436ba29..f49436ec6b 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -72,7 +72,6 @@ body: - **Do not submit a duplicate feature request**: Search for existing feature requests [here](https://github.com/ReVanced/revanced-patches/issues?q=label%3A%22Feature+request%22). - **Review the contribution guidelines**: Make sure your feature request adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-patches/blob/main/CONTRIBUTING.md). - - **Check the troubleshooting guide**: Information about your issue might be found in the [FAQ](https://github.com/ReVanced/revanced-documentation/blob/main/docs/revanced-resources/questions.md) or the [troubleshooting guide](https://github.com/ReVanced/revanced-documentation/blob/main/docs/revanced-resources/troubleshooting.md). - **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app). - type: textarea attributes: diff --git a/.github/workflows/build_pull_request.yml b/.github/workflows/build_pull_request.yml index 32c03cbe64..193a26af05 100644 --- a/.github/workflows/build_pull_request.yml +++ b/.github/workflows/build_pull_request.yml @@ -2,10 +2,6 @@ name: Build pull request on: workflow_dispatch: - inputs: - pr: - description: "PR to build" - required: true pull_request: branches: - dev @@ -14,33 +10,22 @@ jobs: release: name: Build runs-on: ubuntu-latest - permissions: - contents: read - steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v4 with: - ref: ${{ inputs.pr && format('refs/pull/{0}/merge', inputs.pr) || github.ref }} + fetch-depth: 0 - name: Setup Java - uses: actions/setup-java@v5 + uses: actions/setup-java@v4 with: - distribution: 'temurin' - java-version: '17' + distribution: "temurin" + java-version: "17" - name: Cache Gradle - uses: burrunan/gradle-cache-action@v3 + uses: burrunan/gradle-cache-action@v1 - name: Build env: - ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ github.actor }} - ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }} - run: ./gradlew :patches:buildAndroid --no-daemon - - - name: Upload artifacts - uses: actions/upload-artifact@v7 - with: - name: revanced-patches - path: patches/build/libs - archive: false + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: ./gradlew build --no-daemon diff --git a/.github/workflows/open_pull_request.yml b/.github/workflows/open_pull_request.yml index c1402f5490..33c8a7211f 100644 --- a/.github/workflows/open_pull_request.yml +++ b/.github/workflows/open_pull_request.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v4 - name: Open pull request uses: repo-sync/pull-request@v2 diff --git a/.github/workflows/pull_strings.yml b/.github/workflows/pull_strings.yml index f2da51ec74..b27d9166aa 100644 --- a/.github/workflows/pull_strings.yml +++ b/.github/workflows/pull_strings.yml @@ -12,43 +12,25 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v4 with: + fetch-depth: 0 ref: dev - persist-credentials: true - name: Pull strings uses: crowdin/github-action@v2 with: config: crowdin.yml - upload_sources: false download_translations: true - push_translations: false - skip_ref_checkout: true + localization_branch_name: feat/translations + create_pull_request: true + pull_request_title: "chore: Sync translations" + pull_request_body: "Sync translations from [crowdin.com/project/revanced](https://crowdin.com/project/revanced)" + pull_request_base_branch_name: "dev" + commit_message: "chore: Sync translations" + github_user_name: revanced-bot + github_user_email: github@revanced.app env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} - - - name: Process strings - run: | - chmod -R 777 patches/src/main/resources - ./gradlew processStringsFromCrowdin - env: - ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ github.actor }} - ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }} - - - name: Commit changes - uses: stefanzweifel/git-auto-commit-action@v7 - with: - commit_message: "chore: Sync translations from Crowdin" - push_options: '--force' - branch: feat/translations - - - name: Open pull request - uses: repo-sync/pull-request@v2 - with: - source_branch: feat/translations - destination_branch: dev - pr_title: "chore: Sync translations" - pr_body: "Sync translations from [crowdin.com/project/revanced](https://crowdin.com/project/revanced)" diff --git a/.github/workflows/push_strings.yml b/.github/workflows/push_strings.yml index d13f88573d..0cd3b4eb64 100644 --- a/.github/workflows/push_strings.yml +++ b/.github/workflows/push_strings.yml @@ -14,13 +14,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 - - - name: Process strings - env: - ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ github.actor }} - ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }} - run: ./gradlew processStringsForCrowdin + uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Push strings uses: crowdin/github-action@v2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 27a19a17c1..498cca4135 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,33 +13,34 @@ jobs: permissions: contents: write packages: write - id-token: write - attestations: write - artifact-metadata: write runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v4 + with: + # Make sure the release step uses its own credentials: + # https://github.com/cycjimmy/semantic-release-action#private-packages + persist-credentials: false + fetch-depth: 0 - name: Setup Java - uses: actions/setup-java@v5 + uses: actions/setup-java@v4 with: - distribution: 'temurin' - java-version: '17' + distribution: "temurin" + java-version: "17" - name: Cache Gradle - uses: burrunan/gradle-cache-action@v3 + uses: burrunan/gradle-cache-action@v1 - name: Build env: - ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ github.actor }} - ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }} - run: ./gradlew :patches:buildAndroid clean + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: ./gradlew build clean - name: Setup Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@v4 with: - node-version: 'lts/*' + node-version: "lts/*" cache: 'npm' - name: Install dependencies @@ -53,16 +54,6 @@ jobs: fingerprint: ${{ vars.GPG_FINGERPRINT }} - name: Release - uses: cycjimmy/semantic-release-action@v5 - id: release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ github.actor }} - ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }} - - - name: Attest - if: steps.release.outputs.new_release_published == 'true' - uses: actions/attest@v4 - with: - subject-name: 'ReVanced Patches ${{ steps.release.outputs.new_release_git_tag }}' - subject-path: patches/build/libs/patches-*.rvp + run: npm exec semantic-release diff --git a/.github/workflows/update-gradle-wrapper.yml b/.github/workflows/update-gradle-wrapper.yml index f02668b990..8136ad5f31 100644 --- a/.github/workflows/update-gradle-wrapper.yml +++ b/.github/workflows/update-gradle-wrapper.yml @@ -10,9 +10,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v4 - name: Update Gradle Wrapper - uses: gradle-update/update-gradle-wrapper-action@v2 + uses: gradle-update/update-gradle-wrapper-action@v1 with: target-branch: dev diff --git a/.releaserc b/.releaserc index ee495bf966..b3d61b10b1 100644 --- a/.releaserc +++ b/.releaserc @@ -22,7 +22,7 @@ { "assets": [ "CHANGELOG.md", - "gradle.properties" + "gradle.properties", ], "message": "chore: Release v${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" } @@ -33,16 +33,16 @@ "assets": [ { "path": "patches/build/libs/patches-!(*sources*|*javadoc*).rvp?(.asc)" - } + }, ], - "successComment": false + successComment: false } ], [ "@saithodev/semantic-release-backmerge", { - "backmergeBranches": [{"from": "main", "to": "dev"}], - "clearWorkspace": true + backmergeBranches: [{"from": "main", "to": "dev"}], + clearWorkspace: true } ] ] diff --git a/CHANGELOG.md b/CHANGELOG.md index bfebce7e69..94a12103dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5256 +1,3 @@ -# [6.1.0](https://github.com/ReVanced/revanced-patches/compare/v6.0.1...v6.1.0) (2026-03-18) - - -### Bug Fixes - -* **Export internal data documents provider:** Correct S_IFLNK constant and symlink detection mask ([#6819](https://github.com/ReVanced/revanced-patches/issues/6819)) ([252617b](https://github.com/ReVanced/revanced-patches/commit/252617b8dd3f24e1ff9a04ba1d91b43dc29bd757)) -* **YouTube - Custom branding:** Fix double icons and change default branding to ReVanced ([#6806](https://github.com/ReVanced/revanced-patches/issues/6806)) ([e51c529](https://github.com/ReVanced/revanced-patches/commit/e51c5292c171325e7cfa0f5ee85474d9b3961a34)) - - -### Features - -* Add `Spoof root of trust` and `Spoof keystore security level` patch ([#6751](https://github.com/ReVanced/revanced-patches/issues/6751)) ([4bc8c7c](https://github.com/ReVanced/revanced-patches/commit/4bc8c7c0f60a095533f07dc281f0320f8eb22f3c)) -* **Announcements:** Support ReVanced API v5 announcements ([a05386e](https://github.com/ReVanced/revanced-patches/commit/a05386e8bc24c085b5c74f3674c402c5dd5ad468)) -* Change contact email in patches about ([df1c3a4](https://github.com/ReVanced/revanced-patches/commit/df1c3a4a70fd2595d77b539299f1f7301bc60d24)) -* **Instagram:** Add `Enable location sticker redesign` patch ([#6808](https://github.com/ReVanced/revanced-patches/issues/6808)) ([4b699da](https://github.com/ReVanced/revanced-patches/commit/4b699da220e5d1527c390792b6228e2d9cffedb7)) -* **Spoof video streams:** Add Android Reel client to fix playback issues ([#6830](https://github.com/ReVanced/revanced-patches/issues/6830)) ([4b6c3e3](https://github.com/ReVanced/revanced-patches/commit/4b6c3e312328fbf6a1c7065e27d8ff04573e58be)) - -# [6.1.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v6.1.0-dev.3...v6.1.0-dev.4) (2026-03-18) - - -### Bug Fixes - -* **YouTube - Custom branding:** Fix double icons and change default branding to ReVanced ([#6806](https://github.com/ReVanced/revanced-patches/issues/6806)) ([e51c529](https://github.com/ReVanced/revanced-patches/commit/e51c5292c171325e7cfa0f5ee85474d9b3961a34)) - - -### Features - -* Add `Spoof root of trust` and `Spoof keystore security level` patch ([#6751](https://github.com/ReVanced/revanced-patches/issues/6751)) ([4bc8c7c](https://github.com/ReVanced/revanced-patches/commit/4bc8c7c0f60a095533f07dc281f0320f8eb22f3c)) -* **Instagram:** Add `Enable location sticker redesign` patch ([#6808](https://github.com/ReVanced/revanced-patches/issues/6808)) ([4b699da](https://github.com/ReVanced/revanced-patches/commit/4b699da220e5d1527c390792b6228e2d9cffedb7)) - -# [6.1.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v6.1.0-dev.2...v6.1.0-dev.3) (2026-03-18) - - -### Features - -* **Spoof video streams:** Add Android Reel client to fix playback issues ([#6830](https://github.com/ReVanced/revanced-patches/issues/6830)) ([4b6c3e3](https://github.com/ReVanced/revanced-patches/commit/4b6c3e312328fbf6a1c7065e27d8ff04573e58be)) - -# [6.1.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v6.1.0-dev.1...v6.1.0-dev.2) (2026-03-17) - - -### Features - -* **Announcements:** Support ReVanced API v5 announcements ([a05386e](https://github.com/ReVanced/revanced-patches/commit/a05386e8bc24c085b5c74f3674c402c5dd5ad468)) - -# [6.1.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v6.0.2-dev.1...v6.1.0-dev.1) (2026-03-16) - - -### Features - -* Change contact email in patches about ([df1c3a4](https://github.com/ReVanced/revanced-patches/commit/df1c3a4a70fd2595d77b539299f1f7301bc60d24)) - -## [6.0.2-dev.1](https://github.com/ReVanced/revanced-patches/compare/v6.0.1...v6.0.2-dev.1) (2026-03-16) - - -### Bug Fixes - -* **Export internal data documents provider:** Correct S_IFLNK constant and symlink detection mask ([#6819](https://github.com/ReVanced/revanced-patches/issues/6819)) ([252617b](https://github.com/ReVanced/revanced-patches/commit/252617b8dd3f24e1ff9a04ba1d91b43dc29bd757)) - -## [6.0.1](https://github.com/ReVanced/revanced-patches/compare/v6.0.0...v6.0.1) (2026-03-15) - - -### Bug Fixes - -* **GmsCore support:** use `prefixOrReplace` for non-matching APP_AUTHORITIES in content URL transformation ([#6801](https://github.com/ReVanced/revanced-patches/issues/6801)) ([8f6f128](https://github.com/ReVanced/revanced-patches/commit/8f6f128d718c20c56668ed3801b434a5cbb04dfd)) -* **YouTube Music - Hide buttons:** Crashes on startup due to null LayoutParams ([#6799](https://github.com/ReVanced/revanced-patches/issues/6799)) ([3e32c38](https://github.com/ReVanced/revanced-patches/commit/3e32c387328b061f33b361ed022ae18e447a7904)) -* **YouTube:** Use correct query parameters for DeArrow requests ([#6780](https://github.com/ReVanced/revanced-patches/issues/6780)) ([02a48e7](https://github.com/ReVanced/revanced-patches/commit/02a48e7a5f2b1ffd64a80651b49666de27ab7014)) - -## [6.0.1-dev.3](https://github.com/ReVanced/revanced-patches/compare/v6.0.1-dev.2...v6.0.1-dev.3) (2026-03-15) - - -### Bug Fixes - -* **GmsCore support:** use `prefixOrReplace` for non-matching APP_AUTHORITIES in content URL transformation ([#6801](https://github.com/ReVanced/revanced-patches/issues/6801)) ([8f6f128](https://github.com/ReVanced/revanced-patches/commit/8f6f128d718c20c56668ed3801b434a5cbb04dfd)) - -## [6.0.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v6.0.1-dev.1...v6.0.1-dev.2) (2026-03-15) - - -### Bug Fixes - -* **YouTube Music - Hide buttons:** Crashes on startup due to null LayoutParams ([#6799](https://github.com/ReVanced/revanced-patches/issues/6799)) ([3e32c38](https://github.com/ReVanced/revanced-patches/commit/3e32c387328b061f33b361ed022ae18e447a7904)) - -## [6.0.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v6.0.0...v6.0.1-dev.1) (2026-03-15) - - -### Bug Fixes - -* **YouTube:** Use correct query parameters for DeArrow requests ([#6780](https://github.com/ReVanced/revanced-patches/issues/6780)) ([02a48e7](https://github.com/ReVanced/revanced-patches/commit/02a48e7a5f2b1ffd64a80651b49666de27ab7014)) - -# [6.0.0](https://github.com/ReVanced/revanced-patches/compare/v5.50.2...v6.0.0) (2026-03-14) - - -* build(Needs bump)!: Update to ReVanced Patcher v22 ([#6542](https://github.com/ReVanced/revanced-patches/issues/6542)) ([ab2ac36](https://github.com/ReVanced/revanced-patches/commit/ab2ac36e3041cda87b659924ea2b75089f0bdb6e)) - - -### Bug Fixes - -* Add minSdk to all extension projects ([#6778](https://github.com/ReVanced/revanced-patches/issues/6778)) ([7517f57](https://github.com/ReVanced/revanced-patches/commit/7517f57ac7a54e1c914e8dd8cc3e1aa908e28e54)) -* **Check environment:** Use another (also more suitable) API to circumvent a bug ([393700f](https://github.com/ReVanced/revanced-patches/commit/393700f74ac141bfa109988202707b40d35a64ea)) -* **Custom branding:** Fix defaults ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([3e00a99](https://github.com/ReVanced/revanced-patches/commit/3e00a99c1bb3af24f9e8420e8c7c2bbaeb003c6c)) -* **Custom branding:** Resolve background playback crash with custom branded root installation ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([6aba2d1](https://github.com/ReVanced/revanced-patches/commit/6aba2d127472643c346108d481513442fa9a3fde)) -* **Enable debugging:** Add missing preference to log protocol buffer ([26d8a9e](https://github.com/ReVanced/revanced-patches/commit/26d8a9e5f891e08fe3c23601e8238de6a301b8df)) -* Fix return type check to match method successfully ([0a73452](https://github.com/ReVanced/revanced-patches/commit/0a734528dc4407571ae1dba3e80347bc9f236e3e)) -* **GmsCore support:** Handle GmsCore flavors when checking for updates ([2aa19f5](https://github.com/ReVanced/revanced-patches/commit/2aa19f5995fd050c40b15331a77d58144a5a1f69)) -* **GmsCore support:** Insert check after another missing necessary context hook ([3c0c5a8](https://github.com/ReVanced/revanced-patches/commit/3c0c5a86d8e24b47b1c30bc5a7fe994240014e2d)) -* **GmsCore support:** Insert check after necessary context hook ([03e8e3d](https://github.com/ReVanced/revanced-patches/commit/03e8e3d75cb3b03987299885cea5eb615a5cef23)) -* **GmsCore support:** Rename MicroG GmsCore specific strings as well and rename app specific strings correctly ([c2ac1f0](https://github.com/ReVanced/revanced-patches/commit/c2ac1f04a0ac180555a9d19e7ff41525487fbc6d)) -* **GmsCore support:** Try replacing in strings before prefixing to handle more edge cases ([4d94a41](https://github.com/ReVanced/revanced-patches/commit/4d94a41c46f2d4e1bf33debc95b8aa84a64964bb)) -* **Hex patch:** Fix bug in implementation of Boyer-Moore algorithm ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([f59323c](https://github.com/ReVanced/revanced-patches/commit/f59323c87d8da36b39e19936c8ed5c07d3903b16)) -* **Hex:** Add back name, which was accidentally removed from the patch ([6a547a9](https://github.com/ReVanced/revanced-patches/commit/6a547a97e52b7914bb6602f3ecc2c6cecd50e946)) -* **Instagram:** Update fingerprints for version `417.0.0.54.77` ([#6734](https://github.com/ReVanced/revanced-patches/issues/6734)) ([55f510d](https://github.com/ReVanced/revanced-patches/commit/55f510dbedd28678411b4f11d9bbdd303fa68a0d)) -* Move strings to correct patch ([4dfe3fb](https://github.com/ReVanced/revanced-patches/commit/4dfe3fb08812ed572e01e58a8604c1be9e989438)) -* **ProtonVPN - Remove delay:** Make it work on latest version by patching the correct class ([#6757](https://github.com/ReVanced/revanced-patches/issues/6757)) ([e0dc009](https://github.com/ReVanced/revanced-patches/commit/e0dc009780afea9c2f393c4f348cda5ca9c3cbbf)) -* **Reddit clients:** Fix patching broken during patcher migration by searching for strings with contains([#6681](https://github.com/ReVanced/revanced-patches/issues/6681)) ([00da402](https://github.com/ReVanced/revanced-patches/commit/00da4027707068f06ee7041b53d1316a7b218d5d)) -* Rename string keys correctly ([16e00ab](https://github.com/ReVanced/revanced-patches/commit/16e00ab4c0ff10e58adea40c7de72658788fcd97)) -* **Spotify - Sanitize sharing links:** Update patch to latest app versions ([#6685](https://github.com/ReVanced/revanced-patches/issues/6685)) ([bb7448b](https://github.com/ReVanced/revanced-patches/commit/bb7448bc9d789843371d16bfccc9815662913333)) -* Use correct string key ([9d55d00](https://github.com/ReVanced/revanced-patches/commit/9d55d00ff46a2cd18111a91a98dbc8e3137dd0ed)) -* Use custom comparison block for strings in `anyOf` ([56a087d](https://github.com/ReVanced/revanced-patches/commit/56a087dbacf331ccadfe753cbc1ced77e318fc27)) -* Use positional substitutes in strings where multiple are present ([aa8c87f](https://github.com/ReVanced/revanced-patches/commit/aa8c87f8650bd5def5f726f02be5d62d72a3007b)) -* **YouTube - Enable Debugging Patch:** Use correct Protocolbuffer setting name ([#6711](https://github.com/ReVanced/revanced-patches/issues/6711)) ([f934022](https://github.com/ReVanced/revanced-patches/commit/f934022f37ba178ac23abfa9bcd59a0c12abe43f)) -* **YouTube - Exit fullscreen mode:** Handle exiting fullscreen on first opened video ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([88724d4](https://github.com/ReVanced/revanced-patches/commit/88724d47b13d56a90384b0a2588ba82ccdd5b101)) -* **YouTube - Hide ads:** Empty space left when ads are hidden on tablets ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([c1c2aa9](https://github.com/ReVanced/revanced-patches/commit/c1c2aa98b2d7ce900eb152bc736f3c1a5558d9fc)) -* **YouTube - Hide ads:** Fix "Hide YouTube Premium promotions" hiding YouTube Doodles ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([d26e352](https://github.com/ReVanced/revanced-patches/commit/d26e352850c2659a65b13ff1ba50dcd18278603a)) -* **YouTube - Hide ads:** Hide new type of general ad, movie ad and web search result ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([9b12dd1](https://github.com/ReVanced/revanced-patches/commit/9b12dd106546d94004c971b887ffa7627ae5a8d4)) -* **YouTube - Hide ads:** Hide new type of player ad ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([c97aefc](https://github.com/ReVanced/revanced-patches/commit/c97aefc272b83b522e5ac393ec41d03630cee6fb)) -* **YouTube - Hide ads:** Hide video ads does not hide Shorts ads ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([8d274a7](https://github.com/ReVanced/revanced-patches/commit/8d274a7afc3abfafc2b702b27f022316c854dae6)) -* **YouTube - Hide ads:** Support Hide fullscreen ads on Android 13+ devices ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([02b405e](https://github.com/ReVanced/revanced-patches/commit/02b405e6ac5beeff81c7705379e6c6eb1561270d)) -* **YouTube - Hide ads:** YouTube Doodles unclickable when Hide ads is enabled ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([5d45b6d](https://github.com/ReVanced/revanced-patches/commit/5d45b6da74165ca69a336aa36e90daafaaf87411)) -* **YouTube - Hide end screen cards:** Resolve patching 20.31.4x ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([3ff303f](https://github.com/ReVanced/revanced-patches/commit/3ff303f045c4fbda0331e3f1e9fbba50f97dedab)) -* **YouTube - Hide layout components:** Ensure featured places also hide watch history shelf ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([d639faf](https://github.com/ReVanced/revanced-patches/commit/d639faf71f476bcd7fffa08bfbb0e77c02450c9f)) -* **YouTube - Hide layout components:** Fix certain description components not working ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([1bf64eb](https://github.com/ReVanced/revanced-patches/commit/1bf64eb8b06435dea9cd292376c5feda6683e0a6)) -* **YouTube - Hide layout components:** Fix empty space issues (subscribed channels bar, show more button, landscape mode) ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([22ef700](https://github.com/ReVanced/revanced-patches/commit/22ef7002e07df919c30e9274a2479925a4be69c0)) -* **YouTube - Hide layout components:** Fix side effect of Disable translucent status bar ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([5760c58](https://github.com/ReVanced/revanced-patches/commit/5760c5860ac2dc6a41821cc66f849a58e44bf3e7)) -* **YouTube - Hide layout components:** Resolve "Hide community posts" not working in search results ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([3153222](https://github.com/ReVanced/revanced-patches/commit/315322220d6a09814406394414bcfcff61ead786)) -* **YouTube - Hide layout components:** Resolve community posts sometimes showing in player suggestions ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([828df77](https://github.com/ReVanced/revanced-patches/commit/828df77810b551c70e03d888dc0fe1555c488f51)) -* **YouTube - Hide Shorts components:** Action buttons not hidden in 20.22+ ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([a90a0b1](https://github.com/ReVanced/revanced-patches/commit/a90a0b1199e66cace3eb1b8c827314ceaf514ecf)) -* **YouTube - Hide Shorts components:** Do not hide channel page headers when hiding shorts ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([1246e43](https://github.com/ReVanced/revanced-patches/commit/1246e430f2104bc4a33881fa4dbb188201c02202)) -* **YouTube - Hide Shorts components:** Find resource id only for 21.05+ ([63161e9](https://github.com/ReVanced/revanced-patches/commit/63161e9fb357387685294e4a80de94cb351c6713)) -* **YouTube - Hide Shorts components:** Fix sound metadata label hiding other components ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([49d1f65](https://github.com/ReVanced/revanced-patches/commit/49d1f65fcae5b6732b768f6184969a6c796bc5e3)) -* **YouTube - Hide Shorts components:** Hide new type of sound metadata label ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([a6b8d2f](https://github.com/ReVanced/revanced-patches/commit/a6b8d2f1039b7896b21826a46f3f13b32d16b51d)) -* **YouTube - Hide Shorts components:** Resolve hiding Shorts not working ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([ae69bdc](https://github.com/ReVanced/revanced-patches/commit/ae69bdc1d376a05b6854401586408cb6a9bda7eb)) -* **YouTube - Loop video:** Enable loop video not working in playlist ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([06dbf7e](https://github.com/ReVanced/revanced-patches/commit/06dbf7ee80c836404e3698c9db6176da9a2ab8e1)) -* **YouTube - Loop video:** Fix looping button state ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([14d0135](https://github.com/ReVanced/revanced-patches/commit/14d0135b3c41bb0c06fb8cd6569a489c41e51105)) -* **YouTube - Loop video:** Wrong icon applied ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([b34adf6](https://github.com/ReVanced/revanced-patches/commit/b34adf6437294b0b28500c207b5f29ddd2ed294d)) -* **YouTube - Open Shorts in regular player:** Fix back behavior with 20.51 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([46ec3d3](https://github.com/ReVanced/revanced-patches/commit/46ec3d3bdd7d0368e1503a1b1be815eaf9b56525)) -* **YouTube - Open Shorts in regular player:** Resolve back button closing app instead of exiting fullscreen ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([b787c46](https://github.com/ReVanced/revanced-patches/commit/b787c469fd856dff74870fcb61bb3fc3dc5514b7)) -* **YouTube - Playback speed:** Use correct extension method name ([b8b4cfb](https://github.com/ReVanced/revanced-patches/commit/b8b4cfbd016058a158364f4549e7ef6ed4d154e0)) -* **YouTube - Remove background playback restrictions:** Fix background playback not working with certain offline videos ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([2d098f2](https://github.com/ReVanced/revanced-patches/commit/2d098f2352b7dc1f0dc185ac65074443289ef2de)) -* **YouTube - Remove viewer discretion dialog:** Not working on 20.14.43+ ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([64c397e](https://github.com/ReVanced/revanced-patches/commit/64c397eb1c46bdd77f2b05d03c22a841971bea81)) -* **YouTube - Return YouTube Dislike:** Fix incorrect dislike counts after cancel ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([ad10d76](https://github.com/ReVanced/revanced-patches/commit/ad10d760354dba1e8f470972955a706da9b85c02)) -* **YouTube - ReturnYouTubeDislike:** Fix dislikes not showing with 20.31+ ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([2033883](https://github.com/ReVanced/revanced-patches/commit/203388329484616cc83aef2c3bda38a3069839ca)) -* **YouTube - Settings:** Icon not drawn correctly on some systems ([#6683](https://github.com/ReVanced/revanced-patches/issues/6683)) ([ddb6396](https://github.com/ReVanced/revanced-patches/commit/ddb6396b3f3f7a2c29b9fa171e189f9931ba0e02)) -* **YouTube - SponsorBlock:** Do not show context toast when auto skipping in feed ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([88157ac](https://github.com/ReVanced/revanced-patches/commit/88157ac5b791d4d56e8347203a02f5c78014235b)) -* **YouTube - SponsorBlock:** Resolve segments not fetching on experimental app targets ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([2067799](https://github.com/ReVanced/revanced-patches/commit/206779942d9b4e8131c4df1acb1e7eab63ec75a0)) -* **YouTube - SponsorBlock:** Show correct nested skip segment when seeking ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([f5ef68b](https://github.com/ReVanced/revanced-patches/commit/f5ef68b61a5880a574f6d0f06e4b96c00daf11bb)) -* **YouTube - Spoof app version:** Remove target `19.35.36` no longer supported by YouTube ([#6717](https://github.com/ReVanced/revanced-patches/issues/6717)) ([46fb366](https://github.com/ReVanced/revanced-patches/commit/46fb3669ee59534327d7c3d78e07b813d8a2badb)) -* **YouTube - Spoof video streams:** Make it work on 21.x ([#6705](https://github.com/ReVanced/revanced-patches/issues/6705)) ([fdfed3c](https://github.com/ReVanced/revanced-patches/commit/fdfed3c9dd46f477c1cc1b9db0f08054ffa32293)) -* **YouTube Music - Navigation bar:** Hide library tab with 8.24+ ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([cfcae43](https://github.com/ReVanced/revanced-patches/commit/cfcae434652b747345cb31b66748f0cc3220eb4a)) -* **YouTube Music:** Prevent crash on bold icons loading ([#6712](https://github.com/ReVanced/revanced-patches/issues/6712)) ([e9bfb7c](https://github.com/ReVanced/revanced-patches/commit/e9bfb7ca9bcd1499f1abe8872999aefff10cd187)) -* **YouTube:** Add back missing custom filter by adding the preference to the correct screen ([2a10489](https://github.com/ReVanced/revanced-patches/commit/2a10489a869cbab1ed01502bc6fe9330c4052e06)) -* **YouTube:** Change recommended version to 20.37.48 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([3dd305c](https://github.com/ReVanced/revanced-patches/commit/3dd305ca5d092144a924e150a668443b8f7ec3d8)) -* **YouTube:** Changes the default values for some settings ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([dce204b](https://github.com/ReVanced/revanced-patches/commit/dce204b41beb13b675d04afea3129df73a182172)) -* **YouTube:** Do not show bold icons if old settings menus is enabled ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([30bd852](https://github.com/ReVanced/revanced-patches/commit/30bd852ba5236ca25a7cc49fc23f987def27d23a)) -* **YouTube:** Fix patching unsupported 20.13.41 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([ed45375](https://github.com/ReVanced/revanced-patches/commit/ed453751057310a053600c4d50c87532a3f94989)) -* **YouTube:** Ignore cairo flag in debug flag manager ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([093497c](https://github.com/ReVanced/revanced-patches/commit/093497c34f7d6c431ce7958d6b0f85b9dd0373cd)) -* **YouTube:** Remove 19.43.41 that YouTube no longer supports ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([a8526dc](https://github.com/ReVanced/revanced-patches/commit/a8526dc8ae325b3b3d386ad1d23670b05a48da51)) - - -### Features - -* Add overlay buttons animation ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([f6fc6aa](https://github.com/ReVanced/revanced-patches/commit/f6fc6aa5ac6364dc2806e62618c300a8542b3cb0)) -* **Check environment patch:** Support another ReVanced Manager debug variant package name ([e4dea68](https://github.com/ReVanced/revanced-patches/commit/e4dea682c6640ce817d5e30cfddec953fe85436f)) -* **Custom branding:** Default to user-provided icon and name when provided ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([f19c35e](https://github.com/ReVanced/revanced-patches/commit/f19c35e21cc77e8f6f746f7f910d520f86981dd5)) -* **Enable debugging:** Allow overriding String/long/double flags in debug flag manager ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([1f91bc8](https://github.com/ReVanced/revanced-patches/commit/1f91bc8a20134c5519b8e031badfa741f7cac7a7)) -* **GMX Mail:** Add `Force enable Freephone` patch ([#6650](https://github.com/ReVanced/revanced-patches/issues/6650)) ([997b5d6](https://github.com/ReVanced/revanced-patches/commit/997b5d63d1fc1684bea9e5b265f3aca53ad5fd88)) -* **GMX Mail:** Add `Hide ads` and `Hide Premium upgrade button` patches ([#6583](https://github.com/ReVanced/revanced-patches/issues/6583)) ([2976ea3](https://github.com/ReVanced/revanced-patches/commit/2976ea3ddd09d26eeedf646f0a1020fa582d0ec0)) -* Handle multiple branch conditionals jumping to the same instruction index ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([2f7b57d](https://github.com/ReVanced/revanced-patches/commit/2f7b57d071d316985a1fec215045b6b78ede6212)) -* **Instagram:** Add `Disable Reels auto-scroll` patch ([#6736](https://github.com/ReVanced/revanced-patches/issues/6736)) ([806d6c7](https://github.com/ReVanced/revanced-patches/commit/806d6c799fb67c0fb630ae954ef615ff01597b1f)) -* Perform full search of free registers ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([01ef43a](https://github.com/ReVanced/revanced-patches/commit/01ef43ababdf015f1ad3edaf45445da0e72199f2)) -* **Photoshop Mix:** Add `Bypass login` patch ([#6745](https://github.com/ReVanced/revanced-patches/issues/6745)) ([24caae9](https://github.com/ReVanced/revanced-patches/commit/24caae98b7b4d61b388f644cc1512438e408e6b1)) -* Update YouTube & YouTube Music patches ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([88d33b8](https://github.com/ReVanced/revanced-patches/commit/88d33b847de4d2ad834a4940ee257e06e3c3ad31)) -* Use more informative patch error if the same APK is patched twice ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([26e5ce1](https://github.com/ReVanced/revanced-patches/commit/26e5ce1a325c2a6e78a5486d661f7750ecc792a3)) -* **YouTube - Disable haptic feedback:** Add Disable tap and hold haptics setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([f135122](https://github.com/ReVanced/revanced-patches/commit/f135122df1a5e6a8b822652abb2451ea4e4a3d08)) -* **YouTube - Hide ads:** Add Hide player popup ads setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([487a95d](https://github.com/ReVanced/revanced-patches/commit/487a95d3efa878d9b41f1b719924c5504e0a1d0a)) -* **YouTube - Hide layout components:** Add "Hide channel tab filter" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([0adcd8c](https://github.com/ReVanced/revanced-patches/commit/0adcd8c62e12619d5adaac5ee9886613deb53ca4)) -* **YouTube - Hide layout components:** Add "Hide collapse button" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([1554fd9](https://github.com/ReVanced/revanced-patches/commit/1554fd916d1bcc9c67319d55b21072423926fc32)) -* **YouTube - Hide layout components:** Add "Hide comments section in Home feed" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([5278434](https://github.com/ReVanced/revanced-patches/commit/5278434534653ea741e67cc1e5258abb7ca0e21e)) -* **YouTube - Hide layout components:** Add "Hide course progress" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([1927564](https://github.com/ReVanced/revanced-patches/commit/192756443a1b2ede413e2d4ae55eed2bd9d57aac)) -* **YouTube - Hide layout components:** Add "Hide explore this course" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([3e24762](https://github.com/ReVanced/revanced-patches/commit/3e24762c1847dfc467a5d6bf65cc1c3c0931ca0f)) -* **YouTube - Hide layout components:** Add "Hide featured links", "Hide featured videos", "Hide join button", and "Hide subscribe button" options ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([f9e843d](https://github.com/ReVanced/revanced-patches/commit/f9e843d75641d4a87dfbe05fa8fd407ccc0345d6)) -* **YouTube - Hide layout components:** Add "Hide feed flyout menu filter" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([a93de46](https://github.com/ReVanced/revanced-patches/commit/a93de46572a7bd1ff30a1fb653e3f7afb1c67571)) -* **YouTube - Hide layout components:** Add "Hide fullscreen button" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([b07b160](https://github.com/ReVanced/revanced-patches/commit/b07b1609e4bd9341611d6aa0194c9764616719b4)) -* **YouTube - Hide layout components:** Add "Hide latest videos button" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([ebfdd8d](https://github.com/ReVanced/revanced-patches/commit/ebfdd8df2c5323290f6e655ebf0dd1db683f33dd)) -* **YouTube - Hide layout components:** Add "Hide live chat replay button" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([a6bd311](https://github.com/ReVanced/revanced-patches/commit/a6bd3116f97e539482c752e8e4e1b1e8e90ed464)) -* **YouTube - Hide layout components:** Add "Hide quizzes" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([70b9e10](https://github.com/ReVanced/revanced-patches/commit/70b9e103aea817bed1d0972444c7b0726214c69c)) -* **YouTube - Hide layout components:** Add "Hide search box trending results" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([21bf455](https://github.com/ReVanced/revanced-patches/commit/21bf455c3f61e5fd19f97a1580ecb26ac40dcdce)) -* **YouTube - Hide layout components:** Add "Hide subscribed channels bar" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([e41a40f](https://github.com/ReVanced/revanced-patches/commit/e41a40f0d754397f9cea09f387cc901f0397787e)) -* **YouTube - Hide layout components:** Add "Hide video title" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([2cfbe08](https://github.com/ReVanced/revanced-patches/commit/2cfbe08b2137b2520dd37927202a4586af8326ff)) -* **YouTube - Hide layout components:** Apply hide search suggestions only to more recent app targets ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([a43c0e1](https://github.com/ReVanced/revanced-patches/commit/a43c0e111bfe290f7dec3c9b75b882ea9dc5630f)) -* **YouTube - Hide layout components:** Replace "Hide search suggestions" with "Hide You may like section" ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([80f6b01](https://github.com/ReVanced/revanced-patches/commit/80f6b01c64971881bb9144cada0e91bb78b9f38d)) -* **YouTube - Hide Shorts components:** Add "Hide AI button" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([99aace4](https://github.com/ReVanced/revanced-patches/commit/99aace4178ccc9aeaaeb0b19cd6f520c10ef7df2)) -* **YouTube - Hide Shorts components:** Add "Hide in video description" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([e0a8b7b](https://github.com/ReVanced/revanced-patches/commit/e0a8b7bc59113ce57e5b8b358bad9171a4ea1f99)) -* **YouTube - Navigation bar:** Add settings to hide toolbar buttons ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([d72e39f](https://github.com/ReVanced/revanced-patches/commit/d72e39f2a8fc0894667546826ef07cb3edf78e50)) -* **YouTube - Navigation buttons:** Add setting to use narrow navigation bar buttons ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([e48a5d7](https://github.com/ReVanced/revanced-patches/commit/e48a5d76f7651b0edcdb5a9b27e596df41e9c6af)) -* **YouTube - SponsorBlock:** Show skip button if player overlay controls are active ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([35ec655](https://github.com/ReVanced/revanced-patches/commit/35ec655f83ffe7ab661dca07107a74f2f9617037)) -* **YouTube - Theme:** Add "Hide splash screen" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([ca6e184](https://github.com/ReVanced/revanced-patches/commit/ca6e184172e67cca48ea4c70cfe6371e806dd793)) -* **YouTube - Video quality:** Add Hide Premium video quality setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([50a2b67](https://github.com/ReVanced/revanced-patches/commit/50a2b67ef6e6382894636acdc1c2fcf7236ab4ee)) -* **YouTube Music:** Add experimental support for 9.02.50 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([50a102d](https://github.com/ReVanced/revanced-patches/commit/50a102d8afc573936f790991381b0a8d2f8dd54d)) -* **YouTube Music:** Add experimental support for 9.03.52 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([d5b9c0c](https://github.com/ReVanced/revanced-patches/commit/d5b9c0c03d334ff31c9601a48a3beb1a4db98310)) -* **YouTube Music:** Change recommended version to 8.37.56 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([d1e7900](https://github.com/ReVanced/revanced-patches/commit/d1e7900793ceef7b53b140ba9efe25025a8aac01)) -* **YouTube Music:** Support version 8.40.54 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([62f130c](https://github.com/ReVanced/revanced-patches/commit/62f130cc883d69d40c364cac45158012dd01272f)) -* **YouTube Music:** Unofficial support of 8.50.51 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([c1d7cae](https://github.com/ReVanced/revanced-patches/commit/c1d7caeee2cfa425769571b0ebff2da86e709ef9)) -* **YouTube:** Add experimental support for 21.02.32 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([7904b60](https://github.com/ReVanced/revanced-patches/commit/7904b60dbea526af45b4a69dc349c6250453b385)) -* **YouTube:** Add experimental support for 21.03.34 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([1ae36a1](https://github.com/ReVanced/revanced-patches/commit/1ae36a1cc72f0fb29d592206f74fcd40e37acaba)) -* **YouTube:** Add experimental support for 21.04.221 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([66e113a](https://github.com/ReVanced/revanced-patches/commit/66e113a96639d0c99126749125adf234a9b10cab)) -* **YouTube:** Add experimental support for 21.05.264 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([f646c82](https://github.com/ReVanced/revanced-patches/commit/f646c820d7d6027cf013e0968189a1e2cfd9e641)) -* **YouTube:** Add experimental support for 21.06.251 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([44b17d4](https://github.com/ReVanced/revanced-patches/commit/44b17d47588251b9fab5c801a49ace2ce371fa99)) -* **YouTube:** Add experimental support for 21.06.257 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([abb703d](https://github.com/ReVanced/revanced-patches/commit/abb703dcb2ac96f30e699a33d3a896b775bb0851)) -* **YouTube:** Add experimental support for 21.07.240 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([79b0c1f](https://github.com/ReVanced/revanced-patches/commit/79b0c1f72ff5b52b162f3f861d5e10c657efa097)) -* **YouTube:** Add Hide autoplay preview patch ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([79e3955](https://github.com/ReVanced/revanced-patches/commit/79e3955fde7068eac90ae404b3869c27f17bd5f7)) -* **YouTube:** Add more double tap to seek length options ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([fb04071](https://github.com/ReVanced/revanced-patches/commit/fb04071528683d38913c57f628cbab64bf0ef6a4)) -* **YouTube:** Remove obsolete seekbar thumbnail patch ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([9909fc1](https://github.com/ReVanced/revanced-patches/commit/9909fc1e5d490e9edb59894d66c6a929fbaebb3b)) -* **YouTube:** Support version 20.40.45 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([96c85d0](https://github.com/ReVanced/revanced-patches/commit/96c85d03712e79217dc8f97bcda5f38c0e47f064)) - - -### BREAKING CHANGES - -* Deprecated APIs have been removed, and various APIs now use the updated ReVanced Patcher v22 APIs. - -# [6.0.0-dev.26](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.25...v6.0.0-dev.26) (2026-03-14) - - -### Bug Fixes - -* Add minSdk to all extension projects ([#6778](https://github.com/ReVanced/revanced-patches/issues/6778)) ([7517f57](https://github.com/ReVanced/revanced-patches/commit/7517f57ac7a54e1c914e8dd8cc3e1aa908e28e54)) - -# [6.0.0-dev.25](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.24...v6.0.0-dev.25) (2026-03-14) - - -### Features - -* **Instagram:** Add `Disable Reels auto-scroll` patch ([#6736](https://github.com/ReVanced/revanced-patches/issues/6736)) ([806d6c7](https://github.com/ReVanced/revanced-patches/commit/806d6c799fb67c0fb630ae954ef615ff01597b1f)) - -# [6.0.0-dev.24](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.23...v6.0.0-dev.24) (2026-03-09) - - -### Features - -* **Photoshop Mix:** Add `Bypass login` patch ([#6745](https://github.com/ReVanced/revanced-patches/issues/6745)) ([24caae9](https://github.com/ReVanced/revanced-patches/commit/24caae98b7b4d61b388f644cc1512438e408e6b1)) - -# [6.0.0-dev.23](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.22...v6.0.0-dev.23) (2026-03-09) - - -### Bug Fixes - -* **ProtonVPN - Remove delay:** Make it work on latest version by patching the correct class ([#6757](https://github.com/ReVanced/revanced-patches/issues/6757)) ([e0dc009](https://github.com/ReVanced/revanced-patches/commit/e0dc009780afea9c2f393c4f348cda5ca9c3cbbf)) - -# [6.0.0-dev.22](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.21...v6.0.0-dev.22) (2026-03-08) - - -### Bug Fixes - -* **YouTube - Settings:** Icon not drawn correctly on some systems ([#6683](https://github.com/ReVanced/revanced-patches/issues/6683)) ([ddb6396](https://github.com/ReVanced/revanced-patches/commit/ddb6396b3f3f7a2c29b9fa171e189f9931ba0e02)) - -# [6.0.0-dev.21](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.20...v6.0.0-dev.21) (2026-03-08) - - -### Bug Fixes - -* **Instagram:** Update fingerprints for version `417.0.0.54.77` ([#6734](https://github.com/ReVanced/revanced-patches/issues/6734)) ([55f510d](https://github.com/ReVanced/revanced-patches/commit/55f510dbedd28678411b4f11d9bbdd303fa68a0d)) -* **Spotify - Sanitize sharing links:** Update patch to latest app versions ([#6685](https://github.com/ReVanced/revanced-patches/issues/6685)) ([bb7448b](https://github.com/ReVanced/revanced-patches/commit/bb7448bc9d789843371d16bfccc9815662913333)) - -# [6.0.0-dev.20](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.19...v6.0.0-dev.20) (2026-03-08) - - -### Bug Fixes - -* **YouTube - Enable Debugging Patch:** Use correct Protocolbuffer setting name ([#6711](https://github.com/ReVanced/revanced-patches/issues/6711)) ([f934022](https://github.com/ReVanced/revanced-patches/commit/f934022f37ba178ac23abfa9bcd59a0c12abe43f)) - -# [6.0.0-dev.19](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.18...v6.0.0-dev.19) (2026-03-06) - - -### Bug Fixes - -* **Hex:** Add back name, which was accidentally removed from the patch ([6a547a9](https://github.com/ReVanced/revanced-patches/commit/6a547a97e52b7914bb6602f3ecc2c6cecd50e946)) - -# [6.0.0-dev.18](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.17...v6.0.0-dev.18) (2026-03-06) - - -### Bug Fixes - -* **YouTube - Hide Shorts components:** Find resource id only for 21.05+ ([63161e9](https://github.com/ReVanced/revanced-patches/commit/63161e9fb357387685294e4a80de94cb351c6713)) - -# [6.0.0-dev.17](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.16...v6.0.0-dev.17) (2026-03-06) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Make it work on 21.x ([#6705](https://github.com/ReVanced/revanced-patches/issues/6705)) ([fdfed3c](https://github.com/ReVanced/revanced-patches/commit/fdfed3c9dd46f477c1cc1b9db0f08054ffa32293)) - -# [6.0.0-dev.16](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.15...v6.0.0-dev.16) (2026-03-05) - - -### Bug Fixes - -* **YouTube - Spoof app version:** Remove target `19.35.36` no longer supported by YouTube ([#6717](https://github.com/ReVanced/revanced-patches/issues/6717)) ([46fb366](https://github.com/ReVanced/revanced-patches/commit/46fb3669ee59534327d7c3d78e07b813d8a2badb)) -* **YouTube Music:** Prevent crash on bold icons loading ([#6712](https://github.com/ReVanced/revanced-patches/issues/6712)) ([e9bfb7c](https://github.com/ReVanced/revanced-patches/commit/e9bfb7ca9bcd1499f1abe8872999aefff10cd187)) - -# [6.0.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.14...v6.0.0-dev.15) (2026-03-05) - - -### Bug Fixes - -* **Check environment:** Use another (also more suitable) API to circumvent a bug ([393700f](https://github.com/ReVanced/revanced-patches/commit/393700f74ac141bfa109988202707b40d35a64ea)) - -# [6.0.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.13...v6.0.0-dev.14) (2026-03-03) - - -### Bug Fixes - -* **YouTube - Playback speed:** Use correct extension method name ([b8b4cfb](https://github.com/ReVanced/revanced-patches/commit/b8b4cfbd016058a158364f4549e7ef6ed4d154e0)) - -# [6.0.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.12...v6.0.0-dev.13) (2026-03-02) - - -### Bug Fixes - -* Use custom comparison block for strings in `anyOf` ([56a087d](https://github.com/ReVanced/revanced-patches/commit/56a087dbacf331ccadfe753cbc1ced77e318fc27)) - -# [6.0.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.11...v6.0.0-dev.12) (2026-03-02) - - -### Bug Fixes - -* Fix return type check to match method successfully ([0a73452](https://github.com/ReVanced/revanced-patches/commit/0a734528dc4407571ae1dba3e80347bc9f236e3e)) - - -### Features - -* **Check environment patch:** Support another ReVanced Manager debug variant package name ([e4dea68](https://github.com/ReVanced/revanced-patches/commit/e4dea682c6640ce817d5e30cfddec953fe85436f)) - -# [6.0.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.10...v6.0.0-dev.11) (2026-03-02) - - -### Bug Fixes - -* Use correct string key ([9d55d00](https://github.com/ReVanced/revanced-patches/commit/9d55d00ff46a2cd18111a91a98dbc8e3137dd0ed)) - -# [6.0.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.9...v6.0.0-dev.10) (2026-03-01) - - -### Bug Fixes - -* **Enable debugging:** Add missing preference to log protocol buffer ([26d8a9e](https://github.com/ReVanced/revanced-patches/commit/26d8a9e5f891e08fe3c23601e8238de6a301b8df)) - -# [6.0.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.8...v6.0.0-dev.9) (2026-02-28) - - -### Bug Fixes - -* **YouTube:** Add back missing custom filter by adding the preference to the correct screen ([2a10489](https://github.com/ReVanced/revanced-patches/commit/2a10489a869cbab1ed01502bc6fe9330c4052e06)) - -# [6.0.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.7...v6.0.0-dev.8) (2026-02-28) - - -### Bug Fixes - -* **GmsCore support:** Try replacing in strings before prefixing to handle more edge cases ([4d94a41](https://github.com/ReVanced/revanced-patches/commit/4d94a41c46f2d4e1bf33debc95b8aa84a64964bb)) - -# [6.0.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.6...v6.0.0-dev.7) (2026-02-28) - - -### Bug Fixes - -* Rename string keys correctly ([16e00ab](https://github.com/ReVanced/revanced-patches/commit/16e00ab4c0ff10e58adea40c7de72658788fcd97)) - -# [6.0.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.5...v6.0.0-dev.6) (2026-02-28) - - -### Bug Fixes - -* Move strings to correct patch ([4dfe3fb](https://github.com/ReVanced/revanced-patches/commit/4dfe3fb08812ed572e01e58a8604c1be9e989438)) - -# [6.0.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.4...v6.0.0-dev.5) (2026-02-28) - - -### Bug Fixes - -* **Reddit clients:** Fix patching broken during patcher migration by searching for strings with contains([#6681](https://github.com/ReVanced/revanced-patches/issues/6681)) ([00da402](https://github.com/ReVanced/revanced-patches/commit/00da4027707068f06ee7041b53d1316a7b218d5d)) - -# [6.0.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.3...v6.0.0-dev.4) (2026-02-27) - - -### Bug Fixes - -* **Custom branding:** Fix defaults ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([3e00a99](https://github.com/ReVanced/revanced-patches/commit/3e00a99c1bb3af24f9e8420e8c7c2bbaeb003c6c)) -* **Custom branding:** Resolve background playback crash with custom branded root installation ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([6aba2d1](https://github.com/ReVanced/revanced-patches/commit/6aba2d127472643c346108d481513442fa9a3fde)) -* **Hex patch:** Fix bug in implementation of Boyer-Moore algorithm ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([f59323c](https://github.com/ReVanced/revanced-patches/commit/f59323c87d8da36b39e19936c8ed5c07d3903b16)) -* **YouTube - Exit fullscreen mode:** Handle exiting fullscreen on first opened video ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([88724d4](https://github.com/ReVanced/revanced-patches/commit/88724d47b13d56a90384b0a2588ba82ccdd5b101)) -* **YouTube - Hide ads:** Empty space left when ads are hidden on tablets ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([c1c2aa9](https://github.com/ReVanced/revanced-patches/commit/c1c2aa98b2d7ce900eb152bc736f3c1a5558d9fc)) -* **YouTube - Hide ads:** Fix "Hide YouTube Premium promotions" hiding YouTube Doodles ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([d26e352](https://github.com/ReVanced/revanced-patches/commit/d26e352850c2659a65b13ff1ba50dcd18278603a)) -* **YouTube - Hide ads:** Hide new type of general ad, movie ad and web search result ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([9b12dd1](https://github.com/ReVanced/revanced-patches/commit/9b12dd106546d94004c971b887ffa7627ae5a8d4)) -* **YouTube - Hide ads:** Hide new type of player ad ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([c97aefc](https://github.com/ReVanced/revanced-patches/commit/c97aefc272b83b522e5ac393ec41d03630cee6fb)) -* **YouTube - Hide ads:** Hide video ads does not hide Shorts ads ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([8d274a7](https://github.com/ReVanced/revanced-patches/commit/8d274a7afc3abfafc2b702b27f022316c854dae6)) -* **YouTube - Hide ads:** Support Hide fullscreen ads on Android 13+ devices ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([02b405e](https://github.com/ReVanced/revanced-patches/commit/02b405e6ac5beeff81c7705379e6c6eb1561270d)) -* **YouTube - Hide ads:** YouTube Doodles unclickable when Hide ads is enabled ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([5d45b6d](https://github.com/ReVanced/revanced-patches/commit/5d45b6da74165ca69a336aa36e90daafaaf87411)) -* **YouTube - Hide end screen cards:** Resolve patching 20.31.4x ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([3ff303f](https://github.com/ReVanced/revanced-patches/commit/3ff303f045c4fbda0331e3f1e9fbba50f97dedab)) -* **YouTube - Hide layout components:** Ensure featured places also hide watch history shelf ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([d639faf](https://github.com/ReVanced/revanced-patches/commit/d639faf71f476bcd7fffa08bfbb0e77c02450c9f)) -* **YouTube - Hide layout components:** Fix certain description components not working ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([1bf64eb](https://github.com/ReVanced/revanced-patches/commit/1bf64eb8b06435dea9cd292376c5feda6683e0a6)) -* **YouTube - Hide layout components:** Fix empty space issues (subscribed channels bar, show more button, landscape mode) ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([22ef700](https://github.com/ReVanced/revanced-patches/commit/22ef7002e07df919c30e9274a2479925a4be69c0)) -* **YouTube - Hide layout components:** Fix side effect of Disable translucent status bar ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([5760c58](https://github.com/ReVanced/revanced-patches/commit/5760c5860ac2dc6a41821cc66f849a58e44bf3e7)) -* **YouTube - Hide layout components:** Resolve "Hide community posts" not working in search results ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([3153222](https://github.com/ReVanced/revanced-patches/commit/315322220d6a09814406394414bcfcff61ead786)) -* **YouTube - Hide layout components:** Resolve community posts sometimes showing in player suggestions ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([828df77](https://github.com/ReVanced/revanced-patches/commit/828df77810b551c70e03d888dc0fe1555c488f51)) -* **YouTube - Hide Shorts components:** Action buttons not hidden in 20.22+ ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([a90a0b1](https://github.com/ReVanced/revanced-patches/commit/a90a0b1199e66cace3eb1b8c827314ceaf514ecf)) -* **YouTube - Hide Shorts components:** Do not hide channel page headers when hiding shorts ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([1246e43](https://github.com/ReVanced/revanced-patches/commit/1246e430f2104bc4a33881fa4dbb188201c02202)) -* **YouTube - Hide Shorts components:** Fix sound metadata label hiding other components ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([49d1f65](https://github.com/ReVanced/revanced-patches/commit/49d1f65fcae5b6732b768f6184969a6c796bc5e3)) -* **YouTube - Hide Shorts components:** Hide new type of sound metadata label ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([a6b8d2f](https://github.com/ReVanced/revanced-patches/commit/a6b8d2f1039b7896b21826a46f3f13b32d16b51d)) -* **YouTube - Hide Shorts components:** Resolve hiding Shorts not working ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([ae69bdc](https://github.com/ReVanced/revanced-patches/commit/ae69bdc1d376a05b6854401586408cb6a9bda7eb)) -* **YouTube - Loop video:** Enable loop video not working in playlist ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([06dbf7e](https://github.com/ReVanced/revanced-patches/commit/06dbf7ee80c836404e3698c9db6176da9a2ab8e1)) -* **YouTube - Loop video:** Fix looping button state ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([14d0135](https://github.com/ReVanced/revanced-patches/commit/14d0135b3c41bb0c06fb8cd6569a489c41e51105)) -* **YouTube - Loop video:** Wrong icon applied ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([b34adf6](https://github.com/ReVanced/revanced-patches/commit/b34adf6437294b0b28500c207b5f29ddd2ed294d)) -* **YouTube - Open Shorts in regular player:** Fix back behavior with 20.51 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([46ec3d3](https://github.com/ReVanced/revanced-patches/commit/46ec3d3bdd7d0368e1503a1b1be815eaf9b56525)) -* **YouTube - Open Shorts in regular player:** Resolve back button closing app instead of exiting fullscreen ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([b787c46](https://github.com/ReVanced/revanced-patches/commit/b787c469fd856dff74870fcb61bb3fc3dc5514b7)) -* **YouTube - Remove background playback restrictions:** Fix background playback not working with certain offline videos ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([2d098f2](https://github.com/ReVanced/revanced-patches/commit/2d098f2352b7dc1f0dc185ac65074443289ef2de)) -* **YouTube - Remove viewer discretion dialog:** Not working on 20.14.43+ ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([64c397e](https://github.com/ReVanced/revanced-patches/commit/64c397eb1c46bdd77f2b05d03c22a841971bea81)) -* **YouTube - Return YouTube Dislike:** Fix incorrect dislike counts after cancel ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([ad10d76](https://github.com/ReVanced/revanced-patches/commit/ad10d760354dba1e8f470972955a706da9b85c02)) -* **YouTube - ReturnYouTubeDislike:** Fix dislikes not showing with 20.31+ ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([2033883](https://github.com/ReVanced/revanced-patches/commit/203388329484616cc83aef2c3bda38a3069839ca)) -* **YouTube - SponsorBlock:** Do not show context toast when auto skipping in feed ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([88157ac](https://github.com/ReVanced/revanced-patches/commit/88157ac5b791d4d56e8347203a02f5c78014235b)) -* **YouTube - SponsorBlock:** Resolve segments not fetching on experimental app targets ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([2067799](https://github.com/ReVanced/revanced-patches/commit/206779942d9b4e8131c4df1acb1e7eab63ec75a0)) -* **YouTube - SponsorBlock:** Show correct nested skip segment when seeking ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([f5ef68b](https://github.com/ReVanced/revanced-patches/commit/f5ef68b61a5880a574f6d0f06e4b96c00daf11bb)) -* **YouTube Music - Navigation bar:** Hide library tab with 8.24+ ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([cfcae43](https://github.com/ReVanced/revanced-patches/commit/cfcae434652b747345cb31b66748f0cc3220eb4a)) -* **YouTube:** Change recommended version to 20.37.48 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([3dd305c](https://github.com/ReVanced/revanced-patches/commit/3dd305ca5d092144a924e150a668443b8f7ec3d8)) -* **YouTube:** Changes the default values for some settings ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([dce204b](https://github.com/ReVanced/revanced-patches/commit/dce204b41beb13b675d04afea3129df73a182172)) -* **YouTube:** Do not show bold icons if old settings menus is enabled ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([30bd852](https://github.com/ReVanced/revanced-patches/commit/30bd852ba5236ca25a7cc49fc23f987def27d23a)) -* **YouTube:** Fix patching unsupported 20.13.41 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([ed45375](https://github.com/ReVanced/revanced-patches/commit/ed453751057310a053600c4d50c87532a3f94989)) -* **YouTube:** Ignore cairo flag in debug flag manager ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([093497c](https://github.com/ReVanced/revanced-patches/commit/093497c34f7d6c431ce7958d6b0f85b9dd0373cd)) -* **YouTube:** Remove 19.43.41 that YouTube no longer supports ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([a8526dc](https://github.com/ReVanced/revanced-patches/commit/a8526dc8ae325b3b3d386ad1d23670b05a48da51)) - - -### Features - -* Add overlay buttons animation ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([f6fc6aa](https://github.com/ReVanced/revanced-patches/commit/f6fc6aa5ac6364dc2806e62618c300a8542b3cb0)) -* **Custom branding:** Default to user-provided icon and name when provided ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([f19c35e](https://github.com/ReVanced/revanced-patches/commit/f19c35e21cc77e8f6f746f7f910d520f86981dd5)) -* **Enable debugging:** Allow overriding String/long/double flags in debug flag manager ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([1f91bc8](https://github.com/ReVanced/revanced-patches/commit/1f91bc8a20134c5519b8e031badfa741f7cac7a7)) -* Handle multiple branch conditionals jumping to the same instruction index ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([2f7b57d](https://github.com/ReVanced/revanced-patches/commit/2f7b57d071d316985a1fec215045b6b78ede6212)) -* Perform full search of free registers ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([01ef43a](https://github.com/ReVanced/revanced-patches/commit/01ef43ababdf015f1ad3edaf45445da0e72199f2)) -* Update YouTube & YouTube Music patches ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([88d33b8](https://github.com/ReVanced/revanced-patches/commit/88d33b847de4d2ad834a4940ee257e06e3c3ad31)) -* Use more informative patch error if the same APK is patched twice ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([26e5ce1](https://github.com/ReVanced/revanced-patches/commit/26e5ce1a325c2a6e78a5486d661f7750ecc792a3)) -* **YouTube - Disable haptic feedback:** Add Disable tap and hold haptics setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([f135122](https://github.com/ReVanced/revanced-patches/commit/f135122df1a5e6a8b822652abb2451ea4e4a3d08)) -* **YouTube - Hide ads:** Add Hide player popup ads setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([487a95d](https://github.com/ReVanced/revanced-patches/commit/487a95d3efa878d9b41f1b719924c5504e0a1d0a)) -* **YouTube - Hide layout components:** Add "Hide channel tab filter" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([0adcd8c](https://github.com/ReVanced/revanced-patches/commit/0adcd8c62e12619d5adaac5ee9886613deb53ca4)) -* **YouTube - Hide layout components:** Add "Hide collapse button" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([1554fd9](https://github.com/ReVanced/revanced-patches/commit/1554fd916d1bcc9c67319d55b21072423926fc32)) -* **YouTube - Hide layout components:** Add "Hide comments section in Home feed" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([5278434](https://github.com/ReVanced/revanced-patches/commit/5278434534653ea741e67cc1e5258abb7ca0e21e)) -* **YouTube - Hide layout components:** Add "Hide course progress" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([1927564](https://github.com/ReVanced/revanced-patches/commit/192756443a1b2ede413e2d4ae55eed2bd9d57aac)) -* **YouTube - Hide layout components:** Add "Hide explore this course" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([3e24762](https://github.com/ReVanced/revanced-patches/commit/3e24762c1847dfc467a5d6bf65cc1c3c0931ca0f)) -* **YouTube - Hide layout components:** Add "Hide featured links", "Hide featured videos", "Hide join button", and "Hide subscribe button" options ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([f9e843d](https://github.com/ReVanced/revanced-patches/commit/f9e843d75641d4a87dfbe05fa8fd407ccc0345d6)) -* **YouTube - Hide layout components:** Add "Hide feed flyout menu filter" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([a93de46](https://github.com/ReVanced/revanced-patches/commit/a93de46572a7bd1ff30a1fb653e3f7afb1c67571)) -* **YouTube - Hide layout components:** Add "Hide fullscreen button" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([b07b160](https://github.com/ReVanced/revanced-patches/commit/b07b1609e4bd9341611d6aa0194c9764616719b4)) -* **YouTube - Hide layout components:** Add "Hide latest videos button" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([ebfdd8d](https://github.com/ReVanced/revanced-patches/commit/ebfdd8df2c5323290f6e655ebf0dd1db683f33dd)) -* **YouTube - Hide layout components:** Add "Hide live chat replay button" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([a6bd311](https://github.com/ReVanced/revanced-patches/commit/a6bd3116f97e539482c752e8e4e1b1e8e90ed464)) -* **YouTube - Hide layout components:** Add "Hide quizzes" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([70b9e10](https://github.com/ReVanced/revanced-patches/commit/70b9e103aea817bed1d0972444c7b0726214c69c)) -* **YouTube - Hide layout components:** Add "Hide search box trending results" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([21bf455](https://github.com/ReVanced/revanced-patches/commit/21bf455c3f61e5fd19f97a1580ecb26ac40dcdce)) -* **YouTube - Hide layout components:** Add "Hide subscribed channels bar" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([e41a40f](https://github.com/ReVanced/revanced-patches/commit/e41a40f0d754397f9cea09f387cc901f0397787e)) -* **YouTube - Hide layout components:** Add "Hide video title" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([2cfbe08](https://github.com/ReVanced/revanced-patches/commit/2cfbe08b2137b2520dd37927202a4586af8326ff)) -* **YouTube - Hide layout components:** Apply hide search suggestions only to more recent app targets ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([a43c0e1](https://github.com/ReVanced/revanced-patches/commit/a43c0e111bfe290f7dec3c9b75b882ea9dc5630f)) -* **YouTube - Hide layout components:** Replace "Hide search suggestions" with "Hide You may like section" ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([80f6b01](https://github.com/ReVanced/revanced-patches/commit/80f6b01c64971881bb9144cada0e91bb78b9f38d)) -* **YouTube - Hide Shorts components:** Add "Hide AI button" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([99aace4](https://github.com/ReVanced/revanced-patches/commit/99aace4178ccc9aeaaeb0b19cd6f520c10ef7df2)) -* **YouTube - Hide Shorts components:** Add "Hide in video description" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([e0a8b7b](https://github.com/ReVanced/revanced-patches/commit/e0a8b7bc59113ce57e5b8b358bad9171a4ea1f99)) -* **YouTube - Navigation bar:** Add settings to hide toolbar buttons ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([d72e39f](https://github.com/ReVanced/revanced-patches/commit/d72e39f2a8fc0894667546826ef07cb3edf78e50)) -* **YouTube - Navigation buttons:** Add setting to use narrow navigation bar buttons ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([e48a5d7](https://github.com/ReVanced/revanced-patches/commit/e48a5d76f7651b0edcdb5a9b27e596df41e9c6af)) -* **YouTube - SponsorBlock:** Show skip button if player overlay controls are active ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([35ec655](https://github.com/ReVanced/revanced-patches/commit/35ec655f83ffe7ab661dca07107a74f2f9617037)) -* **YouTube - Theme:** Add "Hide splash screen" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([ca6e184](https://github.com/ReVanced/revanced-patches/commit/ca6e184172e67cca48ea4c70cfe6371e806dd793)) -* **YouTube - Video quality:** Add Hide Premium video quality setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([50a2b67](https://github.com/ReVanced/revanced-patches/commit/50a2b67ef6e6382894636acdc1c2fcf7236ab4ee)) -* **YouTube Music:** Add experimental support for 9.02.50 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([50a102d](https://github.com/ReVanced/revanced-patches/commit/50a102d8afc573936f790991381b0a8d2f8dd54d)) -* **YouTube Music:** Add experimental support for 9.03.52 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([d5b9c0c](https://github.com/ReVanced/revanced-patches/commit/d5b9c0c03d334ff31c9601a48a3beb1a4db98310)) -* **YouTube Music:** Change recommended version to 8.37.56 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([d1e7900](https://github.com/ReVanced/revanced-patches/commit/d1e7900793ceef7b53b140ba9efe25025a8aac01)) -* **YouTube Music:** Support version 8.40.54 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([62f130c](https://github.com/ReVanced/revanced-patches/commit/62f130cc883d69d40c364cac45158012dd01272f)) -* **YouTube Music:** Unofficial support of 8.50.51 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([c1d7cae](https://github.com/ReVanced/revanced-patches/commit/c1d7caeee2cfa425769571b0ebff2da86e709ef9)) -* **YouTube:** Add experimental support for 21.02.32 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([7904b60](https://github.com/ReVanced/revanced-patches/commit/7904b60dbea526af45b4a69dc349c6250453b385)) -* **YouTube:** Add experimental support for 21.03.34 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([1ae36a1](https://github.com/ReVanced/revanced-patches/commit/1ae36a1cc72f0fb29d592206f74fcd40e37acaba)) -* **YouTube:** Add experimental support for 21.04.221 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([66e113a](https://github.com/ReVanced/revanced-patches/commit/66e113a96639d0c99126749125adf234a9b10cab)) -* **YouTube:** Add experimental support for 21.05.264 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([f646c82](https://github.com/ReVanced/revanced-patches/commit/f646c820d7d6027cf013e0968189a1e2cfd9e641)) -* **YouTube:** Add experimental support for 21.06.251 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([44b17d4](https://github.com/ReVanced/revanced-patches/commit/44b17d47588251b9fab5c801a49ace2ce371fa99)) -* **YouTube:** Add experimental support for 21.06.257 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([abb703d](https://github.com/ReVanced/revanced-patches/commit/abb703dcb2ac96f30e699a33d3a896b775bb0851)) -* **YouTube:** Add experimental support for 21.07.240 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([79b0c1f](https://github.com/ReVanced/revanced-patches/commit/79b0c1f72ff5b52b162f3f861d5e10c657efa097)) -* **YouTube:** Add Hide autoplay preview patch ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([79e3955](https://github.com/ReVanced/revanced-patches/commit/79e3955fde7068eac90ae404b3869c27f17bd5f7)) -* **YouTube:** Add more double tap to seek length options ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([fb04071](https://github.com/ReVanced/revanced-patches/commit/fb04071528683d38913c57f628cbab64bf0ef6a4)) -* **YouTube:** Remove obsolete seekbar thumbnail patch ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([9909fc1](https://github.com/ReVanced/revanced-patches/commit/9909fc1e5d490e9edb59894d66c6a929fbaebb3b)) -* **YouTube:** Support version 20.40.45 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([96c85d0](https://github.com/ReVanced/revanced-patches/commit/96c85d03712e79217dc8f97bcda5f38c0e47f064)) - -# [6.0.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.51.0-dev.2...v6.0.0-dev.1) (2026-02-27) - - -* build(Needs bump)!: Update to ReVanced Patcher v22 ([#6542](https://github.com/ReVanced/revanced-patches/issues/6542)) ([ab2ac36](https://github.com/ReVanced/revanced-patches/commit/ab2ac36e3041cda87b659924ea2b75089f0bdb6e)) - - -### BREAKING CHANGES - -* Deprecated APIs have been removed, and various APIs now use the updated ReVanced Patcher v22 APIs. - -# [5.51.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.51.0-dev.1...v5.51.0-dev.2) (2026-02-26) - - -### Features - -* **GMX Mail:** Add `Hide ads` and `Hide Premium upgrade button` patches ([#6583](https://github.com/ReVanced/revanced-patches/issues/6583)) ([2976ea3](https://github.com/ReVanced/revanced-patches/commit/2976ea3ddd09d26eeedf646f0a1020fa582d0ec0)) - -# [5.51.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.50.3-dev.4...v5.51.0-dev.1) (2026-02-26) - - -### Features - -* **GMX Mail:** Add `Force enable Freephone` patch ([#6650](https://github.com/ReVanced/revanced-patches/issues/6650)) ([997b5d6](https://github.com/ReVanced/revanced-patches/commit/997b5d63d1fc1684bea9e5b265f3aca53ad5fd88)) - -## [5.50.3-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.50.3-dev.3...v5.50.3-dev.4) (2026-02-23) - - -### Bug Fixes - -* **GmsCore support:** Insert check after another missing necessary context hook ([3c0c5a8](https://github.com/ReVanced/revanced-patches/commit/3c0c5a86d8e24b47b1c30bc5a7fe994240014e2d)) - -## [5.50.3-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.50.3-dev.2...v5.50.3-dev.3) (2026-02-20) - - -### Bug Fixes - -* **GmsCore support:** Insert check after necessary context hook ([03e8e3d](https://github.com/ReVanced/revanced-patches/commit/03e8e3d75cb3b03987299885cea5eb615a5cef23)) - -## [5.50.3-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.50.3-dev.1...v5.50.3-dev.2) (2026-02-16) - - -### Bug Fixes - -* **GmsCore support:** Handle GmsCore flavors when checking for updates ([2aa19f5](https://github.com/ReVanced/revanced-patches/commit/2aa19f5995fd050c40b15331a77d58144a5a1f69)) -* Use positional substitutes in strings where multiple are present ([aa8c87f](https://github.com/ReVanced/revanced-patches/commit/aa8c87f8650bd5def5f726f02be5d62d72a3007b)) - -## [5.50.3-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.50.2...v5.50.3-dev.1) (2026-02-16) - - -### Bug Fixes - -* **GmsCore support:** Rename MicroG GmsCore specific strings as well and rename app specific strings correctly ([c2ac1f0](https://github.com/ReVanced/revanced-patches/commit/c2ac1f04a0ac180555a9d19e7ff41525487fbc6d)) - -## [5.50.2](https://github.com/ReVanced/revanced-patches/compare/v5.50.1...v5.50.2) (2026-02-15) - - -### Bug Fixes - -* Add missing patch option descriptions ([16e42a7](https://github.com/ReVanced/revanced-patches/commit/16e42a75ecbf51e06432f1f6c96758f8d9bdb771)) - -## [5.50.1](https://github.com/ReVanced/revanced-patches/compare/v5.50.0...v5.50.1) (2026-02-15) - - -### Bug Fixes - -* Fix broken release by bumping to v5.50.1 ([d416609](https://github.com/ReVanced/revanced-patches/commit/d4166092571b542925a59328d3d59fbc42eb29e3)) - -## [5.50.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.50.0...v5.50.1-dev.1) (2026-02-15) - - -### Bug Fixes - -* Fix broken release by bumping to v5.50.1 ([d416609](https://github.com/ReVanced/revanced-patches/commit/d4166092571b542925a59328d3d59fbc42eb29e3)) - -# [5.49.0](https://github.com/ReVanced/revanced-patches/compare/v5.48.0...v5.49.0) (2026-02-15) - - -### Bug Fixes - -* Disable `Prevent screenshot detection` by default ([#6511](https://github.com/ReVanced/revanced-patches/issues/6511)) ([5b5c502](https://github.com/ReVanced/revanced-patches/commit/5b5c50254d533faa0e04d542f4859cbef610713e)) -* **Instagram - Open links externally:** Fix patch by handling >4-bit register ([#6538](https://github.com/ReVanced/revanced-patches/issues/6538)) ([f681a6f](https://github.com/ReVanced/revanced-patches/commit/f681a6ffd45f05a61743e7d272cd68c4b743be42)) -* **Instagram:** Make `Change link sharing domain` and `Sanitize sharing links` work with latest versions again ([#6518](https://github.com/ReVanced/revanced-patches/issues/6518)) ([85a9079](https://github.com/ReVanced/revanced-patches/commit/85a9079c25760d0329e518e379eeefe3beeea143)) -* **Letterboxd - Hide ads:** Fix patch by returning the correct return type ([#6527](https://github.com/ReVanced/revanced-patches/issues/6527)) ([80c34b9](https://github.com/ReVanced/revanced-patches/commit/80c34b9d74a42018a0cd52b4a584ee71206bf963)) -* Process strings from Crowdin to strip the app/patch prefixes again ([e566efc](https://github.com/ReVanced/revanced-patches/commit/e566efc51fca45c6284406245a360685a8e90d74)) -* **Strava:** Fix `Add media download` patch ([#6526](https://github.com/ReVanced/revanced-patches/issues/6526)) ([dc9e68b](https://github.com/ReVanced/revanced-patches/commit/dc9e68ba574dd9f35cd742cb63193c5d875addde)) - - -### Features - -* **FotMob:** Add `Hide ads` patch ([#6566](https://github.com/ReVanced/revanced-patches/issues/6566)) ([4b0b737](https://github.com/ReVanced/revanced-patches/commit/4b0b7374f21d13599ef2f1e2f5880e7589b0874e)) -* **GmsCore support:** Reduce amount of necessary changes and add update check ([#6582](https://github.com/ReVanced/revanced-patches/issues/6582)) ([650e6a2](https://github.com/ReVanced/revanced-patches/commit/650e6a271075b57368432cd9d4294fd1ce26cceb)) -* **Instagram:** Add `Disable analytics` patch ([#6531](https://github.com/ReVanced/revanced-patches/issues/6531)) ([ad92864](https://github.com/ReVanced/revanced-patches/commit/ad92864483a21d7eae7952c8f8429cde3d44e848)) -* **Kleinanzeigen:** Add `Hide ads` patch ([#6533](https://github.com/ReVanced/revanced-patches/issues/6533)) ([bd6e544](https://github.com/ReVanced/revanced-patches/commit/bd6e544007d539ac2eb890d9bdcb6850435f96cb)) -* **Kleinanzeigen:** Add `Hide PUR` patch ([#6558](https://github.com/ReVanced/revanced-patches/issues/6558)) ([4958ecf](https://github.com/ReVanced/revanced-patches/commit/4958ecf10c880e9e7f15dd2e58ebaefbf49e417a)) -* **Microsoft Lens:** Remove migration to OneDrive ([#6551](https://github.com/ReVanced/revanced-patches/issues/6551)) ([e389632](https://github.com/ReVanced/revanced-patches/commit/e389632afd52403aba26b6981d098b93cea45e00)) -* **Nothing X:** Add `Show K1 token(s)` patch ([#6490](https://github.com/ReVanced/revanced-patches/issues/6490)) ([421cb28](https://github.com/ReVanced/revanced-patches/commit/421cb2899ef5c0f100fb8007bae8b89137d0e41c)) -* **Strava:** Add `Hide distractions` patch ([#6479](https://github.com/ReVanced/revanced-patches/issues/6479)) ([66b0852](https://github.com/ReVanced/revanced-patches/commit/66b0852f8fa57c82b09997337a304374883d8ba5)) -* **YouTube Music:** Add `Hide layout components` patch ([#6365](https://github.com/ReVanced/revanced-patches/issues/6365)) ([71ce823](https://github.com/ReVanced/revanced-patches/commit/71ce8230a959dcaf2d8cd5dad1a4f21b88819aa0)) -* **YouTube Music:** Add `Unlock Android Auto Media Browser` patch ([#6477](https://github.com/ReVanced/revanced-patches/issues/6477)) ([5edd9dc](https://github.com/ReVanced/revanced-patches/commit/5edd9dccae3b1ab4edf19771a771812e3c9ccf80)) - -# [5.50.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.50.0-dev.7...v5.50.0-dev.8) (2026-02-15) - - -### Features - -* **GmsCore support:** Reduce amount of necessary changes and add update check ([#6582](https://github.com/ReVanced/revanced-patches/issues/6582)) ([650e6a2](https://github.com/ReVanced/revanced-patches/commit/650e6a271075b57368432cd9d4294fd1ce26cceb)) - -# [5.50.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.50.0-dev.6...v5.50.0-dev.7) (2026-02-12) - - -### Bug Fixes - -* **Instagram:** Make `Change link sharing domain` and `Sanitize sharing links` work with latest versions again ([#6518](https://github.com/ReVanced/revanced-patches/issues/6518)) ([85a9079](https://github.com/ReVanced/revanced-patches/commit/85a9079c25760d0329e518e379eeefe3beeea143)) - - -### Features - -* **Instagram:** Add `Disable analytics` patch ([#6531](https://github.com/ReVanced/revanced-patches/issues/6531)) ([ad92864](https://github.com/ReVanced/revanced-patches/commit/ad92864483a21d7eae7952c8f8429cde3d44e848)) -* **Kleinanzeigen:** Add `Hide PUR` patch ([#6558](https://github.com/ReVanced/revanced-patches/issues/6558)) ([4958ecf](https://github.com/ReVanced/revanced-patches/commit/4958ecf10c880e9e7f15dd2e58ebaefbf49e417a)) -* **Microsoft Lens:** Remove migration to OneDrive ([#6551](https://github.com/ReVanced/revanced-patches/issues/6551)) ([e389632](https://github.com/ReVanced/revanced-patches/commit/e389632afd52403aba26b6981d098b93cea45e00)) - -# [5.50.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.50.0-dev.5...v5.50.0-dev.6) (2026-02-06) - - -### Features - -* **FotMob:** Add `Hide ads` patch ([#6566](https://github.com/ReVanced/revanced-patches/issues/6566)) ([4b0b737](https://github.com/ReVanced/revanced-patches/commit/4b0b7374f21d13599ef2f1e2f5880e7589b0874e)) - -# [5.50.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.50.0-dev.4...v5.50.0-dev.5) (2026-02-01) - - -### Bug Fixes - -* Process strings from Crowdin to strip the app/patch prefixes again ([e566efc](https://github.com/ReVanced/revanced-patches/commit/e566efc51fca45c6284406245a360685a8e90d74)) - -# [5.50.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.50.0-dev.3...v5.50.0-dev.4) (2026-01-27) - - -### Bug Fixes - -* **Instagram - Open links externally:** Fix patch by handling >4-bit register ([#6538](https://github.com/ReVanced/revanced-patches/issues/6538)) ([f681a6f](https://github.com/ReVanced/revanced-patches/commit/f681a6ffd45f05a61743e7d272cd68c4b743be42)) - -# [5.50.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.50.0-dev.2...v5.50.0-dev.3) (2026-01-26) - - -### Features - -* **Kleinanzeigen:** Add `Hide ads` patch ([#6533](https://github.com/ReVanced/revanced-patches/issues/6533)) ([bd6e544](https://github.com/ReVanced/revanced-patches/commit/bd6e544007d539ac2eb890d9bdcb6850435f96cb)) - -# [5.50.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.50.0-dev.1...v5.50.0-dev.2) (2026-01-25) - - -### Bug Fixes - -* **Letterboxd - Hide ads:** Fix patch by returning the correct return type ([#6527](https://github.com/ReVanced/revanced-patches/issues/6527)) ([80c34b9](https://github.com/ReVanced/revanced-patches/commit/80c34b9d74a42018a0cd52b4a584ee71206bf963)) - -# [5.50.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.49.0-dev.1...v5.50.0-dev.1) (2026-01-25) - - -### Bug Fixes - -* **Strava:** Fix `Add media download` patch ([#6526](https://github.com/ReVanced/revanced-patches/issues/6526)) ([dc9e68b](https://github.com/ReVanced/revanced-patches/commit/dc9e68ba574dd9f35cd742cb63193c5d875addde)) - - -### Features - -* **Nothing X:** Add `Show K1 token(s)` patch ([#6490](https://github.com/ReVanced/revanced-patches/issues/6490)) ([421cb28](https://github.com/ReVanced/revanced-patches/commit/421cb2899ef5c0f100fb8007bae8b89137d0e41c)) -* **Strava:** Add `Hide distractions` patch ([#6479](https://github.com/ReVanced/revanced-patches/issues/6479)) ([66b0852](https://github.com/ReVanced/revanced-patches/commit/66b0852f8fa57c82b09997337a304374883d8ba5)) -* **YouTube Music:** Add `Unlock Android Auto Media Browser` patch ([#6477](https://github.com/ReVanced/revanced-patches/issues/6477)) ([5edd9dc](https://github.com/ReVanced/revanced-patches/commit/5edd9dccae3b1ab4edf19771a771812e3c9ccf80)) - -# [5.50.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.49.0-dev.1...v5.50.0-dev.1) (2026-01-22) - - -### Features - -* **Nothing X:** Add `Show K1 token(s)` patch ([#6490](https://github.com/ReVanced/revanced-patches/issues/6490)) ([421cb28](https://github.com/ReVanced/revanced-patches/commit/421cb2899ef5c0f100fb8007bae8b89137d0e41c)) -* **Strava:** Add `Hide distractions` patch ([#6479](https://github.com/ReVanced/revanced-patches/issues/6479)) ([66b0852](https://github.com/ReVanced/revanced-patches/commit/66b0852f8fa57c82b09997337a304374883d8ba5)) -* **YouTube Music:** Add `Unlock Android Auto Media Browser` patch ([#6477](https://github.com/ReVanced/revanced-patches/issues/6477)) ([5edd9dc](https://github.com/ReVanced/revanced-patches/commit/5edd9dccae3b1ab4edf19771a771812e3c9ccf80)) - -# [5.50.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.48.0...v5.50.0-dev.1) (2026-01-22) - - -### Features - -* **YouTube Music:** Add `Unlock Android Auto Media Browser` patch ([#6477](https://github.com/ReVanced/revanced-patches/issues/6477)) ([89645dc](https://github.com/ReVanced/revanced-patches/commit/89645dcc2e13603b8f2fedb5e16231cb396e5965)) - -# [5.49.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.48.1-dev.1...v5.49.0-dev.1) (2026-01-22) - - -### Features - -* **YouTube Music:** Add `Hide layout components` patch ([#6365](https://github.com/ReVanced/revanced-patches/issues/6365)) ([71ce823](https://github.com/ReVanced/revanced-patches/commit/71ce8230a959dcaf2d8cd5dad1a4f21b88819aa0)) - -## [5.48.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.48.0...v5.48.1-dev.1) (2026-01-21) - - -### Bug Fixes - -* Disable `Prevent screenshot detection` by default ([#6511](https://github.com/ReVanced/revanced-patches/issues/6511)) ([5b5c502](https://github.com/ReVanced/revanced-patches/commit/5b5c50254d533faa0e04d542f4859cbef610713e)) - -# [5.48.0](https://github.com/ReVanced/revanced-patches/compare/v5.47.0...v5.48.0) (2026-01-19) - - -### Bug Fixes - -* **Boost for Reddit - Fix missing audio in video downloads:** Make it work again by reflecting Reddits latest changes ([#6500](https://github.com/ReVanced/revanced-patches/issues/6500)) ([eecc44b](https://github.com/ReVanced/revanced-patches/commit/eecc44b9567bf2ca72ac99e0dafa483a6803c0f9)) -* **Disney+ - Skip ads:** Remove unsupported package names ([#6422](https://github.com/ReVanced/revanced-patches/issues/6422)) ([44e7dbc](https://github.com/ReVanced/revanced-patches/commit/44e7dbcf4d7eaf94dd0164baba847d3e19250154)) -* Fix build error introduced in `4046bee` ([#6417](https://github.com/ReVanced/revanced-patches/issues/6417)) ([789f0a5](https://github.com/ReVanced/revanced-patches/commit/789f0a562861825065633d172445ebf35a1ba8d8)) -* Fix compilation error introduced in `6bb6281` ([#6409](https://github.com/ReVanced/revanced-patches/issues/6409)) ([71c6cb5](https://github.com/ReVanced/revanced-patches/commit/71c6cb569ebf7b93cf73ee391839e5220557ce7c)) -* Fix compilation error introduced in dc69f243 ([#6392](https://github.com/ReVanced/revanced-patches/issues/6392)) ([a429824](https://github.com/ReVanced/revanced-patches/commit/a429824bb77b49aea14b0b54f2204ae24d5209a1)) -* **Instagram:** `Sanitize sharing links` ([#6483](https://github.com/ReVanced/revanced-patches/issues/6483)) ([8724759](https://github.com/ReVanced/revanced-patches/commit/87247590de3db74680cb02ba1d87bf683b2269e2)) -* **YouTube - Hide layout components:** Hide new type of crowdfunding box ([#6380](https://github.com/ReVanced/revanced-patches/issues/6380)) ([dc69f24](https://github.com/ReVanced/revanced-patches/commit/dc69f2433e2650654e2dffdd76b0b0c8a52bf515)) - - -### Features - -* Add `Disable Sentry telemetry` patch ([#6416](https://github.com/ReVanced/revanced-patches/issues/6416)) ([4cc3159](https://github.com/ReVanced/revanced-patches/commit/4cc315952db557c565872de9e8484805f2e42305)) -* Add `Prevent screenshot detection` patch ([#6482](https://github.com/ReVanced/revanced-patches/issues/6482)) ([83c0127](https://github.com/ReVanced/revanced-patches/commit/83c0127ebb8f53ab8a067758619faaac5596c145)) -* Disable Play Integrity patch ([#6412](https://github.com/ReVanced/revanced-patches/issues/6412)) ([6312fe8](https://github.com/ReVanced/revanced-patches/commit/6312fe8d60da24465c0c1b0fa4e94ceb79873d9c)) -* **Instagram - Hides navigation buttons:** Add more buttons to hide ([#6390](https://github.com/ReVanced/revanced-patches/issues/6390)) ([6bb6281](https://github.com/ReVanced/revanced-patches/commit/6bb62811493da04812cc3e392e68d874f95cbef9)) -* **Instagram:** Add `Hide highlights tray` patch ([#6489](https://github.com/ReVanced/revanced-patches/issues/6489)) ([8725a49](https://github.com/ReVanced/revanced-patches/commit/8725a49ba3a06fee0280ffcf4be62cd960cd301e)) -* **Instagram:** Add `Remove build expired popup` patch ([#6488](https://github.com/ReVanced/revanced-patches/issues/6488)) ([18c0b04](https://github.com/ReVanced/revanced-patches/commit/18c0b04f0cd1bf8cd78b05af3b8ebe3a6a5f9e48)) -* **Instagram:** Disable `Disable Reels scrolling` by default ([3401467](https://github.com/ReVanced/revanced-patches/commit/3401467a6d49fc75b6757a15e5c848330c1b7307)) -* **Letterboxd:** Add `Unlock app icons` patch ([#6415](https://github.com/ReVanced/revanced-patches/issues/6415)) ([d25dcfe](https://github.com/ReVanced/revanced-patches/commit/d25dcfe49ac331c9b3dca739ba0be95dbab669cc)) -* **ProtonVPN:** Add `Unlock split tunneling` patch ([#6353](https://github.com/ReVanced/revanced-patches/issues/6353)) ([e0f3346](https://github.com/ReVanced/revanced-patches/commit/e0f33468e6e96b9f10cf35ec67622d6488528c90)) -* **SBS On Demand:** Add `Remove ads` patch ([#6378](https://github.com/ReVanced/revanced-patches/issues/6378)) ([315931c](https://github.com/ReVanced/revanced-patches/commit/315931cbf8f61cd4b3a54ace1ff03685d748614c)) -* **Strava:** Add `Add 'Give Kudos' button to 'Group Activity'` patch ([#6475](https://github.com/ReVanced/revanced-patches/issues/6475)) ([4c4ba1c](https://github.com/ReVanced/revanced-patches/commit/4c4ba1c78c9f4568a2b572f5c69e9c6c734e1a7f)) -* **Strava:** Add `Add media download` patch ([#6449](https://github.com/ReVanced/revanced-patches/issues/6449)) ([778d13c](https://github.com/ReVanced/revanced-patches/commit/778d13ce8b28ca6df3a665530320e4a21a27ae44)) -* **Strava:** Add `Block Snowplow tracking` patch ([#6413](https://github.com/ReVanced/revanced-patches/issues/6413)) ([c47beae](https://github.com/ReVanced/revanced-patches/commit/c47beae21376dd17ab8bc09afe73e9094481bde9)) -* **Strava:** Add `Disable Quick Edit` patch ([#6452](https://github.com/ReVanced/revanced-patches/issues/6452)) ([f5cbb31](https://github.com/ReVanced/revanced-patches/commit/f5cbb31724d15f7e939b96ee0186fd0a108f9fdc)) -* **Strava:** Add `Enable password login` patch ([#6396](https://github.com/ReVanced/revanced-patches/issues/6396)) ([8f3f4c9](https://github.com/ReVanced/revanced-patches/commit/8f3f4c95bb8f151fc9a2c272bf7d0e905c2f01fc)) -* **Strava:** Add `Overwrite media upload parameters` patch ([#6410](https://github.com/ReVanced/revanced-patches/issues/6410)) ([b42ae27](https://github.com/ReVanced/revanced-patches/commit/b42ae27ce66ebad9e9cfc5b70fc121df5bad7567)) -* **YouTube:** Add `Pause on audio interrupt` patch ([#6464](https://github.com/ReVanced/revanced-patches/issues/6464)) ([19f146c](https://github.com/ReVanced/revanced-patches/commit/19f146c01dc381b3cccd61e61ba4901872ff12d8)) - -# [5.48.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.12...v5.48.0-dev.13) (2026-01-19) - - -### Features - -* Add `Prevent screenshot detection` patch ([#6482](https://github.com/ReVanced/revanced-patches/issues/6482)) ([83c0127](https://github.com/ReVanced/revanced-patches/commit/83c0127ebb8f53ab8a067758619faaac5596c145)) - -# [5.48.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.11...v5.48.0-dev.12) (2026-01-19) - - -### Features - -* **Instagram:** Add `Remove build expired popup` patch ([#6488](https://github.com/ReVanced/revanced-patches/issues/6488)) ([18c0b04](https://github.com/ReVanced/revanced-patches/commit/18c0b04f0cd1bf8cd78b05af3b8ebe3a6a5f9e48)) -* **Strava:** Add `Add 'Give Kudos' button to 'Group Activity'` patch ([#6475](https://github.com/ReVanced/revanced-patches/issues/6475)) ([4c4ba1c](https://github.com/ReVanced/revanced-patches/commit/4c4ba1c78c9f4568a2b572f5c69e9c6c734e1a7f)) - -# [5.48.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.10...v5.48.0-dev.11) (2026-01-19) - - -### Features - -* **Instagram:** Add `Hide highlights tray` patch ([#6489](https://github.com/ReVanced/revanced-patches/issues/6489)) ([8725a49](https://github.com/ReVanced/revanced-patches/commit/8725a49ba3a06fee0280ffcf4be62cd960cd301e)) - -# [5.48.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.9...v5.48.0-dev.10) (2026-01-19) - - -### Bug Fixes - -* **Boost for Reddit - Fix missing audio in video downloads:** Make it work again by reflecting Reddits latest changes ([#6500](https://github.com/ReVanced/revanced-patches/issues/6500)) ([eecc44b](https://github.com/ReVanced/revanced-patches/commit/eecc44b9567bf2ca72ac99e0dafa483a6803c0f9)) -* **Instagram:** `Sanitize sharing links` ([#6483](https://github.com/ReVanced/revanced-patches/issues/6483)) ([8724759](https://github.com/ReVanced/revanced-patches/commit/87247590de3db74680cb02ba1d87bf683b2269e2)) - - -### Features - -* **Instagram:** Disable `Disable Reels scrolling` by default ([3401467](https://github.com/ReVanced/revanced-patches/commit/3401467a6d49fc75b6757a15e5c848330c1b7307)) -* **Strava:** Add `Add media download` patch ([#6449](https://github.com/ReVanced/revanced-patches/issues/6449)) ([778d13c](https://github.com/ReVanced/revanced-patches/commit/778d13ce8b28ca6df3a665530320e4a21a27ae44)) -* **YouTube:** Add `Pause on audio interrupt` patch ([#6464](https://github.com/ReVanced/revanced-patches/issues/6464)) ([19f146c](https://github.com/ReVanced/revanced-patches/commit/19f146c01dc381b3cccd61e61ba4901872ff12d8)) - -# [5.48.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.8...v5.48.0-dev.9) (2026-01-08) - - -### Features - -* Add `Disable Sentry telemetry` patch ([#6416](https://github.com/ReVanced/revanced-patches/issues/6416)) ([4cc3159](https://github.com/ReVanced/revanced-patches/commit/4cc315952db557c565872de9e8484805f2e42305)) -* Disable Play Integrity patch ([#6412](https://github.com/ReVanced/revanced-patches/issues/6412)) ([6312fe8](https://github.com/ReVanced/revanced-patches/commit/6312fe8d60da24465c0c1b0fa4e94ceb79873d9c)) - -# [5.48.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.7...v5.48.0-dev.8) (2026-01-04) - - -### Features - -* **Letterboxd:** Add `Unlock app icons` patch ([#6415](https://github.com/ReVanced/revanced-patches/issues/6415)) ([d25dcfe](https://github.com/ReVanced/revanced-patches/commit/d25dcfe49ac331c9b3dca739ba0be95dbab669cc)) - -# [5.48.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.6...v5.48.0-dev.7) (2026-01-04) - - -### Features - -* **Strava:** Add `Disable Quick Edit` patch ([#6452](https://github.com/ReVanced/revanced-patches/issues/6452)) ([f5cbb31](https://github.com/ReVanced/revanced-patches/commit/f5cbb31724d15f7e939b96ee0186fd0a108f9fdc)) -* **Strava:** Add `Overwrite media upload parameters` patch ([#6410](https://github.com/ReVanced/revanced-patches/issues/6410)) ([b42ae27](https://github.com/ReVanced/revanced-patches/commit/b42ae27ce66ebad9e9cfc5b70fc121df5bad7567)) - -# [5.48.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.5...v5.48.0-dev.6) (2026-01-04) - - -### Bug Fixes - -* Fix build error introduced in `4046bee` ([#6417](https://github.com/ReVanced/revanced-patches/issues/6417)) ([789f0a5](https://github.com/ReVanced/revanced-patches/commit/789f0a562861825065633d172445ebf35a1ba8d8)) - -# [5.48.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.4...v5.48.0-dev.5) (2025-12-30) - - -### Bug Fixes - -* **Disney+ - Skip ads:** Remove unsupported package names ([#6422](https://github.com/ReVanced/revanced-patches/issues/6422)) ([44e7dbc](https://github.com/ReVanced/revanced-patches/commit/44e7dbcf4d7eaf94dd0164baba847d3e19250154)) - -# [5.48.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.3...v5.48.0-dev.4) (2025-12-29) - - -### Features - -* **Strava:** Add `Block Snowplow tracking` patch ([#6413](https://github.com/ReVanced/revanced-patches/issues/6413)) ([c47beae](https://github.com/ReVanced/revanced-patches/commit/c47beae21376dd17ab8bc09afe73e9094481bde9)) - -# [5.48.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.2...v5.48.0-dev.3) (2025-12-28) - - -### Bug Fixes - -* Fix compilation error introduced in `6bb6281` ([#6409](https://github.com/ReVanced/revanced-patches/issues/6409)) ([71c6cb5](https://github.com/ReVanced/revanced-patches/commit/71c6cb569ebf7b93cf73ee391839e5220557ce7c)) - - -### Features - -* **Instagram - Hides navigation buttons:** Add more buttons to hide ([#6390](https://github.com/ReVanced/revanced-patches/issues/6390)) ([6bb6281](https://github.com/ReVanced/revanced-patches/commit/6bb62811493da04812cc3e392e68d874f95cbef9)) - -# [5.48.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.1...v5.48.0-dev.2) (2025-12-27) - - -### Features - -* **Strava:** Add `Enable password login` patch ([#6396](https://github.com/ReVanced/revanced-patches/issues/6396)) ([8f3f4c9](https://github.com/ReVanced/revanced-patches/commit/8f3f4c95bb8f151fc9a2c272bf7d0e905c2f01fc)) - -# [5.48.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.47.0...v5.48.0-dev.1) (2025-12-23) - - -### Bug Fixes - -* Fix compilation error introduced in dc69f243 ([#6392](https://github.com/ReVanced/revanced-patches/issues/6392)) ([a429824](https://github.com/ReVanced/revanced-patches/commit/a429824bb77b49aea14b0b54f2204ae24d5209a1)) -* **YouTube - Hide layout components:** Hide new type of crowdfunding box ([#6380](https://github.com/ReVanced/revanced-patches/issues/6380)) ([dc69f24](https://github.com/ReVanced/revanced-patches/commit/dc69f2433e2650654e2dffdd76b0b0c8a52bf515)) - - -### Features - -* **ProtonVPN:** Add `Unlock split tunneling` patch ([#6353](https://github.com/ReVanced/revanced-patches/issues/6353)) ([e0f3346](https://github.com/ReVanced/revanced-patches/commit/e0f33468e6e96b9f10cf35ec67622d6488528c90)) -* **SBS On Demand:** Add `Remove ads` patch ([#6378](https://github.com/ReVanced/revanced-patches/issues/6378)) ([315931c](https://github.com/ReVanced/revanced-patches/commit/315931cbf8f61cd4b3a54ace1ff03685d748614c)) - -# [5.47.0](https://github.com/ReVanced/revanced-patches/compare/v5.46.0...v5.47.0) (2025-12-18) - - -### Bug Fixes - -* **Instagram - Disable signature check:** Change patch to default excluded ([#6283](https://github.com/ReVanced/revanced-patches/issues/6283)) ([bb745b5](https://github.com/ReVanced/revanced-patches/commit/bb745b555b3808b7679c5995319aa365630fbd76)) -* **Lightroom:** Add `Disable version check` patch to fix opening the app ([#6315](https://github.com/ReVanced/revanced-patches/issues/6315)) ([018d176](https://github.com/ReVanced/revanced-patches/commit/018d176914a06a30e9007a3eb2e6b0f459078413)) -* **Reddit - Hide ads:** Update patch for new versions of Reddit ([#6342](https://github.com/ReVanced/revanced-patches/issues/6342)) ([f8bd123](https://github.com/ReVanced/revanced-patches/commit/f8bd1239cc0f0bd1c2dca39f846951bf512891e3)) -* **Spotify:** Make patches work with latest versions again ([#6359](https://github.com/ReVanced/revanced-patches/issues/6359)) ([34830ba](https://github.com/ReVanced/revanced-patches/commit/34830ba63b436146064f0f89f948d51cd0cb9146)) -* **YouTube - Hide layout components:** Fix "Hide Subscribe button" in channel page not working ([#6363](https://github.com/ReVanced/revanced-patches/issues/6363)) ([ded8370](https://github.com/ReVanced/revanced-patches/commit/ded83702077701aac8a8749d71bf7376427f37d6)) -* **YouTube - Hide player flyout menu items:** Allow hiding audio menu with 'Android No SDK' client type ([9495cf4](https://github.com/ReVanced/revanced-patches/commit/9495cf49ef8a872be64de6c971c1919b4b9a8720)) -* **YouTube - Sanitize sharing links:** Handle non hierarchical urls ([654d091](https://github.com/ReVanced/revanced-patches/commit/654d091e650cda37650b57cbf3ba6f1cdd6d47d3)) - - -### Features - -* **Disney+ - SkipAds:** Add other package names the patch is compatible with ([#6372](https://github.com/ReVanced/revanced-patches/issues/6372)) ([1f4f252](https://github.com/ReVanced/revanced-patches/commit/1f4f252c81e9a89267f6e37548e66027b1bc1a1a)) -* **Disney+:** Add `Skip ads` patch ([#6343](https://github.com/ReVanced/revanced-patches/issues/6343)) ([6bd7dca](https://github.com/ReVanced/revanced-patches/commit/6bd7dca75bd2ea335a596aa93a8b767d39be5f83)) -* **IdAustria - Remove device integrity check:** Update patch to work with latest version ([#6360](https://github.com/ReVanced/revanced-patches/issues/6360)) ([0ea3491](https://github.com/ReVanced/revanced-patches/commit/0ea3491227fc50c03555d43d3fec78eb82906b26)) -* **Instagram:** Add `Anonymous story viewing` patch ([#6263](https://github.com/ReVanced/revanced-patches/issues/6263)) ([94ae84a](https://github.com/ReVanced/revanced-patches/commit/94ae84ad0fc3a9197c82d5356301d464730c3b17)) -* **Instagram:** Add `Disable auto story flipping` patch ([#6262](https://github.com/ReVanced/revanced-patches/issues/6262)) ([2f0de15](https://github.com/ReVanced/revanced-patches/commit/2f0de15e67e4f99ed6ecdc136d04cceb23b0d069)) -* **Instagram:** Add `Disable Reels scrolling` patch ([#6317](https://github.com/ReVanced/revanced-patches/issues/6317)) ([0928dcd](https://github.com/ReVanced/revanced-patches/commit/0928dcd00dc2a9c1eef9a23c1e26ff5dc9ee670a)) -* **Letterboxd:** Add `Hide ads` patch ([#6309](https://github.com/ReVanced/revanced-patches/issues/6309)) ([0af0ee9](https://github.com/ReVanced/revanced-patches/commit/0af0ee92c48bb2ffc332197e05439e20c5c05d83)) -* **Peacock TV:** Add `Hide ads` patch ([#6348](https://github.com/ReVanced/revanced-patches/issues/6348)) ([847ee18](https://github.com/ReVanced/revanced-patches/commit/847ee189a971e6d4a99823998569f8e561b8319c)) -* **ProtonVPN:** Add `Remove delay` patch ([#6326](https://github.com/ReVanced/revanced-patches/issues/6326)) ([bbd8932](https://github.com/ReVanced/revanced-patches/commit/bbd8932b2e740aff96ba047332e541bff3e09436)) -* **Spoof SIM provider:** Spoof additional TelephonyManager methods ([#6293](https://github.com/ReVanced/revanced-patches/issues/6293)) ([ac583d4](https://github.com/ReVanced/revanced-patches/commit/ac583d40d0f4c0e6544e3661ff3e82a25912f2b0)) -* **YouTube - Hide layout components:** Add "Hide cell divider", "Hide featured links", and "Hide featured videos" options ([#6335](https://github.com/ReVanced/revanced-patches/issues/6335)) ([a5d197b](https://github.com/ReVanced/revanced-patches/commit/a5d197b9775b98d7a37bfdee9e5f726d5e04d8cf)) -* **YouTube - Hide layout components:** Add "Hide Join button" and "Hide Subscribe button" options for channel page ([#6345](https://github.com/ReVanced/revanced-patches/issues/6345)) ([02831a6](https://github.com/ReVanced/revanced-patches/commit/02831a6069fc30ffa3a87f8e4de653d003a2187e)) -* **YouTube - Hide Shorts components:** Add "Hide auto-dubbed label" and "Hide live preview" options ([#6334](https://github.com/ReVanced/revanced-patches/issues/6334)) ([a7c220a](https://github.com/ReVanced/revanced-patches/commit/a7c220a4aea93ea7ae7005b5760443d7571c4228)) - -# [5.47.0-dev.18](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.17...v5.47.0-dev.18) (2025-12-18) - - -### Features - -* **Disney+ - SkipAds:** Add other package names the patch is compatible with ([#6372](https://github.com/ReVanced/revanced-patches/issues/6372)) ([1f4f252](https://github.com/ReVanced/revanced-patches/commit/1f4f252c81e9a89267f6e37548e66027b1bc1a1a)) - -# [5.47.0-dev.17](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.16...v5.47.0-dev.17) (2025-12-18) - - -### Bug Fixes - -* **Reddit - Hide ads:** Update patch for new versions of Reddit ([#6342](https://github.com/ReVanced/revanced-patches/issues/6342)) ([f8bd123](https://github.com/ReVanced/revanced-patches/commit/f8bd1239cc0f0bd1c2dca39f846951bf512891e3)) - -# [5.47.0-dev.16](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.15...v5.47.0-dev.16) (2025-12-15) - - -### Bug Fixes - -* **Lightroom:** Add `Disable version check` patch to fix opening the app ([#6315](https://github.com/ReVanced/revanced-patches/issues/6315)) ([018d176](https://github.com/ReVanced/revanced-patches/commit/018d176914a06a30e9007a3eb2e6b0f459078413)) - - -### Features - -* **IdAustria - Remove device integrity check:** Update patch to work with latest version ([#6360](https://github.com/ReVanced/revanced-patches/issues/6360)) ([0ea3491](https://github.com/ReVanced/revanced-patches/commit/0ea3491227fc50c03555d43d3fec78eb82906b26)) - -# [5.47.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.14...v5.47.0-dev.15) (2025-12-13) - - -### Bug Fixes - -* **YouTube - Hide layout components:** Fix "Hide Subscribe button" in channel page not working ([#6363](https://github.com/ReVanced/revanced-patches/issues/6363)) ([ded8370](https://github.com/ReVanced/revanced-patches/commit/ded83702077701aac8a8749d71bf7376427f37d6)) - -# [5.47.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.13...v5.47.0-dev.14) (2025-12-13) - - -### Bug Fixes - -* **Spotify:** Make patches work with latest versions again ([#6359](https://github.com/ReVanced/revanced-patches/issues/6359)) ([34830ba](https://github.com/ReVanced/revanced-patches/commit/34830ba63b436146064f0f89f948d51cd0cb9146)) - -# [5.47.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.12...v5.47.0-dev.13) (2025-12-10) - - -### Features - -* **Peacock TV:** Add `Hide ads` patch ([#6348](https://github.com/ReVanced/revanced-patches/issues/6348)) ([847ee18](https://github.com/ReVanced/revanced-patches/commit/847ee189a971e6d4a99823998569f8e561b8319c)) - -# [5.47.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.11...v5.47.0-dev.12) (2025-12-08) - - -### Features - -* **YouTube - Hide layout components:** Add "Hide Join button" and "Hide Subscribe button" options for channel page ([#6345](https://github.com/ReVanced/revanced-patches/issues/6345)) ([02831a6](https://github.com/ReVanced/revanced-patches/commit/02831a6069fc30ffa3a87f8e4de653d003a2187e)) - -# [5.47.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.10...v5.47.0-dev.11) (2025-12-08) - - -### Features - -* **Disney+:** Add `Skip ads` patch ([#6343](https://github.com/ReVanced/revanced-patches/issues/6343)) ([6bd7dca](https://github.com/ReVanced/revanced-patches/commit/6bd7dca75bd2ea335a596aa93a8b767d39be5f83)) - -# [5.47.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.9...v5.47.0-dev.10) (2025-12-08) - - -### Features - -* **YouTube - Hide Shorts components:** Add "Hide auto-dubbed label" and "Hide live preview" options ([#6334](https://github.com/ReVanced/revanced-patches/issues/6334)) ([a7c220a](https://github.com/ReVanced/revanced-patches/commit/a7c220a4aea93ea7ae7005b5760443d7571c4228)) - -# [5.47.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.8...v5.47.0-dev.9) (2025-12-08) - - -### Features - -* **YouTube - Hide layout components:** Add "Hide cell divider", "Hide featured links", and "Hide featured videos" options ([#6335](https://github.com/ReVanced/revanced-patches/issues/6335)) ([a5d197b](https://github.com/ReVanced/revanced-patches/commit/a5d197b9775b98d7a37bfdee9e5f726d5e04d8cf)) - -# [5.47.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.7...v5.47.0-dev.8) (2025-12-08) - - -### Features - -* **Instagram:** Add `Disable Reels scrolling` patch ([#6317](https://github.com/ReVanced/revanced-patches/issues/6317)) ([0928dcd](https://github.com/ReVanced/revanced-patches/commit/0928dcd00dc2a9c1eef9a23c1e26ff5dc9ee670a)) -* **ProtonVPN:** Add `Remove delay` patch ([#6326](https://github.com/ReVanced/revanced-patches/issues/6326)) ([bbd8932](https://github.com/ReVanced/revanced-patches/commit/bbd8932b2e740aff96ba047332e541bff3e09436)) - -# [5.47.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.6...v5.47.0-dev.7) (2025-12-03) - - -### Features - -* **Spoof SIM provider:** Spoof additional TelephonyManager methods ([#6293](https://github.com/ReVanced/revanced-patches/issues/6293)) ([ac583d4](https://github.com/ReVanced/revanced-patches/commit/ac583d40d0f4c0e6544e3661ff3e82a25912f2b0)) - -# [5.47.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.5...v5.47.0-dev.6) (2025-11-24) - - -### Features - -* **Letterboxd:** Add `Hide ads` patch ([#6309](https://github.com/ReVanced/revanced-patches/issues/6309)) ([0af0ee9](https://github.com/ReVanced/revanced-patches/commit/0af0ee92c48bb2ffc332197e05439e20c5c05d83)) - -# [5.47.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.4...v5.47.0-dev.5) (2025-11-13) - - -### Bug Fixes - -* **YouTube - Hide player flyout menu items:** Allow hiding audio menu with 'Android No SDK' client type ([9495cf4](https://github.com/ReVanced/revanced-patches/commit/9495cf49ef8a872be64de6c971c1919b4b9a8720)) - -# [5.47.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.3...v5.47.0-dev.4) (2025-11-12) - - -### Bug Fixes - -* **YouTube - Sanitize sharing links:** Handle non hierarchical urls ([654d091](https://github.com/ReVanced/revanced-patches/commit/654d091e650cda37650b57cbf3ba6f1cdd6d47d3)) - -# [5.47.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.2...v5.47.0-dev.3) (2025-11-12) - - -### Features - -* **Instagram:** Add `Disable auto story flipping` patch ([#6262](https://github.com/ReVanced/revanced-patches/issues/6262)) ([2f0de15](https://github.com/ReVanced/revanced-patches/commit/2f0de15e67e4f99ed6ecdc136d04cceb23b0d069)) - -# [5.47.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.1...v5.47.0-dev.2) (2025-11-12) - - -### Bug Fixes - -* **Instagram - Disable signature check:** Change patch to default excluded ([#6283](https://github.com/ReVanced/revanced-patches/issues/6283)) ([bb745b5](https://github.com/ReVanced/revanced-patches/commit/bb745b555b3808b7679c5995319aa365630fbd76)) - -# [5.47.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.46.0...v5.47.0-dev.1) (2025-11-12) - - -### Features - -* **Instagram:** Add `Anonymous story viewing` patch ([#6263](https://github.com/ReVanced/revanced-patches/issues/6263)) ([94ae84a](https://github.com/ReVanced/revanced-patches/commit/94ae84ad0fc3a9197c82d5356301d464730c3b17)) - -# [5.46.0](https://github.com/ReVanced/revanced-patches/compare/v5.45.0...v5.46.0) (2025-11-10) - - -### Bug Fixes - -* **Duolingo - Disable ads:** Constrain patch to last working app target ([f238ae9](https://github.com/ReVanced/revanced-patches/commit/f238ae9895000f01d1dccb800cc8efde0d5362bd)) -* **Instagram - Hide navigation buttons:** Constrain patch to last working app target ([e030e9c](https://github.com/ReVanced/revanced-patches/commit/e030e9c07a7748e117ac44f6776a9f6317b20623)) -* **Spotify - Hide Create button:** Remove obsolete patch that is no longer needed ([#6252](https://github.com/ReVanced/revanced-patches/issues/6252)) ([59d85b2](https://github.com/ReVanced/revanced-patches/commit/59d85b28a7fcb285ff5f2bb6ae654020d76b2019)) -* **YouTube - Check watch history domain name resolution:** Fix false positive warning message if the internet connection fails halfway into the DNS check ([5726353](https://github.com/ReVanced/revanced-patches/commit/57263538c79f5a561c449229ac8e068c641285d3)) -* **YouTube - Hide layout components:** Fix "Hide Hype points" ([#6247](https://github.com/ReVanced/revanced-patches/issues/6247)) ([5821440](https://github.com/ReVanced/revanced-patches/commit/582144026d28e57bb7adcbba39244f3c7cdbc0f3)) -* **YouTube - Settings:** Add additional languages to ReVanced language preference ([d390b54](https://github.com/ReVanced/revanced-patches/commit/d390b54dab92d75b4e0d3e38344eae489dd69d98)) -* **YouTube - Settings:** Resolve settings search crash when searching for specific words ([#6231](https://github.com/ReVanced/revanced-patches/issues/6231)) ([76dcfae](https://github.com/ReVanced/revanced-patches/commit/76dcfaefd8679e45a70f265b0239436e60c055cf)) - - -### Features - -* **YouTube - Debugging:** Add setting to block experimental client flags ([#6196](https://github.com/ReVanced/revanced-patches/issues/6196)) ([2e9d695](https://github.com/ReVanced/revanced-patches/commit/2e9d6959c94df7588b9e34b18770e9f437e91926)) -* **YouTube - Hide layout components:** Add "Hide Hype points" ([#6230](https://github.com/ReVanced/revanced-patches/issues/6230)) ([a52c015](https://github.com/ReVanced/revanced-patches/commit/a52c0153b12c3f6f0ad260e03d2e9850c0466392)) -* **YouTube - Hide layout components:** Add video description "Hide Featured content" and "Hide Subscribe button" ([#6253](https://github.com/ReVanced/revanced-patches/issues/6253)) ([da4cf94](https://github.com/ReVanced/revanced-patches/commit/da4cf940911a4406e2c9dd558b60305385a80c61)) -* **YouTube - Hide player flyout menu items:** Add "Hide Listen with YouTube Music" ([#6232](https://github.com/ReVanced/revanced-patches/issues/6232)) ([858edbf](https://github.com/ReVanced/revanced-patches/commit/858edbf3e7f394fcc766d767c8dc54cf5ba24370)) -* **YouTube Music:** Add `Change miniplayer color` patch ([#6259](https://github.com/ReVanced/revanced-patches/issues/6259)) ([ab808ae](https://github.com/ReVanced/revanced-patches/commit/ab808aeb773592cb26c848d8456478a346ec3bad)) -* **YouTube Music:** Add `Hide buttons` patch ([#6255](https://github.com/ReVanced/revanced-patches/issues/6255)) ([7a18ebc](https://github.com/ReVanced/revanced-patches/commit/7a18ebc7ab74ba30c5d5284a4856c55cdfc31097)) - -# [5.46.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.9...v5.46.0-dev.10) (2025-11-09) - - -### Features - -* **YouTube - Hide layout components:** Add video description "Hide Featured content" and "Hide Subscribe button" ([#6253](https://github.com/ReVanced/revanced-patches/issues/6253)) ([da4cf94](https://github.com/ReVanced/revanced-patches/commit/da4cf940911a4406e2c9dd558b60305385a80c61)) - -# [5.46.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.8...v5.46.0-dev.9) (2025-11-09) - - -### Features - -* **YouTube Music:** Add `Change miniplayer color` patch ([#6259](https://github.com/ReVanced/revanced-patches/issues/6259)) ([ab808ae](https://github.com/ReVanced/revanced-patches/commit/ab808aeb773592cb26c848d8456478a346ec3bad)) - -# [5.46.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.7...v5.46.0-dev.8) (2025-11-09) - - -### Features - -* **YouTube Music:** Add `Hide buttons` patch ([#6255](https://github.com/ReVanced/revanced-patches/issues/6255)) ([7a18ebc](https://github.com/ReVanced/revanced-patches/commit/7a18ebc7ab74ba30c5d5284a4856c55cdfc31097)) - -# [5.46.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.6...v5.46.0-dev.7) (2025-11-08) - - -### Bug Fixes - -* **YouTube - Settings:** Add additional languages to ReVanced language preference ([d390b54](https://github.com/ReVanced/revanced-patches/commit/d390b54dab92d75b4e0d3e38344eae489dd69d98)) - -# [5.46.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.5...v5.46.0-dev.6) (2025-11-08) - - -### Features - -* **YouTube - Debugging:** Add setting to block experimental client flags ([#6196](https://github.com/ReVanced/revanced-patches/issues/6196)) ([2e9d695](https://github.com/ReVanced/revanced-patches/commit/2e9d6959c94df7588b9e34b18770e9f437e91926)) - -# [5.46.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.4...v5.46.0-dev.5) (2025-11-07) - - -### Bug Fixes - -* **Duolingo - Disable ads:** Constrain patch to last working app target ([f238ae9](https://github.com/ReVanced/revanced-patches/commit/f238ae9895000f01d1dccb800cc8efde0d5362bd)) -* **Instagram - Hide navigation buttons:** Constrain patch to last working app target ([e030e9c](https://github.com/ReVanced/revanced-patches/commit/e030e9c07a7748e117ac44f6776a9f6317b20623)) -* **Spotify - Hide Create button:** Remove obsolete patch that is no longer needed ([#6252](https://github.com/ReVanced/revanced-patches/issues/6252)) ([59d85b2](https://github.com/ReVanced/revanced-patches/commit/59d85b28a7fcb285ff5f2bb6ae654020d76b2019)) - -# [5.46.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.3...v5.46.0-dev.4) (2025-11-07) - - -### Bug Fixes - -* **YouTube - Check watch history domain name resolution:** Fix false positive warning message if the internet connection fails halfway into the DNS check ([5726353](https://github.com/ReVanced/revanced-patches/commit/57263538c79f5a561c449229ac8e068c641285d3)) - -# [5.46.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.2...v5.46.0-dev.3) (2025-11-06) - - -### Bug Fixes - -* **YouTube - Hide layout components:** Fix "Hide Hype points" ([#6247](https://github.com/ReVanced/revanced-patches/issues/6247)) ([5821440](https://github.com/ReVanced/revanced-patches/commit/582144026d28e57bb7adcbba39244f3c7cdbc0f3)) - -# [5.46.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.1...v5.46.0-dev.2) (2025-11-04) - - -### Bug Fixes - -* **YouTube - Settings:** Resolve settings search crash when searching for specific words ([#6231](https://github.com/ReVanced/revanced-patches/issues/6231)) ([76dcfae](https://github.com/ReVanced/revanced-patches/commit/76dcfaefd8679e45a70f265b0239436e60c055cf)) - -# [5.46.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.45.0...v5.46.0-dev.1) (2025-11-04) - - -### Features - -* **YouTube - Hide layout components:** Add "Hide Hype points" ([#6230](https://github.com/ReVanced/revanced-patches/issues/6230)) ([a52c015](https://github.com/ReVanced/revanced-patches/commit/a52c0153b12c3f6f0ad260e03d2e9850c0466392)) -* **YouTube - Hide player flyout menu items:** Add "Hide Listen with YouTube Music" ([#6232](https://github.com/ReVanced/revanced-patches/issues/6232)) ([858edbf](https://github.com/ReVanced/revanced-patches/commit/858edbf3e7f394fcc766d767c8dc54cf5ba24370)) - -# [5.45.0](https://github.com/ReVanced/revanced-patches/compare/v5.44.0...v5.45.0) (2025-11-01) - - -### Bug Fixes - -* **Instagram:** Update failing fingerprints on newer versions ([#6181](https://github.com/ReVanced/revanced-patches/issues/6181)) ([c73a03c](https://github.com/ReVanced/revanced-patches/commit/c73a03c9e18a12262939c974cdf16221221d1487)) -* **TikTok - Downloads:** Fix download path setting ([#6191](https://github.com/ReVanced/revanced-patches/issues/6191)) ([3e4990a](https://github.com/ReVanced/revanced-patches/commit/3e4990afff4c86b93970b153db713ad0f813124d)) -* **YouTube - Change header:** Do not mirror header graphic with RTL languages ([a0c5604](https://github.com/ReVanced/revanced-patches/commit/a0c56049510ce040e1ccd49257864672c343344d)) -* **YouTube - Force original audio:** Fall back to visionOS and not Android Studio if Android VR is not available ([6d01863](https://github.com/ReVanced/revanced-patches/commit/6d01863ec70617d9abc864ce6686ed9764dd151d)) -* **YouTube - Spoof video streams:** Remove spoof stream audio selector that no longer works ([292fae4](https://github.com/ReVanced/revanced-patches/commit/292fae440c6d5694c5e84407becec2d91f1fd156)) -* **YouTube Music - Hide category bar:** Correctly hide the category bar in newer app targets ([#6175](https://github.com/ReVanced/revanced-patches/issues/6175)) ([13cf172](https://github.com/ReVanced/revanced-patches/commit/13cf1724bf2f946c7129cab0db96721c90f9fe89)) - - -### Features - -* **Spoof video streams:** Add experimental "Android No SDK" client type ([5f23bfe](https://github.com/ReVanced/revanced-patches/commit/5f23bfe833c6e01617a7dbc5325b4a3fb931e53e)) -* **TikTok:** Add `Sanitize sharing links` patch ([#6176](https://github.com/ReVanced/revanced-patches/issues/6176)) ([ef44eaa](https://github.com/ReVanced/revanced-patches/commit/ef44eaa119b9d6c5faec051e22d20f883d0da4f1)) -* **YouTube - Change Header:** Use SVG for header logo ([#6178](https://github.com/ReVanced/revanced-patches/issues/6178)) ([e9f45ce](https://github.com/ReVanced/revanced-patches/commit/e9f45ce92695d5857473ff71c14b190bded28a73)) - -# [5.45.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.45.0-dev.5...v5.45.0-dev.6) (2025-11-01) - - -### Features - -* **Spoof video streams:** Add experimental "Android No SDK" client type ([5f23bfe](https://github.com/ReVanced/revanced-patches/commit/5f23bfe833c6e01617a7dbc5325b4a3fb931e53e)) - -# [5.45.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.45.0-dev.4...v5.45.0-dev.5) (2025-11-01) - - -### Bug Fixes - -* **TikTok - Downloads:** Fix download path setting ([#6191](https://github.com/ReVanced/revanced-patches/issues/6191)) ([3e4990a](https://github.com/ReVanced/revanced-patches/commit/3e4990afff4c86b93970b153db713ad0f813124d)) -* **YouTube - Spoof video streams:** Remove spoof stream audio selector that no longer works ([292fae4](https://github.com/ReVanced/revanced-patches/commit/292fae440c6d5694c5e84407becec2d91f1fd156)) - -# [5.45.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.45.0-dev.3...v5.45.0-dev.4) (2025-10-30) - - -### Bug Fixes - -* **YouTube - Change header:** Do not mirror header graphic with RTL languages ([a0c5604](https://github.com/ReVanced/revanced-patches/commit/a0c56049510ce040e1ccd49257864672c343344d)) - -# [5.45.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.45.0-dev.2...v5.45.0-dev.3) (2025-10-27) - - -### Features - -* **YouTube - Change Header:** Use SVG for header logo ([#6178](https://github.com/ReVanced/revanced-patches/issues/6178)) ([e9f45ce](https://github.com/ReVanced/revanced-patches/commit/e9f45ce92695d5857473ff71c14b190bded28a73)) - -# [5.45.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.45.0-dev.1...v5.45.0-dev.2) (2025-10-26) - - -### Bug Fixes - -* **YouTube - Force original audio:** Fall back to visionOS and not Android Studio if Android VR is not available ([6d01863](https://github.com/ReVanced/revanced-patches/commit/6d01863ec70617d9abc864ce6686ed9764dd151d)) -* **YouTube Music - Hide category bar:** Correctly hide the category bar in newer app targets ([#6175](https://github.com/ReVanced/revanced-patches/issues/6175)) ([13cf172](https://github.com/ReVanced/revanced-patches/commit/13cf1724bf2f946c7129cab0db96721c90f9fe89)) - -# [5.45.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.44.0...v5.45.0-dev.1) (2025-10-26) - - -### Bug Fixes - -* **Instagram:** Update failing fingerprints on newer versions ([#6181](https://github.com/ReVanced/revanced-patches/issues/6181)) ([c73a03c](https://github.com/ReVanced/revanced-patches/commit/c73a03c9e18a12262939c974cdf16221221d1487)) - - -### Features - -* **TikTok:** Add `Sanitize sharing links` patch ([#6176](https://github.com/ReVanced/revanced-patches/issues/6176)) ([ef44eaa](https://github.com/ReVanced/revanced-patches/commit/ef44eaa119b9d6c5faec051e22d20f883d0da4f1)) - -# [5.44.0](https://github.com/ReVanced/revanced-patches/compare/v5.43.1...v5.44.0) (2025-10-24) - - -### Bug Fixes - -* **Google Photos - Spoof features:** Add support for Pixel 10 devices ([#6161](https://github.com/ReVanced/revanced-patches/issues/6161)) ([754b719](https://github.com/ReVanced/revanced-patches/commit/754b71959a0155413eb33cf1bdc2c8976eaca634)) -* **X / Twitter - Change link sharing domain:** Use bytecode patching to resolve patching with Manager ([#6125](https://github.com/ReVanced/revanced-patches/issues/6125)) ([0af8c8a](https://github.com/ReVanced/revanced-patches/commit/0af8c8a766ae4ba6926404d59da2f14d649f91f7)) -* **YouTube - Hide layout components:** Hide new kind of community post ([#6146](https://github.com/ReVanced/revanced-patches/issues/6146)) ([cfd244b](https://github.com/ReVanced/revanced-patches/commit/cfd244b4088daacd2788ec38357ac521e4b296d5)) -* **YouTube Music:** Resolve patching 7.29 target ([2e4c6fd](https://github.com/ReVanced/revanced-patches/commit/2e4c6fdcadeef45a80733e374421d52e5e8af910)) - - -### Features - -* Add `Custom network security` patch ([#6151](https://github.com/ReVanced/revanced-patches/issues/6151)) ([e7336d2](https://github.com/ReVanced/revanced-patches/commit/e7336d2ef361cc5d6fe6e8442b36d9cf1f542931)) -* **Duolingo - Enable debug menu:** Support latest app target ([#6163](https://github.com/ReVanced/revanced-patches/issues/6163)) ([08baa19](https://github.com/ReVanced/revanced-patches/commit/08baa19b4a62e62bd103d177c3f4454de199cf16)) -* **Duolingo:** Add `Skip energy recharge ads` patch ([#6167](https://github.com/ReVanced/revanced-patches/issues/6167)) ([591e106](https://github.com/ReVanced/revanced-patches/commit/591e106098c6eff431b8b7ac7d985ce7373d701e)) -* **Samsung Radio:** Add `Disable device checks` patch ([#6145](https://github.com/ReVanced/revanced-patches/issues/6145)) ([de97562](https://github.com/ReVanced/revanced-patches/commit/de97562c5ddc8ec707761c1e04e74c4e18f9c158)) - -# [5.44.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.44.0-dev.3...v5.44.0-dev.4) (2025-10-24) - - -### Features - -* Add `Custom network security` patch ([#6151](https://github.com/ReVanced/revanced-patches/issues/6151)) ([e7336d2](https://github.com/ReVanced/revanced-patches/commit/e7336d2ef361cc5d6fe6e8442b36d9cf1f542931)) -* **Duolingo:** Add `Skip energy recharge ads` patch ([#6167](https://github.com/ReVanced/revanced-patches/issues/6167)) ([591e106](https://github.com/ReVanced/revanced-patches/commit/591e106098c6eff431b8b7ac7d985ce7373d701e)) - -# [5.44.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.44.0-dev.2...v5.44.0-dev.3) (2025-10-22) - - -### Features - -* **Duolingo - Enable debug menu:** Support latest app target ([#6163](https://github.com/ReVanced/revanced-patches/issues/6163)) ([08baa19](https://github.com/ReVanced/revanced-patches/commit/08baa19b4a62e62bd103d177c3f4454de199cf16)) - -# [5.44.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.44.0-dev.1...v5.44.0-dev.2) (2025-10-22) - - -### Bug Fixes - -* **Google Photos - Spoof features:** Add support for Pixel 10 devices ([#6161](https://github.com/ReVanced/revanced-patches/issues/6161)) ([754b719](https://github.com/ReVanced/revanced-patches/commit/754b71959a0155413eb33cf1bdc2c8976eaca634)) - -# [5.44.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.43.2-dev.3...v5.44.0-dev.1) (2025-10-22) - - -### Features - -* **Samsung Radio:** Add `Disable device checks` patch ([#6145](https://github.com/ReVanced/revanced-patches/issues/6145)) ([de97562](https://github.com/ReVanced/revanced-patches/commit/de97562c5ddc8ec707761c1e04e74c4e18f9c158)) - -## [5.43.2-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.43.2-dev.2...v5.43.2-dev.3) (2025-10-19) - - -### Bug Fixes - -* **YouTube - Hide layout components:** Hide new kind of community post ([#6146](https://github.com/ReVanced/revanced-patches/issues/6146)) ([cfd244b](https://github.com/ReVanced/revanced-patches/commit/cfd244b4088daacd2788ec38357ac521e4b296d5)) - -## [5.43.2-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.43.2-dev.1...v5.43.2-dev.2) (2025-10-17) - - -### Bug Fixes - -* **YouTube Music:** Resolve patching 7.29 target ([2e4c6fd](https://github.com/ReVanced/revanced-patches/commit/2e4c6fdcadeef45a80733e374421d52e5e8af910)) - -## [5.43.2-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.43.1...v5.43.2-dev.1) (2025-10-16) - - -### Bug Fixes - -* **X / Twitter - Change link sharing domain:** Use bytecode patching to resolve patching with Manager ([#6125](https://github.com/ReVanced/revanced-patches/issues/6125)) ([0af8c8a](https://github.com/ReVanced/revanced-patches/commit/0af8c8a766ae4ba6926404d59da2f14d649f91f7)) - -## [5.43.1](https://github.com/ReVanced/revanced-patches/compare/v5.43.0...v5.43.1) (2025-10-15) - - -### Bug Fixes - -* **X / Twitter - Change link sharing domain:** Resolve duplicate patch option ([#6119](https://github.com/ReVanced/revanced-patches/issues/6119)) ([7563990](https://github.com/ReVanced/revanced-patches/commit/75639907502382f63fa127a886362d4a4573e6e3)) -* **X / Twitter:** Do not crash Manager when clicking on domain patch option ([2a1e318](https://github.com/ReVanced/revanced-patches/commit/2a1e31860f22f537d51b40a5b71d9ad9d538789e)) - -## [5.43.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.43.1-dev.1...v5.43.1-dev.2) (2025-10-14) - - -### Bug Fixes - -* **X / Twitter:** Do not crash Manager when clicking on domain patch option ([2a1e318](https://github.com/ReVanced/revanced-patches/commit/2a1e31860f22f537d51b40a5b71d9ad9d538789e)) - -## [5.43.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.43.0...v5.43.1-dev.1) (2025-10-14) - - -### Bug Fixes - -* **X / Twitter - Change link sharing domain:** Resolve duplicate patch option ([#6119](https://github.com/ReVanced/revanced-patches/issues/6119)) ([7563990](https://github.com/ReVanced/revanced-patches/commit/75639907502382f63fa127a886362d4a4573e6e3)) - -# [5.43.0](https://github.com/ReVanced/revanced-patches/compare/v5.42.1...v5.43.0) (2025-10-14) - - -### Bug Fixes - -* **Custom branding:** Use white notification icon for expanded status bar panel ([95eee59](https://github.com/ReVanced/revanced-patches/commit/95eee59a87a680e212a3ba06e1afefee8d91ee9d)) -* **Instagram - Change sharing domain:** Display patch option ([#6089](https://github.com/ReVanced/revanced-patches/issues/6089)) ([be2b144](https://github.com/ReVanced/revanced-patches/commit/be2b144cc9c4108ec37e16f3dd20573d88ffaa2b)) -* **X / Twitter - Change Link Sharing Domain:** Change link domain of share copy action ([#6091](https://github.com/ReVanced/revanced-patches/issues/6091)) ([5484625](https://github.com/ReVanced/revanced-patches/commit/54846253d748f4e7e30b2bba427c7d2fb9c341e2)) -* **YouTube - Custom branding:** Do not add a broken custom icon if the user provides an invalid custom icon path ([6555f6e](https://github.com/ReVanced/revanced-patches/commit/6555f6e6f8b52c2f1ddab1f52c6704cd2d8cfc12)) -* **YouTube - Custom branding:** Use ReVanced icon for status bar notification icon ([#6108](https://github.com/ReVanced/revanced-patches/issues/6108)) ([10ea250](https://github.com/ReVanced/revanced-patches/commit/10ea250d4a91f8ab3b7f865612a403fc93a857b5)) -* **YouTube - Force original audio:** Do not use translated audio if stream spoofing is off and force audio is on ([0c19dba](https://github.com/ReVanced/revanced-patches/commit/0c19dbaf30bcb95a29448d98b028ebeea54cc7d3)) - - -### Features - -* **Instagram:** Add `Hide suggested content` patch ([#6075](https://github.com/ReVanced/revanced-patches/issues/6075)) ([50f0b9c](https://github.com/ReVanced/revanced-patches/commit/50f0b9c5eee95ff5f9974e344802e1d2a4aab47b)) - -# [5.43.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.43.0-dev.3...v5.43.0-dev.4) (2025-10-14) - - -### Bug Fixes - -* **YouTube - Force original audio:** Do not use translated audio if stream spoofing is off and force audio is on ([0c19dba](https://github.com/ReVanced/revanced-patches/commit/0c19dbaf30bcb95a29448d98b028ebeea54cc7d3)) - -# [5.43.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.43.0-dev.2...v5.43.0-dev.3) (2025-10-14) - - -### Bug Fixes - -* **Custom branding:** Use white notification icon for expanded status bar panel ([95eee59](https://github.com/ReVanced/revanced-patches/commit/95eee59a87a680e212a3ba06e1afefee8d91ee9d)) - -# [5.43.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.43.0-dev.1...v5.43.0-dev.2) (2025-10-14) - - -### Bug Fixes - -* **YouTube - Custom branding:** Use ReVanced icon for status bar notification icon ([#6108](https://github.com/ReVanced/revanced-patches/issues/6108)) ([10ea250](https://github.com/ReVanced/revanced-patches/commit/10ea250d4a91f8ab3b7f865612a403fc93a857b5)) - -# [5.43.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.42.2-dev.3...v5.43.0-dev.1) (2025-10-11) - - -### Features - -* **Instagram:** Add `Hide suggested content` patch ([#6075](https://github.com/ReVanced/revanced-patches/issues/6075)) ([50f0b9c](https://github.com/ReVanced/revanced-patches/commit/50f0b9c5eee95ff5f9974e344802e1d2a4aab47b)) - -## [5.42.2-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.42.2-dev.2...v5.42.2-dev.3) (2025-10-11) - - -### Bug Fixes - -* **YouTube - Custom branding:** Do not add a broken custom icon if the user provides an invalid custom icon path ([6555f6e](https://github.com/ReVanced/revanced-patches/commit/6555f6e6f8b52c2f1ddab1f52c6704cd2d8cfc12)) - -## [5.42.2-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.42.2-dev.1...v5.42.2-dev.2) (2025-10-10) - - -### Bug Fixes - -* **X / Twitter - Change Link Sharing Domain:** Change link domain of share copy action ([#6091](https://github.com/ReVanced/revanced-patches/issues/6091)) ([5484625](https://github.com/ReVanced/revanced-patches/commit/54846253d748f4e7e30b2bba427c7d2fb9c341e2)) - -## [5.42.2-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.42.1...v5.42.2-dev.1) (2025-10-09) - - -### Bug Fixes - -* **Instagram - Change sharing domain:** Display patch option ([#6089](https://github.com/ReVanced/revanced-patches/issues/6089)) ([be2b144](https://github.com/ReVanced/revanced-patches/commit/be2b144cc9c4108ec37e16f3dd20573d88ffaa2b)) - -## [5.42.1](https://github.com/ReVanced/revanced-patches/compare/v5.42.0...v5.42.1) (2025-10-08) - - -### Bug Fixes - -* **YouTube - Custom Branding:** Resolve startup crash with root installation ([fd4b2e1](https://github.com/ReVanced/revanced-patches/commit/fd4b2e1bb98c6e507178e5b46b896ef7d320bc3d)) - -## [5.42.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.42.0...v5.42.1-dev.1) (2025-10-08) - - -### Bug Fixes - -* **YouTube - Custom Branding:** Resolve startup crash with root installation ([fd4b2e1](https://github.com/ReVanced/revanced-patches/commit/fd4b2e1bb98c6e507178e5b46b896ef7d320bc3d)) - -# [5.42.0](https://github.com/ReVanced/revanced-patches/compare/v5.41.0...v5.42.0) (2025-10-08) - - -### Bug Fixes - -* **Custom branding:** Update ReVanced logo ([#6049](https://github.com/ReVanced/revanced-patches/issues/6049)) ([9441e7a](https://github.com/ReVanced/revanced-patches/commit/9441e7acb4817e12d1443d438ef6c448518bd614)) -* **Custom branding:** Update ReVanced logo sizing ([#6029](https://github.com/ReVanced/revanced-patches/issues/6029)) ([ae4b947](https://github.com/ReVanced/revanced-patches/commit/ae4b9474d3fb62528fc21397c19954d31605e9da)) -* **Instagram - Hide navigation buttons:** Resolve app startup crash ([080a226](https://github.com/ReVanced/revanced-patches/commit/080a2266146798be71789c939deef2f289697523)) -* **Spotify:** Change `Hide Create button` patch to default off ([#6067](https://github.com/ReVanced/revanced-patches/issues/6067)) ([19949e1](https://github.com/ReVanced/revanced-patches/commit/19949e1695cc252ff0f94a33b6e3fb62e967d7fd)) -* **X / Twitter:** Remove non functional and obsolete patch `Open links with app chooser` ([#6033](https://github.com/ReVanced/revanced-patches/issues/6033)) ([673609c](https://github.com/ReVanced/revanced-patches/commit/673609c2aa87988cdc138eab101b9750fe6a7b62)) -* **YouTube - Force original audio:** Change patch to default on ([#6070](https://github.com/ReVanced/revanced-patches/issues/6070)) ([bd4ba2d](https://github.com/ReVanced/revanced-patches/commit/bd4ba2dae85ee6fd8d7e6078c3de775ca336e0b6)) -* **YouTube - Force original language:** Resolve some videos using Swedish audio track ([9d67316](https://github.com/ReVanced/revanced-patches/commit/9d6731660ba0e19b863d05d54aa04f74a879f69b)) -* **YouTube - Hide end screen cards:** Hide new type of end screen card ([#6027](https://github.com/ReVanced/revanced-patches/issues/6027)) ([76b0364](https://github.com/ReVanced/revanced-patches/commit/76b0364c5b5562c6a0d178d2bbe5b220f48aaca9)) -* **YouTube - Spoof video streams:** Add "Allow Android VR AV1" setting ([#6071](https://github.com/ReVanced/revanced-patches/issues/6071)) ([f03256c](https://github.com/ReVanced/revanced-patches/commit/f03256c471e1ee6a12267c1b56b531ca8f89278c)) -* **YouTube - Spoof video streams:** Do not allow VR AV1 if "Force AVC" is enabled ([7afeaeb](https://github.com/ReVanced/revanced-patches/commit/7afeaebb5cc22eb4f4512d8aa0cf4e835e7a2daf)) -* **YouTube - Spoof video streams:** Resolve playback dropping frames ([#6051](https://github.com/ReVanced/revanced-patches/issues/6051)) ([a62ee43](https://github.com/ReVanced/revanced-patches/commit/a62ee43441b197f5c8352ae373bb8919ad66f0bd)) -* **YouTube Music - GmsCore support:** Handle sharing links to certain apps such as Instagram ([#6026](https://github.com/ReVanced/revanced-patches/issues/6026)) ([328234f](https://github.com/ReVanced/revanced-patches/commit/328234f39ada81542e596f04e8ce410c787c15c8)) -* **YouTube Music - Hide cast button:** Fix patching error ([28799a5](https://github.com/ReVanced/revanced-patches/commit/28799a548a73651134ef304cb6cb542cf8e55abe)) -* **YouTube Music - Hide cast button:** Resolve button not hiding ([7817885](https://github.com/ReVanced/revanced-patches/commit/7817885cffed66608039ab45881537cbd3069c9d)) -* **YouTube:** Resolve UI components not hiding for some users ([#6054](https://github.com/ReVanced/revanced-patches/issues/6054)) ([6b26346](https://github.com/ReVanced/revanced-patches/commit/6b2634691423f5ce25a28b3f2fbc420977b81748)) - - -### Features - -* **Custom branding:** Add in-app settings to change icon and name ([#6059](https://github.com/ReVanced/revanced-patches/issues/6059)) ([a50f3b5](https://github.com/ReVanced/revanced-patches/commit/a50f3b5177808f07d84041c946caccb5a08ad387)) -* **Instagram:** Add `Custom share domain` patch ([#5998](https://github.com/ReVanced/revanced-patches/issues/5998)) ([20c4131](https://github.com/ReVanced/revanced-patches/commit/20c413120bad97af6121718e76b22a1b5540aa44)) -* **Instagram:** Add `Enable developer menu` patch ([#6043](https://github.com/ReVanced/revanced-patches/issues/6043)) ([2154d89](https://github.com/ReVanced/revanced-patches/commit/2154d89242fd8d7f7460145d5d35a4f1986944a3)) -* **Instagram:** Add `Open links externally` patch ([#6012](https://github.com/ReVanced/revanced-patches/issues/6012)) ([08e8ead](https://github.com/ReVanced/revanced-patches/commit/08e8ead04ffff47a4608a3db7aadc8d5feccd4ad)) -* **Instagram:** Add `Sanitize sharing links` patch ([#5986](https://github.com/ReVanced/revanced-patches/issues/5986)) ([963a4ef](https://github.com/ReVanced/revanced-patches/commit/963a4ef43fd513de7a2d7d019992f06b62fdcc10)) -* **Viber:** Add `Hide navigation buttons` patch ([#5991](https://github.com/ReVanced/revanced-patches/issues/5991)) ([5cb46c4](https://github.com/ReVanced/revanced-patches/commit/5cb46c4e9180ebc16eddb983dad73d137d8ec047)) -* **YouTube Music:** Add `Custom branding` patch ([#6007](https://github.com/ReVanced/revanced-patches/issues/6007)) ([4c8b56f](https://github.com/ReVanced/revanced-patches/commit/4c8b56f5466b244737f501654eb7c5d34b6b2f88)) -* **YouTube Music:** Add `Force original audio` patch ([#6036](https://github.com/ReVanced/revanced-patches/issues/6036)) ([d0d53d1](https://github.com/ReVanced/revanced-patches/commit/d0d53d109e451759a029326873adfa36fba12b23)) - -# [5.42.0](https://github.com/ReVanced/revanced-patches/compare/v5.41.0...v5.42.0) (2025-10-08) - - -### Bug Fixes - -* **Custom branding:** Update ReVanced logo ([#6049](https://github.com/ReVanced/revanced-patches/issues/6049)) ([9441e7a](https://github.com/ReVanced/revanced-patches/commit/9441e7acb4817e12d1443d438ef6c448518bd614)) -* **Custom branding:** Update ReVanced logo sizing ([#6029](https://github.com/ReVanced/revanced-patches/issues/6029)) ([ae4b947](https://github.com/ReVanced/revanced-patches/commit/ae4b9474d3fb62528fc21397c19954d31605e9da)) -* **Instagram - Hide navigation buttons:** Resolve app startup crash ([080a226](https://github.com/ReVanced/revanced-patches/commit/080a2266146798be71789c939deef2f289697523)) -* **Spotify:** Change `Hide Create button` patch to default off ([#6067](https://github.com/ReVanced/revanced-patches/issues/6067)) ([19949e1](https://github.com/ReVanced/revanced-patches/commit/19949e1695cc252ff0f94a33b6e3fb62e967d7fd)) -* **X / Twitter:** Remove non functional and obsolete patch `Open links with app chooser` ([#6033](https://github.com/ReVanced/revanced-patches/issues/6033)) ([673609c](https://github.com/ReVanced/revanced-patches/commit/673609c2aa87988cdc138eab101b9750fe6a7b62)) -* **YouTube - Force original audio:** Change patch to default on ([#6070](https://github.com/ReVanced/revanced-patches/issues/6070)) ([bd4ba2d](https://github.com/ReVanced/revanced-patches/commit/bd4ba2dae85ee6fd8d7e6078c3de775ca336e0b6)) -* **YouTube - Force original language:** Resolve some videos using Swedish audio track ([9d67316](https://github.com/ReVanced/revanced-patches/commit/9d6731660ba0e19b863d05d54aa04f74a879f69b)) -* **YouTube - Hide end screen cards:** Hide new type of end screen card ([#6027](https://github.com/ReVanced/revanced-patches/issues/6027)) ([76b0364](https://github.com/ReVanced/revanced-patches/commit/76b0364c5b5562c6a0d178d2bbe5b220f48aaca9)) -* **YouTube - Spoof video streams:** Add "Allow Android VR AV1" setting ([#6071](https://github.com/ReVanced/revanced-patches/issues/6071)) ([f03256c](https://github.com/ReVanced/revanced-patches/commit/f03256c471e1ee6a12267c1b56b531ca8f89278c)) -* **YouTube - Spoof video streams:** Do not allow VR AV1 if "Force AVC" is enabled ([7afeaeb](https://github.com/ReVanced/revanced-patches/commit/7afeaebb5cc22eb4f4512d8aa0cf4e835e7a2daf)) -* **YouTube - Spoof video streams:** Resolve playback dropping frames ([#6051](https://github.com/ReVanced/revanced-patches/issues/6051)) ([a62ee43](https://github.com/ReVanced/revanced-patches/commit/a62ee43441b197f5c8352ae373bb8919ad66f0bd)) -* **YouTube Music - GmsCore support:** Handle sharing links to certain apps such as Instagram ([#6026](https://github.com/ReVanced/revanced-patches/issues/6026)) ([328234f](https://github.com/ReVanced/revanced-patches/commit/328234f39ada81542e596f04e8ce410c787c15c8)) -* **YouTube Music - Hide cast button:** Fix patching error ([28799a5](https://github.com/ReVanced/revanced-patches/commit/28799a548a73651134ef304cb6cb542cf8e55abe)) -* **YouTube Music - Hide cast button:** Resolve button not hiding ([7817885](https://github.com/ReVanced/revanced-patches/commit/7817885cffed66608039ab45881537cbd3069c9d)) -* **YouTube:** Resolve UI components not hiding for some users ([#6054](https://github.com/ReVanced/revanced-patches/issues/6054)) ([6b26346](https://github.com/ReVanced/revanced-patches/commit/6b2634691423f5ce25a28b3f2fbc420977b81748)) - - -### Features - -* **Custom branding:** Add in-app settings to change icon and name ([#6059](https://github.com/ReVanced/revanced-patches/issues/6059)) ([a50f3b5](https://github.com/ReVanced/revanced-patches/commit/a50f3b5177808f07d84041c946caccb5a08ad387)) -* **Instagram:** Add `Custom share domain` patch ([#5998](https://github.com/ReVanced/revanced-patches/issues/5998)) ([20c4131](https://github.com/ReVanced/revanced-patches/commit/20c413120bad97af6121718e76b22a1b5540aa44)) -* **Instagram:** Add `Enable developer menu` patch ([#6043](https://github.com/ReVanced/revanced-patches/issues/6043)) ([2154d89](https://github.com/ReVanced/revanced-patches/commit/2154d89242fd8d7f7460145d5d35a4f1986944a3)) -* **Instagram:** Add `Open links externally` patch ([#6012](https://github.com/ReVanced/revanced-patches/issues/6012)) ([08e8ead](https://github.com/ReVanced/revanced-patches/commit/08e8ead04ffff47a4608a3db7aadc8d5feccd4ad)) -* **Instagram:** Add `Sanitize sharing links` patch ([#5986](https://github.com/ReVanced/revanced-patches/issues/5986)) ([963a4ef](https://github.com/ReVanced/revanced-patches/commit/963a4ef43fd513de7a2d7d019992f06b62fdcc10)) -* **Viber:** Add `Hide navigation buttons` patch ([#5991](https://github.com/ReVanced/revanced-patches/issues/5991)) ([5cb46c4](https://github.com/ReVanced/revanced-patches/commit/5cb46c4e9180ebc16eddb983dad73d137d8ec047)) -* **YouTube Music:** Add `Custom branding` patch ([#6007](https://github.com/ReVanced/revanced-patches/issues/6007)) ([4c8b56f](https://github.com/ReVanced/revanced-patches/commit/4c8b56f5466b244737f501654eb7c5d34b6b2f88)) -* **YouTube Music:** Add `Force original audio` patch ([#6036](https://github.com/ReVanced/revanced-patches/issues/6036)) ([d0d53d1](https://github.com/ReVanced/revanced-patches/commit/d0d53d109e451759a029326873adfa36fba12b23)) - -# [5.42.0-dev.19](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.18...v5.42.0-dev.19) (2025-10-07) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Do not allow VR AV1 if "Force AVC" is enabled ([7afeaeb](https://github.com/ReVanced/revanced-patches/commit/7afeaebb5cc22eb4f4512d8aa0cf4e835e7a2daf)) - -# [5.42.0-dev.18](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.17...v5.42.0-dev.18) (2025-10-07) - - -### Features - -* **Custom branding:** Add in-app settings to change icon and name ([#6059](https://github.com/ReVanced/revanced-patches/issues/6059)) ([a50f3b5](https://github.com/ReVanced/revanced-patches/commit/a50f3b5177808f07d84041c946caccb5a08ad387)) - -# [5.42.0-dev.17](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.16...v5.42.0-dev.17) (2025-10-07) - - -### Bug Fixes - -* **YouTube - Force original audio:** Change patch to default on ([#6070](https://github.com/ReVanced/revanced-patches/issues/6070)) ([bd4ba2d](https://github.com/ReVanced/revanced-patches/commit/bd4ba2dae85ee6fd8d7e6078c3de775ca336e0b6)) - -# [5.42.0-dev.16](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.15...v5.42.0-dev.16) (2025-10-07) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Add "Allow Android VR AV1" setting ([#6071](https://github.com/ReVanced/revanced-patches/issues/6071)) ([f03256c](https://github.com/ReVanced/revanced-patches/commit/f03256c471e1ee6a12267c1b56b531ca8f89278c)) - -# [5.42.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.14...v5.42.0-dev.15) (2025-10-07) - - -### Features - -* **Instagram:** Add `Enable developer menu` patch ([#6043](https://github.com/ReVanced/revanced-patches/issues/6043)) ([2154d89](https://github.com/ReVanced/revanced-patches/commit/2154d89242fd8d7f7460145d5d35a4f1986944a3)) - -# [5.42.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.13...v5.42.0-dev.14) (2025-10-07) - - -### Features - -* **Instagram:** Add `Custom share domain` patch ([#5998](https://github.com/ReVanced/revanced-patches/issues/5998)) ([20c4131](https://github.com/ReVanced/revanced-patches/commit/20c413120bad97af6121718e76b22a1b5540aa44)) - -# [5.42.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.12...v5.42.0-dev.13) (2025-10-07) - - -### Bug Fixes - -* **Spotify:** Change `Hide Create button` patch to default off ([#6067](https://github.com/ReVanced/revanced-patches/issues/6067)) ([19949e1](https://github.com/ReVanced/revanced-patches/commit/19949e1695cc252ff0f94a33b6e3fb62e967d7fd)) - -# [5.42.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.11...v5.42.0-dev.12) (2025-10-03) - - -### Bug Fixes - -* **Custom branding:** Update ReVanced logo ([#6049](https://github.com/ReVanced/revanced-patches/issues/6049)) ([9441e7a](https://github.com/ReVanced/revanced-patches/commit/9441e7acb4817e12d1443d438ef6c448518bd614)) - - -### Features - -* **Instagram:** Add `Sanitize sharing links` patch ([#5986](https://github.com/ReVanced/revanced-patches/issues/5986)) ([963a4ef](https://github.com/ReVanced/revanced-patches/commit/963a4ef43fd513de7a2d7d019992f06b62fdcc10)) - -# [5.42.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.10...v5.42.0-dev.11) (2025-10-03) - - -### Bug Fixes - -* **YouTube:** Resolve UI components not hiding for some users ([#6054](https://github.com/ReVanced/revanced-patches/issues/6054)) ([6b26346](https://github.com/ReVanced/revanced-patches/commit/6b2634691423f5ce25a28b3f2fbc420977b81748)) - -# [5.42.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.9...v5.42.0-dev.10) (2025-10-02) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Resolve playback dropping frames ([#6051](https://github.com/ReVanced/revanced-patches/issues/6051)) ([a62ee43](https://github.com/ReVanced/revanced-patches/commit/a62ee43441b197f5c8352ae373bb8919ad66f0bd)) - -# [5.42.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.8...v5.42.0-dev.9) (2025-10-01) - - -### Bug Fixes - -* **Custom branding:** Update ReVanced logo sizing ([#6029](https://github.com/ReVanced/revanced-patches/issues/6029)) ([ae4b947](https://github.com/ReVanced/revanced-patches/commit/ae4b9474d3fb62528fc21397c19954d31605e9da)) - -# [5.42.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.7...v5.42.0-dev.8) (2025-10-01) - - -### Bug Fixes - -* **YouTube - Force original language:** Resolve some videos using Swedish audio track ([9d67316](https://github.com/ReVanced/revanced-patches/commit/9d6731660ba0e19b863d05d54aa04f74a879f69b)) - - -### Features - -* **YouTube Music:** Add `Force original audio` patch ([#6036](https://github.com/ReVanced/revanced-patches/issues/6036)) ([d0d53d1](https://github.com/ReVanced/revanced-patches/commit/d0d53d109e451759a029326873adfa36fba12b23)) - -# [5.42.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.6...v5.42.0-dev.7) (2025-10-01) - - -### Features - -* **Instagram:** Add `Open links externally` patch ([#6012](https://github.com/ReVanced/revanced-patches/issues/6012)) ([08e8ead](https://github.com/ReVanced/revanced-patches/commit/08e8ead04ffff47a4608a3db7aadc8d5feccd4ad)) - -# [5.42.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.5...v5.42.0-dev.6) (2025-09-30) - - -### Bug Fixes - -* **X / Twitter:** Remove non functional and obsolete patch `Open links with app chooser` ([#6033](https://github.com/ReVanced/revanced-patches/issues/6033)) ([673609c](https://github.com/ReVanced/revanced-patches/commit/673609c2aa87988cdc138eab101b9750fe6a7b62)) - -# [5.42.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.4...v5.42.0-dev.5) (2025-09-28) - - -### Features - -* **YouTube Music:** Add `Custom branding` patch ([#6007](https://github.com/ReVanced/revanced-patches/issues/6007)) ([4c8b56f](https://github.com/ReVanced/revanced-patches/commit/4c8b56f5466b244737f501654eb7c5d34b6b2f88)) - -# [5.42.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.3...v5.42.0-dev.4) (2025-09-28) - - -### Bug Fixes - -* **YouTube Music - GmsCore support:** Handle sharing links to certain apps such as Instagram ([#6026](https://github.com/ReVanced/revanced-patches/issues/6026)) ([328234f](https://github.com/ReVanced/revanced-patches/commit/328234f39ada81542e596f04e8ce410c787c15c8)) - -# [5.42.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.2...v5.42.0-dev.3) (2025-09-28) - - -### Bug Fixes - -* **YouTube - Hide end screen cards:** Hide new type of end screen card ([#6027](https://github.com/ReVanced/revanced-patches/issues/6027)) ([76b0364](https://github.com/ReVanced/revanced-patches/commit/76b0364c5b5562c6a0d178d2bbe5b220f48aaca9)) - -# [5.42.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.1...v5.42.0-dev.2) (2025-09-27) - - -### Bug Fixes - -* **Instagram - Hide navigation buttons:** Resolve app startup crash ([080a226](https://github.com/ReVanced/revanced-patches/commit/080a2266146798be71789c939deef2f289697523)) - -# [5.42.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.41.1-dev.2...v5.42.0-dev.1) (2025-09-27) - - -### Features - -* **Viber:** Add `Hide navigation buttons` patch ([#5991](https://github.com/ReVanced/revanced-patches/issues/5991)) ([5cb46c4](https://github.com/ReVanced/revanced-patches/commit/5cb46c4e9180ebc16eddb983dad73d137d8ec047)) - -## [5.41.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.41.1-dev.1...v5.41.1-dev.2) (2025-09-27) - - -### Bug Fixes - -* **YouTube Music - Hide cast button:** Fix patching error ([28799a5](https://github.com/ReVanced/revanced-patches/commit/28799a548a73651134ef304cb6cb542cf8e55abe)) - -## [5.41.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.41.0...v5.41.1-dev.1) (2025-09-27) - - -### Bug Fixes - -* **YouTube Music - Hide cast button:** Resolve button not hiding ([7817885](https://github.com/ReVanced/revanced-patches/commit/7817885cffed66608039ab45881537cbd3069c9d)) - -# [5.41.0](https://github.com/ReVanced/revanced-patches/compare/v5.40.0...v5.41.0) (2025-09-27) - - -### Bug Fixes - -* **Instagram - Hide navigation buttons:** Remove button based on name ([#5971](https://github.com/ReVanced/revanced-patches/issues/5971)) ([6fa4043](https://github.com/ReVanced/revanced-patches/commit/6fa404331b5162682d83fba5f38ed570c31495fc)) -* **Instagram - Limit feed to followed profiles:** Preserve favorites feed ([#5963](https://github.com/ReVanced/revanced-patches/issues/5963)) ([ef51401](https://github.com/ReVanced/revanced-patches/commit/ef514017f46025d9aef6884424caeb0670514e7a)) -* **TikTok:** Show correct dialog restart text, use correct font color for non-dark mode ([d1a1293](https://github.com/ReVanced/revanced-patches/commit/d1a12930c35f630793a0f240d4203c2ff9060158)) -* **Twitch - Settings:** Fix missing style resources ([#5970](https://github.com/ReVanced/revanced-patches/issues/5970)) ([8c22995](https://github.com/ReVanced/revanced-patches/commit/8c229954d7f232a7a472ca49f1b5e7cdc475bbcc)) -* **YouTube - Hide Shorts components:** Fix "Hide preview comment" ([#5990](https://github.com/ReVanced/revanced-patches/issues/5990)) ([dd4e2cd](https://github.com/ReVanced/revanced-patches/commit/dd4e2cd0855ccc51b94593004fdd8150ac3b41cc)) -* **YouTube - Return YouTube Dislike:** Do not show error toast if API returns 401 status ([#5949](https://github.com/ReVanced/revanced-patches/issues/5949)) ([58d088a](https://github.com/ReVanced/revanced-patches/commit/58d088ab307440a6912a867246da799b7dd6499b)) -* **YouTube - Settings:** Handle on screen back swipe gesture ([#6002](https://github.com/ReVanced/revanced-patches/issues/6002)) ([6f92b6c](https://github.com/ReVanced/revanced-patches/commit/6f92b6c50beab091f5f7ef7386579eda38cb4c66)) -* **YouTube - Settings:** Use an overlay to show search results ([#5806](https://github.com/ReVanced/revanced-patches/issues/5806)) ([ece8076](https://github.com/ReVanced/revanced-patches/commit/ece8076f7cefd752b97515014bc50fe4fd80171e)) -* **YouTube - SponsorBlock:** Show category color dot in voting dialog menu ([4be00d0](https://github.com/ReVanced/revanced-patches/commit/4be00d09b7b87dcfac324de8709af06e9f730791)) -* **YouTube - SponsorBlock:** Show category color in create new segment menu ([#5987](https://github.com/ReVanced/revanced-patches/issues/5987)) ([ffd933c](https://github.com/ReVanced/revanced-patches/commit/ffd933c6734274cdde5aaec0159b67f173f9228c)) -* **YouTube - Spoof video streams:** Update client side effects summary text ([a0a62dd](https://github.com/ReVanced/revanced-patches/commit/a0a62ddad26cfab3e04907fae5532e1ba1fdf710)) - - -### Features - -* **Tumblr:** Add `Disable Tumblr TV` patch ([#5959](https://github.com/ReVanced/revanced-patches/issues/5959)) ([212418b](https://github.com/ReVanced/revanced-patches/commit/212418b8db9a730ae9efa89ad2bef24952afbadd)) -* **YouTube - Hide layout components:** Add "Hide Emoji and Timestamp buttons" setting ([#5992](https://github.com/ReVanced/revanced-patches/issues/5992)) ([2b555f6](https://github.com/ReVanced/revanced-patches/commit/2b555f67f07e0de5703c630888ce2fbba3145192)) -* **YouTube - Hide layout components:** Add "Hide view count" and "Hide upload time" settings ([#5983](https://github.com/ReVanced/revanced-patches/issues/5983)) ([7a37d85](https://github.com/ReVanced/revanced-patches/commit/7a37d858fb937c6bdc2219103dac765b62600e6c)) -* **YouTube - Loop video:** Add player button to change loop video state ([#5961](https://github.com/ReVanced/revanced-patches/issues/5961)) ([dfb5407](https://github.com/ReVanced/revanced-patches/commit/dfb5407e67222e80e23c8935e04b6dbf1a43d757)) -* **YouTube - Spoof app version:** Add spoof target `20.05.46` that fixes transcript functionality ([5823f0e](https://github.com/ReVanced/revanced-patches/commit/5823f0e982e87b4a35d30feeca8a7e16edfebc5f)) -* **YouTube Music:** Add `Check watch history domain name resolution` ([#5979](https://github.com/ReVanced/revanced-patches/issues/5979)) ([8af70fe](https://github.com/ReVanced/revanced-patches/commit/8af70fe2d10c0f4da2d7e53bd00f5b3979775d5d)) -* **YouTube Music:** Add `Sanitize sharing links` patch ([#5952](https://github.com/ReVanced/revanced-patches/issues/5952)) ([45c1ee8](https://github.com/ReVanced/revanced-patches/commit/45c1ee8a12dc777a371875d90741a05cf5d8e9dd)) -* **YouTube Music:** Add `Theme` patch ([#5984](https://github.com/ReVanced/revanced-patches/issues/5984)) ([3bd76d6](https://github.com/ReVanced/revanced-patches/commit/3bd76d60d664befff29c24c9de56dac1486a6e67)) -* **YouTube:** Add `Disable video codecs` patch ([#5981](https://github.com/ReVanced/revanced-patches/issues/5981)) ([bfbffbd](https://github.com/ReVanced/revanced-patches/commit/bfbffbd1f5aa867027053e25b343a51a606216a3)) - -# [5.41.0-dev.18](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.17...v5.41.0-dev.18) (2025-09-26) - - -### Bug Fixes - -* **YouTube - Settings:** Handle on screen back swipe gesture ([#6002](https://github.com/ReVanced/revanced-patches/issues/6002)) ([6f92b6c](https://github.com/ReVanced/revanced-patches/commit/6f92b6c50beab091f5f7ef7386579eda38cb4c66)) - -# [5.41.0-dev.17](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.16...v5.41.0-dev.17) (2025-09-26) - - -### Bug Fixes - -* **YouTube - SponsorBlock:** Show category color dot in voting dialog menu ([4be00d0](https://github.com/ReVanced/revanced-patches/commit/4be00d09b7b87dcfac324de8709af06e9f730791)) - -# [5.41.0-dev.16](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.15...v5.41.0-dev.16) (2025-09-26) - - -### Features - -* **YouTube Music:** Add `Theme` patch ([#5984](https://github.com/ReVanced/revanced-patches/issues/5984)) ([3bd76d6](https://github.com/ReVanced/revanced-patches/commit/3bd76d60d664befff29c24c9de56dac1486a6e67)) - -# [5.41.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.14...v5.41.0-dev.15) (2025-09-25) - - -### Features - -* **YouTube - Hide layout components:** Add "Hide view count" and "Hide upload time" settings ([#5983](https://github.com/ReVanced/revanced-patches/issues/5983)) ([7a37d85](https://github.com/ReVanced/revanced-patches/commit/7a37d858fb937c6bdc2219103dac765b62600e6c)) - -# [5.41.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.13...v5.41.0-dev.14) (2025-09-24) - - -### Features - -* **YouTube - Hide layout components:** Add "Hide Emoji and Timestamp buttons" setting ([#5992](https://github.com/ReVanced/revanced-patches/issues/5992)) ([2b555f6](https://github.com/ReVanced/revanced-patches/commit/2b555f67f07e0de5703c630888ce2fbba3145192)) - -# [5.41.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.12...v5.41.0-dev.13) (2025-09-24) - - -### Bug Fixes - -* **YouTube - Hide Shorts components:** Fix "Hide preview comment" ([#5990](https://github.com/ReVanced/revanced-patches/issues/5990)) ([dd4e2cd](https://github.com/ReVanced/revanced-patches/commit/dd4e2cd0855ccc51b94593004fdd8150ac3b41cc)) - -# [5.41.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.11...v5.41.0-dev.12) (2025-09-24) - - -### Bug Fixes - -* **YouTube - SponsorBlock:** Show category color in create new segment menu ([#5987](https://github.com/ReVanced/revanced-patches/issues/5987)) ([ffd933c](https://github.com/ReVanced/revanced-patches/commit/ffd933c6734274cdde5aaec0159b67f173f9228c)) - -# [5.41.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.10...v5.41.0-dev.11) (2025-09-23) - - -### Features - -* **YouTube:** Add `Disable video codecs` patch ([#5981](https://github.com/ReVanced/revanced-patches/issues/5981)) ([bfbffbd](https://github.com/ReVanced/revanced-patches/commit/bfbffbd1f5aa867027053e25b343a51a606216a3)) - -# [5.41.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.9...v5.41.0-dev.10) (2025-09-23) - - -### Bug Fixes - -* **TikTok:** Show correct dialog restart text, use correct font color for non-dark mode ([d1a1293](https://github.com/ReVanced/revanced-patches/commit/d1a12930c35f630793a0f240d4203c2ff9060158)) - -# [5.41.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.8...v5.41.0-dev.9) (2025-09-23) - - -### Bug Fixes - -* **Instagram - Hide navigation buttons:** Remove button based on name ([#5971](https://github.com/ReVanced/revanced-patches/issues/5971)) ([6fa4043](https://github.com/ReVanced/revanced-patches/commit/6fa404331b5162682d83fba5f38ed570c31495fc)) - -# [5.41.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.7...v5.41.0-dev.8) (2025-09-23) - - -### Features - -* **YouTube Music:** Add `Check watch history domain name resolution` ([#5979](https://github.com/ReVanced/revanced-patches/issues/5979)) ([8af70fe](https://github.com/ReVanced/revanced-patches/commit/8af70fe2d10c0f4da2d7e53bd00f5b3979775d5d)) - -# [5.41.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.6...v5.41.0-dev.7) (2025-09-23) - - -### Features - -* **Tumblr:** Add `Disable Tumblr TV` patch ([#5959](https://github.com/ReVanced/revanced-patches/issues/5959)) ([212418b](https://github.com/ReVanced/revanced-patches/commit/212418b8db9a730ae9efa89ad2bef24952afbadd)) - -# [5.41.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.5...v5.41.0-dev.6) (2025-09-22) - - -### Features - -* **YouTube - Spoof app version:** Add spoof target `20.05.46` that fixes transcript functionality ([5823f0e](https://github.com/ReVanced/revanced-patches/commit/5823f0e982e87b4a35d30feeca8a7e16edfebc5f)) - -# [5.41.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.4...v5.41.0-dev.5) (2025-09-22) - - -### Bug Fixes - -* **Twitch - Settings:** Fix missing style resources ([#5970](https://github.com/ReVanced/revanced-patches/issues/5970)) ([8c22995](https://github.com/ReVanced/revanced-patches/commit/8c229954d7f232a7a472ca49f1b5e7cdc475bbcc)) - -# [5.41.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.3...v5.41.0-dev.4) (2025-09-22) - - -### Bug Fixes - -* **Instagram - Limit feed to followed profiles:** Preserve favorites feed ([#5963](https://github.com/ReVanced/revanced-patches/issues/5963)) ([ef51401](https://github.com/ReVanced/revanced-patches/commit/ef514017f46025d9aef6884424caeb0670514e7a)) - -# [5.41.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.2...v5.41.0-dev.3) (2025-09-22) - - -### Features - -* **YouTube - Loop video:** Add player button to change loop video state ([#5961](https://github.com/ReVanced/revanced-patches/issues/5961)) ([dfb5407](https://github.com/ReVanced/revanced-patches/commit/dfb5407e67222e80e23c8935e04b6dbf1a43d757)) - -# [5.41.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.1...v5.41.0-dev.2) (2025-09-21) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Update client side effects summary text ([a0a62dd](https://github.com/ReVanced/revanced-patches/commit/a0a62ddad26cfab3e04907fae5532e1ba1fdf710)) - -# [5.41.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.40.1-dev.1...v5.41.0-dev.1) (2025-09-21) - - -### Features - -* **YouTube Music:** Add `Sanitize sharing links` patch ([#5952](https://github.com/ReVanced/revanced-patches/issues/5952)) ([45c1ee8](https://github.com/ReVanced/revanced-patches/commit/45c1ee8a12dc777a371875d90741a05cf5d8e9dd)) - -## [5.40.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.40.0...v5.40.1-dev.1) (2025-09-21) - - -### Bug Fixes - -* **YouTube - Return YouTube Dislike:** Do not show error toast if API returns 401 status ([#5949](https://github.com/ReVanced/revanced-patches/issues/5949)) ([58d088a](https://github.com/ReVanced/revanced-patches/commit/58d088ab307440a6912a867246da799b7dd6499b)) -* **YouTube - Settings:** Use an overlay to show search results ([#5806](https://github.com/ReVanced/revanced-patches/issues/5806)) ([ece8076](https://github.com/ReVanced/revanced-patches/commit/ece8076f7cefd752b97515014bc50fe4fd80171e)) - -# [5.40.0](https://github.com/ReVanced/revanced-patches/compare/v5.39.0...v5.40.0) (2025-09-21) - - -### Bug Fixes - -* **Instagram - Limit feed to followed profiles:** Change patch to default off ([767f1e3](https://github.com/ReVanced/revanced-patches/commit/767f1e3695327bdbc4daea8b50a80d4c0a38456a)) -* **Spoof video streams:** Resolve occasional playback stuttering ([5c7c8b5](https://github.com/ReVanced/revanced-patches/commit/5c7c8b536416ec53cd98f7d59d11850aa1b70f11)) -* **YouTube - Force original audio:** Show UI setting summary if spoofing to Android Studio ([b7026b7](https://github.com/ReVanced/revanced-patches/commit/b7026b70865bc44de07b30f84ba8b8b608930d5b)) -* **YouTube - Spoof video streams:** Add "Force original audio" disclaimer for Android Studio client ([f97d332](https://github.com/ReVanced/revanced-patches/commit/f97d33206b4c97244f0bd0c672c4b91eaf477b0b)) -* **YouTube - Spoof video streams:** Add stream audio selector disclaimer for Android Studio client ([a8a4107](https://github.com/ReVanced/revanced-patches/commit/a8a410708d50f34ac4bd2ca29bbbc3cde00bbf93)) - - -### Features - -* **Instagram:** Add `Limit feed to followed profiles` patch ([#5908](https://github.com/ReVanced/revanced-patches/issues/5908)) ([8ba9a19](https://github.com/ReVanced/revanced-patches/commit/8ba9a19ade24c5fe9bd6d4e49772b7663522780e)) -* **Viber - Hide ads:** Support latest app target ([#5863](https://github.com/ReVanced/revanced-patches/issues/5863)) ([e6cce85](https://github.com/ReVanced/revanced-patches/commit/e6cce8554116df3c0ea6dbb7440c59c9e73d8334)) -* **YouTube - Hide video action buttons:** Add "Hide comments" button ([db796fb](https://github.com/ReVanced/revanced-patches/commit/db796fb8830b813e1ed626d491c4a797171e69e7)) -* **YouTube Music:** Add `Enable debugging` patch ([#5939](https://github.com/ReVanced/revanced-patches/issues/5939)) ([418f594](https://github.com/ReVanced/revanced-patches/commit/418f5945c213313f9a77cac9a5c326d89c754dfd)) -* **YouTube Music:** Add `Hide cast button` and `Navigation bar` patches ([#5934](https://github.com/ReVanced/revanced-patches/issues/5934)) ([651d358](https://github.com/ReVanced/revanced-patches/commit/651d3580967a252b57cbf4afbba02d6a4601ccfe)) -* **YouTube Music:** Support version `8.10.52` ([#5941](https://github.com/ReVanced/revanced-patches/issues/5941)) ([01c0f1b](https://github.com/ReVanced/revanced-patches/commit/01c0f1bd1ac6edb8aea758f88ffffcdea74a29b7)) -* **YouTube:** Support version `20.14.43` ([#5940](https://github.com/ReVanced/revanced-patches/issues/5940)) ([f7f4a1b](https://github.com/ReVanced/revanced-patches/commit/f7f4a1b0f0186598266b41a2c6a781fdee49e440)) - -# [5.40.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.10...v5.40.0-dev.11) (2025-09-20) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Add stream audio selector disclaimer for Android Studio client ([a8a4107](https://github.com/ReVanced/revanced-patches/commit/a8a410708d50f34ac4bd2ca29bbbc3cde00bbf93)) - -# [5.40.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.9...v5.40.0-dev.10) (2025-09-20) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Add "Force original audio" disclaimer for Android Studio client ([f97d332](https://github.com/ReVanced/revanced-patches/commit/f97d33206b4c97244f0bd0c672c4b91eaf477b0b)) - -# [5.40.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.8...v5.40.0-dev.9) (2025-09-20) - - -### Features - -* **YouTube Music:** Support version `8.10.52` ([#5941](https://github.com/ReVanced/revanced-patches/issues/5941)) ([01c0f1b](https://github.com/ReVanced/revanced-patches/commit/01c0f1bd1ac6edb8aea758f88ffffcdea74a29b7)) - -# [5.40.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.7...v5.40.0-dev.8) (2025-09-20) - - -### Features - -* **YouTube:** Support version `20.14.43` ([#5940](https://github.com/ReVanced/revanced-patches/issues/5940)) ([f7f4a1b](https://github.com/ReVanced/revanced-patches/commit/f7f4a1b0f0186598266b41a2c6a781fdee49e440)) - -# [5.40.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.6...v5.40.0-dev.7) (2025-09-20) - - -### Features - -* **YouTube - Hide video action buttons:** Add "Hide comments" button ([db796fb](https://github.com/ReVanced/revanced-patches/commit/db796fb8830b813e1ed626d491c4a797171e69e7)) - -# [5.40.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.5...v5.40.0-dev.6) (2025-09-20) - - -### Features - -* **YouTube Music:** Add `Enable debugging` patch ([#5939](https://github.com/ReVanced/revanced-patches/issues/5939)) ([418f594](https://github.com/ReVanced/revanced-patches/commit/418f5945c213313f9a77cac9a5c326d89c754dfd)) - -# [5.40.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.4...v5.40.0-dev.5) (2025-09-20) - - -### Features - -* **YouTube Music:** Add `Hide cast button` and `Navigation bar` patches ([#5934](https://github.com/ReVanced/revanced-patches/issues/5934)) ([651d358](https://github.com/ReVanced/revanced-patches/commit/651d3580967a252b57cbf4afbba02d6a4601ccfe)) - -# [5.40.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.3...v5.40.0-dev.4) (2025-09-20) - - -### Bug Fixes - -* **Spoof video streams:** Resolve occasional playback stuttering ([5c7c8b5](https://github.com/ReVanced/revanced-patches/commit/5c7c8b536416ec53cd98f7d59d11850aa1b70f11)) - -# [5.40.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.2...v5.40.0-dev.3) (2025-09-19) - - -### Bug Fixes - -* **Instagram - Limit feed to followed profiles:** Change patch to default off ([767f1e3](https://github.com/ReVanced/revanced-patches/commit/767f1e3695327bdbc4daea8b50a80d4c0a38456a)) - -# [5.40.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.1...v5.40.0-dev.2) (2025-09-18) - - -### Features - -* **Instagram:** Add `Limit feed to followed profiles` patch ([#5908](https://github.com/ReVanced/revanced-patches/issues/5908)) ([8ba9a19](https://github.com/ReVanced/revanced-patches/commit/8ba9a19ade24c5fe9bd6d4e49772b7663522780e)) - -# [5.40.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.39.1-dev.1...v5.40.0-dev.1) (2025-09-17) - - -### Features - -* **Viber - Hide ads:** Support latest app target ([#5863](https://github.com/ReVanced/revanced-patches/issues/5863)) ([e6cce85](https://github.com/ReVanced/revanced-patches/commit/e6cce8554116df3c0ea6dbb7440c59c9e73d8334)) - -## [5.39.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.39.0...v5.39.1-dev.1) (2025-09-17) - - -### Bug Fixes - -* **YouTube - Force original audio:** Show UI setting summary if spoofing to Android Studio ([b7026b7](https://github.com/ReVanced/revanced-patches/commit/b7026b70865bc44de07b30f84ba8b8b608930d5b)) - -# [5.39.0](https://github.com/ReVanced/revanced-patches/compare/v5.38.0...v5.39.0) (2025-09-17) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Do not use Android Creator for livestreams ([cbe576b](https://github.com/ReVanced/revanced-patches/commit/cbe576bc384ef5f5ee2fa341147925ed0dff568b)) -* **YouTube - Spoof video streams:** Show Android Studio in spoof stream menu ([c9f741e](https://github.com/ReVanced/revanced-patches/commit/c9f741e616c7acab0cd4558e02b0c4ec18392c10)) -* **YouTube Music - Spoof video streams:** Remove iPadOS client ([7eeffd3](https://github.com/ReVanced/revanced-patches/commit/7eeffd3392c57555342173103d3a417c038d0970)) - - -### Features - -* **YouTube - Hide video action buttons:** Add "Hide Shop button" setting ([a84db7b](https://github.com/ReVanced/revanced-patches/commit/a84db7be7fde2e9bb3ac41aec709a1681e845fe1)) - -# [5.39.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.39.0-dev.1...v5.39.0-dev.2) (2025-09-17) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Show Android Studio in spoof stream menu ([c9f741e](https://github.com/ReVanced/revanced-patches/commit/c9f741e616c7acab0cd4558e02b0c4ec18392c10)) - -# [5.39.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.38.1-dev.2...v5.39.0-dev.1) (2025-09-17) - - -### Features - -* **YouTube - Hide video action buttons:** Add "Hide Shop button" setting ([a84db7b](https://github.com/ReVanced/revanced-patches/commit/a84db7be7fde2e9bb3ac41aec709a1681e845fe1)) - -## [5.38.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.38.1-dev.1...v5.38.1-dev.2) (2025-09-16) - - -### Bug Fixes - -* **YouTube Music - Spoof video streams:** Remove iPadOS client ([7eeffd3](https://github.com/ReVanced/revanced-patches/commit/7eeffd3392c57555342173103d3a417c038d0970)) - -## [5.38.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.38.0...v5.38.1-dev.1) (2025-09-16) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Do not use Android Creator for livestreams ([cbe576b](https://github.com/ReVanced/revanced-patches/commit/cbe576bc384ef5f5ee2fa341147925ed0dff568b)) - -# [5.38.0](https://github.com/ReVanced/revanced-patches/compare/v5.37.0...v5.38.0) (2025-09-16) - - -### Bug Fixes - -* **Instagram - Hide navigation buttons:** Support v397.1.0.52.81 ([#5855](https://github.com/ReVanced/revanced-patches/issues/5855)) ([f11d1ef](https://github.com/ReVanced/revanced-patches/commit/f11d1ef9907082512f139d4ab0e2e9f707de7e48)) -* **Spoof video streams:** Remove Android TV and iOS TV clients, add experimental VisionOS, add temporary fix for `Force original audio` to work with any spoof client ([#5861](https://github.com/ReVanced/revanced-patches/issues/5861)) ([abe3943](https://github.com/ReVanced/revanced-patches/commit/abe3943f98fd86dcd74c7e07cf65d3c7fc24fef9)) -* **YouTube - Spoof video streams:** Show settings summary if `Force original audio` is enabled ([3776dda](https://github.com/ReVanced/revanced-patches/commit/3776dda710a7780717b7e6f2cdc1333ab67b92fc)) -* **YouTube Music - Spoof video streams:** Fix playback issues when using a cellular network ([fa04c8e](https://github.com/ReVanced/revanced-patches/commit/fa04c8eecfbdd0b6ed082b464ca9032536d71762)) -* **YouTube Music:** Use correct light/dark mode settings UI ([1475643](https://github.com/ReVanced/revanced-patches/commit/1475643f84e9ee4af2ba360a2274001ff1570dad)) - - -### Features - -* **Instagram:** Add `Hide explore feed` patch ([#5856](https://github.com/ReVanced/revanced-patches/issues/5856)) ([1d65887](https://github.com/ReVanced/revanced-patches/commit/1d65887e015a067196f5a84db486fff355c96596)) -* **YouTube - Spoof video streams:** Add iPadOS client ([2726231](https://github.com/ReVanced/revanced-patches/commit/2726231404384d87f101d825e10a17c944e8f1bd)) -* **YouTube Music:** Add `Settings` patch ([#5838](https://github.com/ReVanced/revanced-patches/issues/5838)) ([5e20bd8](https://github.com/ReVanced/revanced-patches/commit/5e20bd80f138d7ca94f18857194c46e489c435dc)) - -# [5.38.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.38.0-dev.4...v5.38.0-dev.5) (2025-09-16) - - -### Bug Fixes - -* **YouTube Music:** Use correct light/dark mode settings UI ([1475643](https://github.com/ReVanced/revanced-patches/commit/1475643f84e9ee4af2ba360a2274001ff1570dad)) - -# [5.38.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.38.0-dev.3...v5.38.0-dev.4) (2025-09-16) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Show settings summary if `Force original audio` is enabled ([3776dda](https://github.com/ReVanced/revanced-patches/commit/3776dda710a7780717b7e6f2cdc1333ab67b92fc)) - -# [5.38.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.38.0-dev.2...v5.38.0-dev.3) (2025-09-16) - - -### Features - -* **YouTube - Spoof video streams:** Add iPadOS client ([2726231](https://github.com/ReVanced/revanced-patches/commit/2726231404384d87f101d825e10a17c944e8f1bd)) - -# [5.38.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.38.0-dev.1...v5.38.0-dev.2) (2025-09-16) - - -### Features - -* **YouTube Music:** Add `Settings` patch ([#5838](https://github.com/ReVanced/revanced-patches/issues/5838)) ([5e20bd8](https://github.com/ReVanced/revanced-patches/commit/5e20bd80f138d7ca94f18857194c46e489c435dc)) - -# [5.38.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.37.1-dev.3...v5.38.0-dev.1) (2025-09-15) - - -### Features - -* **Instagram:** Add `Hide explore feed` patch ([#5856](https://github.com/ReVanced/revanced-patches/issues/5856)) ([1d65887](https://github.com/ReVanced/revanced-patches/commit/1d65887e015a067196f5a84db486fff355c96596)) - -## [5.37.1-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.37.1-dev.2...v5.37.1-dev.3) (2025-09-15) - - -### Bug Fixes - -* **Spoof video streams:** Remove Android TV and iOS TV clients, add experimental VisionOS, add temporary fix for `Force original audio` to work with any spoof client ([#5861](https://github.com/ReVanced/revanced-patches/issues/5861)) ([abe3943](https://github.com/ReVanced/revanced-patches/commit/abe3943f98fd86dcd74c7e07cf65d3c7fc24fef9)) - -## [5.37.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.37.1-dev.1...v5.37.1-dev.2) (2025-09-15) - - -### Bug Fixes - -* **Instagram - Hide navigation buttons:** Support v397.1.0.52.81 ([#5855](https://github.com/ReVanced/revanced-patches/issues/5855)) ([f11d1ef](https://github.com/ReVanced/revanced-patches/commit/f11d1ef9907082512f139d4ab0e2e9f707de7e48)) - -## [5.37.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.37.0...v5.37.1-dev.1) (2025-09-15) - - -### Bug Fixes - -* **YouTube Music - Spoof video streams:** Fix playback issues when using a cellular network ([fa04c8e](https://github.com/ReVanced/revanced-patches/commit/fa04c8eecfbdd0b6ed082b464ca9032536d71762)) - -# [5.37.0](https://github.com/ReVanced/revanced-patches/compare/v5.36.0...v5.37.0) (2025-09-15) - - -### Bug Fixes - -* **Instagram - Hide navigation buttons:** Add constrain to known working version ([e6c79f1](https://github.com/ReVanced/revanced-patches/commit/e6c79f13834c83fef04e4dee5e628cb0b9a27765)) -* Resolve patching with dev branch ([09b941a](https://github.com/ReVanced/revanced-patches/commit/09b941abf0e8029999565082b02a88b5de507ec4)) -* **Spotify:** Remove broken `Spoof client` patch ([#5833](https://github.com/ReVanced/revanced-patches/issues/5833)) ([dcd4245](https://github.com/ReVanced/revanced-patches/commit/dcd42454bd5f87dddd720534f6120c4ef90063a3)) -* **Viber - Hide ads:** Add constrain to known working version ([2db0948](https://github.com/ReVanced/revanced-patches/commit/2db0948beaf2b68391a1fe7f21e92d31c7df61e7)) -* **YouTube Music - Spoof streaming data:** Fix audio playback stuttering ([#5839](https://github.com/ReVanced/revanced-patches/issues/5839)) ([2a85a3b](https://github.com/ReVanced/revanced-patches/commit/2a85a3b29092729ae16d1fd93803634ce5f08e95)) - - -### Features - -* **Viber:** Add `Hide ads` patch ([#5826](https://github.com/ReVanced/revanced-patches/issues/5826)) ([0abfab7](https://github.com/ReVanced/revanced-patches/commit/0abfab79d7cda15bf17c53679fbfffb021662649)) - -# [5.37.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.37.0-dev.5...v5.37.0-dev.6) (2025-09-15) - - -### Bug Fixes - -* **Instagram - Hide navigation buttons:** Add constrain to known working version ([e6c79f1](https://github.com/ReVanced/revanced-patches/commit/e6c79f13834c83fef04e4dee5e628cb0b9a27765)) - -# [5.37.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.37.0-dev.4...v5.37.0-dev.5) (2025-09-15) - - -### Bug Fixes - -* **Viber - Hide ads:** Add constrain to known working version ([2db0948](https://github.com/ReVanced/revanced-patches/commit/2db0948beaf2b68391a1fe7f21e92d31c7df61e7)) - -# [5.37.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.37.0-dev.3...v5.37.0-dev.4) (2025-09-14) - - -### Bug Fixes - -* **YouTube Music - Spoof streaming data:** Fix audio playback stuttering ([#5839](https://github.com/ReVanced/revanced-patches/issues/5839)) ([2a85a3b](https://github.com/ReVanced/revanced-patches/commit/2a85a3b29092729ae16d1fd93803634ce5f08e95)) - -# [5.37.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.37.0-dev.2...v5.37.0-dev.3) (2025-09-14) - - -### Bug Fixes - -* **Spotify:** Remove broken `Spoof client` patch ([#5833](https://github.com/ReVanced/revanced-patches/issues/5833)) ([dcd4245](https://github.com/ReVanced/revanced-patches/commit/dcd42454bd5f87dddd720534f6120c4ef90063a3)) - -# [5.37.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.37.0-dev.1...v5.37.0-dev.2) (2025-09-14) - - -### Bug Fixes - -* Resolve patching with dev branch ([09b941a](https://github.com/ReVanced/revanced-patches/commit/09b941abf0e8029999565082b02a88b5de507ec4)) - -# [5.37.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.36.0...v5.37.0-dev.1) (2025-09-14) - - -### Features - -* **Viber:** Add `Hide ads` patch ([#5826](https://github.com/ReVanced/revanced-patches/issues/5826)) ([0abfab7](https://github.com/ReVanced/revanced-patches/commit/0abfab79d7cda15bf17c53679fbfffb021662649)) - -# [5.36.0](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.36.0) (2025-09-14) - - -### Bug Fixes - -* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](https://github.com/ReVanced/revanced-patches/commit/88b47ef414cd073ec3800258b32aceb6f383a411)) -* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](https://github.com/ReVanced/revanced-patches/commit/8cd8e59bbc3a878269276b8ae5f627b044d157f0)) -* **YouTube Music:** Resolve playback issues, change recommended app target to `7.29.52` ([#5813](https://github.com/ReVanced/revanced-patches/issues/5813)) ([a53b00d](https://github.com/ReVanced/revanced-patches/commit/a53b00dd514dbe2b3406f3c1013a4f58a7f481c5)) - - -### Features - -* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](https://github.com/ReVanced/revanced-patches/commit/9d4aa5cd16a6f9e95cf7c626351b46b86ca80efe)) - -# [5.36.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.36.0-dev.1) (2025-09-13) - - -### Bug Fixes - -* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](https://github.com/ReVanced/revanced-patches/commit/88b47ef414cd073ec3800258b32aceb6f383a411)) -* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](https://github.com/ReVanced/revanced-patches/commit/8cd8e59bbc3a878269276b8ae5f627b044d157f0)) -* **YouTube Music:** Resolve playback issues, change recommended app target to `7.29.52` ([#5813](https://github.com/ReVanced/revanced-patches/issues/5813)) ([a53b00d](https://github.com/ReVanced/revanced-patches/commit/a53b00dd514dbe2b3406f3c1013a4f58a7f481c5)) - - -### Features - -* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](https://github.com/ReVanced/revanced-patches/commit/9d4aa5cd16a6f9e95cf7c626351b46b86ca80efe)) - -# [5.36.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.36.0-dev.1) (2025-09-13) - - -### Bug Fixes - -* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](https://github.com/ReVanced/revanced-patches/commit/88b47ef414cd073ec3800258b32aceb6f383a411)) -* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](https://github.com/ReVanced/revanced-patches/commit/8cd8e59bbc3a878269276b8ae5f627b044d157f0)) -* **YouTube Music:** Resolve playback issues, change recommended app target to `7.29.52` ([#5813](https://github.com/ReVanced/revanced-patches/issues/5813)) ([a53b00d](https://github.com/ReVanced/revanced-patches/commit/a53b00dd514dbe2b3406f3c1013a4f58a7f481c5)) - - -### Features - -* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](https://github.com/ReVanced/revanced-patches/commit/9d4aa5cd16a6f9e95cf7c626351b46b86ca80efe)) - -# [5.36.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.36.0-dev.1) (2025-09-13) - - -### Bug Fixes - -* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](https://github.com/ReVanced/revanced-patches/commit/88b47ef414cd073ec3800258b32aceb6f383a411)) -* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](https://github.com/ReVanced/revanced-patches/commit/8cd8e59bbc3a878269276b8ae5f627b044d157f0)) - - -### Features - -* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](https://github.com/ReVanced/revanced-patches/commit/9d4aa5cd16a6f9e95cf7c626351b46b86ca80efe)) - -# [5.36.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.36.0-dev.1) (2025-09-12) - - -### Bug Fixes - -* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](https://github.com/ReVanced/revanced-patches/commit/88b47ef414cd073ec3800258b32aceb6f383a411)) -* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](https://github.com/ReVanced/revanced-patches/commit/8cd8e59bbc3a878269276b8ae5f627b044d157f0)) - - -### Features - -* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](https://github.com/ReVanced/revanced-patches/commit/9d4aa5cd16a6f9e95cf7c626351b46b86ca80efe)) - -# [5.36.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.1-dev.1...v5.36.0-dev.1) (2025-09-12) - - -### Features - -* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([2e042c4](https://github.com/ReVanced/revanced-patches/commit/2e042c4b3366fa3daf991d5560fcae991d00ad12)) - -## [5.35.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.35.1-dev.1) (2025-09-11) - - -### Bug Fixes - -* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([8491516](https://github.com/ReVanced/revanced-patches/commit/849151637389b8f399356d0d331bb74482f3f05d)) -* **YouTube - Hide layout components:** Hide new type of Playable shelf ([3af4126](https://github.com/ReVanced/revanced-patches/commit/3af41265338ddaab52d009f53370c57abddd4599)) - -# [5.35.0](https://github.com/ReVanced/revanced-patches/compare/v5.34.0...v5.35.0) (2025-09-09) - - -### Bug Fixes - -* **Instagram - Hide navigation buttons:** Fix Manager patching error ([0d10e94](https://github.com/ReVanced/revanced-patches/commit/0d10e94663283fac09f3efc57c9b9805c38c4e13)) -* **Proton mail:** Constrain patches to last working app target ([21c34b9](https://github.com/ReVanced/revanced-patches/commit/21c34b908e07a97de8c31c7c828b44a8cc4739b6)) -* Revert dependency updates to fix Manager pre-release patching ([4c7a1a8](https://github.com/ReVanced/revanced-patches/commit/4c7a1a8554c67797bf663e5230f566c5a9b229af)) -* **Spotify - Unlock Premium:** Make compatible with latest versions again by fixing fingerprint ([#5684](https://github.com/ReVanced/revanced-patches/issues/5684)) ([30dcff1](https://github.com/ReVanced/revanced-patches/commit/30dcff13a56883efc499b71faadb403877cd1c67)) -* **YouTube - Hide layout components:** Hide Playable shelf header ([fbb5046](https://github.com/ReVanced/revanced-patches/commit/fbb50463f0e3f533a278c5251cfbce59f09ce641)) - - -### Features - -* **BaconReader:** Add `Fix Redgifs API` patch ([#5761](https://github.com/ReVanced/revanced-patches/issues/5761)) ([08868c0](https://github.com/ReVanced/revanced-patches/commit/08868c00d3c4f1f37f4a77f333a03ca5a3259b59)) -* **Boost/Sync for Reddit:** Add `Fix Redgifs` patch ([#5725](https://github.com/ReVanced/revanced-patches/issues/5725)) ([c5e8079](https://github.com/ReVanced/revanced-patches/commit/c5e8079eab08075a72078cd0fa79f3beb1f75d98)) -* **Instagram:** Add `Hide navigation buttons` patch ([#5678](https://github.com/ReVanced/revanced-patches/issues/5678)) ([415cf0f](https://github.com/ReVanced/revanced-patches/commit/415cf0fb5b9b3dcaf4592943a69eea1c10447b07)) -* **Instagram:** Add `Hide Stories from Home` patch ([#5756](https://github.com/ReVanced/revanced-patches/issues/5756)) ([3ae3251](https://github.com/ReVanced/revanced-patches/commit/3ae3251dc0317b6ced136fe9aa14be369642f203)) - -# [5.35.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.35.0-dev.4...v5.35.0-dev.5) (2025-09-06) - - -### Features - -* **BaconReader:** Add `Fix Redgifs API` patch ([#5761](https://github.com/ReVanced/revanced-patches/issues/5761)) ([08868c0](https://github.com/ReVanced/revanced-patches/commit/08868c00d3c4f1f37f4a77f333a03ca5a3259b59)) -* **Instagram:** Add `Hide Stories from Home` patch ([#5756](https://github.com/ReVanced/revanced-patches/issues/5756)) ([3ae3251](https://github.com/ReVanced/revanced-patches/commit/3ae3251dc0317b6ced136fe9aa14be369642f203)) - -# [5.35.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.35.0-dev.3...v5.35.0-dev.4) (2025-09-04) - - -### Features - -* **Boost/Sync for Reddit:** Add `Fix Redgifs` patch ([#5725](https://github.com/ReVanced/revanced-patches/issues/5725)) ([c5e8079](https://github.com/ReVanced/revanced-patches/commit/c5e8079eab08075a72078cd0fa79f3beb1f75d98)) - -# [5.35.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.35.0-dev.2...v5.35.0-dev.3) (2025-09-04) - - -### Bug Fixes - -* **Instagram - Hide navigation buttons:** Fix Manager patching error ([0d10e94](https://github.com/ReVanced/revanced-patches/commit/0d10e94663283fac09f3efc57c9b9805c38c4e13)) - -# [5.35.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.35.0-dev.1...v5.35.0-dev.2) (2025-09-04) - - -### Bug Fixes - -* Revert dependency updates to fix Manager pre-release patching ([4c7a1a8](https://github.com/ReVanced/revanced-patches/commit/4c7a1a8554c67797bf663e5230f566c5a9b229af)) - -# [5.35.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.34.1-dev.3...v5.35.0-dev.1) (2025-09-03) - - -### Features - -* **Instagram:** Add `Hide navigation buttons` patch ([#5678](https://github.com/ReVanced/revanced-patches/issues/5678)) ([415cf0f](https://github.com/ReVanced/revanced-patches/commit/415cf0fb5b9b3dcaf4592943a69eea1c10447b07)) - -## [5.34.1-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.34.1-dev.2...v5.34.1-dev.3) (2025-08-24) - - -### Bug Fixes - -* **YouTube - Hide layout components:** Hide Playable shelf header ([fbb5046](https://github.com/ReVanced/revanced-patches/commit/fbb50463f0e3f533a278c5251cfbce59f09ce641)) - -## [5.34.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.34.1-dev.1...v5.34.1-dev.2) (2025-08-22) - - -### Bug Fixes - -* **Proton mail:** Constrain patches to last working app target ([21c34b9](https://github.com/ReVanced/revanced-patches/commit/21c34b908e07a97de8c31c7c828b44a8cc4739b6)) - -## [5.34.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.34.0...v5.34.1-dev.1) (2025-08-21) - - -### Bug Fixes - -* **Spotify - Unlock Premium:** Make compatible with latest versions again by fixing fingerprint ([#5684](https://github.com/ReVanced/revanced-patches/issues/5684)) ([30dcff1](https://github.com/ReVanced/revanced-patches/commit/30dcff13a56883efc499b71faadb403877cd1c67)) - -# [5.34.0](https://github.com/ReVanced/revanced-patches/compare/v5.33.0...v5.34.0) (2025-08-19) - - -### Bug Fixes - -* **Backdrops:** Remove broken patch that is no longer supported ([#5627](https://github.com/ReVanced/revanced-patches/issues/5627)) ([ebb8332](https://github.com/ReVanced/revanced-patches/commit/ebb83320838aa99dd4417d45a50333dd42c1218a)) -* **pixiv - Hide ads:** Constrain patch to last working app target ([d8ea56c](https://github.com/ReVanced/revanced-patches/commit/d8ea56ca4be47df1c43f96ec41b91c800f1d9daf)) -* **Twitch:** Constrain patches to last working app targets ([#5373](https://github.com/ReVanced/revanced-patches/issues/5373)) ([29a4748](https://github.com/ReVanced/revanced-patches/commit/29a47481c4efa209a3a53df60613b59a73adbe07)) -* **YouTube - Hide layout components:** Do not hide community posts on channel profiles ([#5634](https://github.com/ReVanced/revanced-patches/issues/5634)) ([9e3d5a2](https://github.com/ReVanced/revanced-patches/commit/9e3d5a2b36106479470f3f69920518b57e8c4dca)) -* **YouTube - Player Controls:** Fix chapter title overlapping the bottom buttons ([#5673](https://github.com/ReVanced/revanced-patches/issues/5673)) ([09ccee7](https://github.com/ReVanced/revanced-patches/commit/09ccee71384df338bbf8acc1097f619a372c4868)) -* **YouTube - SponsorBlock:** Do not hide voting or create button when the video ends ([6aba4e2](https://github.com/ReVanced/revanced-patches/commit/6aba4e284de9bb94b49eea8be2baf2870eecbbcf)) -* **YouTube - Video playback:** Disable HDR video does not disable Dolby Vision HDR ([#5661](https://github.com/ReVanced/revanced-patches/issues/5661)) ([6dab988](https://github.com/ReVanced/revanced-patches/commit/6dab98810645b96bd0387ba7d607e5d8ffb1b5bb)) -* **YouTube - Video quality:** Fix additional incorrect quality resolutions used by YouTube ([a2a1fbe](https://github.com/ReVanced/revanced-patches/commit/a2a1fbe2959be8334c54cfc3426c24a960c55c8f)) -* **YouTube - Video quality:** Show FHD+ icon for 1080p 60fps enhanced bitrate ([76bed37](https://github.com/ReVanced/revanced-patches/commit/76bed3734093713af24ef065d5ffc5b1cd83f29a)) -* **YouTube:** Use correct fade out animation when tapping to dismiss the video overlay ([#5670](https://github.com/ReVanced/revanced-patches/issues/5670)) ([cce6737](https://github.com/ReVanced/revanced-patches/commit/cce6737f627fc7621bbde50a5653b6af14c6f31a)) - - -### Features - -* **Instagram:** Support latest app version ([#5611](https://github.com/ReVanced/revanced-patches/issues/5611)) ([26fe690](https://github.com/ReVanced/revanced-patches/commit/26fe690dfbefe6c412c5f81f208a3b1d2fbd7a0a)) -* **NU.nl:** Support latest app version ([#5643](https://github.com/ReVanced/revanced-patches/issues/5643)) ([7338e4a](https://github.com/ReVanced/revanced-patches/commit/7338e4a5a99f913256120d0d58fede3aa4ee8922)) -* **YouTube - Hide player flyout menu items:** Add option to hide quality flyout menu ([eb55068](https://github.com/ReVanced/revanced-patches/commit/eb5506856a2eaf2a8585e598868ddba3e1429159)) -* **YouTube - Hide video action buttons:** Add "Hide Hype button" setting ([f13f377](https://github.com/ReVanced/revanced-patches/commit/f13f3770e7c4fd5bff8f3e224fb1b1ead50a3c18)) -* **YouTube - Hide video action buttons:** Add "Hide Promote button" setting ([1959396](https://github.com/ReVanced/revanced-patches/commit/1959396a53f4c07b94acddc5c0ee6cdf7ade7c7b)) -* **YouTube - Playback speed:** Show current playback speed on player speed dialog button ([#5607](https://github.com/ReVanced/revanced-patches/issues/5607)) ([279436a](https://github.com/ReVanced/revanced-patches/commit/279436a3657b50f98bb4cc64dc88dc14e422f204)) -* **YouTube:** Add `Disable sign in to TV popup` patch ([#5639](https://github.com/ReVanced/revanced-patches/issues/5639)) ([d0e5bd0](https://github.com/ReVanced/revanced-patches/commit/d0e5bd0479a8910b081c483ed2a6ab4d7134e3c3)) - -# [5.34.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.12...v5.34.0-dev.13) (2025-08-19) - - -### Bug Fixes - -* **YouTube - Player Controls:** Fix chapter title overlapping the bottom buttons ([#5673](https://github.com/ReVanced/revanced-patches/issues/5673)) ([09ccee7](https://github.com/ReVanced/revanced-patches/commit/09ccee71384df338bbf8acc1097f619a372c4868)) - -# [5.34.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.12...v5.34.0-dev.13) (2025-08-18) - - -### Bug Fixes - -* **YouTube - Player Controls:** Fix chapter title overlapping the bottom buttons ([#5673](https://github.com/ReVanced/revanced-patches/issues/5673)) ([09ccee7](https://github.com/ReVanced/revanced-patches/commit/09ccee71384df338bbf8acc1097f619a372c4868)) - -# [5.34.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.11...v5.34.0-dev.12) (2025-08-18) - - -### Bug Fixes - -* **YouTube:** Use correct fade out animation when tapping to dismiss the video overlay ([#5670](https://github.com/ReVanced/revanced-patches/issues/5670)) ([cce6737](https://github.com/ReVanced/revanced-patches/commit/cce6737f627fc7621bbde50a5653b6af14c6f31a)) - -# [5.34.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.10...v5.34.0-dev.11) (2025-08-16) - - -### Bug Fixes - -* **YouTube - SponsorBlock:** Do not hide voting or create button when the video ends ([6aba4e2](https://github.com/ReVanced/revanced-patches/commit/6aba4e284de9bb94b49eea8be2baf2870eecbbcf)) - -# [5.34.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.9...v5.34.0-dev.10) (2025-08-16) - - -### Bug Fixes - -* **YouTube - Video playback:** Disable HDR video does not disable Dolby Vision HDR ([#5661](https://github.com/ReVanced/revanced-patches/issues/5661)) ([6dab988](https://github.com/ReVanced/revanced-patches/commit/6dab98810645b96bd0387ba7d607e5d8ffb1b5bb)) - - -### Features - -* **YouTube - Hide video action buttons:** Add "Hide Promote button" setting ([1959396](https://github.com/ReVanced/revanced-patches/commit/1959396a53f4c07b94acddc5c0ee6cdf7ade7c7b)) - -# [5.34.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.9...v5.34.0-dev.10) (2025-08-16) - - -### Features - -* **YouTube - Hide video action buttons:** Add "Hide Promote button" setting ([1959396](https://github.com/ReVanced/revanced-patches/commit/1959396a53f4c07b94acddc5c0ee6cdf7ade7c7b)) - -# [5.34.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.9...v5.34.0-dev.10) (2025-08-16) - - -### Features - -* **YouTube - Hide video action buttons:** Add "Hide Promote button" setting ([1959396](https://github.com/ReVanced/revanced-patches/commit/1959396a53f4c07b94acddc5c0ee6cdf7ade7c7b)) - -# [5.34.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.8...v5.34.0-dev.9) (2025-08-16) - - -### Features - -* **YouTube - Hide video action buttons:** Add "Hide Hype button" setting ([f13f377](https://github.com/ReVanced/revanced-patches/commit/f13f3770e7c4fd5bff8f3e224fb1b1ead50a3c18)) - -# [5.34.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.7...v5.34.0-dev.8) (2025-08-15) - - -### Features - -* **NU.nl:** Support latest app version ([#5643](https://github.com/ReVanced/revanced-patches/issues/5643)) ([7338e4a](https://github.com/ReVanced/revanced-patches/commit/7338e4a5a99f913256120d0d58fede3aa4ee8922)) -* **YouTube:** Add `Disable sign in to TV popup` patch ([#5639](https://github.com/ReVanced/revanced-patches/issues/5639)) ([d0e5bd0](https://github.com/ReVanced/revanced-patches/commit/d0e5bd0479a8910b081c483ed2a6ab4d7134e3c3)) - -# [5.34.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.6...v5.34.0-dev.7) (2025-08-13) - - -### Bug Fixes - -* **YouTube - Video quality:** Fix additional incorrect quality resolutions used by YouTube ([a2a1fbe](https://github.com/ReVanced/revanced-patches/commit/a2a1fbe2959be8334c54cfc3426c24a960c55c8f)) - -# [5.34.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.5...v5.34.0-dev.6) (2025-08-11) - - -### Bug Fixes - -* **YouTube - Video quality:** Show FHD+ icon for 1080p 60fps enhanced bitrate ([76bed37](https://github.com/ReVanced/revanced-patches/commit/76bed3734093713af24ef065d5ffc5b1cd83f29a)) - -# [5.34.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.4...v5.34.0-dev.5) (2025-08-10) - - -### Features - -* **YouTube - Hide player flyout menu items:** Add option to hide quality flyout menu ([eb55068](https://github.com/ReVanced/revanced-patches/commit/eb5506856a2eaf2a8585e598868ddba3e1429159)) - -# [5.34.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.3...v5.34.0-dev.4) (2025-08-10) - - -### Bug Fixes - -* **YouTube - Hide layout components:** Do not hide community posts on channel profiles ([#5634](https://github.com/ReVanced/revanced-patches/issues/5634)) ([9e3d5a2](https://github.com/ReVanced/revanced-patches/commit/9e3d5a2b36106479470f3f69920518b57e8c4dca)) - -# [5.34.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.2...v5.34.0-dev.3) (2025-08-09) - - -### Bug Fixes - -* **pixiv - Hide ads:** Constrain patch to last working app target ([d8ea56c](https://github.com/ReVanced/revanced-patches/commit/d8ea56ca4be47df1c43f96ec41b91c800f1d9daf)) - -# [5.34.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.1...v5.34.0-dev.2) (2025-08-09) - - -### Bug Fixes - -* **Backdrops:** Remove broken patch that is no longer supported ([#5627](https://github.com/ReVanced/revanced-patches/issues/5627)) ([ebb8332](https://github.com/ReVanced/revanced-patches/commit/ebb83320838aa99dd4417d45a50333dd42c1218a)) - - -### Features - -* **YouTube - Playback speed:** Show current playback speed on player speed dialog button ([#5607](https://github.com/ReVanced/revanced-patches/issues/5607)) ([279436a](https://github.com/ReVanced/revanced-patches/commit/279436a3657b50f98bb4cc64dc88dc14e422f204)) - -# [5.34.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.33.0...v5.34.0-dev.1) (2025-08-08) - - -### Bug Fixes - -* **Twitch:** Constrain patches to last working app targets ([#5373](https://github.com/ReVanced/revanced-patches/issues/5373)) ([29a4748](https://github.com/ReVanced/revanced-patches/commit/29a47481c4efa209a3a53df60613b59a73adbe07)) - - -### Features - -* **Instagram:** Support latest app version ([#5611](https://github.com/ReVanced/revanced-patches/issues/5611)) ([26fe690](https://github.com/ReVanced/revanced-patches/commit/26fe690dfbefe6c412c5f81f208a3b1d2fbd7a0a)) - -# [5.33.0](https://github.com/ReVanced/revanced-patches/compare/v5.32.0...v5.33.0) (2025-08-05) - - -### Bug Fixes - -* **Messenger - Hide Facebook button:** Support the latest app version ([#5590](https://github.com/ReVanced/revanced-patches/issues/5590)) ([0cab98d](https://github.com/ReVanced/revanced-patches/commit/0cab98df1689dbf7a042f18f4a961d47da1430ad)) -* **NFC Tools:** Remove broken patch that is no longer supported ([#5584](https://github.com/ReVanced/revanced-patches/issues/5584)) ([cd3a6be](https://github.com/ReVanced/revanced-patches/commit/cd3a6be75c6bd3cc33c0b17a044bd6147f27b5ce)) -* **YouTube - Force original audio:** Disable a/b feature flag that forces localized audio ([#5582](https://github.com/ReVanced/revanced-patches/issues/5582)) ([9fe13ee](https://github.com/ReVanced/revanced-patches/commit/9fe13ee1af104c009efd19b826adef375e48e191)) -* **YouTube - Litho filter:** Correctly filter identifier of older YouTube targets ([bf29d69](https://github.com/ReVanced/revanced-patches/commit/bf29d6909e389819bad878ad3b94bbc90d823cc9)) -* **YouTube - Playback speed:** Use old speed menu for player button if enabled ([1e8f436](https://github.com/ReVanced/revanced-patches/commit/1e8f4368e117f4b278c24709231cb32546e46dc0)) -* **YouTube - Video quality:** Fix 144p default not always used ([2f7483a](https://github.com/ReVanced/revanced-patches/commit/2f7483a2d789c28a243b58bb7a252c0d590858ee)) -* **YouTube - Video quality:** Fix dialog quality list check mark not always shown ([295f0f2](https://github.com/ReVanced/revanced-patches/commit/295f0f216b5e8aa9d68457862e73e312b7342703)) -* **YouTube - Video quality:** Fix wrong qualities sometimes shown in player button dialog ([7378ae3](https://github.com/ReVanced/revanced-patches/commit/7378ae3c5fc88f91bf5cd6db47c6cd170a8c5a4f)) -* **YouTube - Video quality:** Use 1080p enhanced bitrate for Premium users ([#5565](https://github.com/ReVanced/revanced-patches/issues/5565)) ([bd3ace0](https://github.com/ReVanced/revanced-patches/commit/bd3ace0bd04ccd0369adb49d63aa0cf986402346)) - - -### Features - -* **ORF ON:** Add `Remove root detection` patch ([#5551](https://github.com/ReVanced/revanced-patches/issues/5551)) ([6c6aa35](https://github.com/ReVanced/revanced-patches/commit/6c6aa35411a139dddc3a15dd757fbeded5d1a0a3)) -* **YouTube - Playback speed:** Add "Restore old playback speed menu" option ([#5552](https://github.com/ReVanced/revanced-patches/issues/5552)) ([b01f15b](https://github.com/ReVanced/revanced-patches/commit/b01f15b9acb0427aed99b0141ae271831b7936bf)) -* **YouTube:** Add player button to change video quality ([#5435](https://github.com/ReVanced/revanced-patches/issues/5435)) ([d5f51bf](https://github.com/ReVanced/revanced-patches/commit/d5f51bf400dd22626ff65d7563b6fde70d53fb25)) - - -### Performance Improvements - -* **YouTube:** Filter identifier callback only on root component creation ([#5558](https://github.com/ReVanced/revanced-patches/issues/5558)) ([ccac46e](https://github.com/ReVanced/revanced-patches/commit/ccac46eebc2e14b094454e37ef4461d48a62c53f)) - -# [5.33.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.12...v5.33.0-dev.13) (2025-08-05) - - -### Bug Fixes - -* **Messenger - Hide Facebook button:** Support the latest app version ([#5590](https://github.com/ReVanced/revanced-patches/issues/5590)) ([0cab98d](https://github.com/ReVanced/revanced-patches/commit/0cab98df1689dbf7a042f18f4a961d47da1430ad)) - -# [5.33.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.11...v5.33.0-dev.12) (2025-08-04) - - -### Bug Fixes - -* **YouTube - Video quality:** Fix dialog quality list check mark not always shown ([295f0f2](https://github.com/ReVanced/revanced-patches/commit/295f0f216b5e8aa9d68457862e73e312b7342703)) - -# [5.33.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.10...v5.33.0-dev.11) (2025-08-04) - - -### Bug Fixes - -* **YouTube - Video quality:** Fix 144p default not always used ([2f7483a](https://github.com/ReVanced/revanced-patches/commit/2f7483a2d789c28a243b58bb7a252c0d590858ee)) - -# [5.33.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.9...v5.33.0-dev.10) (2025-08-04) - - -### Bug Fixes - -* **YouTube - Video quality:** Fix wrong qualities sometimes shown in player button dialog ([7378ae3](https://github.com/ReVanced/revanced-patches/commit/7378ae3c5fc88f91bf5cd6db47c6cd170a8c5a4f)) - -# [5.33.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.8...v5.33.0-dev.9) (2025-08-04) - - -### Bug Fixes - -* **YouTube - Force original audio:** Disable a/b feature flag that forces localized audio ([#5582](https://github.com/ReVanced/revanced-patches/issues/5582)) ([9fe13ee](https://github.com/ReVanced/revanced-patches/commit/9fe13ee1af104c009efd19b826adef375e48e191)) - -# [5.33.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.7...v5.33.0-dev.8) (2025-08-03) - - -### Bug Fixes - -* **NFC Tools:** Remove broken patch that is no longer supported ([#5584](https://github.com/ReVanced/revanced-patches/issues/5584)) ([cd3a6be](https://github.com/ReVanced/revanced-patches/commit/cd3a6be75c6bd3cc33c0b17a044bd6147f27b5ce)) - -# [5.33.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.6...v5.33.0-dev.7) (2025-08-03) - - -### Features - -* **YouTube:** Add player button to change video quality ([#5435](https://github.com/ReVanced/revanced-patches/issues/5435)) ([d5f51bf](https://github.com/ReVanced/revanced-patches/commit/d5f51bf400dd22626ff65d7563b6fde70d53fb25)) - -# [5.33.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.5...v5.33.0-dev.6) (2025-07-31) - - -### Bug Fixes - -* **YouTube - Video quality:** Use 1080p enhanced bitrate for Premium users ([#5565](https://github.com/ReVanced/revanced-patches/issues/5565)) ([bd3ace0](https://github.com/ReVanced/revanced-patches/commit/bd3ace0bd04ccd0369adb49d63aa0cf986402346)) - -# [5.33.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.4...v5.33.0-dev.5) (2025-07-31) - - -### Bug Fixes - -* **YouTube - Litho filter:** Correctly filter identifier of older YouTube targets ([bf29d69](https://github.com/ReVanced/revanced-patches/commit/bf29d6909e389819bad878ad3b94bbc90d823cc9)) - -# [5.33.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.3...v5.33.0-dev.4) (2025-07-30) - - -### Performance Improvements - -* **YouTube:** Filter identifier callback only on root component creation ([#5558](https://github.com/ReVanced/revanced-patches/issues/5558)) ([ccac46e](https://github.com/ReVanced/revanced-patches/commit/ccac46eebc2e14b094454e37ef4461d48a62c53f)) - -# [5.33.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.2...v5.33.0-dev.3) (2025-07-30) - - -### Bug Fixes - -* **YouTube - Playback speed:** Use old speed menu for player button if enabled ([1e8f436](https://github.com/ReVanced/revanced-patches/commit/1e8f4368e117f4b278c24709231cb32546e46dc0)) - -# [5.33.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.1...v5.33.0-dev.2) (2025-07-29) - - -### Features - -* **ORF ON:** Add `Remove root detection` patch ([#5551](https://github.com/ReVanced/revanced-patches/issues/5551)) ([6c6aa35](https://github.com/ReVanced/revanced-patches/commit/6c6aa35411a139dddc3a15dd757fbeded5d1a0a3)) - -# [5.33.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.32.0...v5.33.0-dev.1) (2025-07-28) - - -### Features - -* **YouTube - Playback speed:** Add "Restore old playback speed menu" option ([#5552](https://github.com/ReVanced/revanced-patches/issues/5552)) ([b01f15b](https://github.com/ReVanced/revanced-patches/commit/b01f15b9acb0427aed99b0141ae271831b7936bf)) - -# [5.32.0](https://github.com/ReVanced/revanced-patches/compare/v5.31.2...v5.32.0) (2025-07-27) - - -### Bug Fixes - -* **Messenger - Hide inbox ads:** Support the latest app version ([2959c02](https://github.com/ReVanced/revanced-patches/commit/2959c0214dfa703ee623ef1f89bded7f78c9d252)) -* **YouTube - Hide layout components:** Fix "Hide ticket shelf" ([#5516](https://github.com/ReVanced/revanced-patches/issues/5516)) ([3b85c71](https://github.com/ReVanced/revanced-patches/commit/3b85c71433325fff49e01c77c7b9ff8ddd0a7068)) -* **YouTube - GmsCore support:** Fix search suggestions when logged out by using correct search provider ([#5483](https://github.com/ReVanced/revanced-patches/issues/5483)) ([e86fdc8](https://github.com/ReVanced/revanced-patches/commit/e86fdc86b161a6077960b85149e83bacbac664e7)) - - -### Features - -* **Prime Video:** Add `Playback speed` patch ([#5444](https://github.com/ReVanced/revanced-patches/issues/5444)) ([22cf313](https://github.com/ReVanced/revanced-patches/commit/22cf313a7b99b69e17b9d488c514802043a5dc10)) -* **YouTube - External downloads:** Improve the selection of the external downloader package ([#5504](https://github.com/ReVanced/revanced-patches/issues/5504)) ([5de9aa9](https://github.com/ReVanced/revanced-patches/commit/5de9aa9fad4f24186da045fb188f8718d6f63d7a)) -* **YT Music:** Support latest versions ([#5524](https://github.com/ReVanced/revanced-patches/issues/5524)) ([551dcf0](https://github.com/ReVanced/revanced-patches/commit/551dcf01ca9c489a779196b49c8744727d79d6bc)) - -# [5.32.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.32.0-dev.4...v5.32.0-dev.5) (2025-07-26) - - -### Features - -* **YT Music:** Support latest versions ([#5524](https://github.com/ReVanced/revanced-patches/issues/5524)) ([551dcf0](https://github.com/ReVanced/revanced-patches/commit/551dcf01ca9c489a779196b49c8744727d79d6bc)) - -# [5.32.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.32.0-dev.3...v5.32.0-dev.4) (2025-07-25) - - -### Bug Fixes - -* **Messenger - Hide inbox ads:** Support the latest app version ([2959c02](https://github.com/ReVanced/revanced-patches/commit/2959c0214dfa703ee623ef1f89bded7f78c9d252)) - -# [5.32.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.32.0-dev.2...v5.32.0-dev.3) (2025-07-24) - - -### Features - -* **YouTube - External downloads:** Improve the selection of the external downloader package ([#5504](https://github.com/ReVanced/revanced-patches/issues/5504)) ([5de9aa9](https://github.com/ReVanced/revanced-patches/commit/5de9aa9fad4f24186da045fb188f8718d6f63d7a)) - -# [5.32.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.32.0-dev.1...v5.32.0-dev.2) (2025-07-23) - - -### Bug Fixes - -* **YouTube - Hide layout components:** Fix "Hide ticket shelf" ([#5516](https://github.com/ReVanced/revanced-patches/issues/5516)) ([3b85c71](https://github.com/ReVanced/revanced-patches/commit/3b85c71433325fff49e01c77c7b9ff8ddd0a7068)) - -# [5.32.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.31.3-dev.1...v5.32.0-dev.1) (2025-07-16) - - -### Features - -* **Prime Video:** Add `Playback speed` patch ([#5444](https://github.com/ReVanced/revanced-patches/issues/5444)) ([22cf313](https://github.com/ReVanced/revanced-patches/commit/22cf313a7b99b69e17b9d488c514802043a5dc10)) - -## [5.31.3-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.31.2...v5.31.3-dev.1) (2025-07-16) - - -### Bug Fixes - -* **YouTube - GmsCore support:** Fix search suggestions when logged out by using correct search provider ([#5483](https://github.com/ReVanced/revanced-patches/issues/5483)) ([e86fdc8](https://github.com/ReVanced/revanced-patches/commit/e86fdc86b161a6077960b85149e83bacbac664e7)) - -## [5.31.2](https://github.com/ReVanced/revanced-patches/compare/v5.31.1...v5.31.2) (2025-07-14) - - -### Bug Fixes - -* **Spotify - Spoof client:** Fix login failing by spoofing login request in addition ([#5448](https://github.com/ReVanced/revanced-patches/issues/5448)) ([4e59ddc](https://github.com/ReVanced/revanced-patches/commit/4e59ddc62388d09f71b89593fc8b76933d9facea)) -* **YouTube - Disable double tap actions:** Remove old incompatible targets ([857053e](https://github.com/ReVanced/revanced-patches/commit/857053e29b72ded10a84b0ac693fa107705342d9)) -* **YouTube - Hide layout components:** Hide quick actions does not work ([#5423](https://github.com/ReVanced/revanced-patches/issues/5423)) ([9c66729](https://github.com/ReVanced/revanced-patches/commit/9c6672946d44001e106bdac9041e2d79ef3f6ab2)) -* **YouTube - Hide layout components:** Show correct custom header logo if 'Hide YouTube Doodles' is enabled ([#5431](https://github.com/ReVanced/revanced-patches/issues/5431)) ([20cc141](https://github.com/ReVanced/revanced-patches/commit/20cc141e61f75de1a1749247c4f4aed167dee8ea)) -* **YouTube - Settings:** Back button/gesture closes search instead of exiting ([#5418](https://github.com/ReVanced/revanced-patches/issues/5418)) ([134b278](https://github.com/ReVanced/revanced-patches/commit/134b278baa7b90d2c4b06200cabacabf55ebc055)) - -## [5.31.2-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.31.2-dev.4...v5.31.2-dev.5) (2025-07-14) - - -### Bug Fixes - -* **Spotify - Spoof client:** Fix login failing by spoofing login request in addition ([#5448](https://github.com/ReVanced/revanced-patches/issues/5448)) ([4e59ddc](https://github.com/ReVanced/revanced-patches/commit/4e59ddc62388d09f71b89593fc8b76933d9facea)) - -## [5.31.2-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.31.2-dev.3...v5.31.2-dev.4) (2025-07-13) - - -### Bug Fixes - -* **YouTube - Settings:** Back button/gesture closes search instead of exiting ([#5418](https://github.com/ReVanced/revanced-patches/issues/5418)) ([134b278](https://github.com/ReVanced/revanced-patches/commit/134b278baa7b90d2c4b06200cabacabf55ebc055)) - -## [5.31.2-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.31.2-dev.2...v5.31.2-dev.3) (2025-07-13) - - -### Bug Fixes - -* **YouTube - Disable double tap actions:** Remove old incompatible targets ([857053e](https://github.com/ReVanced/revanced-patches/commit/857053e29b72ded10a84b0ac693fa107705342d9)) - -## [5.31.2-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.31.2-dev.1...v5.31.2-dev.2) (2025-07-12) - - -### Bug Fixes - -* **YouTube - Hide layout components:** Show correct custom header logo if 'Hide YouTube Doodles' is enabled ([#5431](https://github.com/ReVanced/revanced-patches/issues/5431)) ([20cc141](https://github.com/ReVanced/revanced-patches/commit/20cc141e61f75de1a1749247c4f4aed167dee8ea)) - -## [5.31.2-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.31.1...v5.31.2-dev.1) (2025-07-12) - - -### Bug Fixes - -* **YouTube - Hide layout components:** Hide quick actions does not work ([#5423](https://github.com/ReVanced/revanced-patches/issues/5423)) ([9c66729](https://github.com/ReVanced/revanced-patches/commit/9c6672946d44001e106bdac9041e2d79ef3f6ab2)) - -## [5.31.1](https://github.com/ReVanced/revanced-patches/compare/v5.31.0...v5.31.1) (2025-07-11) - - -### Bug Fixes - -* **Spotify - Unlock Premium:** Fix hiding context menu ads for latest version ([#5415](https://github.com/ReVanced/revanced-patches/issues/5415)) ([dcde393](https://github.com/ReVanced/revanced-patches/commit/dcde3935bde3172576d0f9f5ff9eb62ecfff7dfe)) - -## [5.31.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.31.0...v5.31.1-dev.1) (2025-07-11) - - -### Bug Fixes - -* **Spotify - Unlock Premium:** Fix hiding context menu ads for latest version ([#5415](https://github.com/ReVanced/revanced-patches/issues/5415)) ([dcde393](https://github.com/ReVanced/revanced-patches/commit/dcde3935bde3172576d0f9f5ff9eb62ecfff7dfe)) - -# [5.31.0](https://github.com/ReVanced/revanced-patches/compare/v5.30.0...v5.31.0) (2025-07-11) - - -### Bug Fixes - -* **Bacon Reader - Spoof client:** Use www instead of ssl API to fix auth related issues ([#5402](https://github.com/ReVanced/revanced-patches/issues/5402)) ([72459bb](https://github.com/ReVanced/revanced-patches/commit/72459bb2eaf4691e32822dfdd1db3240e2fe98dd)) -* Correctly name `Enable ROM signature spoofing` patch ([d85881a](https://github.com/ReVanced/revanced-patches/commit/d85881a6768232a999534677bebb248e640fe5ab)) -* Fix accidental changes ([e2ac841](https://github.com/ReVanced/revanced-patches/commit/e2ac8419756e3c7d62e2c0430a2918a3c1c63666)) -* Fix refactoring typo ([ec0ae42](https://github.com/ReVanced/revanced-patches/commit/ec0ae42496628cdeb2a639020fce94316b41b751)) -* Handle empty list of announcements ([de9d720](https://github.com/ReVanced/revanced-patches/commit/de9d7209f4e818a618a7fd9000013ae8ebd728f2)) -* **SoundCloud:** Constrain patches to last working app target ([e8ea89f](https://github.com/ReVanced/revanced-patches/commit/e8ea89fc1a3f0531a0af7529663f13328aca4fe7)) -* **Spotify - Unlock Premium:** Remove wrongfully hidden non ad browse sections ([#5403](https://github.com/ReVanced/revanced-patches/issues/5403)) ([8633544](https://github.com/ReVanced/revanced-patches/commit/8633544decc0814d7a548fbc5576b4bdd1d7eee0)) -* **Spotify:** Remove other ads type from the browse screen ([#5333](https://github.com/ReVanced/revanced-patches/issues/5333)) ([c68533a](https://github.com/ReVanced/revanced-patches/commit/c68533a33a399ca813380b5c9ccddce434ceadf8)) -* **Sync for Reddit - Spoof client:** Use www instead of ssl API to fix auth related issues ([#5392](https://github.com/ReVanced/revanced-patches/issues/5392)) ([47e6b62](https://github.com/ReVanced/revanced-patches/commit/47e6b62f3d8b07960cfb2963f441222d3e67df92)) -* **YouTube - Hide ads:** Hide new type of general ad ([#5345](https://github.com/ReVanced/revanced-patches/issues/5345)) ([f23716b](https://github.com/ReVanced/revanced-patches/commit/f23716bc52c03d8d0271bfe38b19247e6de7021d)) -* **YouTube - Hide layout components:** Do not hide playlist sort button if 'Hide AI comments summary' is on ([5f3e48e](https://github.com/ReVanced/revanced-patches/commit/5f3e48ec5853f6439800ef58239291c34bcab5f6)) -* **YouTube - Playback speed:** Allow custom speeds with 0.01x precision ([#5360](https://github.com/ReVanced/revanced-patches/issues/5360)) ([0eecef0](https://github.com/ReVanced/revanced-patches/commit/0eecef00fc93d2a217944978e29dce82e3134e35)) -* **YouTube - Slide to seek:** Show tap and hold 2x speed overlay when active ([#5398](https://github.com/ReVanced/revanced-patches/issues/5398)) ([dbc9c5f](https://github.com/ReVanced/revanced-patches/commit/dbc9c5f00c1f5bbb95f8822667cc1ac3c613fa00)) - - -### Features - -* **Cricbuzz - Hide ads:** Hide Cricbuzz11 UI elements ([#5381](https://github.com/ReVanced/revanced-patches/issues/5381)) ([a42c98f](https://github.com/ReVanced/revanced-patches/commit/a42c98f8b51fd37d815fd38b75a2b7ccc4fb049b)) -* **Lightroom:** Constrain patches to last working version ([#5335](https://github.com/ReVanced/revanced-patches/issues/5335)) ([32ce70e](https://github.com/ReVanced/revanced-patches/commit/32ce70e994f354b9a569376bb89eb38b3190e6f9)) -* **Spotify - Spoof client:** Fix issues like songs skipping by spoofing to iOS ([#5388](https://github.com/ReVanced/revanced-patches/issues/5388)) ([e36d4c1](https://github.com/ReVanced/revanced-patches/commit/e36d4c1986b58815c7659e6ef44011166873f9c8)) -* **Spotify:** Remove support for old versions ([#5404](https://github.com/ReVanced/revanced-patches/issues/5404)) ([9d31238](https://github.com/ReVanced/revanced-patches/commit/9d31238803a45e957472760fc40c3862da2cf3f0)) -* **YouTube - Change header:** Add in-app setting to change the app header ([#5346](https://github.com/ReVanced/revanced-patches/issues/5346)) ([9ba45b6](https://github.com/ReVanced/revanced-patches/commit/9ba45b6680595d732b47e8fa54bee98b7c7af179)) -* **YouTube - Hide layout components:** Add `Hide channel links preview` and `Hide 'Visit Community' button` in channel page ([#5320](https://github.com/ReVanced/revanced-patches/issues/5320)) ([9d9cce3](https://github.com/ReVanced/revanced-patches/commit/9d9cce3ec5550b2fea88df745f1700bb2f17eb9e)) -* **YouTube:** Disable two-finger tap gesture for skipping chapters ([#5374](https://github.com/ReVanced/revanced-patches/issues/5374)) ([71db0a2](https://github.com/ReVanced/revanced-patches/commit/71db0a2661b5f76eb5048cdeed83f26fbfdf4fee)) - -# [5.31.0-dev.17](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.16...v5.31.0-dev.17) (2025-07-11) - - -### Bug Fixes - -* **Spotify - Unlock Premium:** Remove wrongfully hidden non ad browse sections ([#5403](https://github.com/ReVanced/revanced-patches/issues/5403)) ([8633544](https://github.com/ReVanced/revanced-patches/commit/8633544decc0814d7a548fbc5576b4bdd1d7eee0)) - - -### Features - -* **Spotify:** Remove support for old versions ([#5404](https://github.com/ReVanced/revanced-patches/issues/5404)) ([9d31238](https://github.com/ReVanced/revanced-patches/commit/9d31238803a45e957472760fc40c3862da2cf3f0)) - -# [5.31.0-dev.16](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.15...v5.31.0-dev.16) (2025-07-11) - - -### Features - -* **Spotify - Spoof client:** Fix issues like songs skipping by spoofing to iOS ([#5388](https://github.com/ReVanced/revanced-patches/issues/5388)) ([e36d4c1](https://github.com/ReVanced/revanced-patches/commit/e36d4c1986b58815c7659e6ef44011166873f9c8)) -* **YouTube:** Disable two-finger tap gesture for skipping chapters ([#5374](https://github.com/ReVanced/revanced-patches/issues/5374)) ([71db0a2](https://github.com/ReVanced/revanced-patches/commit/71db0a2661b5f76eb5048cdeed83f26fbfdf4fee)) - -# [5.31.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.14...v5.31.0-dev.15) (2025-07-11) - - -### Bug Fixes - -* Handle empty list of announcements ([de9d720](https://github.com/ReVanced/revanced-patches/commit/de9d7209f4e818a618a7fd9000013ae8ebd728f2)) - -# [5.31.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.13...v5.31.0-dev.14) (2025-07-10) - - -### Bug Fixes - -* **Bacon Reader - Spoof client:** Use www instead of ssl API to fix auth related issues ([#5402](https://github.com/ReVanced/revanced-patches/issues/5402)) ([72459bb](https://github.com/ReVanced/revanced-patches/commit/72459bb2eaf4691e32822dfdd1db3240e2fe98dd)) - -# [5.31.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.12...v5.31.0-dev.13) (2025-07-10) - - -### Bug Fixes - -* **YouTube - Slide to seek:** Show tap and hold 2x speed overlay when active ([#5398](https://github.com/ReVanced/revanced-patches/issues/5398)) ([dbc9c5f](https://github.com/ReVanced/revanced-patches/commit/dbc9c5f00c1f5bbb95f8822667cc1ac3c613fa00)) - -# [5.31.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.11...v5.31.0-dev.12) (2025-07-09) - - -### Bug Fixes - -* **Sync for Reddit - Spoof client:** Use www instead of ssl API to fix auth related issues ([#5392](https://github.com/ReVanced/revanced-patches/issues/5392)) ([47e6b62](https://github.com/ReVanced/revanced-patches/commit/47e6b62f3d8b07960cfb2963f441222d3e67df92)) - -# [5.31.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.10...v5.31.0-dev.11) (2025-07-09) - - -### Features - -* **Cricbuzz - Hide ads:** Hide Cricbuzz11 UI elements ([#5381](https://github.com/ReVanced/revanced-patches/issues/5381)) ([a42c98f](https://github.com/ReVanced/revanced-patches/commit/a42c98f8b51fd37d815fd38b75a2b7ccc4fb049b)) - -# [5.31.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.9...v5.31.0-dev.10) (2025-07-09) - - -### Bug Fixes - -* **YouTube - Hide layout components:** Do not hide playlist sort button if 'Hide AI comments summary' is on ([5f3e48e](https://github.com/ReVanced/revanced-patches/commit/5f3e48ec5853f6439800ef58239291c34bcab5f6)) - -# [5.31.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.8...v5.31.0-dev.9) (2025-07-07) - - -### Bug Fixes - -* Fix accidental changes ([e2ac841](https://github.com/ReVanced/revanced-patches/commit/e2ac8419756e3c7d62e2c0430a2918a3c1c63666)) - -# [5.31.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.7...v5.31.0-dev.8) (2025-07-07) - - -### Bug Fixes - -* Correctly name `Enable ROM signature spoofing` patch ([d85881a](https://github.com/ReVanced/revanced-patches/commit/d85881a6768232a999534677bebb248e640fe5ab)) - -# [5.31.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.6...v5.31.0-dev.7) (2025-07-06) - - -### Bug Fixes - -* Fix refactoring typo ([ec0ae42](https://github.com/ReVanced/revanced-patches/commit/ec0ae42496628cdeb2a639020fce94316b41b751)) - -# [5.31.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.5...v5.31.0-dev.6) (2025-07-06) - - -### Bug Fixes - -* **YouTube - Playback speed:** Allow custom speeds with 0.01x precision ([#5360](https://github.com/ReVanced/revanced-patches/issues/5360)) ([0eecef0](https://github.com/ReVanced/revanced-patches/commit/0eecef00fc93d2a217944978e29dce82e3134e35)) - -# [5.31.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.4...v5.31.0-dev.5) (2025-07-05) - - -### Features - -* **YouTube - Change header:** Add in-app setting to change the app header ([#5346](https://github.com/ReVanced/revanced-patches/issues/5346)) ([9ba45b6](https://github.com/ReVanced/revanced-patches/commit/9ba45b6680595d732b47e8fa54bee98b7c7af179)) - -# [5.31.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.3...v5.31.0-dev.4) (2025-07-04) - - -### Bug Fixes - -* **YouTube - Hide ads:** Hide new type of general ad ([#5345](https://github.com/ReVanced/revanced-patches/issues/5345)) ([f23716b](https://github.com/ReVanced/revanced-patches/commit/f23716bc52c03d8d0271bfe38b19247e6de7021d)) - -# [5.31.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.2...v5.31.0-dev.3) (2025-07-04) - - -### Bug Fixes - -* **Spotify:** Remove other ads type from the browse screen ([#5333](https://github.com/ReVanced/revanced-patches/issues/5333)) ([c68533a](https://github.com/ReVanced/revanced-patches/commit/c68533a33a399ca813380b5c9ccddce434ceadf8)) - -# [5.31.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.1...v5.31.0-dev.2) (2025-07-04) - - -### Features - -* **YouTube - Hide layout components:** Add `Hide channel links preview` and `Hide 'Visit Community' button` in channel page ([#5320](https://github.com/ReVanced/revanced-patches/issues/5320)) ([9d9cce3](https://github.com/ReVanced/revanced-patches/commit/9d9cce3ec5550b2fea88df745f1700bb2f17eb9e)) - -# [5.31.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.30.0...v5.31.0-dev.1) (2025-07-04) - - -### Bug Fixes - -* **SoundCloud:** Constrain patches to last working app target ([e8ea89f](https://github.com/ReVanced/revanced-patches/commit/e8ea89fc1a3f0531a0af7529663f13328aca4fe7)) - - -### Features - -* **Lightroom:** Constrain patches to last working version ([#5335](https://github.com/ReVanced/revanced-patches/issues/5335)) ([32ce70e](https://github.com/ReVanced/revanced-patches/commit/32ce70e994f354b9a569376bb89eb38b3190e6f9)) - -# [5.30.0](https://github.com/ReVanced/revanced-patches/compare/v5.29.0...v5.30.0) (2025-07-02) - - -### Bug Fixes - -* **Spotify - Spoof client patch:** Block sending bad integrity verdicts to potentially fix account suspensions ([#5274](https://github.com/ReVanced/revanced-patches/issues/5274)) ([f7b574c](https://github.com/ReVanced/revanced-patches/commit/f7b574ca79c5a616cfe33a3fc75bd8cf68571f7d)) -* **Spotify - Spoof client:** Handle remaining edge cases to obtain a session ([#5285](https://github.com/ReVanced/revanced-patches/issues/5285)) ([2bb2d59](https://github.com/ReVanced/revanced-patches/commit/2bb2d594936093774e232ad8b274c81e805c5bf6)) -* **Spotify - Spoof client:** Skip native login screens ([#5228](https://github.com/ReVanced/revanced-patches/issues/5228)) ([c5ebc63](https://github.com/ReVanced/revanced-patches/commit/c5ebc6336ed17cc9cc7f1348282a2aa3c173fb95)) -* **Spotify - Unlock Premium:** Fix hiding context menu ads on newest versions ([#5318](https://github.com/ReVanced/revanced-patches/issues/5318)) ([73fd832](https://github.com/ReVanced/revanced-patches/commit/73fd83222e089a5fd6e1526e5c12f5a1e9893a35)) -* **Spotify - Unlock Premium:** Fix hiding context menu ads on newest versions by simplifying fingerprint ([#5318](https://github.com/ReVanced/revanced-patches/issues/5318)) ([dad0ff4](https://github.com/ReVanced/revanced-patches/commit/dad0ff4fba74c2b020fbde6c6d5eb66e10e6f1f7)) -* **Spotify:** Add `Spoof client` patch to fix various issues by using a web platform access token ([#5173](https://github.com/ReVanced/revanced-patches/issues/5173)) ([b7b75bb](https://github.com/ReVanced/revanced-patches/commit/b7b75bb9d8d5fd505121e752b8a20e61ff28d1b2)) -* **YouTube - Hide ads:** Fix "Hide shopping links" ([#5267](https://github.com/ReVanced/revanced-patches/issues/5267)) ([2fe4607](https://github.com/ReVanced/revanced-patches/commit/2fe46079d78ab98076d3a4cdf01c8bfdbdea45c0)) -* **YouTube - Hide layout components:** Fix "Hide AI Comments summary" in Comments ([#5284](https://github.com/ReVanced/revanced-patches/issues/5284)) ([d42370e](https://github.com/ReVanced/revanced-patches/commit/d42370ef71f4608abc64b6ef4a3fb0c5bd5e3eb6)) -* **YouTube - Hide layout components:** Fix "Hide AI-generated video summary" in video description ([#5269](https://github.com/ReVanced/revanced-patches/issues/5269)) ([5203da0](https://github.com/ReVanced/revanced-patches/commit/5203da0ae58e467657bc915ab0af5b9904c4f492)) -* **YouTube - Hide layout components:** Fix "Hide ticket shelf" hiding unwanted components ([#5292](https://github.com/ReVanced/revanced-patches/issues/5292)) ([d6b1f7a](https://github.com/ReVanced/revanced-patches/commit/d6b1f7a6e18b1c0eb4374c5e22a1c746dcb3a522)) -* **YouTube - Hide Shorts components:** Fix hiding of untoggled components ([#5266](https://github.com/ReVanced/revanced-patches/issues/5266)) ([008e192](https://github.com/ReVanced/revanced-patches/commit/008e192779a8658e894d5718baa732717bf96e40)) -* **YouTube - SponsorBlock:** Do not show undo skip if PiP is active ([#5314](https://github.com/ReVanced/revanced-patches/issues/5314)) ([18af8de](https://github.com/ReVanced/revanced-patches/commit/18af8dead2c6c7f0d99cd75b69948240e0bcd12c)) - - -### Features - -* **Spotify:** Remove ads section from browse ([#5193](https://github.com/ReVanced/revanced-patches/issues/5193)) ([ebd4dcc](https://github.com/ReVanced/revanced-patches/commit/ebd4dccf12a5fbd31d2d53c19a792c389a4641d7)) -* **YouTube - Hide layout components:** Add `Hide in history` option to filter bar ([#5271](https://github.com/ReVanced/revanced-patches/issues/5271)) ([ba242a3](https://github.com/ReVanced/revanced-patches/commit/ba242a36b040b82e84870e5e240734637125a472)) -* **YouTube - SponsorBlock:** Add "Undo automatic skip toast" ([#5277](https://github.com/ReVanced/revanced-patches/issues/5277)) ([7fa169a](https://github.com/ReVanced/revanced-patches/commit/7fa169ae262c880019c5a069a2d6bdc7f94885f1)) - -# [5.30.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.9...v5.30.0-dev.10) (2025-07-02) - - -### Bug Fixes - -* **Spotify - Unlock Premium:** Fix hiding context menu ads on newest versions by simplifying fingerprint ([#5318](https://github.com/ReVanced/revanced-patches/issues/5318)) ([dad0ff4](https://github.com/ReVanced/revanced-patches/commit/dad0ff4fba74c2b020fbde6c6d5eb66e10e6f1f7)) - -# [5.30.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.8...v5.30.0-dev.9) (2025-07-02) - - -### Bug Fixes - -* **Spotify - Unlock Premium:** Fix hiding context menu ads on newest versions ([#5318](https://github.com/ReVanced/revanced-patches/issues/5318)) ([73fd832](https://github.com/ReVanced/revanced-patches/commit/73fd83222e089a5fd6e1526e5c12f5a1e9893a35)) - -# [5.30.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.7...v5.30.0-dev.8) (2025-07-02) - - -### Bug Fixes - -* **Spotify - Spoof client:** Skip native login screens ([#5228](https://github.com/ReVanced/revanced-patches/issues/5228)) ([c5ebc63](https://github.com/ReVanced/revanced-patches/commit/c5ebc6336ed17cc9cc7f1348282a2aa3c173fb95)) - -# [5.30.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.6...v5.30.0-dev.7) (2025-07-01) - - -### Bug Fixes - -* **Spotify - Spoof client:** Handle remaining edge cases to obtain a session ([#5285](https://github.com/ReVanced/revanced-patches/issues/5285)) ([2bb2d59](https://github.com/ReVanced/revanced-patches/commit/2bb2d594936093774e232ad8b274c81e805c5bf6)) - -# [5.30.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.5...v5.30.0-dev.6) (2025-07-01) - - -### Bug Fixes - -* **YouTube - SponsorBlock:** Do not show undo skip if PiP is active ([#5314](https://github.com/ReVanced/revanced-patches/issues/5314)) ([18af8de](https://github.com/ReVanced/revanced-patches/commit/18af8dead2c6c7f0d99cd75b69948240e0bcd12c)) - -# [5.30.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.4...v5.30.0-dev.5) (2025-06-30) - - -### Bug Fixes - -* **YouTube - Hide layout components:** Fix "Hide ticket shelf" hiding unwanted components ([#5292](https://github.com/ReVanced/revanced-patches/issues/5292)) ([d6b1f7a](https://github.com/ReVanced/revanced-patches/commit/d6b1f7a6e18b1c0eb4374c5e22a1c746dcb3a522)) - -# [5.30.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.3...v5.30.0-dev.4) (2025-06-30) - - -### Features - -* **YouTube - SponsorBlock:** Add "Undo automatic skip toast" ([#5277](https://github.com/ReVanced/revanced-patches/issues/5277)) ([7fa169a](https://github.com/ReVanced/revanced-patches/commit/7fa169ae262c880019c5a069a2d6bdc7f94885f1)) - -# [5.30.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.2...v5.30.0-dev.3) (2025-06-28) - - -### Bug Fixes - -* **YouTube - Hide layout components:** Fix "Hide AI Comments summary" in Comments ([#5284](https://github.com/ReVanced/revanced-patches/issues/5284)) ([d42370e](https://github.com/ReVanced/revanced-patches/commit/d42370ef71f4608abc64b6ef4a3fb0c5bd5e3eb6)) - -# [5.30.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.1...v5.30.0-dev.2) (2025-06-27) - - -### Bug Fixes - -* **Spotify - Spoof client patch:** Block sending bad integrity verdicts to potentially fix account suspensions ([#5274](https://github.com/ReVanced/revanced-patches/issues/5274)) ([f7b574c](https://github.com/ReVanced/revanced-patches/commit/f7b574ca79c5a616cfe33a3fc75bd8cf68571f7d)) - -# [5.30.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.29.1-dev.1...v5.30.0-dev.1) (2025-06-27) - - -### Bug Fixes - -* **YouTube - Hide ads:** Fix "Hide shopping links" ([#5267](https://github.com/ReVanced/revanced-patches/issues/5267)) ([2fe4607](https://github.com/ReVanced/revanced-patches/commit/2fe46079d78ab98076d3a4cdf01c8bfdbdea45c0)) -* **YouTube - Hide layout components:** Fix "Hide AI-generated video summary" in video description ([#5269](https://github.com/ReVanced/revanced-patches/issues/5269)) ([5203da0](https://github.com/ReVanced/revanced-patches/commit/5203da0ae58e467657bc915ab0af5b9904c4f492)) -* **YouTube - Hide Shorts components:** Fix hiding of untoggled components ([#5266](https://github.com/ReVanced/revanced-patches/issues/5266)) ([008e192](https://github.com/ReVanced/revanced-patches/commit/008e192779a8658e894d5718baa732717bf96e40)) - - -### Features - -* **Spotify:** Remove ads section from browse ([#5193](https://github.com/ReVanced/revanced-patches/issues/5193)) ([ebd4dcc](https://github.com/ReVanced/revanced-patches/commit/ebd4dccf12a5fbd31d2d53c19a792c389a4641d7)) -* **YouTube - Hide layout components:** Add `Hide in history` option to filter bar ([#5271](https://github.com/ReVanced/revanced-patches/issues/5271)) ([ba242a3](https://github.com/ReVanced/revanced-patches/commit/ba242a36b040b82e84870e5e240734637125a472)) - -## [5.29.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.29.0...v5.29.1-dev.1) (2025-06-26) - - -### Bug Fixes - -* **Spotify:** Add `Spoof client` patch to fix various issues by using a web platform access token ([#5173](https://github.com/ReVanced/revanced-patches/issues/5173)) ([b7b75bb](https://github.com/ReVanced/revanced-patches/commit/b7b75bb9d8d5fd505121e752b8a20e61ff28d1b2)) - -# [5.29.0](https://github.com/ReVanced/revanced-patches/compare/v5.28.0...v5.29.0) (2025-06-26) - - -### Bug Fixes - -* Add scrollable content to modern style settings dialogs ([#5211](https://github.com/ReVanced/revanced-patches/issues/5211)) ([e6876d5](https://github.com/ReVanced/revanced-patches/commit/e6876d510d28f6a3a41ec1722a033b3e30a22c65)) -* **Google Photos:** Resolve startup crash for Android 5.0 devices ([0294533](https://github.com/ReVanced/revanced-patches/commit/0294533c4d9a321aea086eedb4e46385ae9a026e)) -* **YouTube - Hide ads:** Hide new type of product ad in video description ([#5225](https://github.com/ReVanced/revanced-patches/issues/5225)) ([1e2efad](https://github.com/ReVanced/revanced-patches/commit/1e2efad7b2714c395ed6b0a77cbbf8a2265df520)) -* **YouTube - Hide layout components:** Fix "Hide video description attributes" ([#5250](https://github.com/ReVanced/revanced-patches/issues/5250)) ([2f22d45](https://github.com/ReVanced/revanced-patches/commit/2f22d45eb80745ac64fbea44c8055ebe7925a586)) -* **YouTube - Hide Shorts components:** Fix "Hide Use this sound button" ([#5233](https://github.com/ReVanced/revanced-patches/issues/5233)) ([5d6ec9e](https://github.com/ReVanced/revanced-patches/commit/5d6ec9e94a6221a0f32762d5bede893e9e7457fc)) -* **YouTube - Hide Shorts components:** Fix "Hide Use this template button" ([#5249](https://github.com/ReVanced/revanced-patches/issues/5249)) ([b399ecb](https://github.com/ReVanced/revanced-patches/commit/b399ecbb6a222d82dd5e4b3417c9f7eff4324adb)) -* **YouTube:** Always use single threaded layout to resolve layout bugs in unpatched YouTube ([#5226](https://github.com/ReVanced/revanced-patches/issues/5226)) ([1f539b1](https://github.com/ReVanced/revanced-patches/commit/1f539b1396526b2c767d77a804bd0308ee4a42ec)) -* **YouTube:** Fix refactoring app startup exception ([1b00c90](https://github.com/ReVanced/revanced-patches/commit/1b00c907f4b90f4659afb4a54ba61ac2835b460d)) - - -### Features - -* Add `Spoof app signature` patch ([#5158](https://github.com/ReVanced/revanced-patches/issues/5158)) ([78b25aa](https://github.com/ReVanced/revanced-patches/commit/78b25aa4e87ec3f9df1d57831b48a39029969416)) -* **Cricbuzz:** Add `Hide ads` patch ([#4998](https://github.com/ReVanced/revanced-patches/issues/4998)) ([83ccfa8](https://github.com/ReVanced/revanced-patches/commit/83ccfa8e1b5d5a44c55ef659484acf3cc08d3346)) -* **Crunchyroll:** Add `Hide ads` patch ([#5201](https://github.com/ReVanced/revanced-patches/issues/5201)) ([46b4398](https://github.com/ReVanced/revanced-patches/commit/46b4398fd6ca223391ed8f497a8347c2313421b7)) -* **YouTube - Hide Shorts components:** Add `Hide Effects button` ([#5255](https://github.com/ReVanced/revanced-patches/issues/5255)) ([240897a](https://github.com/ReVanced/revanced-patches/commit/240897a94008ce9a148c87bb41b978d553d5a6f5)) -* **YouTube - Hide video action buttons:** Add `Hide Stop ads` ([#5245](https://github.com/ReVanced/revanced-patches/issues/5245)) ([274dcc6](https://github.com/ReVanced/revanced-patches/commit/274dcc676e009be63eb6970de33abacd34dc6560)) -* **YouTube:** Add an option to disable toasts when changing default playback speed or quality ([#5230](https://github.com/ReVanced/revanced-patches/issues/5230)) ([c68cde3](https://github.com/ReVanced/revanced-patches/commit/c68cde3a896450874cc571be5c4723387db96032)) -* **YouTube:** Support version `20.13.41` ([#5253](https://github.com/ReVanced/revanced-patches/issues/5253)) ([d284c3d](https://github.com/ReVanced/revanced-patches/commit/d284c3dd3277430b6885e7c27ee02d062dcefc85)) - -# [5.29.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.10...v5.29.0-dev.11) (2025-06-26) - - -### Features - -* **Cricbuzz:** Add `Hide ads` patch ([#4998](https://github.com/ReVanced/revanced-patches/issues/4998)) ([83ccfa8](https://github.com/ReVanced/revanced-patches/commit/83ccfa8e1b5d5a44c55ef659484acf3cc08d3346)) - -# [5.29.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.9...v5.29.0-dev.10) (2025-06-25) - - -### Features - -* **YouTube - Hide Shorts components:** Add `Hide Effects button` ([#5255](https://github.com/ReVanced/revanced-patches/issues/5255)) ([240897a](https://github.com/ReVanced/revanced-patches/commit/240897a94008ce9a148c87bb41b978d553d5a6f5)) - -# [5.29.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.8...v5.29.0-dev.9) (2025-06-25) - - -### Features - -* Add `Spoof app signature` patch ([#5158](https://github.com/ReVanced/revanced-patches/issues/5158)) ([78b25aa](https://github.com/ReVanced/revanced-patches/commit/78b25aa4e87ec3f9df1d57831b48a39029969416)) - -# [5.29.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.7...v5.29.0-dev.8) (2025-06-25) - - -### Features - -* **YouTube:** Support version `20.13.41` ([#5253](https://github.com/ReVanced/revanced-patches/issues/5253)) ([d284c3d](https://github.com/ReVanced/revanced-patches/commit/d284c3dd3277430b6885e7c27ee02d062dcefc85)) - -# [5.29.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.6...v5.29.0-dev.7) (2025-06-24) - - -### Bug Fixes - -* **YouTube - Hide layout components:** Fix "Hide video description attributes" ([#5250](https://github.com/ReVanced/revanced-patches/issues/5250)) ([2f22d45](https://github.com/ReVanced/revanced-patches/commit/2f22d45eb80745ac64fbea44c8055ebe7925a586)) -* **YouTube - Hide Shorts components:** Fix "Hide Use this template button" ([#5249](https://github.com/ReVanced/revanced-patches/issues/5249)) ([b399ecb](https://github.com/ReVanced/revanced-patches/commit/b399ecbb6a222d82dd5e4b3417c9f7eff4324adb)) - -# [5.29.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.5...v5.29.0-dev.6) (2025-06-24) - - -### Features - -* **YouTube - Hide video action buttons:** Add `Hide Stop ads` ([#5245](https://github.com/ReVanced/revanced-patches/issues/5245)) ([274dcc6](https://github.com/ReVanced/revanced-patches/commit/274dcc676e009be63eb6970de33abacd34dc6560)) - -# [5.29.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.4...v5.29.0-dev.5) (2025-06-23) - - -### Bug Fixes - -* **Google Photos:** Resolve startup crash for Android 5.0 devices ([0294533](https://github.com/ReVanced/revanced-patches/commit/0294533c4d9a321aea086eedb4e46385ae9a026e)) - -# [5.29.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.3...v5.29.0-dev.4) (2025-06-23) - - -### Bug Fixes - -* **YouTube - Hide Shorts components:** Fix "Hide Use this sound button" ([#5233](https://github.com/ReVanced/revanced-patches/issues/5233)) ([5d6ec9e](https://github.com/ReVanced/revanced-patches/commit/5d6ec9e94a6221a0f32762d5bede893e9e7457fc)) - -# [5.29.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.2...v5.29.0-dev.3) (2025-06-23) - - -### Bug Fixes - -* **YouTube:** Fix refactoring app startup exception ([1b00c90](https://github.com/ReVanced/revanced-patches/commit/1b00c907f4b90f4659afb4a54ba61ac2835b460d)) - -# [5.29.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.1...v5.29.0-dev.2) (2025-06-23) - - -### Features - -* **Crunchyroll:** Add `Hide ads` patch ([#5201](https://github.com/ReVanced/revanced-patches/issues/5201)) ([46b4398](https://github.com/ReVanced/revanced-patches/commit/46b4398fd6ca223391ed8f497a8347c2313421b7)) - -# [5.29.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.28.1-dev.2...v5.29.0-dev.1) (2025-06-23) - - -### Bug Fixes - -* **YouTube:** Always use single threaded layout to resolve layout bugs in unpatched YouTube ([#5226](https://github.com/ReVanced/revanced-patches/issues/5226)) ([1f539b1](https://github.com/ReVanced/revanced-patches/commit/1f539b1396526b2c767d77a804bd0308ee4a42ec)) - - -### Features - -* **YouTube:** Add an option to disable toasts when changing default playback speed or quality ([#5230](https://github.com/ReVanced/revanced-patches/issues/5230)) ([c68cde3](https://github.com/ReVanced/revanced-patches/commit/c68cde3a896450874cc571be5c4723387db96032)) - -## [5.28.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.28.1-dev.1...v5.28.1-dev.2) (2025-06-23) - - -### Bug Fixes - -* **YouTube - Hide ads:** Hide new type of product ad in video description ([#5225](https://github.com/ReVanced/revanced-patches/issues/5225)) ([1e2efad](https://github.com/ReVanced/revanced-patches/commit/1e2efad7b2714c395ed6b0a77cbbf8a2265df520)) - -## [5.28.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.28.0...v5.28.1-dev.1) (2025-06-22) - - -### Bug Fixes - -* Add scrollable content to modern style settings dialogs ([#5211](https://github.com/ReVanced/revanced-patches/issues/5211)) ([e6876d5](https://github.com/ReVanced/revanced-patches/commit/e6876d510d28f6a3a41ec1722a033b3e30a22c65)) - -# [5.28.0](https://github.com/ReVanced/revanced-patches/compare/v5.27.0...v5.28.0) (2025-06-20) - - -### Bug Fixes - -* **Google Photos:** Resolve startup crash if MicroG GmsCore does not already have granted permissions ([a93d74d](https://github.com/ReVanced/revanced-patches/commit/a93d74d26e7ef87a3745df2b9fe82722d65a0e59)) -* **Messenger - Remove Meta AI:** Improve patch logic ([#5153](https://github.com/ReVanced/revanced-patches/issues/5153)) ([4ad4887](https://github.com/ReVanced/revanced-patches/commit/4ad488744d87543c31e453dc7b6d8182b3a7f440)) -* **Pandora - Disable ads:** Support latest app target ([#5185](https://github.com/ReVanced/revanced-patches/issues/5185)) ([ca83047](https://github.com/ReVanced/revanced-patches/commit/ca83047f5c4acbb267d5b98db80ad111999086e0)) -* **Spotify:** Fix `Hide Create button` and `Sanitize sharing links` for older but supported app targets ([#5159](https://github.com/ReVanced/revanced-patches/issues/5159)) ([e7dd061](https://github.com/ReVanced/revanced-patches/commit/e7dd061c513af90861c0ab0d7adc6ee43be57ce2)) -* **Threads - Hide ads:** Constrain patch to the last working app target ([#5189](https://github.com/ReVanced/revanced-patches/issues/5189)) ([3558c44](https://github.com/ReVanced/revanced-patches/commit/3558c44a05c13f19fefdbbf14b364181a79f17c0)) -* **YouTube:** Remove old app targets that are no longer supported by YouTube ([#5192](https://github.com/ReVanced/revanced-patches/issues/5192)) ([c9e54e1](https://github.com/ReVanced/revanced-patches/commit/c9e54e1d36243945ac1ec3108fe38edf0e15d772)) - - -### Features - -* **Spotify:** Add `Change lyrics provider` patch ([#4937](https://github.com/ReVanced/revanced-patches/issues/4937)) ([8736b6a](https://github.com/ReVanced/revanced-patches/commit/8736b6a80b48cb1f4562c9f9919804006ddb18bd)) -* Use modern style settings dialogs ([#5109](https://github.com/ReVanced/revanced-patches/issues/5109)) ([312b6dc](https://github.com/ReVanced/revanced-patches/commit/312b6dc04e01c2758cd304ca8606306027aa2f01)) - -# [5.28.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.7...v5.28.0-dev.8) (2025-06-19) - - -### Bug Fixes - -* **Messenger - Remove Meta AI:** Improve patch logic ([#5153](https://github.com/ReVanced/revanced-patches/issues/5153)) ([4ad4887](https://github.com/ReVanced/revanced-patches/commit/4ad488744d87543c31e453dc7b6d8182b3a7f440)) - -# [5.28.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.6...v5.28.0-dev.7) (2025-06-18) - - -### Bug Fixes - -* **YouTube:** Remove old app targets that are no longer supported by YouTube ([#5192](https://github.com/ReVanced/revanced-patches/issues/5192)) ([c9e54e1](https://github.com/ReVanced/revanced-patches/commit/c9e54e1d36243945ac1ec3108fe38edf0e15d772)) - -# [5.28.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.5...v5.28.0-dev.6) (2025-06-17) - - -### Bug Fixes - -* **Threads - Hide ads:** Constrain patch to the last working app target ([#5189](https://github.com/ReVanced/revanced-patches/issues/5189)) ([3558c44](https://github.com/ReVanced/revanced-patches/commit/3558c44a05c13f19fefdbbf14b364181a79f17c0)) - -# [5.28.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.4...v5.28.0-dev.5) (2025-06-17) - - -### Bug Fixes - -* **Pandora - Disable ads:** Support latest app target ([#5185](https://github.com/ReVanced/revanced-patches/issues/5185)) ([ca83047](https://github.com/ReVanced/revanced-patches/commit/ca83047f5c4acbb267d5b98db80ad111999086e0)) - -# [5.28.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.3...v5.28.0-dev.4) (2025-06-13) - - -### Features - -* Use modern style settings dialogs ([#5109](https://github.com/ReVanced/revanced-patches/issues/5109)) ([312b6dc](https://github.com/ReVanced/revanced-patches/commit/312b6dc04e01c2758cd304ca8606306027aa2f01)) - -# [5.28.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.2...v5.28.0-dev.3) (2025-06-11) - - -### Bug Fixes - -* **Spotify:** Fix `Hide Create button` and `Sanitize sharing links` for older but supported app targets ([#5159](https://github.com/ReVanced/revanced-patches/issues/5159)) ([e7dd061](https://github.com/ReVanced/revanced-patches/commit/e7dd061c513af90861c0ab0d7adc6ee43be57ce2)) - -# [5.28.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.1...v5.28.0-dev.2) (2025-06-11) - - -### Bug Fixes - -* **Google Photos:** Resolve startup crash if MicroG GmsCore does not already have granted permissions ([a93d74d](https://github.com/ReVanced/revanced-patches/commit/a93d74d26e7ef87a3745df2b9fe82722d65a0e59)) - -# [5.28.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.27.0...v5.28.0-dev.1) (2025-06-11) - - -### Features - -* **Spotify:** Add `Change lyrics provider` patch ([#4937](https://github.com/ReVanced/revanced-patches/issues/4937)) ([8736b6a](https://github.com/ReVanced/revanced-patches/commit/8736b6a80b48cb1f4562c9f9919804006ddb18bd)) - -# [5.27.0](https://github.com/ReVanced/revanced-patches/compare/v5.26.0...v5.27.0) (2025-06-09) - - -### Bug Fixes - -* **Bandcamp - Remove play limits:** Support latest app version ([#5124](https://github.com/ReVanced/revanced-patches/issues/5124)) ([863e92b](https://github.com/ReVanced/revanced-patches/commit/863e92b20ad6682f10524e475ed18f879048ecae)) -* **Spotify:** `Hide Create button` patch failing in edge cases ([#5131](https://github.com/ReVanced/revanced-patches/issues/5131)) ([0923600](https://github.com/ReVanced/revanced-patches/commit/0923600739a126329fc62100b500216860d7005e)) -* **Spotify:** Prevent hiding all navigation bar buttons ([#5122](https://github.com/ReVanced/revanced-patches/issues/5122)) ([8afbef0](https://github.com/ReVanced/revanced-patches/commit/8afbef01343c1e3e6e7e4a4cec6319aebfa4b11c)) -* **YouTube - Hide layout components:** Remove broken option 'Hide comments emoji picker' ([#5121](https://github.com/ReVanced/revanced-patches/issues/5121)) ([9a6a639](https://github.com/ReVanced/revanced-patches/commit/9a6a639c4905b00d6dffb0923c839c8e3ae54d0c)) -* **YouTube - Hide Shorts components:** Disable A/B player flags that prevents hiding buttons ([bef0dac](https://github.com/ReVanced/revanced-patches/commit/bef0dacac54caf1ca9511d7bc19b19140ccb4eaf)) -* **YouTube - Video quality:** Remove non-functional Shorts 144p default quality ([3113cd6](https://github.com/ReVanced/revanced-patches/commit/3113cd6d092952c8657454452f34c0ae85358ec9)) - - -### Features - -* Add `Hide app icon` patch ([#4977](https://github.com/ReVanced/revanced-patches/issues/4977)) ([92311b8](https://github.com/ReVanced/revanced-patches/commit/92311b8e5675f3d4b80ed690d34b699fb847e3cd)) -* **Google Photos:** Add `Enable DCIM folders backup control` patch ([#5138](https://github.com/ReVanced/revanced-patches/issues/5138)) ([328d232](https://github.com/ReVanced/revanced-patches/commit/328d232fe77406fa93a14768fc66e7b998506fba)) -* **Messenger:** Add `Hide Facebook button` patch ([#5057](https://github.com/ReVanced/revanced-patches/issues/5057)) ([9175b23](https://github.com/ReVanced/revanced-patches/commit/9175b23e8360d13c8c1c9c8602ca0b5931d13627)) -* **YouTube - Hide player overlay buttons:** Add in app setting for "Hide player control buttons background" ([#5147](https://github.com/ReVanced/revanced-patches/issues/5147)) ([dd8afa2](https://github.com/ReVanced/revanced-patches/commit/dd8afa2b07b50be24d764c0f6ddc9e1bbdb91bf1)) -* **YouTube - Hide Shorts components:** Add hide 'New posts' button ([ac6b916](https://github.com/ReVanced/revanced-patches/commit/ac6b916c0c212167c4645e2110500dc811b3e54a)) -* **YouTube - Theme:** Add option for black and white splash screen animation ([#5119](https://github.com/ReVanced/revanced-patches/issues/5119)) ([42db0c2](https://github.com/ReVanced/revanced-patches/commit/42db0c2e36fefccdbeaa072edcec48b1e05b6270)) - -# [5.27.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.8...v5.27.0-dev.9) (2025-06-09) - - -### Features - -* **Messenger:** Add `Hide Facebook button` patch ([#5057](https://github.com/ReVanced/revanced-patches/issues/5057)) ([9175b23](https://github.com/ReVanced/revanced-patches/commit/9175b23e8360d13c8c1c9c8602ca0b5931d13627)) - -# [5.27.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.7...v5.27.0-dev.8) (2025-06-09) - - -### Features - -* Add `Hide app icon` patch ([#4977](https://github.com/ReVanced/revanced-patches/issues/4977)) ([92311b8](https://github.com/ReVanced/revanced-patches/commit/92311b8e5675f3d4b80ed690d34b699fb847e3cd)) - -# [5.27.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.6...v5.27.0-dev.7) (2025-06-08) - - -### Features - -* **YouTube - Hide player overlay buttons:** Add in app setting for "Hide player control buttons background" ([#5147](https://github.com/ReVanced/revanced-patches/issues/5147)) ([dd8afa2](https://github.com/ReVanced/revanced-patches/commit/dd8afa2b07b50be24d764c0f6ddc9e1bbdb91bf1)) - -# [5.27.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.5...v5.27.0-dev.6) (2025-06-08) - - -### Features - -* **YouTube - Hide Shorts components:** Add hide 'New posts' button ([ac6b916](https://github.com/ReVanced/revanced-patches/commit/ac6b916c0c212167c4645e2110500dc811b3e54a)) - -# [5.27.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.4...v5.27.0-dev.5) (2025-06-08) - - -### Features - -* **Google Photos:** Add `Enable DCIM folders backup control` patch ([#5138](https://github.com/ReVanced/revanced-patches/issues/5138)) ([328d232](https://github.com/ReVanced/revanced-patches/commit/328d232fe77406fa93a14768fc66e7b998506fba)) - -# [5.27.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.3...v5.27.0-dev.4) (2025-06-06) - - -### Bug Fixes - -* **Bandcamp - Remove play limits:** Support latest app version ([#5124](https://github.com/ReVanced/revanced-patches/issues/5124)) ([863e92b](https://github.com/ReVanced/revanced-patches/commit/863e92b20ad6682f10524e475ed18f879048ecae)) - -# [5.27.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.2...v5.27.0-dev.3) (2025-06-06) - - -### Bug Fixes - -* **Spotify:** `Hide Create button` patch failing in edge cases ([#5131](https://github.com/ReVanced/revanced-patches/issues/5131)) ([0923600](https://github.com/ReVanced/revanced-patches/commit/0923600739a126329fc62100b500216860d7005e)) - -# [5.27.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.1...v5.27.0-dev.2) (2025-06-06) - - -### Bug Fixes - -* **YouTube - Video quality:** Remove non-functional Shorts 144p default quality ([3113cd6](https://github.com/ReVanced/revanced-patches/commit/3113cd6d092952c8657454452f34c0ae85358ec9)) - -# [5.27.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.26.1-dev.3...v5.27.0-dev.1) (2025-06-05) - - -### Features - -* **YouTube - Theme:** Add option for black and white splash screen animation ([#5119](https://github.com/ReVanced/revanced-patches/issues/5119)) ([42db0c2](https://github.com/ReVanced/revanced-patches/commit/42db0c2e36fefccdbeaa072edcec48b1e05b6270)) - -## [5.26.1-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.26.1-dev.2...v5.26.1-dev.3) (2025-06-05) - - -### Bug Fixes - -* **Spotify:** Prevent hiding all navigation bar buttons ([#5122](https://github.com/ReVanced/revanced-patches/issues/5122)) ([8afbef0](https://github.com/ReVanced/revanced-patches/commit/8afbef01343c1e3e6e7e4a4cec6319aebfa4b11c)) - -## [5.26.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.26.1-dev.1...v5.26.1-dev.2) (2025-06-05) - - -### Bug Fixes - -* **YouTube - Hide layout components:** Remove broken option 'Hide comments emoji picker' ([#5121](https://github.com/ReVanced/revanced-patches/issues/5121)) ([9a6a639](https://github.com/ReVanced/revanced-patches/commit/9a6a639c4905b00d6dffb0923c839c8e3ae54d0c)) - -## [5.26.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.26.0...v5.26.1-dev.1) (2025-06-05) - - -### Bug Fixes - -* **YouTube - Hide Shorts components:** Disable A/B player flags that prevents hiding buttons ([bef0dac](https://github.com/ReVanced/revanced-patches/commit/bef0dacac54caf1ca9511d7bc19b19140ccb4eaf)) - -# [5.26.0](https://github.com/ReVanced/revanced-patches/compare/v5.25.0...v5.26.0) (2025-06-04) - - -### Bug Fixes - -* **Spotify - Custom theme:** Apply accent color in more places ([#5039](https://github.com/ReVanced/revanced-patches/issues/5039)) ([9357887](https://github.com/ReVanced/revanced-patches/commit/9357887b6fca7aaf34dfb0163645b6a998e1db76)) -* **YouTube - Hide Shorts components:** Disable A/B player that prevents hiding buttons ([#5104](https://github.com/ReVanced/revanced-patches/issues/5104)) ([835b7bd](https://github.com/ReVanced/revanced-patches/commit/835b7bd7bd667abd632822c98898972e5124dbb6)) -* **YouTube:** Support A/B Shorts layout for RYD and component hiding ([#5081](https://github.com/ReVanced/revanced-patches/issues/5081)) ([8ecacaa](https://github.com/ReVanced/revanced-patches/commit/8ecacaad27162d9380014a9a13ac9220b12257b2)) - - -### Features - -* **Proton Mail:** Add `Remove free accounts limit` patch ([#4970](https://github.com/ReVanced/revanced-patches/issues/4970)) ([b0440ad](https://github.com/ReVanced/revanced-patches/commit/b0440ad6af0e190e516974ce896dcc54c8d2e122)) -* **Spotify:** Add `Hide Create button` patch ([#5062](https://github.com/ReVanced/revanced-patches/issues/5062)) ([3201681](https://github.com/ReVanced/revanced-patches/commit/32016819d2adbdfdd5e028941d56feda36d20b00)) -* **Sync for Reddit:** Add `Fix post thumbnails` patch ([e1ec30c](https://github.com/ReVanced/revanced-patches/commit/e1ec30c5b07560a39d7b8ab293b0c1f39fd59ef2)) -* **YouTube - Hide Shorts components:** Add option to hide comment panel ([#5102](https://github.com/ReVanced/revanced-patches/issues/5102)) ([22b9bee](https://github.com/ReVanced/revanced-patches/commit/22b9beedd3243a8d6a5635f591b91cdcf307be37)) -* **YouTube - Playback Speed:** Use modern custom speed dialog ([#5069](https://github.com/ReVanced/revanced-patches/issues/5069)) ([9a1e6ca](https://github.com/ReVanced/revanced-patches/commit/9a1e6ca178d9833ee2c681fb130b9290a4e89cd8)) - -# [5.26.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.7...v5.26.0-dev.8) (2025-06-04) - - -### Bug Fixes - -* **YouTube - Hide Shorts components:** Disable A/B player that prevents hiding buttons ([#5104](https://github.com/ReVanced/revanced-patches/issues/5104)) ([835b7bd](https://github.com/ReVanced/revanced-patches/commit/835b7bd7bd667abd632822c98898972e5124dbb6)) - -# [5.26.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.6...v5.26.0-dev.7) (2025-06-04) - - -### Features - -* **YouTube - Hide Shorts components:** Add option to hide comment panel ([#5102](https://github.com/ReVanced/revanced-patches/issues/5102)) ([22b9bee](https://github.com/ReVanced/revanced-patches/commit/22b9beedd3243a8d6a5635f591b91cdcf307be37)) - -# [5.26.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.5...v5.26.0-dev.6) (2025-06-03) - - -### Features - -* **Sync for Reddit:** Add `Fix post thumbnails` patch ([e1ec30c](https://github.com/ReVanced/revanced-patches/commit/e1ec30c5b07560a39d7b8ab293b0c1f39fd59ef2)) - -# [5.26.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.4...v5.26.0-dev.5) (2025-06-03) - - -### Bug Fixes - -* **Spotify - Custom theme:** Apply accent color in more places ([#5039](https://github.com/ReVanced/revanced-patches/issues/5039)) ([9357887](https://github.com/ReVanced/revanced-patches/commit/9357887b6fca7aaf34dfb0163645b6a998e1db76)) - -# [5.26.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.3...v5.26.0-dev.4) (2025-06-03) - - -### Features - -* **Spotify:** Add `Hide Create button` patch ([#5062](https://github.com/ReVanced/revanced-patches/issues/5062)) ([3201681](https://github.com/ReVanced/revanced-patches/commit/32016819d2adbdfdd5e028941d56feda36d20b00)) - -# [5.26.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.2...v5.26.0-dev.3) (2025-06-01) - - -### Features - -* **YouTube - Playback Speed:** Use modern custom speed dialog ([#5069](https://github.com/ReVanced/revanced-patches/issues/5069)) ([9a1e6ca](https://github.com/ReVanced/revanced-patches/commit/9a1e6ca178d9833ee2c681fb130b9290a4e89cd8)) - -# [5.26.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.1...v5.26.0-dev.2) (2025-06-01) - - -### Bug Fixes - -* **YouTube:** Support A/B Shorts layout for RYD and component hiding ([#5081](https://github.com/ReVanced/revanced-patches/issues/5081)) ([8ecacaa](https://github.com/ReVanced/revanced-patches/commit/8ecacaad27162d9380014a9a13ac9220b12257b2)) - -# [5.26.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.25.0...v5.26.0-dev.1) (2025-05-30) - - -### Features - -* **Proton Mail:** Add `Remove free accounts limit` patch ([#4970](https://github.com/ReVanced/revanced-patches/issues/4970)) ([b0440ad](https://github.com/ReVanced/revanced-patches/commit/b0440ad6af0e190e516974ce896dcc54c8d2e122)) - -# [5.25.0](https://github.com/ReVanced/revanced-patches/compare/v5.24.0...v5.25.0) (2025-05-29) - - -### Bug Fixes - -* **Disable Pairip license check:** Change patch to default off ([74b6a94](https://github.com/ReVanced/revanced-patches/commit/74b6a94577ac3f73b04bd0cce98fb7011a6607fd)) -* **Hide ADB status:** Resolve app crash on startup ([#5029](https://github.com/ReVanced/revanced-patches/issues/5029)) ([1abebd5](https://github.com/ReVanced/revanced-patches/commit/1abebd5f3b73250c6638d2d8a274b92ea8268924)) -* **Messenger:** Remove outdated `Disable switching emoji to sticker` patch ([#5044](https://github.com/ReVanced/revanced-patches/issues/5044)) ([7b182ca](https://github.com/ReVanced/revanced-patches/commit/7b182cab825ee3a4a3ca528c744c9d2a351c7cf8)) -* **Spotify Lite:** Remove obsolete `Enable on demand` patch ([#5046](https://github.com/ReVanced/revanced-patches/issues/5046)) ([4886d47](https://github.com/ReVanced/revanced-patches/commit/4886d47506c94b03c1f190ecc4947d3d91df6a47)) -* **YouTube - GmsCore support:** Restore patch functionality from prior merge ([7686bbe](https://github.com/ReVanced/revanced-patches/commit/7686bbe975644e1e582fa52f166879da5694ed93)) -* **YouTube - Hide ads:** Hide new type of general ad ([#5004](https://github.com/ReVanced/revanced-patches/issues/5004)) ([37e59d2](https://github.com/ReVanced/revanced-patches/commit/37e59d2771528c631dc13e73dac095fec95c6485)) -* **YouTube - Open Shorts in regular player:** Do not exit app when pressing back button in regular player ([#5020](https://github.com/ReVanced/revanced-patches/issues/5020)) ([3384f8d](https://github.com/ReVanced/revanced-patches/commit/3384f8dd0ff2a345f2e387f4ed1570079a83ccb6)) -* **YouTube:** Better handle incorrect duplicate translations ([20abac5](https://github.com/ReVanced/revanced-patches/commit/20abac52121fbecb65d87d0982f3380e1cf4e20e)) -* **Yuka - Unlock premium:** Remove broken patch that is no longer supported ([#5018](https://github.com/ReVanced/revanced-patches/issues/5018)) ([fac6e59](https://github.com/ReVanced/revanced-patches/commit/fac6e59d281e21e57abdcfc899cd1aeb18e5c2b8)) - - -### Features - -* Add `Disable pairip license check` patch ([#4927](https://github.com/ReVanced/revanced-patches/issues/4927)) ([42d2c27](https://github.com/ReVanced/revanced-patches/commit/42d2c277982ef63e6ad42d85e46f13c3ab50243c)) -* **Messenger:** Add `Remove Meta AI` patch ([#4945](https://github.com/ReVanced/revanced-patches/issues/4945)) ([012dff7](https://github.com/ReVanced/revanced-patches/commit/012dff7b6511b9e519ccac96f6713cf1a1b327b4)) -* **Prime Video:** Add `Rename shared permissions` patch ([#5049](https://github.com/ReVanced/revanced-patches/issues/5049)) ([80f1fc6](https://github.com/ReVanced/revanced-patches/commit/80f1fc629e30e391bd5877f07dbdf4b6613bd1cf)) -* **Spotify:** Add `Fix Facebook login` patch ([#5023](https://github.com/ReVanced/revanced-patches/issues/5023)) ([34932dc](https://github.com/ReVanced/revanced-patches/commit/34932dc43933d346a5a3adadc62c0dbd38a633b5)) -* **Threads:** Hide Ads ([#5064](https://github.com/ReVanced/revanced-patches/issues/5064)) ([3c4cecb](https://github.com/ReVanced/revanced-patches/commit/3c4cecb966c2f99bfde99552686dda19ade5f67e)) -* **YouTube - Enable debugging:** Add settings menu to share debug logs ([#5021](https://github.com/ReVanced/revanced-patches/issues/5021)) ([1ec4a88](https://github.com/ReVanced/revanced-patches/commit/1ec4a88464a2a2810c02cf072950b618d183779a)) -* **YouTube - Settings:** Add a color picker ([#4981](https://github.com/ReVanced/revanced-patches/issues/4981)) ([1e0e398](https://github.com/ReVanced/revanced-patches/commit/1e0e398574329173aff11a4dc9acfc3fcdeabe16)) -* **YouTube - Swipe controls:** Add separate color settings for the brightness and volume bars ([#5043](https://github.com/ReVanced/revanced-patches/issues/5043)) ([80f50e8](https://github.com/ReVanced/revanced-patches/commit/80f50e8c50ca6a8366b7fd7b01459fb16fa1074a)) -* **YouTube:** Add `Disable haptic feedback` patch ([#5033](https://github.com/ReVanced/revanced-patches/issues/5033)) ([bbe7974](https://github.com/ReVanced/revanced-patches/commit/bbe79744a513c96f9016476e8435f999e94c45d7)) - -# [5.25.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.13...v5.25.0-dev.14) (2025-05-29) - - -### Features - -* **Threads:** Hide Ads ([#5064](https://github.com/ReVanced/revanced-patches/issues/5064)) ([3c4cecb](https://github.com/ReVanced/revanced-patches/commit/3c4cecb966c2f99bfde99552686dda19ade5f67e)) - -# [5.25.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.12...v5.25.0-dev.13) (2025-05-28) - - -### Features - -* **Prime Video:** Add `Rename shared permissions` patch ([#5049](https://github.com/ReVanced/revanced-patches/issues/5049)) ([80f1fc6](https://github.com/ReVanced/revanced-patches/commit/80f1fc629e30e391bd5877f07dbdf4b6613bd1cf)) - -# [5.25.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.11...v5.25.0-dev.12) (2025-05-28) - - -### Features - -* **YouTube - Swipe controls:** Add separate color settings for the brightness and volume bars ([#5043](https://github.com/ReVanced/revanced-patches/issues/5043)) ([80f50e8](https://github.com/ReVanced/revanced-patches/commit/80f50e8c50ca6a8366b7fd7b01459fb16fa1074a)) - -# [5.25.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.10...v5.25.0-dev.11) (2025-05-27) - - -### Features - -* **YouTube - Enable debugging:** Add settings menu to share debug logs ([#5021](https://github.com/ReVanced/revanced-patches/issues/5021)) ([1ec4a88](https://github.com/ReVanced/revanced-patches/commit/1ec4a88464a2a2810c02cf072950b618d183779a)) -* **YouTube:** Add `Disable haptic feedback` patch ([#5033](https://github.com/ReVanced/revanced-patches/issues/5033)) ([bbe7974](https://github.com/ReVanced/revanced-patches/commit/bbe79744a513c96f9016476e8435f999e94c45d7)) - -# [5.25.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.9...v5.25.0-dev.10) (2025-05-27) - - -### Bug Fixes - -* **Messenger:** Remove outdated `Disable switching emoji to sticker` patch ([#5044](https://github.com/ReVanced/revanced-patches/issues/5044)) ([7b182ca](https://github.com/ReVanced/revanced-patches/commit/7b182cab825ee3a4a3ca528c744c9d2a351c7cf8)) -* **Spotify Lite:** Remove obsolete `Enable on demand` patch ([#5046](https://github.com/ReVanced/revanced-patches/issues/5046)) ([4886d47](https://github.com/ReVanced/revanced-patches/commit/4886d47506c94b03c1f190ecc4947d3d91df6a47)) - -# [5.25.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.8...v5.25.0-dev.9) (2025-05-26) - - -### Features - -* **Spotify:** Add `Fix Facebook login` patch ([#5023](https://github.com/ReVanced/revanced-patches/issues/5023)) ([34932dc](https://github.com/ReVanced/revanced-patches/commit/34932dc43933d346a5a3adadc62c0dbd38a633b5)) -* **YouTube - Settings:** Add a color picker ([#4981](https://github.com/ReVanced/revanced-patches/issues/4981)) ([1e0e398](https://github.com/ReVanced/revanced-patches/commit/1e0e398574329173aff11a4dc9acfc3fcdeabe16)) - -# [5.25.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.8...v5.25.0-dev.9) (2025-05-26) - - -### Features - -* **Spotify:** Add `Fix Facebook login` patch ([#5023](https://github.com/ReVanced/revanced-patches/issues/5023)) ([34932dc](https://github.com/ReVanced/revanced-patches/commit/34932dc43933d346a5a3adadc62c0dbd38a633b5)) -* **YouTube - Settings:** Add a color picker ([#4981](https://github.com/ReVanced/revanced-patches/issues/4981)) ([1e0e398](https://github.com/ReVanced/revanced-patches/commit/1e0e398574329173aff11a4dc9acfc3fcdeabe16)) - -# [5.25.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.8...v5.25.0-dev.9) (2025-05-26) - - -### Features - -* **Spotify:** Add `Fix Facebook login` patch ([#5023](https://github.com/ReVanced/revanced-patches/issues/5023)) ([34932dc](https://github.com/ReVanced/revanced-patches/commit/34932dc43933d346a5a3adadc62c0dbd38a633b5)) -* **YouTube - Settings:** Add a color picker ([#4981](https://github.com/ReVanced/revanced-patches/issues/4981)) ([1e0e398](https://github.com/ReVanced/revanced-patches/commit/1e0e398574329173aff11a4dc9acfc3fcdeabe16)) - -# [5.25.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.7...v5.25.0-dev.8) (2025-05-25) - - -### Bug Fixes - -* **Hide ADB status:** Resolve app crash on startup ([#5029](https://github.com/ReVanced/revanced-patches/issues/5029)) ([1abebd5](https://github.com/ReVanced/revanced-patches/commit/1abebd5f3b73250c6638d2d8a274b92ea8268924)) - -# [5.25.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.6...v5.25.0-dev.7) (2025-05-24) - - -### Bug Fixes - -* **YouTube - Open Shorts in regular player:** Do not exit app when pressing back button in regular player ([#5020](https://github.com/ReVanced/revanced-patches/issues/5020)) ([3384f8d](https://github.com/ReVanced/revanced-patches/commit/3384f8dd0ff2a345f2e387f4ed1570079a83ccb6)) - -# [5.25.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.5...v5.25.0-dev.6) (2025-05-23) - - -### Bug Fixes - -* **Yuka - Unlock premium:** Remove broken patch that is no longer supported ([#5018](https://github.com/ReVanced/revanced-patches/issues/5018)) ([fac6e59](https://github.com/ReVanced/revanced-patches/commit/fac6e59d281e21e57abdcfc899cd1aeb18e5c2b8)) - -# [5.25.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.4...v5.25.0-dev.5) (2025-05-22) - - -### Bug Fixes - -* **YouTube:** Better handle incorrect duplicate translations ([20abac5](https://github.com/ReVanced/revanced-patches/commit/20abac52121fbecb65d87d0982f3380e1cf4e20e)) - -# [5.25.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.3...v5.25.0-dev.4) (2025-05-22) - - -### Bug Fixes - -* **YouTube - GmsCore support:** Restore patch functionality from prior merge ([7686bbe](https://github.com/ReVanced/revanced-patches/commit/7686bbe975644e1e582fa52f166879da5694ed93)) - -# [5.25.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.2...v5.25.0-dev.3) (2025-05-22) - - -### Bug Fixes - -* **YouTube - Hide ads:** Hide new type of general ad ([#5004](https://github.com/ReVanced/revanced-patches/issues/5004)) ([37e59d2](https://github.com/ReVanced/revanced-patches/commit/37e59d2771528c631dc13e73dac095fec95c6485)) - -# [5.25.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.1...v5.25.0-dev.2) (2025-05-22) - - -### Bug Fixes - -* **Disable Pairip license check:** Change patch to default off ([74b6a94](https://github.com/ReVanced/revanced-patches/commit/74b6a94577ac3f73b04bd0cce98fb7011a6607fd)) - -# [5.25.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.24.0...v5.25.0-dev.1) (2025-05-22) - - -### Features - -* Add `Disable pairip license check` patch ([#4927](https://github.com/ReVanced/revanced-patches/issues/4927)) ([42d2c27](https://github.com/ReVanced/revanced-patches/commit/42d2c277982ef63e6ad42d85e46f13c3ab50243c)) -* **Messenger:** Add `Remove Meta AI` patch ([#4945](https://github.com/ReVanced/revanced-patches/issues/4945)) ([012dff7](https://github.com/ReVanced/revanced-patches/commit/012dff7b6511b9e519ccac96f6713cf1a1b327b4)) - -# [5.24.0](https://github.com/ReVanced/revanced-patches/compare/v5.23.0...v5.24.0) (2025-05-19) - - -### Bug Fixes - -* **Spotify - Fix third party launchers widgets:** Add missing compatibility annotation ([0493f80](https://github.com/ReVanced/revanced-patches/commit/0493f8035b26b90c5f8e42be2e2a5ce73d8685a5)) -* **YouTube - Hide layout components:** Fix `Hide video recommendation labels` ([#4956](https://github.com/ReVanced/revanced-patches/issues/4956)) ([ae05ac3](https://github.com/ReVanced/revanced-patches/commit/ae05ac38151ebd3197953af97ca0dd847a04cc2d)) -* **YouTube - Settings:** Correctly show summary text if search box is closed before searching ([d0ae835](https://github.com/ReVanced/revanced-patches/commit/d0ae835d3381fc659c9bb4a2d130d4db8a1499cf)) -* **YouTube - SponsorBlock:** Fix segment category summary not showing category description ([06934a6](https://github.com/ReVanced/revanced-patches/commit/06934a60d91b40a5cdf7f4cd92deae4a136c149b)) - - -### Features - -* **GmsCore support:** Open vendor specific DontKillMyApp if available ([#4952](https://github.com/ReVanced/revanced-patches/issues/4952)) ([b89927a](https://github.com/ReVanced/revanced-patches/commit/b89927a10e3b909a3c37fbb75c16a7abbce44560)) -* **NU.nl:** Support version `11.3.0` ([#4925](https://github.com/ReVanced/revanced-patches/issues/4925)) ([bedde60](https://github.com/ReVanced/revanced-patches/commit/bedde60fc1a52b0fd491174b3b5b887435eb621a)) -* **Spotify:** Add `Fix third party launchers widgets` patch ([#4893](https://github.com/ReVanced/revanced-patches/issues/4893)) ([23bfdc9](https://github.com/ReVanced/revanced-patches/commit/23bfdc98fbbcc8ecf0ffbf8704f58dd2272e4af2)) -* **YouTube - Hide description components:** Add `Hide Ask` ([#4972](https://github.com/ReVanced/revanced-patches/issues/4972)) ([ebc94a5](https://github.com/ReVanced/revanced-patches/commit/ebc94a5da6214b67399c9c01515689bd4b20547c)) -* **YouTube - Hide layout components:** Add `Hide ticket shelf` ([#4969](https://github.com/ReVanced/revanced-patches/issues/4969)) ([6436af7](https://github.com/ReVanced/revanced-patches/commit/6436af7e77c77d2034dfceba8bc51132ad7632be)) -* **YouTube - Hide player components:** Hide related video overlay in fullscreen ([#4938](https://github.com/ReVanced/revanced-patches/issues/4938)) ([ac9be97](https://github.com/ReVanced/revanced-patches/commit/ac9be9760c9965e54df196b227a310d64ead4bf5)) -* **YouTube - Settings:** Add ability to search in settings ([#4881](https://github.com/ReVanced/revanced-patches/issues/4881)) ([aca8b20](https://github.com/ReVanced/revanced-patches/commit/aca8b207c15f254bcc9ad94bc7dfb895f21d4058)) - -# [5.24.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.8...v5.24.0-dev.9) (2025-05-18) - - -### Bug Fixes - -* **YouTube - SponsorBlock:** Fix segment category summary not showing category description ([06934a6](https://github.com/ReVanced/revanced-patches/commit/06934a60d91b40a5cdf7f4cd92deae4a136c149b)) - -# [5.24.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.7...v5.24.0-dev.8) (2025-05-17) - - -### Bug Fixes - -* **YouTube - Settings:** Correctly show summary text if search box is closed before searching ([d0ae835](https://github.com/ReVanced/revanced-patches/commit/d0ae835d3381fc659c9bb4a2d130d4db8a1499cf)) - -# [5.24.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.6...v5.24.0-dev.7) (2025-05-17) - - -### Features - -* **YouTube - Hide layout components:** Add `Hide ticket shelf` ([#4969](https://github.com/ReVanced/revanced-patches/issues/4969)) ([6436af7](https://github.com/ReVanced/revanced-patches/commit/6436af7e77c77d2034dfceba8bc51132ad7632be)) - -# [5.24.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.5...v5.24.0-dev.6) (2025-05-17) - - -### Features - -* **YouTube - Hide description components:** Add `Hide Ask` ([#4972](https://github.com/ReVanced/revanced-patches/issues/4972)) ([ebc94a5](https://github.com/ReVanced/revanced-patches/commit/ebc94a5da6214b67399c9c01515689bd4b20547c)) - -# [5.24.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.4...v5.24.0-dev.5) (2025-05-17) - - -### Bug Fixes - -* **Spotify - Fix third party launchers widgets:** Add missing compatibility annotation ([0493f80](https://github.com/ReVanced/revanced-patches/commit/0493f8035b26b90c5f8e42be2e2a5ce73d8685a5)) - - -### Features - -* **YouTube - Settings:** Add ability to search in settings ([#4881](https://github.com/ReVanced/revanced-patches/issues/4881)) ([aca8b20](https://github.com/ReVanced/revanced-patches/commit/aca8b207c15f254bcc9ad94bc7dfb895f21d4058)) - -# [5.24.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.3...v5.24.0-dev.4) (2025-05-16) - - -### Features - -* **Spotify:** Add `Fix third party launchers widgets` patch ([#4893](https://github.com/ReVanced/revanced-patches/issues/4893)) ([23bfdc9](https://github.com/ReVanced/revanced-patches/commit/23bfdc98fbbcc8ecf0ffbf8704f58dd2272e4af2)) - -# [5.24.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.2...v5.24.0-dev.3) (2025-05-14) - - -### Bug Fixes - -* **YouTube - Hide layout components:** Fix `Hide video recommendation labels` ([#4956](https://github.com/ReVanced/revanced-patches/issues/4956)) ([ae05ac3](https://github.com/ReVanced/revanced-patches/commit/ae05ac38151ebd3197953af97ca0dd847a04cc2d)) - -# [5.24.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.1...v5.24.0-dev.2) (2025-05-14) - - -### Features - -* **GmsCore support:** Open vendor specific DontKillMyApp if available ([#4952](https://github.com/ReVanced/revanced-patches/issues/4952)) ([b89927a](https://github.com/ReVanced/revanced-patches/commit/b89927a10e3b909a3c37fbb75c16a7abbce44560)) -* **YouTube - Hide player components:** Hide related video overlay in fullscreen ([#4938](https://github.com/ReVanced/revanced-patches/issues/4938)) ([ac9be97](https://github.com/ReVanced/revanced-patches/commit/ac9be9760c9965e54df196b227a310d64ead4bf5)) - -# [5.24.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.23.0...v5.24.0-dev.1) (2025-05-12) - - -### Features - -* **NU.nl:** Support version `11.3.0` ([#4925](https://github.com/ReVanced/revanced-patches/issues/4925)) ([bedde60](https://github.com/ReVanced/revanced-patches/commit/bedde60fc1a52b0fd491174b3b5b887435eb621a)) - -# [5.23.0](https://github.com/ReVanced/revanced-patches/compare/v5.22.0...v5.23.0) (2025-05-10) - - -### Bug Fixes - -* Correct incorrect fingerprint ([c3bab89](https://github.com/ReVanced/revanced-patches/commit/c3bab89fc4189e38c10eee0caa36289de7e29dfa)) -* Fix incorrect fingerprints ([#4917](https://github.com/ReVanced/revanced-patches/issues/4917)) ([49ca329](https://github.com/ReVanced/revanced-patches/commit/49ca3290a726cdba7bc9b62ffcd8d46e6f04778e)) -* **Spotify - Unlock Spotify Premium:** Remove pop up premium ads ([#4842](https://github.com/ReVanced/revanced-patches/issues/4842)) ([00aa200](https://github.com/ReVanced/revanced-patches/commit/00aa2000ba2eef15a0dd827c2bd84c2e85c412e0)) -* **YouTube:** Improve litho filtering performance ([#4904](https://github.com/ReVanced/revanced-patches/issues/4904)) ([7b43986](https://github.com/ReVanced/revanced-patches/commit/7b43986871a68e5cb43331d2fb2fdb9ef67438ad)) -* **YouTube:** Simplify litho filtering patch ([#4910](https://github.com/ReVanced/revanced-patches/issues/4910)) ([bd53955](https://github.com/ReVanced/revanced-patches/commit/bd53955df738bb7b819eb91a3e776e9d2ca5c74a)) - - -### Features - -* **Lightroom:** Constrain patches to last working version ([efef03b](https://github.com/ReVanced/revanced-patches/commit/efef03b80da21552d0d8be6913faba64e4fb5ed1)) -* **Pandora:** Add `Disable audio ads` and `Unlimited skips` patch ([#4841](https://github.com/ReVanced/revanced-patches/issues/4841)) ([0cf7a4c](https://github.com/ReVanced/revanced-patches/commit/0cf7a4c6be615ed0a52a6bacf87592f5f43ff575)) -* **Prime Video:** Add `Skip ads` patch ([#4824](https://github.com/ReVanced/revanced-patches/issues/4824)) ([bb672c4](https://github.com/ReVanced/revanced-patches/commit/bb672c4674ddc201b8b2648c3906cfc31ef43f10)) -* **Spotify:** Add `Sanitize sharing links` patch ([#4829](https://github.com/ReVanced/revanced-patches/issues/4829)) ([2e3511d](https://github.com/ReVanced/revanced-patches/commit/2e3511d03c8198bbdb9336888df038a33fb3ab8c)) - -# [5.23.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.6...v5.23.0-dev.7) (2025-05-06) - - -### Bug Fixes - -* Fix incorrect fingerprints ([#4917](https://github.com/ReVanced/revanced-patches/issues/4917)) ([49ca329](https://github.com/ReVanced/revanced-patches/commit/49ca3290a726cdba7bc9b62ffcd8d46e6f04778e)) - -# [5.23.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.5...v5.23.0-dev.6) (2025-05-06) - - -### Bug Fixes - -* Correct incorrect fingerprint ([c3bab89](https://github.com/ReVanced/revanced-patches/commit/c3bab89fc4189e38c10eee0caa36289de7e29dfa)) - -# [5.23.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.4...v5.23.0-dev.5) (2025-05-06) - - -### Bug Fixes - -* **Spotify - Unlock Spotify Premium:** Remove pop up premium ads ([#4842](https://github.com/ReVanced/revanced-patches/issues/4842)) ([00aa200](https://github.com/ReVanced/revanced-patches/commit/00aa2000ba2eef15a0dd827c2bd84c2e85c412e0)) - - -### Features - -* **Pandora:** Add `Disable audio ads` and `Unlimited skips` patch ([#4841](https://github.com/ReVanced/revanced-patches/issues/4841)) ([0cf7a4c](https://github.com/ReVanced/revanced-patches/commit/0cf7a4c6be615ed0a52a6bacf87592f5f43ff575)) -* **Prime Video:** Add `Skip ads` patch ([#4824](https://github.com/ReVanced/revanced-patches/issues/4824)) ([bb672c4](https://github.com/ReVanced/revanced-patches/commit/bb672c4674ddc201b8b2648c3906cfc31ef43f10)) - -# [5.23.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.3...v5.23.0-dev.4) (2025-05-06) - - -### Features - -* **Spotify:** Add `Sanitize sharing links` patch ([#4829](https://github.com/ReVanced/revanced-patches/issues/4829)) ([2e3511d](https://github.com/ReVanced/revanced-patches/commit/2e3511d03c8198bbdb9336888df038a33fb3ab8c)) - -# [5.23.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.2...v5.23.0-dev.3) (2025-05-05) - - -### Bug Fixes - -* **YouTube:** Simplify litho filtering patch ([#4910](https://github.com/ReVanced/revanced-patches/issues/4910)) ([bd53955](https://github.com/ReVanced/revanced-patches/commit/bd53955df738bb7b819eb91a3e776e9d2ca5c74a)) - -# [5.23.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.1...v5.23.0-dev.2) (2025-05-04) - - -### Bug Fixes - -* **YouTube:** Improve litho filtering performance ([#4904](https://github.com/ReVanced/revanced-patches/issues/4904)) ([7b43986](https://github.com/ReVanced/revanced-patches/commit/7b43986871a68e5cb43331d2fb2fdb9ef67438ad)) - -# [5.23.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.22.0...v5.23.0-dev.1) (2025-05-02) - - -### Features - -* **Lightroom:** Constrain patches to last working version ([efef03b](https://github.com/ReVanced/revanced-patches/commit/efef03b80da21552d0d8be6913faba64e4fb5ed1)) - -# [5.22.0](https://github.com/ReVanced/revanced-patches/compare/v5.21.0...v5.22.0) (2025-05-01) - - -### Bug Fixes - -* **TikTok - Feed filter:** Hide ads in following feed ([#4844](https://github.com/ReVanced/revanced-patches/issues/4844)) ([c255ac1](https://github.com/ReVanced/revanced-patches/commit/c255ac18e0b2dcf917bd0559876be5a2a81023db)) -* **YouTube - Hide layout components:** Hide new type of community posts ([#4888](https://github.com/ReVanced/revanced-patches/issues/4888)) ([f0c9c35](https://github.com/ReVanced/revanced-patches/commit/f0c9c35778ab43a99149ee5ad0ccfd8aeb09f638)) -* **YouTube - Hide Shorts components:** Hide action buttons A/B button layout ([#4889](https://github.com/ReVanced/revanced-patches/issues/4889)) ([9dcd3d3](https://github.com/ReVanced/revanced-patches/commit/9dcd3d35dddf019547ab6ce431bac7a5a8a4c291)) -* **YouTube - Shorts autoplay:** Fix autoplay with YT 20.12 ([06b35b2](https://github.com/ReVanced/revanced-patches/commit/06b35b2a7d7371915881e8f430c32ce15fa224de)) -* **YouTube - Spoof app version:** Do not hide spoof version in general settings menu ([#4861](https://github.com/ReVanced/revanced-patches/issues/4861)) ([f459c3c](https://github.com/ReVanced/revanced-patches/commit/f459c3c7fae3a1b8addf3354488dcef9f95255cc)) - - -### Features - -* **TikTok - Feed Filter:** Remove TikTok Shop from feed. ([#4851](https://github.com/ReVanced/revanced-patches/issues/4851)) ([f198bec](https://github.com/ReVanced/revanced-patches/commit/f198bece653e3e1adf083129dedb77c1d1a633d7)) -* **YouTube - GmsCore support:** Show troubleshooting in app text if the user recently changed their account details ([#4879](https://github.com/ReVanced/revanced-patches/issues/4879)) ([ab4bdc8](https://github.com/ReVanced/revanced-patches/commit/ab4bdc8a2519cee15f79bf95d89e7ea56ea464ee)) - -# [5.22.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.22.0-dev.3...v5.22.0-dev.4) (2025-04-30) - - -### Bug Fixes - -* **YouTube - Hide layout components:** Hide new type of community posts ([#4888](https://github.com/ReVanced/revanced-patches/issues/4888)) ([f0c9c35](https://github.com/ReVanced/revanced-patches/commit/f0c9c35778ab43a99149ee5ad0ccfd8aeb09f638)) -* **YouTube - Hide Shorts components:** Hide action buttons A/B button layout ([#4889](https://github.com/ReVanced/revanced-patches/issues/4889)) ([9dcd3d3](https://github.com/ReVanced/revanced-patches/commit/9dcd3d35dddf019547ab6ce431bac7a5a8a4c291)) - -# [5.22.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.22.0-dev.2...v5.22.0-dev.3) (2025-04-29) - - -### Features - -* **YouTube - GmsCore support:** Show troubleshooting in app text if the user recently changed their account details ([#4879](https://github.com/ReVanced/revanced-patches/issues/4879)) ([ab4bdc8](https://github.com/ReVanced/revanced-patches/commit/ab4bdc8a2519cee15f79bf95d89e7ea56ea464ee)) - -# [5.22.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.22.0-dev.1...v5.22.0-dev.2) (2025-04-27) - - -### Bug Fixes - -* **YouTube - Shorts autoplay:** Fix autoplay with YT 20.12 ([06b35b2](https://github.com/ReVanced/revanced-patches/commit/06b35b2a7d7371915881e8f430c32ce15fa224de)) - -# [5.22.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.21.0...v5.22.0-dev.1) (2025-04-26) - - -### Bug Fixes - -* **TikTok - Feed filter:** Hide ads in following feed ([#4844](https://github.com/ReVanced/revanced-patches/issues/4844)) ([c255ac1](https://github.com/ReVanced/revanced-patches/commit/c255ac18e0b2dcf917bd0559876be5a2a81023db)) -* **YouTube - Spoof app version:** Do not hide spoof version in general settings menu ([#4861](https://github.com/ReVanced/revanced-patches/issues/4861)) ([f459c3c](https://github.com/ReVanced/revanced-patches/commit/f459c3c7fae3a1b8addf3354488dcef9f95255cc)) - - -### Features - -* **TikTok - Feed Filter:** Remove TikTok Shop from feed. ([#4851](https://github.com/ReVanced/revanced-patches/issues/4851)) ([f198bec](https://github.com/ReVanced/revanced-patches/commit/f198bece653e3e1adf083129dedb77c1d1a633d7)) - -# [5.21.0](https://github.com/ReVanced/revanced-patches/compare/v5.20.1...v5.21.0) (2025-04-25) - - -### Bug Fixes - -* `Hide ADB status` patch ([#4814](https://github.com/ReVanced/revanced-patches/issues/4814)) ([dc89be0](https://github.com/ReVanced/revanced-patches/commit/dc89be0e94880733f862b250d95d4848f02c594d)) -* **GmsCore Support:** Correct the description to refer to the app being patched ([2bbcf9d](https://github.com/ReVanced/revanced-patches/commit/2bbcf9d82ca2f442572a6aa886cc611b0d56ff0a)) -* **Wide search bar:** Fix patching `19.16.39` ([433dbc3](https://github.com/ReVanced/revanced-patches/commit/433dbc3bf81823369e146035c954281e84d3a436)) -* **YouTube - Change start page:** Add option to always override start page on app launch ([#4832](https://github.com/ReVanced/revanced-patches/issues/4832)) ([5062e24](https://github.com/ReVanced/revanced-patches/commit/5062e24433ba38eba397438e8fde32099109d3c3)) -* **YouTube - Disable auto captions:** Correctly hide captions with YT 20.12 ([5ecbe82](https://github.com/ReVanced/revanced-patches/commit/5ecbe823ed5197533328cc37f1de5cd1f048a217)) -* **YouTube - Hide video action buttons:** Add option to hide 'Ask' button ([#4852](https://github.com/ReVanced/revanced-patches/issues/4852)) ([43bcf5a](https://github.com/ReVanced/revanced-patches/commit/43bcf5a098c9008cc11dc7df9680437d5effbb32)) -* **YouTube - Hide video action buttons:** Hide A/B layout buttons ([4db5d3c](https://github.com/ReVanced/revanced-patches/commit/4db5d3c3d5ac04faf70cc07fb309b324d752e7e3)) -* **YouTube - Wide search bar:** Do not force phone layout for tablet devices ([#4827](https://github.com/ReVanced/revanced-patches/issues/4827)) ([0cb38f9](https://github.com/ReVanced/revanced-patches/commit/0cb38f9f367a7fe742d8ca336150049181d637b6)) - - -### Features - -* Add `Hide ADB status` patch ([#4585](https://github.com/ReVanced/revanced-patches/issues/4585)) ([1ea8047](https://github.com/ReVanced/revanced-patches/commit/1ea8047aefdaa358e9af8038923ac54d68a39176)) -* **X / Twitter:** Support version `10.86.0-release.0` ([#4805](https://github.com/ReVanced/revanced-patches/issues/4805)) ([655b390](https://github.com/ReVanced/revanced-patches/commit/655b39043ad77efcb4380de67c3f603666e7bc49)) -* **YouTube - Swipe controls:** Add option for vertical progress bar ([#4811](https://github.com/ReVanced/revanced-patches/issues/4811)) ([ebee07e](https://github.com/ReVanced/revanced-patches/commit/ebee07ec3aba6fd3adbd8e0af30390e197879d89)) -* **YouTube:** Support version `20.12.46` ([#4779](https://github.com/ReVanced/revanced-patches/issues/4779)) ([703359f](https://github.com/ReVanced/revanced-patches/commit/703359f0c16b613c204cf16cf42227b628f664fa)) - -# [5.21.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.21.0-dev.11...v5.21.0-dev.12) (2025-04-24) - - -### Bug Fixes - -* **YouTube - Hide video action buttons:** Add option to hide 'Ask' button ([#4852](https://github.com/ReVanced/revanced-patches/issues/4852)) ([43bcf5a](https://github.com/ReVanced/revanced-patches/commit/43bcf5a098c9008cc11dc7df9680437d5effbb32)) - -# [5.21.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.21.0-dev.10...v5.21.0-dev.11) (2025-04-24) - - -### Bug Fixes - -* **GmsCore Support:** Correct the description to refer to the app being patched ([2bbcf9d](https://github.com/ReVanced/revanced-patches/commit/2bbcf9d82ca2f442572a6aa886cc611b0d56ff0a)) - -# [5.21.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.21.0-dev.9...v5.21.0-dev.10) (2025-04-23) - - -### Features - -* **YouTube - Swipe controls:** Add option for vertical progress bar ([#4811](https://github.com/ReVanced/revanced-patches/issues/4811)) ([ebee07e](https://github.com/ReVanced/revanced-patches/commit/ebee07ec3aba6fd3adbd8e0af30390e197879d89)) - -# [5.21.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.21.0-dev.8...v5.21.0-dev.9) (2025-04-21) - - -### Bug Fixes - -* **YouTube - Hide video action buttons:** Hide A/B layout buttons ([4db5d3c](https://github.com/ReVanced/revanced-patches/commit/4db5d3c3d5ac04faf70cc07fb309b324d752e7e3)) - -# [5.21.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.21.0-dev.7...v5.21.0-dev.8) (2025-04-20) - - -### Bug Fixes - -* **Wide search bar:** Fix patching `19.16.39` ([433dbc3](https://github.com/ReVanced/revanced-patches/commit/433dbc3bf81823369e146035c954281e84d3a436)) - -# [5.21.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.21.0-dev.6...v5.21.0-dev.7) (2025-04-20) - - -### Bug Fixes - -* **YouTube - Change start page:** Add option to always override start page on app launch ([#4832](https://github.com/ReVanced/revanced-patches/issues/4832)) ([5062e24](https://github.com/ReVanced/revanced-patches/commit/5062e24433ba38eba397438e8fde32099109d3c3)) - -# [5.21.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.21.0-dev.5...v5.21.0-dev.6) (2025-04-19) - - -### Bug Fixes - -* **YouTube - Wide search bar:** Do not force phone layout for tablet devices ([#4827](https://github.com/ReVanced/revanced-patches/issues/4827)) ([0cb38f9](https://github.com/ReVanced/revanced-patches/commit/0cb38f9f367a7fe742d8ca336150049181d637b6)) - -# [5.21.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.21.0-dev.4...v5.21.0-dev.5) (2025-04-18) - - -### Bug Fixes - -* `Hide ADB status` patch ([#4814](https://github.com/ReVanced/revanced-patches/issues/4814)) ([dc89be0](https://github.com/ReVanced/revanced-patches/commit/dc89be0e94880733f862b250d95d4848f02c594d)) - -# [5.21.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.21.0-dev.3...v5.21.0-dev.4) (2025-04-17) - - -### Bug Fixes - -* **YouTube - Disable auto captions:** Correctly hide captions with YT 20.12 ([5ecbe82](https://github.com/ReVanced/revanced-patches/commit/5ecbe823ed5197533328cc37f1de5cd1f048a217)) - -# [5.21.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.21.0-dev.2...v5.21.0-dev.3) (2025-04-16) - - -### Features - -* **X / Twitter:** Support version `10.86.0-release.0` ([#4805](https://github.com/ReVanced/revanced-patches/issues/4805)) ([655b390](https://github.com/ReVanced/revanced-patches/commit/655b39043ad77efcb4380de67c3f603666e7bc49)) - -# [5.21.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.21.0-dev.1...v5.21.0-dev.2) (2025-04-16) - - -### Features - -* Add `Hide ADB status` patch ([#4585](https://github.com/ReVanced/revanced-patches/issues/4585)) ([1ea8047](https://github.com/ReVanced/revanced-patches/commit/1ea8047aefdaa358e9af8038923ac54d68a39176)) - -# [5.21.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.20.1...v5.21.0-dev.1) (2025-04-16) - - -### Features - -* **YouTube:** Support version `20.12.46` ([#4779](https://github.com/ReVanced/revanced-patches/issues/4779)) ([703359f](https://github.com/ReVanced/revanced-patches/commit/703359f0c16b613c204cf16cf42227b628f664fa)) - -## [5.20.1](https://github.com/ReVanced/revanced-patches/compare/v5.20.0...v5.20.1) (2025-04-15) - - -### Bug Fixes - -* **Spotify - Custom theme:** Support latest app target ([#4800](https://github.com/ReVanced/revanced-patches/issues/4800)) ([03d0eb2](https://github.com/ReVanced/revanced-patches/commit/03d0eb2f8c0f3e48d53bdab38d34057f2020bb65)) - -## [5.20.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.20.0...v5.20.1-dev.1) (2025-04-15) - - -### Bug Fixes - -* **Spotify - Custom theme:** Support latest app target ([#4800](https://github.com/ReVanced/revanced-patches/issues/4800)) ([03d0eb2](https://github.com/ReVanced/revanced-patches/commit/03d0eb2f8c0f3e48d53bdab38d34057f2020bb65)) - -# [5.20.0](https://github.com/ReVanced/revanced-patches/compare/v5.19.1...v5.20.0) (2025-04-15) - - -### Bug Fixes - -* **Duolingo - Hide ads:** Support lastest app release ([#4790](https://github.com/ReVanced/revanced-patches/issues/4790)) ([215fccb](https://github.com/ReVanced/revanced-patches/commit/215fccbaf2fdd54251c46cbda106029eb304996b)) -* **Spotify - Unlock Spotify Premium:** Remove premium restriction for 'Spotify Connect' ([#4782](https://github.com/ReVanced/revanced-patches/issues/4782)) ([50f5b1a](https://github.com/ReVanced/revanced-patches/commit/50f5b1ac54372542d76e87626f00ddefb54da125)) -* **Spotify:** Fix login by replacing `Spoof signature` patch with new `Spoof package info` patch ([#4794](https://github.com/ReVanced/revanced-patches/issues/4794)) ([d639151](https://github.com/ReVanced/revanced-patches/commit/d639151641352ce651037b17fb65bd58953cd51c)) -* **YouTube - Remove background playback restrictions:** Restore PiP button functionality after screen is unlocked ([6837348](https://github.com/ReVanced/revanced-patches/commit/6837348c45156d6743a63fef8b6e045087afbda8)) - - -### Features - -* Add `Set target SDK version 34` patch (Disable edge-to-edge display) ([#4780](https://github.com/ReVanced/revanced-patches/issues/4780)) ([dcf6178](https://github.com/ReVanced/revanced-patches/commit/dcf6178f19f86dd1b57d54c855b8c47b086dd33a)) -* **Spotify - Custom theme:** Add option to use unmodified player background gradient ([#4741](https://github.com/ReVanced/revanced-patches/issues/4741)) ([0ee3693](https://github.com/ReVanced/revanced-patches/commit/0ee36939f43f325afca37119db1cf1af3b63be27)) -* **YouTube - Swipe controls:** Add option to change volume swipe sensitivity (step size) ([#4557](https://github.com/ReVanced/revanced-patches/issues/4557)) ([8957325](https://github.com/ReVanced/revanced-patches/commit/8957325d78eb42e087c4c1ff0abedb2146aa4423)) - -# [5.20.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.20.0-dev.6...v5.20.0-dev.7) (2025-04-15) - - -### Bug Fixes - -* **Spotify:** Fix login by replacing `Spoof signature` patch with new `Spoof package info` patch ([#4794](https://github.com/ReVanced/revanced-patches/issues/4794)) ([d639151](https://github.com/ReVanced/revanced-patches/commit/d639151641352ce651037b17fb65bd58953cd51c)) - -# [5.20.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.20.0-dev.5...v5.20.0-dev.6) (2025-04-15) - - -### Bug Fixes - -* **Duolingo - Hide ads:** Support lastest app release ([#4790](https://github.com/ReVanced/revanced-patches/issues/4790)) ([215fccb](https://github.com/ReVanced/revanced-patches/commit/215fccbaf2fdd54251c46cbda106029eb304996b)) - -# [5.20.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.20.0-dev.4...v5.20.0-dev.5) (2025-04-14) - - -### Features - -* **YouTube - Swipe controls:** Add option to change volume swipe sensitivity (step size) ([#4557](https://github.com/ReVanced/revanced-patches/issues/4557)) ([8957325](https://github.com/ReVanced/revanced-patches/commit/8957325d78eb42e087c4c1ff0abedb2146aa4423)) - -# [5.20.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.20.0-dev.3...v5.20.0-dev.4) (2025-04-14) - - -### Bug Fixes - -* **Spotify - Unlock Spotify Premium:** Remove premium restriction for 'Spotify Connect' ([#4782](https://github.com/ReVanced/revanced-patches/issues/4782)) ([50f5b1a](https://github.com/ReVanced/revanced-patches/commit/50f5b1ac54372542d76e87626f00ddefb54da125)) - -# [5.20.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.20.0-dev.2...v5.20.0-dev.3) (2025-04-13) - - -### Bug Fixes - -* **YouTube - Remove background playback restrictions:** Restore PiP button functionality after screen is unlocked ([6837348](https://github.com/ReVanced/revanced-patches/commit/6837348c45156d6743a63fef8b6e045087afbda8)) - -# [5.20.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.20.0-dev.1...v5.20.0-dev.2) (2025-04-13) - - -### Features - -* **Spotify - Custom theme:** Add option to use unmodified player background gradient ([#4741](https://github.com/ReVanced/revanced-patches/issues/4741)) ([0ee3693](https://github.com/ReVanced/revanced-patches/commit/0ee36939f43f325afca37119db1cf1af3b63be27)) - -# [5.20.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.19.1...v5.20.0-dev.1) (2025-04-13) - - -### Features - -* Add `Set target SDK version 34` patch (Disable edge-to-edge display) ([#4780](https://github.com/ReVanced/revanced-patches/issues/4780)) ([dcf6178](https://github.com/ReVanced/revanced-patches/commit/dcf6178f19f86dd1b57d54c855b8c47b086dd33a)) - -## [5.19.1](https://github.com/ReVanced/revanced-patches/compare/v5.19.0...v5.19.1) (2025-04-12) - - -### Bug Fixes - -* **Google Photos:** Restore patching with ReVanced Manager ([#4773](https://github.com/ReVanced/revanced-patches/issues/4773)) ([3e18e86](https://github.com/ReVanced/revanced-patches/commit/3e18e868bbd9fd0600fe81a7fe8767b4bd89a00e)) -* **Spotify:** Restore patching with ReVanced Manager ([#4769](https://github.com/ReVanced/revanced-patches/issues/4769)) ([89d44da](https://github.com/ReVanced/revanced-patches/commit/89d44da171c3f56f13112d1d82bc4ea4a56c7c06)) - -## [5.19.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.19.1-dev.1...v5.19.1-dev.2) (2025-04-12) - - -### Bug Fixes - -* **Google Photos:** Restore patching with ReVanced Manager ([#4773](https://github.com/ReVanced/revanced-patches/issues/4773)) ([3e18e86](https://github.com/ReVanced/revanced-patches/commit/3e18e868bbd9fd0600fe81a7fe8767b4bd89a00e)) - -## [5.19.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.19.0...v5.19.1-dev.1) (2025-04-12) - - -### Bug Fixes - -* **Spotify:** Restore patching with ReVanced Manager ([#4769](https://github.com/ReVanced/revanced-patches/issues/4769)) ([89d44da](https://github.com/ReVanced/revanced-patches/commit/89d44da171c3f56f13112d1d82bc4ea4a56c7c06)) - -# [5.19.0](https://github.com/ReVanced/revanced-patches/compare/v5.18.0...v5.19.0) (2025-04-12) - - -### Bug Fixes - -* **Google Photos - Restore hidden 'Back up while charging' toggle:** Constrain to last working app target ([#4761](https://github.com/ReVanced/revanced-patches/issues/4761)) ([152bb7c](https://github.com/ReVanced/revanced-patches/commit/152bb7c3ee7cf36bc07460e7a3444631ec540441)) -* **Google Photos:** Remove obsolete non functional patch `Restore hidden 'Back up while charging' toggle` ([#4764](https://github.com/ReVanced/revanced-patches/issues/4764)) ([56e48f4](https://github.com/ReVanced/revanced-patches/commit/56e48f4c89da51f81ff11a79a164eaa5b440690e)) -* **Spotify - Custom theme:** Override more color resources ([#4690](https://github.com/ReVanced/revanced-patches/issues/4690)) ([d7a7a0b](https://github.com/ReVanced/revanced-patches/commit/d7a7a0b982dbafa181b04f984a5f7618fb067c2a)) -* **Spotify - Unlock Spotify Premium:** Remove restrictions for Google voice assistant ([#4702](https://github.com/ReVanced/revanced-patches/issues/4702)) ([106202f](https://github.com/ReVanced/revanced-patches/commit/106202f9ebb7699c4ba4ae46b82133e35f1ac6b9)) -* **Spotify:** Remove ads sections from home ([#4722](https://github.com/ReVanced/revanced-patches/issues/4722)) ([0b9a5e7](https://github.com/ReVanced/revanced-patches/commit/0b9a5e7f89a89d971762b3539166d4f145111481)) -* **Twitter - Hide recommended users:** Make hiding work again by filtering for new entryId prefix ([#4456](https://github.com/ReVanced/revanced-patches/issues/4456)) ([ff846b0](https://github.com/ReVanced/revanced-patches/commit/ff846b0b7ef5060caaffedb08c1f901172f5b2d1)) -* **YouTube - Hide layout components:** Do not hide video description music/game links if hide horizontal shelves is enabled ([3864f35](https://github.com/ReVanced/revanced-patches/commit/3864f3550153617e23ad9979fb543d8a7fb4dc0a)) -* **YouTube - Hide player flyout menu items:** Show more detailed summary text for 'Hide Audio track' if using Android spoof client ([#4756](https://github.com/ReVanced/revanced-patches/issues/4756)) ([b67bbb2](https://github.com/ReVanced/revanced-patches/commit/b67bbb299669336addb68cf52a8ce5b39c68cec0)) -* **YouTube - Remove background playback restrictions:** Do not show media controls when playing Shorts from the feed ([2ed675c](https://github.com/ReVanced/revanced-patches/commit/2ed675cdd058fb5876381a9d30dee5263f6b2e26)) -* **YouTube - Return YouTube Dislike:** Correctly update label after disliking a Short with 20.07 ([0bb3e32](https://github.com/ReVanced/revanced-patches/commit/0bb3e32244fa10809aee5c4e549f77ed4054537e)) -* **YouTube - Return YouTube Dislike:** Fix inconsistent label after disliking a Short ([ea92a2e](https://github.com/ReVanced/revanced-patches/commit/ea92a2e36c7aab3bd115f7d0ec40467179485b32)) -* **YouTube - Seekbar:** Correctly hide the feed seekbar with target 20.07 ([ddc6e4c](https://github.com/ReVanced/revanced-patches/commit/ddc6e4c34fe35fa34bd859bf34e25645a23dbdc9)) -* **YouTube:** Combine multiple seekbar patches into a single patch ([#4705](https://github.com/ReVanced/revanced-patches/issues/4705)) ([503b7eb](https://github.com/ReVanced/revanced-patches/commit/503b7eb8d413ef7f248394f128f3b2a6f3192ba6)) - - -### Features - -* **Angulus:** Add `Hide ads` patch ([#4604](https://github.com/ReVanced/revanced-patches/issues/4604)) ([87c86b5](https://github.com/ReVanced/revanced-patches/commit/87c86b53a91b0054ac892a3f02bbe7bf83bbf813)) -* **Messenger:** Add `Remove Meta AI tab` patch ([#4726](https://github.com/ReVanced/revanced-patches/issues/4726)) ([e3fad97](https://github.com/ReVanced/revanced-patches/commit/e3fad97484d7eb962aeb53d44a0047b34a881071)) -* **Photomath:** Support latest version ([#4672](https://github.com/ReVanced/revanced-patches/issues/4672)) ([8e16483](https://github.com/ReVanced/revanced-patches/commit/8e1648322948151e4565fb0d86e0f37d0a02d73f)) -* **Proton Mail:** Add `Remove 'Sent from' signature` patch ([#4514](https://github.com/ReVanced/revanced-patches/issues/4514)) ([34c14c9](https://github.com/ReVanced/revanced-patches/commit/34c14c9b443092824d035afd77adb678c6f89e3e)) -* **Spotify:** Add `Check environment` patch ([#4765](https://github.com/ReVanced/revanced-patches/issues/4765)) ([6d7101c](https://github.com/ReVanced/revanced-patches/commit/6d7101cb2e546e01a934eff9cad1264367aeafe3)) -* **Spotify:** Add limited support for version `8.6.98.900` (last version that supports Kenwood and Pioneer car stereos) ([#4750](https://github.com/ReVanced/revanced-patches/issues/4750)) ([a3fde87](https://github.com/ReVanced/revanced-patches/commit/a3fde874af993125ba7a741820e7bd48e3641b84)) -* **Strava - Disable subscription suggestions:** Make compatible with latest version ([#4739](https://github.com/ReVanced/revanced-patches/issues/4739)) ([649a2c0](https://github.com/ReVanced/revanced-patches/commit/649a2c06161c72a2040b179dbed5b415847d7527)) -* **YouTube - Settings:** Add icons to the ReVanced settings ([#4496](https://github.com/ReVanced/revanced-patches/issues/4496)) ([d0c85f0](https://github.com/ReVanced/revanced-patches/commit/d0c85f044083d720c63a8ea4ff15d42eefeb9db7)) - -# [5.19.0-dev.17](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.16...v5.19.0-dev.17) (2025-04-12) - - -### Features - -* **Spotify:** Add `Check environment` patch ([#4765](https://github.com/ReVanced/revanced-patches/issues/4765)) ([6d7101c](https://github.com/ReVanced/revanced-patches/commit/6d7101cb2e546e01a934eff9cad1264367aeafe3)) - -# [5.19.0-dev.16](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.15...v5.19.0-dev.16) (2025-04-11) - - -### Bug Fixes - -* **Google Photos:** Remove obsolete non functional patch `Restore hidden 'Back up while charging' toggle` ([#4764](https://github.com/ReVanced/revanced-patches/issues/4764)) ([56e48f4](https://github.com/ReVanced/revanced-patches/commit/56e48f4c89da51f81ff11a79a164eaa5b440690e)) - -# [5.19.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.14...v5.19.0-dev.15) (2025-04-11) - - -### Bug Fixes - -* **Google Photos - Restore hidden 'Back up while charging' toggle:** Constrain to last working app target ([#4761](https://github.com/ReVanced/revanced-patches/issues/4761)) ([152bb7c](https://github.com/ReVanced/revanced-patches/commit/152bb7c3ee7cf36bc07460e7a3444631ec540441)) - -# [5.19.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.13...v5.19.0-dev.14) (2025-04-11) - - -### Bug Fixes - -* **Spotify - Unlock Spotify Premium:** Remove restrictions for Google voice assistant ([#4702](https://github.com/ReVanced/revanced-patches/issues/4702)) ([106202f](https://github.com/ReVanced/revanced-patches/commit/106202f9ebb7699c4ba4ae46b82133e35f1ac6b9)) - -# [5.19.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.12...v5.19.0-dev.13) (2025-04-11) - - -### Features - -* **Spotify:** Add limited support for version `8.6.98.900` (last version that supports Kenwood and Pioneer car stereos) ([#4750](https://github.com/ReVanced/revanced-patches/issues/4750)) ([a3fde87](https://github.com/ReVanced/revanced-patches/commit/a3fde874af993125ba7a741820e7bd48e3641b84)) - -# [5.19.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.11...v5.19.0-dev.12) (2025-04-11) - - -### Features - -* **Strava - Disable subscription suggestions:** Make compatible with latest version ([#4739](https://github.com/ReVanced/revanced-patches/issues/4739)) ([649a2c0](https://github.com/ReVanced/revanced-patches/commit/649a2c06161c72a2040b179dbed5b415847d7527)) - -# [5.19.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.10...v5.19.0-dev.11) (2025-04-10) - - -### Features - -* **Messenger:** Add `Remove Meta AI tab` patch ([#4726](https://github.com/ReVanced/revanced-patches/issues/4726)) ([e3fad97](https://github.com/ReVanced/revanced-patches/commit/e3fad97484d7eb962aeb53d44a0047b34a881071)) - -# [5.19.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.9...v5.19.0-dev.10) (2025-04-10) - - -### Bug Fixes - -* **YouTube - Hide layout components:** Do not hide video description music/game links if hide horizontal shelves is enabled ([3864f35](https://github.com/ReVanced/revanced-patches/commit/3864f3550153617e23ad9979fb543d8a7fb4dc0a)) - -# [5.19.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.8...v5.19.0-dev.9) (2025-04-10) - - -### Bug Fixes - -* **YouTube - Hide player flyout menu items:** Show more detailed summary text for 'Hide Audio track' if using Android spoof client ([#4756](https://github.com/ReVanced/revanced-patches/issues/4756)) ([b67bbb2](https://github.com/ReVanced/revanced-patches/commit/b67bbb299669336addb68cf52a8ce5b39c68cec0)) - -# [5.19.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.7...v5.19.0-dev.8) (2025-04-09) - - -### Bug Fixes - -* **YouTube - Return YouTube Dislike:** Fix inconsistent label after disliking a Short ([ea92a2e](https://github.com/ReVanced/revanced-patches/commit/ea92a2e36c7aab3bd115f7d0ec40467179485b32)) - -# [5.19.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.6...v5.19.0-dev.7) (2025-04-07) - - -### Bug Fixes - -* **YouTube - Return YouTube Dislike:** Correctly update label after disliking a Short with 20.07 ([0bb3e32](https://github.com/ReVanced/revanced-patches/commit/0bb3e32244fa10809aee5c4e549f77ed4054537e)) - -# [5.19.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.5...v5.19.0-dev.6) (2025-04-04) - - -### Bug Fixes - -* **Spotify:** Remove ads sections from home ([#4722](https://github.com/ReVanced/revanced-patches/issues/4722)) ([0b9a5e7](https://github.com/ReVanced/revanced-patches/commit/0b9a5e7f89a89d971762b3539166d4f145111481)) - -# [5.19.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.4...v5.19.0-dev.5) (2025-04-02) - - -### Bug Fixes - -* **Spotify - Custom theme:** Override more color resources ([#4690](https://github.com/ReVanced/revanced-patches/issues/4690)) ([d7a7a0b](https://github.com/ReVanced/revanced-patches/commit/d7a7a0b982dbafa181b04f984a5f7618fb067c2a)) - -# [5.19.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.3...v5.19.0-dev.4) (2025-04-02) - - -### Bug Fixes - -* **YouTube - Seekbar:** Correctly hide the feed seekbar with target 20.07 ([ddc6e4c](https://github.com/ReVanced/revanced-patches/commit/ddc6e4c34fe35fa34bd859bf34e25645a23dbdc9)) - -# [5.19.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.2...v5.19.0-dev.3) (2025-04-02) - - -### Features - -* **Proton Mail:** Add `Remove 'Sent from' signature` patch ([#4514](https://github.com/ReVanced/revanced-patches/issues/4514)) ([34c14c9](https://github.com/ReVanced/revanced-patches/commit/34c14c9b443092824d035afd77adb678c6f89e3e)) - -# [5.19.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.1...v5.19.0-dev.2) (2025-04-02) - - -### Features - -* **YouTube - Settings:** Add icons to the ReVanced settings ([#4496](https://github.com/ReVanced/revanced-patches/issues/4496)) ([d0c85f0](https://github.com/ReVanced/revanced-patches/commit/d0c85f044083d720c63a8ea4ff15d42eefeb9db7)) - -# [5.19.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.18.1-dev.2...v5.19.0-dev.1) (2025-04-01) - - -### Bug Fixes - -* **Twitter - Hide recommended users:** Make hiding work again by filtering for new entryId prefix ([#4456](https://github.com/ReVanced/revanced-patches/issues/4456)) ([ff846b0](https://github.com/ReVanced/revanced-patches/commit/ff846b0b7ef5060caaffedb08c1f901172f5b2d1)) - - -### Features - -* **Angulus:** Add `Hide ads` patch ([#4604](https://github.com/ReVanced/revanced-patches/issues/4604)) ([87c86b5](https://github.com/ReVanced/revanced-patches/commit/87c86b53a91b0054ac892a3f02bbe7bf83bbf813)) -* **Photomath:** Support latest version ([#4672](https://github.com/ReVanced/revanced-patches/issues/4672)) ([8e16483](https://github.com/ReVanced/revanced-patches/commit/8e1648322948151e4565fb0d86e0f37d0a02d73f)) - -## [5.18.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.18.1-dev.1...v5.18.1-dev.2) (2025-04-01) - - -### Bug Fixes - -* **YouTube:** Combine multiple seekbar patches into a single patch ([#4705](https://github.com/ReVanced/revanced-patches/issues/4705)) ([503b7eb](https://github.com/ReVanced/revanced-patches/commit/503b7eb8d413ef7f248394f128f3b2a6f3192ba6)) - -## [5.18.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.18.0...v5.18.1-dev.1) (2025-03-31) - - -### Bug Fixes - -* **YouTube - Remove background playback restrictions:** Do not show media controls when playing Shorts from the feed ([2ed675c](https://github.com/ReVanced/revanced-patches/commit/2ed675cdd058fb5876381a9d30dee5263f6b2e26)) - -# [5.18.0](https://github.com/ReVanced/revanced-patches/compare/v5.17.0...v5.18.0) (2025-03-28) - - -### Bug Fixes - -* **Spotify:** Ignore optional attributes if not present ([#4688](https://github.com/ReVanced/revanced-patches/issues/4688)) ([84f5854](https://github.com/ReVanced/revanced-patches/commit/84f585492e4be3604c6c7680ffb3bebcea5a675f)) - - -### Features - -* **YouTube:** Support version `20.07.39` ([#4677](https://github.com/ReVanced/revanced-patches/issues/4677)) ([c1379f6](https://github.com/ReVanced/revanced-patches/commit/c1379f6e520c683d2c9d6a490a69ca542168b3b3)) - -# [5.18.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.18.0-dev.1...v5.18.0-dev.2) (2025-03-28) - - -### Bug Fixes - -* **Spotify:** Ignore optional attributes if not present ([#4688](https://github.com/ReVanced/revanced-patches/issues/4688)) ([84f5854](https://github.com/ReVanced/revanced-patches/commit/84f585492e4be3604c6c7680ffb3bebcea5a675f)) - -# [5.18.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.17.0...v5.18.0-dev.1) (2025-03-28) - - -### Features - -* **YouTube:** Support version `20.07.39` ([#4677](https://github.com/ReVanced/revanced-patches/issues/4677)) ([c1379f6](https://github.com/ReVanced/revanced-patches/commit/c1379f6e520c683d2c9d6a490a69ca542168b3b3)) - -# [5.17.0](https://github.com/ReVanced/revanced-patches/compare/v5.16.1...v5.17.0) (2025-03-28) - - -### Bug Fixes - -* **Facebook - Hide 'Sponsored Stories':** Constrain patch to latest compatible version ([#4657](https://github.com/ReVanced/revanced-patches/issues/4657)) ([46bd1c8](https://github.com/ReVanced/revanced-patches/commit/46bd1c829acd5f83600025e0ceb7d482ae80be69)) -* **Spotify - Unlock Premium:** Override additional attributes ([#4651](https://github.com/ReVanced/revanced-patches/issues/4651)) ([568b40d](https://github.com/ReVanced/revanced-patches/commit/568b40da9692eae9039bbb3cec513a61ca627c24)) -* **Spotify - Unlock Premium:** Use correct patch description convention ([a486522](https://github.com/ReVanced/revanced-patches/commit/a4865228f8481d2efc8fbf4e90902a03289d9a3f)) -* **X / Twitter:** Constrain patches to latest compatible versions ([#4683](https://github.com/ReVanced/revanced-patches/issues/4683)) ([f579728](https://github.com/ReVanced/revanced-patches/commit/f5797289f45186052537982c7f5db6f2b0769aee)) -* **YouTube - Navigation buttons:** Add user dialog message to 'Disable translucent status bar' ([a4a0e68](https://github.com/ReVanced/revanced-patches/commit/a4a0e6869e23d15ee09262460f4e290c90629eeb)) - - -### Features - -* **Spotify - Unlock Premium:** Disable the "Spotify Premium" upsell experiment in context menus ([9a10ee4](https://github.com/ReVanced/revanced-patches/commit/9a10ee4d22fb53da2012a182e038749d3ad72377)) - -# [5.17.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.17.0-dev.3...v5.17.0-dev.4) (2025-03-28) - - -### Bug Fixes - -* **X / Twitter:** Constrain patches to latest compatible versions ([#4683](https://github.com/ReVanced/revanced-patches/issues/4683)) ([f579728](https://github.com/ReVanced/revanced-patches/commit/f5797289f45186052537982c7f5db6f2b0769aee)) - -# [5.17.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.17.0-dev.2...v5.17.0-dev.3) (2025-03-28) - - -### Bug Fixes - -* **Spotify - Unlock Premium:** Override additional attributes ([#4651](https://github.com/ReVanced/revanced-patches/issues/4651)) ([568b40d](https://github.com/ReVanced/revanced-patches/commit/568b40da9692eae9039bbb3cec513a61ca627c24)) - -# [5.17.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.17.0-dev.1...v5.17.0-dev.2) (2025-03-27) - - -### Bug Fixes - -* **YouTube - Navigation buttons:** Add user dialog message to 'Disable translucent status bar' ([a4a0e68](https://github.com/ReVanced/revanced-patches/commit/a4a0e6869e23d15ee09262460f4e290c90629eeb)) - -# [5.17.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.16.2-dev.1...v5.17.0-dev.1) (2025-03-27) - - -### Bug Fixes - -* **Spotify - Unlock Premium:** Use correct patch description convention ([a486522](https://github.com/ReVanced/revanced-patches/commit/a4865228f8481d2efc8fbf4e90902a03289d9a3f)) - - -### Features - -* **Spotify - Unlock Premium:** Disable the "Spotify Premium" upsell experiment in context menus ([9a10ee4](https://github.com/ReVanced/revanced-patches/commit/9a10ee4d22fb53da2012a182e038749d3ad72377)) - -## [5.16.2-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.16.1...v5.16.2-dev.1) (2025-03-26) - - -### Bug Fixes - -* **Facebook - Hide 'Sponsored Stories':** Constrain patch to latest compatible version ([#4657](https://github.com/ReVanced/revanced-patches/issues/4657)) ([46bd1c8](https://github.com/ReVanced/revanced-patches/commit/46bd1c829acd5f83600025e0ceb7d482ae80be69)) - -## [5.16.1](https://github.com/ReVanced/revanced-patches/compare/v5.16.0...v5.16.1) (2025-03-26) - - -### Bug Fixes - -* **Spotify - Unlock Premium:** Override streaming attribute attempting to fix streaming issues ([06be36c](https://github.com/ReVanced/revanced-patches/commit/06be36cddf3430b4179dff696b3d15718cd6963b)) - -## [5.16.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.16.0...v5.16.1-dev.1) (2025-03-26) - - -### Bug Fixes - -* **Spotify - Unlock Premium:** Override streaming attribute attempting to fix streaming issues ([06be36c](https://github.com/ReVanced/revanced-patches/commit/06be36cddf3430b4179dff696b3d15718cd6963b)) - -# [5.16.0](https://github.com/ReVanced/revanced-patches/compare/v5.15.0...v5.16.0) (2025-03-26) - - -### Bug Fixes - -* **YouTube - Settings:** System navigation bar is located above the settings ui on Android 15+ ([f7497be](https://github.com/ReVanced/revanced-patches/commit/f7497be2c5e4abcde6eb55b84955124a28f55cae)) - - -### Features - -* **Spotify:** Add `Unlock premium` patch ([#4644](https://github.com/ReVanced/revanced-patches/issues/4644)) ([f048c50](https://github.com/ReVanced/revanced-patches/commit/f048c50e56fc1f5a5c607860be4206ef83b528fe)) -* **YouTube - Comments:** Add `Hide AI Comments summary` ([#4634](https://github.com/ReVanced/revanced-patches/issues/4634)) ([e9b7f26](https://github.com/ReVanced/revanced-patches/commit/e9b7f263f739bd130f6ea79913851a52355977c5)) -* **YouTube - Video description:** Add `Hide AI-generated video summary` ([#4636](https://github.com/ReVanced/revanced-patches/issues/4636)) ([521fd48](https://github.com/ReVanced/revanced-patches/commit/521fd48602432ab436d8711c19d7130b2b05af12)) - -# [5.16.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.16.0-dev.1...v5.16.0-dev.2) (2025-03-26) - - -### Features - -* **Spotify:** Add `Unlock premium` patch ([#4644](https://github.com/ReVanced/revanced-patches/issues/4644)) ([f048c50](https://github.com/ReVanced/revanced-patches/commit/f048c50e56fc1f5a5c607860be4206ef83b528fe)) - -# [5.16.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.15.0...v5.16.0-dev.1) (2025-03-24) - - -### Bug Fixes - -* **YouTube - Settings:** System navigation bar is located above the settings ui on Android 15+ ([f7497be](https://github.com/ReVanced/revanced-patches/commit/f7497be2c5e4abcde6eb55b84955124a28f55cae)) - - -### Features - -* **YouTube - Comments:** Add `Hide AI Comments summary` ([#4634](https://github.com/ReVanced/revanced-patches/issues/4634)) ([e9b7f26](https://github.com/ReVanced/revanced-patches/commit/e9b7f263f739bd130f6ea79913851a52355977c5)) -* **YouTube - Video description:** Add `Hide AI-generated video summary` ([#4636](https://github.com/ReVanced/revanced-patches/issues/4636)) ([521fd48](https://github.com/ReVanced/revanced-patches/commit/521fd48602432ab436d8711c19d7130b2b05af12)) - -# [5.15.0](https://github.com/ReVanced/revanced-patches/compare/v5.14.0...v5.15.0) (2025-03-21) - - -### Bug Fixes - -* **YouTube - Spoof app version:** Change oldest spoof target to 19.01.34 ([5012439](https://github.com/ReVanced/revanced-patches/commit/5012439a8e53b2a4ab5e85c47976e1ab28a51208)) -* **YouTube - Spoof app version:** Remove broken spoof targets that YouTube no longer supports ([#4610](https://github.com/ReVanced/revanced-patches/issues/4610)) ([883fbe7](https://github.com/ReVanced/revanced-patches/commit/883fbe71233c57cb1241e57c122b43f40722acc7)) -* **YouTube:** Do not show restart prompt more than once if setting change is canceled ([49797fe](https://github.com/ReVanced/revanced-patches/commit/49797fe8d0c4a0981ef621a31356c4315ae3777b)) - - -### Features - -* **YouTube - SponsorBlock:** Add opacity setting to category segment colors ([#4582](https://github.com/ReVanced/revanced-patches/issues/4582)) ([6e8ffba](https://github.com/ReVanced/revanced-patches/commit/6e8ffbade9e03658f725622631e44dabf2995861)) - -# [5.15.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.15.0-dev.3...v5.15.0-dev.4) (2025-03-21) - - -### Bug Fixes - -* **YouTube - Spoof app version:** Change oldest spoof target to 19.01.34 ([5012439](https://github.com/ReVanced/revanced-patches/commit/5012439a8e53b2a4ab5e85c47976e1ab28a51208)) - -# [5.15.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.15.0-dev.2...v5.15.0-dev.3) (2025-03-20) - - -### Bug Fixes - -* **YouTube:** Do not show restart prompt more than once if setting change is canceled ([49797fe](https://github.com/ReVanced/revanced-patches/commit/49797fe8d0c4a0981ef621a31356c4315ae3777b)) - -# [5.15.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.15.0-dev.1...v5.15.0-dev.2) (2025-03-19) - - -### Bug Fixes - -* **YouTube - Spoof app version:** Remove broken spoof targets that YouTube no longer supports ([#4610](https://github.com/ReVanced/revanced-patches/issues/4610)) ([883fbe7](https://github.com/ReVanced/revanced-patches/commit/883fbe71233c57cb1241e57c122b43f40722acc7)) - -# [5.15.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.14.0...v5.15.0-dev.1) (2025-03-19) - - -### Features - -* **YouTube - SponsorBlock:** Add opacity setting to category segment colors ([#4582](https://github.com/ReVanced/revanced-patches/issues/4582)) ([6e8ffba](https://github.com/ReVanced/revanced-patches/commit/6e8ffbade9e03658f725622631e44dabf2995861)) - -# [5.14.0](https://github.com/ReVanced/revanced-patches/compare/v5.13.0...v5.14.0) (2025-03-09) - - -### Bug Fixes - -* **Boost for reddit - Client spoof:** Use a different user agent to combat Reddit's API issues ([5d3c817](https://github.com/ReVanced/revanced-patches/commit/5d3c8175b34a3f6ae2732b25db0851773a8c000d)) -* **YouTube - Change form factor:** Restore Automotive form factor watch history menu, channel pages, and community posts ([#4541](https://github.com/ReVanced/revanced-patches/issues/4541)) ([aa5c001](https://github.com/ReVanced/revanced-patches/commit/aa5c001968446e5270c756256724e917009612cd)) -* **YouTube - Hide ads:** Hide new type of buttoned ad ([#4528](https://github.com/ReVanced/revanced-patches/issues/4528)) ([4387a7b](https://github.com/ReVanced/revanced-patches/commit/4387a7b131f49729e902e008bb4cec073635c040)) -* **YouTube - Hide layout components:** Do not hide Movie/Courses start page content if 'Hide horizontal shelves' is enabled ([62a6164](https://github.com/ReVanced/revanced-patches/commit/62a6164b88b64200b517a5ba6b800d8214dbbad8)) -* **YouTube - Theme:** Resolve dark mode startup crash with Android 9.0 ([741c2d5](https://github.com/ReVanced/revanced-patches/commit/741c2d59406f5d602554bb3a3c0b8982f42848b4)) -* **YouTube:** Change language settings menu to use native language names ([#4568](https://github.com/ReVanced/revanced-patches/issues/4568)) ([6f3f8fd](https://github.com/ReVanced/revanced-patches/commit/6f3f8fdce05501e4fa4423c2170a916fbea3b199)) -* **YouTube:** Combine `Restore old video quality menu` and `Remember video quality` into `Video quality` patch ([#4552](https://github.com/ReVanced/revanced-patches/issues/4552)) ([ee67b76](https://github.com/ReVanced/revanced-patches/commit/ee67b763d5c5947a5b1ef4420b1efa820ed6af83)) - - -### Features - -* **Infinity for Reddit:** Add support for package name on IzzyOnDroid ([#4554](https://github.com/ReVanced/revanced-patches/issues/4554)) ([cf9f959](https://github.com/ReVanced/revanced-patches/commit/cf9f959923076c10a7f0a29f6ba277f5a055ec07)) -* **Spotify:** Add `Spoof signature` patch ([#4576](https://github.com/ReVanced/revanced-patches/issues/4576)) ([3646c70](https://github.com/ReVanced/revanced-patches/commit/3646c70556b67a6b7ecf9b86869ebf03c3611333)) -* **YouTube - Remember video quality:** Add separate Shorts default quality settings ([#4543](https://github.com/ReVanced/revanced-patches/issues/4543)) ([88142ab](https://github.com/ReVanced/revanced-patches/commit/88142ab464192b564b1b8d56a6b45663f77f5e00)) - -# [5.14.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.8...v5.14.0-dev.9) (2025-03-09) - - -### Features - -* **Spotify:** Add `Spoof signature` patch ([#4576](https://github.com/ReVanced/revanced-patches/issues/4576)) ([3646c70](https://github.com/ReVanced/revanced-patches/commit/3646c70556b67a6b7ecf9b86869ebf03c3611333)) - -# [5.14.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.7...v5.14.0-dev.8) (2025-03-09) - - -### Bug Fixes - -* **YouTube - Theme:** Resolve dark mode startup crash with Android 9.0 ([741c2d5](https://github.com/ReVanced/revanced-patches/commit/741c2d59406f5d602554bb3a3c0b8982f42848b4)) - -# [5.14.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.6...v5.14.0-dev.7) (2025-03-08) - - -### Bug Fixes - -* **YouTube:** Change language settings menu to use native language names ([#4568](https://github.com/ReVanced/revanced-patches/issues/4568)) ([6f3f8fd](https://github.com/ReVanced/revanced-patches/commit/6f3f8fdce05501e4fa4423c2170a916fbea3b199)) - -# [5.14.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.5...v5.14.0-dev.6) (2025-03-07) - - -### Bug Fixes - -* **YouTube - Hide layout components:** Do not hide Movie/Courses start page content if 'Hide horizontal shelves' is enabled ([62a6164](https://github.com/ReVanced/revanced-patches/commit/62a6164b88b64200b517a5ba6b800d8214dbbad8)) - -# [5.14.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.4...v5.14.0-dev.5) (2025-03-06) - - -### Features - -* **Infinity for Reddit:** Add support for package name on IzzyOnDroid ([#4554](https://github.com/ReVanced/revanced-patches/issues/4554)) ([cf9f959](https://github.com/ReVanced/revanced-patches/commit/cf9f959923076c10a7f0a29f6ba277f5a055ec07)) - -# [5.14.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.3...v5.14.0-dev.4) (2025-03-06) - - -### Bug Fixes - -* **YouTube:** Combine `Restore old video quality menu` and `Remember video quality` into `Video quality` patch ([#4552](https://github.com/ReVanced/revanced-patches/issues/4552)) ([ee67b76](https://github.com/ReVanced/revanced-patches/commit/ee67b763d5c5947a5b1ef4420b1efa820ed6af83)) - -# [5.14.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.2...v5.14.0-dev.3) (2025-03-06) - - -### Bug Fixes - -* **Boost for reddit - Client spoof:** Use a different user agent to combat Reddit's API issues ([5d3c817](https://github.com/ReVanced/revanced-patches/commit/5d3c8175b34a3f6ae2732b25db0851773a8c000d)) - -# [5.14.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.1...v5.14.0-dev.2) (2025-03-06) - - -### Bug Fixes - -* **YouTube - Hide ads:** Hide new type of buttoned ad ([#4528](https://github.com/ReVanced/revanced-patches/issues/4528)) ([4387a7b](https://github.com/ReVanced/revanced-patches/commit/4387a7b131f49729e902e008bb4cec073635c040)) - -# [5.14.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.13.1-dev.1...v5.14.0-dev.1) (2025-03-06) - - -### Features - -* **YouTube - Remember video quality:** Add separate Shorts default quality settings ([#4543](https://github.com/ReVanced/revanced-patches/issues/4543)) ([88142ab](https://github.com/ReVanced/revanced-patches/commit/88142ab464192b564b1b8d56a6b45663f77f5e00)) - -## [5.13.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.13.0...v5.13.1-dev.1) (2025-03-06) - - -### Bug Fixes - -* **YouTube - Change form factor:** Restore Automotive form factor watch history menu, channel pages, and community posts ([#4541](https://github.com/ReVanced/revanced-patches/issues/4541)) ([aa5c001](https://github.com/ReVanced/revanced-patches/commit/aa5c001968446e5270c756256724e917009612cd)) - -# [5.13.0](https://github.com/ReVanced/revanced-patches/compare/v5.12.0...v5.13.0) (2025-03-03) - - -### Bug Fixes - -* **TikTok:** Resolve startup app crash ([18c0fc2](https://github.com/ReVanced/revanced-patches/commit/18c0fc2a7f186f50a904fd25dbaa739abdd24993)) -* **TikTok:** Resolve startup app crash ([6466398](https://github.com/ReVanced/revanced-patches/commit/64663983b84de1f28636205f61bf0a24c83968d1)) -* **TikTok:** Resolve startup app crash ([c14bc24](https://github.com/ReVanced/revanced-patches/commit/c14bc244550de30eca975ca7c09e8eb0c47534b5)) -* **TikTok:** Resolve startup app crash ([d700076](https://github.com/ReVanced/revanced-patches/commit/d7000768a5e5a688c9f4e48858ac34e352222c1e)) -* **YouTube - Copy video URL:** Use correct button ordering ([5e622cc](https://github.com/ReVanced/revanced-patches/commit/5e622ccf66d34af31c6026fa7f4d332460c6ecb0)) -* **YouTube - Hide filter bar:** Fix `Hide in feed` not working in subscriptions feed ([#4512](https://github.com/ReVanced/revanced-patches/issues/4512)) ([634d0ee](https://github.com/ReVanced/revanced-patches/commit/634d0ee12e31491c7ee1d4ceb002daf8366a3c15)) -* **YouTube - Hide layout components:** Do not hide 'Show anyway' button in search results ([4ac8854](https://github.com/ReVanced/revanced-patches/commit/4ac8854b99808a8957f3b0b7438e1e0cdedffbaf)) -* **YouTube - Hide player components:** Show correct end video thumbnail if `Hide end screen suggested video` is enabled ([#4502](https://github.com/ReVanced/revanced-patches/issues/4502)) ([6c4885a](https://github.com/ReVanced/revanced-patches/commit/6c4885a1d5dfff50100b01840b5552d92e83ee4a)) -* **YouTube - Hide video action buttons:** Move 'Disable Like and Subscribe glow' to action buttons settings menu ([29b265d](https://github.com/ReVanced/revanced-patches/commit/29b265d8fdaa48502650be9623bfc518a57a0bb1)) -* **YouTube - Return YouTube Dislike:** Use correct number formatting if using a different ReVanced language ([edf66f4](https://github.com/ReVanced/revanced-patches/commit/edf66f4e16d46156cb8b8e31d18cb8dbcb87737e)) -* **YouTube - Spoof app version:** Force old settings menus if spoofing to older app targets ([#4490](https://github.com/ReVanced/revanced-patches/issues/4490)) ([45e7c46](https://github.com/ReVanced/revanced-patches/commit/45e7c46dd9c70c926b8b1a97ada668f90f5f6f8c)) -* **YouTube - Spoof video streams:** Resolve playback issues with dynamic player config ([#4521](https://github.com/ReVanced/revanced-patches/issues/4521)) ([647e764](https://github.com/ReVanced/revanced-patches/commit/647e7642efc0c00db17ccb6a620d1c96ccf4afed)) -* **YouTube - Swipe controls:** Adjust the overlay text size ([#4503](https://github.com/ReVanced/revanced-patches/issues/4503)) ([6dc4bf7](https://github.com/ReVanced/revanced-patches/commit/6dc4bf75e09ed6f05534919d7b769b720043abce)) -* **YouTube:** Do not hide player controls when using double tap to skip forward ([#4487](https://github.com/ReVanced/revanced-patches/issues/4487)) ([63fe870](https://github.com/ReVanced/revanced-patches/commit/63fe870d48ca2217327b952bde241b7f16ced850)) -* **YouTube:** Fix player button fade out animations ([#4469](https://github.com/ReVanced/revanced-patches/issues/4469)) ([bf8e775](https://github.com/ReVanced/revanced-patches/commit/bf8e7759f9bdbdfef419a879fb3dd7cf0dff0098)) -* **YouTube:** Resolve button flickering when taping seekbar ([#4500](https://github.com/ReVanced/revanced-patches/issues/4500)) ([1f08047](https://github.com/ReVanced/revanced-patches/commit/1f08047b48cc9555a4887d16ec7219a55a77251f)) - - -### Features - -* **Infinity for Reddit:** Add support for Infinity for Reddit Plus ([#4511](https://github.com/ReVanced/revanced-patches/issues/4511)) ([d74732b](https://github.com/ReVanced/revanced-patches/commit/d74732b7596104321bde263201d95649e4bd0eee)) -* **NU.nl:** Add `Hide ads` and `Spoof Certificate` patch ([#4368](https://github.com/ReVanced/revanced-patches/issues/4368)) ([f3268fb](https://github.com/ReVanced/revanced-patches/commit/f3268fb03ca25fb5465e36015b6c9dec2c84a655)) -* **YouTube - Navigation buttons:** Add 'Hide notifications' setting ([#4485](https://github.com/ReVanced/revanced-patches/issues/4485)) ([506d241](https://github.com/ReVanced/revanced-patches/commit/506d2414bbc760e764e5a514b32926083d6ecb6b)) -* **YouTube - Swipe controls:** Swipe controls UI improvements ([#4422](https://github.com/ReVanced/revanced-patches/issues/4422)) ([198e4d2](https://github.com/ReVanced/revanced-patches/commit/198e4d2a2315c24a09eb9ecfefbd131a75384d2c)) - -# [5.13.0-dev.19](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.18...v5.13.0-dev.19) (2025-03-02) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Resolve playback issues with dynamic player config ([#4521](https://github.com/ReVanced/revanced-patches/issues/4521)) ([647e764](https://github.com/ReVanced/revanced-patches/commit/647e7642efc0c00db17ccb6a620d1c96ccf4afed)) - -# [5.13.0-dev.18](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.17...v5.13.0-dev.18) (2025-02-28) - - -### Features - -* **Infinity for Reddit:** Add support for Infinity for Reddit Plus ([#4511](https://github.com/ReVanced/revanced-patches/issues/4511)) ([d74732b](https://github.com/ReVanced/revanced-patches/commit/d74732b7596104321bde263201d95649e4bd0eee)) - -# [5.13.0-dev.17](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.16...v5.13.0-dev.17) (2025-02-27) - - -### Bug Fixes - -* **YouTube - Hide filter bar:** Fix `Hide in feed` not working in subscriptions feed ([#4512](https://github.com/ReVanced/revanced-patches/issues/4512)) ([634d0ee](https://github.com/ReVanced/revanced-patches/commit/634d0ee12e31491c7ee1d4ceb002daf8366a3c15)) - -# [5.13.0-dev.16](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.15...v5.13.0-dev.16) (2025-02-27) - - -### Features - -* **NU.nl:** Add `Hide ads` and `Spoof Certificate` patch ([#4368](https://github.com/ReVanced/revanced-patches/issues/4368)) ([f3268fb](https://github.com/ReVanced/revanced-patches/commit/f3268fb03ca25fb5465e36015b6c9dec2c84a655)) - -# [5.13.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.14...v5.13.0-dev.15) (2025-02-25) - - -### Bug Fixes - -* **YouTube - Hide player components:** Show correct end video thumbnail if `Hide end screen suggested video` is enabled ([#4502](https://github.com/ReVanced/revanced-patches/issues/4502)) ([6c4885a](https://github.com/ReVanced/revanced-patches/commit/6c4885a1d5dfff50100b01840b5552d92e83ee4a)) - -# [5.13.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.13...v5.13.0-dev.14) (2025-02-25) - - -### Bug Fixes - -* **YouTube - Swipe controls:** Adjust the overlay text size ([#4503](https://github.com/ReVanced/revanced-patches/issues/4503)) ([6dc4bf7](https://github.com/ReVanced/revanced-patches/commit/6dc4bf75e09ed6f05534919d7b769b720043abce)) - -# [5.13.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.12...v5.13.0-dev.13) (2025-02-24) - - -### Bug Fixes - -* **YouTube:** Resolve button flickering when taping seekbar ([#4500](https://github.com/ReVanced/revanced-patches/issues/4500)) ([1f08047](https://github.com/ReVanced/revanced-patches/commit/1f08047b48cc9555a4887d16ec7219a55a77251f)) - -# [5.13.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.11...v5.13.0-dev.12) (2025-02-24) - - -### Bug Fixes - -* **YouTube - Return YouTube Dislike:** Use correct number formatting if using a different ReVanced language ([edf66f4](https://github.com/ReVanced/revanced-patches/commit/edf66f4e16d46156cb8b8e31d18cb8dbcb87737e)) - -# [5.13.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.10...v5.13.0-dev.11) (2025-02-23) - - -### Bug Fixes - -* **TikTok:** Resolve startup app crash ([18c0fc2](https://github.com/ReVanced/revanced-patches/commit/18c0fc2a7f186f50a904fd25dbaa739abdd24993)) - -# [5.13.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.9...v5.13.0-dev.10) (2025-02-22) - - -### Bug Fixes - -* **YouTube - Copy video URL:** Use correct button ordering ([5e622cc](https://github.com/ReVanced/revanced-patches/commit/5e622ccf66d34af31c6026fa7f4d332460c6ecb0)) - -# [5.13.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.8...v5.13.0-dev.9) (2025-02-22) - - -### Bug Fixes - -* **YouTube:** Do not hide player controls when using double tap to skip forward ([#4487](https://github.com/ReVanced/revanced-patches/issues/4487)) ([63fe870](https://github.com/ReVanced/revanced-patches/commit/63fe870d48ca2217327b952bde241b7f16ced850)) - -# [5.13.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.7...v5.13.0-dev.8) (2025-02-22) - - -### Bug Fixes - -* **YouTube - Spoof app version:** Force old settings menus if spoofing to older app targets ([#4490](https://github.com/ReVanced/revanced-patches/issues/4490)) ([45e7c46](https://github.com/ReVanced/revanced-patches/commit/45e7c46dd9c70c926b8b1a97ada668f90f5f6f8c)) - -# [5.13.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.6...v5.13.0-dev.7) (2025-02-22) - - -### Bug Fixes - -* **TikTok:** Resolve startup app crash ([6466398](https://github.com/ReVanced/revanced-patches/commit/64663983b84de1f28636205f61bf0a24c83968d1)) - -# [5.13.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.5...v5.13.0-dev.6) (2025-02-21) - - -### Features - -* **YouTube - Navigation buttons:** Add 'Hide notifications' setting ([#4485](https://github.com/ReVanced/revanced-patches/issues/4485)) ([506d241](https://github.com/ReVanced/revanced-patches/commit/506d2414bbc760e764e5a514b32926083d6ecb6b)) - -# [5.13.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.4...v5.13.0-dev.5) (2025-02-19) - - -### Bug Fixes - -* **TikTok:** Resolve startup app crash ([c14bc24](https://github.com/ReVanced/revanced-patches/commit/c14bc244550de30eca975ca7c09e8eb0c47534b5)) - -# [5.13.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.3...v5.13.0-dev.4) (2025-02-19) - - -### Bug Fixes - -* **TikTok:** Resolve startup app crash ([d700076](https://github.com/ReVanced/revanced-patches/commit/d7000768a5e5a688c9f4e48858ac34e352222c1e)) - -# [5.13.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.2...v5.13.0-dev.3) (2025-02-19) - - -### Bug Fixes - -* **YouTube:** Fix player button fade out animations ([#4469](https://github.com/ReVanced/revanced-patches/issues/4469)) ([bf8e775](https://github.com/ReVanced/revanced-patches/commit/bf8e7759f9bdbdfef419a879fb3dd7cf0dff0098)) - -# [5.13.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.1...v5.13.0-dev.2) (2025-02-18) - - -### Bug Fixes - -* **YouTube - Hide video action buttons:** Move 'Disable Like and Subscribe glow' to action buttons settings menu ([29b265d](https://github.com/ReVanced/revanced-patches/commit/29b265d8fdaa48502650be9623bfc518a57a0bb1)) - -# [5.13.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.12.0...v5.13.0-dev.1) (2025-02-18) - - -### Bug Fixes - -* **YouTube - Hide layout components:** Do not hide 'Show anyway' button in search results ([4ac8854](https://github.com/ReVanced/revanced-patches/commit/4ac8854b99808a8957f3b0b7438e1e0cdedffbaf)) - - -### Features - -* **YouTube - Swipe controls:** Swipe controls UI improvements ([#4422](https://github.com/ReVanced/revanced-patches/issues/4422)) ([198e4d2](https://github.com/ReVanced/revanced-patches/commit/198e4d2a2315c24a09eb9ecfefbd131a75384d2c)) - -# [5.12.0](https://github.com/ReVanced/revanced-patches/compare/v5.11.0...v5.12.0) (2025-02-17) - - -### Bug Fixes - -* Allow changing default settings for existing app installs ([#4464](https://github.com/ReVanced/revanced-patches/issues/4464)) ([1bd7986](https://github.com/ReVanced/revanced-patches/commit/1bd7986823e774a929c8a9102a7cc96e245d5274)) -* **Windy.app:** Remove obsolete `Unlock pro` patch ([#4428](https://github.com/ReVanced/revanced-patches/issues/4428)) ([83d116e](https://github.com/ReVanced/revanced-patches/commit/83d116e8fd3935ee431cfdf0b8e095d04ee77259)) -* **YouTube - Spoof video streams:** Change default client to `Android TV` ([#4465](https://github.com/ReVanced/revanced-patches/issues/4465)) ([0412c79](https://github.com/ReVanced/revanced-patches/commit/0412c7901dc8599b6079d9c3ba26452f88af642b)) -* **YouTube:** Remove obsolete 18.x targets ([#4454](https://github.com/ReVanced/revanced-patches/issues/4454)) ([a006758](https://github.com/ReVanced/revanced-patches/commit/a0067581d0f877e1b4eb1f888a25786f09676b2e)) - - -### Features - -* **Return YouTube Dislike:** add `Show estimated likes` setting ([#4443](https://github.com/ReVanced/revanced-patches/issues/4443)) ([9a88b42](https://github.com/ReVanced/revanced-patches/commit/9a88b4239fd63d5f91105fec8e7d59d318a5d09a)) -* **YouTube - SponsorBlock:** Redesign skip buttons ([#4427](https://github.com/ReVanced/revanced-patches/issues/4427)) ([8f4883f](https://github.com/ReVanced/revanced-patches/commit/8f4883fc002420bfb4056401e23445c99e1d3fce)) -* **YouTube Music:** Support version `8.05.50` ([#4439](https://github.com/ReVanced/revanced-patches/issues/4439)) ([b31fed9](https://github.com/ReVanced/revanced-patches/commit/b31fed98901fcda1bce6f05eb0de63280c689fa0)) -* **YouTube Music:** Support version `8.05.51` ([128441e](https://github.com/ReVanced/revanced-patches/commit/128441e78bc0d096c3fc2f57782ab90c39c3ae4b)) - -# [5.12.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.12.0-dev.6...v5.12.0-dev.7) (2025-02-16) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Change default client to `Android TV` ([#4465](https://github.com/ReVanced/revanced-patches/issues/4465)) ([0412c79](https://github.com/ReVanced/revanced-patches/commit/0412c7901dc8599b6079d9c3ba26452f88af642b)) - - -### Features - -* **YouTube Music:** Support version `8.05.51` ([128441e](https://github.com/ReVanced/revanced-patches/commit/128441e78bc0d096c3fc2f57782ab90c39c3ae4b)) - -# [5.12.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.12.0-dev.5...v5.12.0-dev.6) (2025-02-16) - - -### Bug Fixes - -* Allow changing default settings for existing app installs ([#4464](https://github.com/ReVanced/revanced-patches/issues/4464)) ([1bd7986](https://github.com/ReVanced/revanced-patches/commit/1bd7986823e774a929c8a9102a7cc96e245d5274)) - -# [5.12.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.12.0-dev.4...v5.12.0-dev.5) (2025-02-13) - - -### Bug Fixes - -* **YouTube:** Remove obsolete 18.x targets ([#4454](https://github.com/ReVanced/revanced-patches/issues/4454)) ([a006758](https://github.com/ReVanced/revanced-patches/commit/a0067581d0f877e1b4eb1f888a25786f09676b2e)) - -# [5.12.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.12.0-dev.3...v5.12.0-dev.4) (2025-02-11) - - -### Features - -* **YouTube Music:** Support version `8.05.50` ([#4439](https://github.com/ReVanced/revanced-patches/issues/4439)) ([b31fed9](https://github.com/ReVanced/revanced-patches/commit/b31fed98901fcda1bce6f05eb0de63280c689fa0)) - -# [5.12.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.12.0-dev.2...v5.12.0-dev.3) (2025-02-11) - - -### Bug Fixes - -* **Windy.app:** Remove obsolete `Unlock pro` patch ([#4428](https://github.com/ReVanced/revanced-patches/issues/4428)) ([83d116e](https://github.com/ReVanced/revanced-patches/commit/83d116e8fd3935ee431cfdf0b8e095d04ee77259)) - -# [5.12.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.12.0-dev.1...v5.12.0-dev.2) (2025-02-11) - - -### Features - -* **Return YouTube Dislike:** add `Show estimated likes` setting ([#4443](https://github.com/ReVanced/revanced-patches/issues/4443)) ([9a88b42](https://github.com/ReVanced/revanced-patches/commit/9a88b4239fd63d5f91105fec8e7d59d318a5d09a)) - -# [5.12.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.11.0...v5.12.0-dev.1) (2025-02-10) - - -### Features - -* **YouTube - SponsorBlock:** Redesign skip buttons ([#4427](https://github.com/ReVanced/revanced-patches/issues/4427)) ([8f4883f](https://github.com/ReVanced/revanced-patches/commit/8f4883fc002420bfb4056401e23445c99e1d3fce)) - -# [5.11.0](https://github.com/ReVanced/revanced-patches/compare/v5.10.0...v5.11.0) (2025-02-07) - - -### Bug Fixes - -* Fix broken `Remove screen capture restriction`, `Remove screenshot restriction`, `Spoof Wi-Fi connection`, and `Export internal data documents provider` patch ([#4405](https://github.com/ReVanced/revanced-patches/issues/4405)) ([1d52b74](https://github.com/ReVanced/revanced-patches/commit/1d52b7478d34e699d8c629eeaa9fdbb470b7d5c8)) -* **YouTube - Enable slide to seek:** Change patch to default include ([50358cd](https://github.com/ReVanced/revanced-patches/commit/50358cddea3eef4051d248040d23f774521dce00)) -* **YouTube - Hide layout components:** Hide new type of community post ([#4404](https://github.com/ReVanced/revanced-patches/issues/4404)) ([f67ab2b](https://github.com/ReVanced/revanced-patches/commit/f67ab2baf25d543ceb55fcec48bda441ebf2b998)) -* **YouTube - Theme:** Use custom seekbar color for cairo startup animation ([#4399](https://github.com/ReVanced/revanced-patches/issues/4399)) ([1cba294](https://github.com/ReVanced/revanced-patches/commit/1cba2948a6787118eb380ffcec35ee4fb99447ea)) - - -### Features - -* **YouTube - Change start page:** Add additional start pages ([#4413](https://github.com/ReVanced/revanced-patches/issues/4413)) ([b434182](https://github.com/ReVanced/revanced-patches/commit/b434182df69313c4eb5f0dfd98101cb80e46ead2)) - -# [5.11.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.11.0-dev.1...v5.11.0-dev.2) (2025-02-06) - - -### Bug Fixes - -* Fix broken `Remove screen capture restriction`, `Remove screenshot restriction`, `Spoof Wi-Fi connection`, and `Export internal data documents provider` patch ([#4405](https://github.com/ReVanced/revanced-patches/issues/4405)) ([1d52b74](https://github.com/ReVanced/revanced-patches/commit/1d52b7478d34e699d8c629eeaa9fdbb470b7d5c8)) - -# [5.11.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.10.1-dev.3...v5.11.0-dev.1) (2025-02-05) - - -### Features - -* **YouTube - Change start page:** Add additional start pages ([#4413](https://github.com/ReVanced/revanced-patches/issues/4413)) ([b434182](https://github.com/ReVanced/revanced-patches/commit/b434182df69313c4eb5f0dfd98101cb80e46ead2)) - -## [5.10.1-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.10.1-dev.2...v5.10.1-dev.3) (2025-02-03) - - -### Bug Fixes - -* **YouTube - Hide layout components:** Hide new type of community post ([#4404](https://github.com/ReVanced/revanced-patches/issues/4404)) ([f67ab2b](https://github.com/ReVanced/revanced-patches/commit/f67ab2baf25d543ceb55fcec48bda441ebf2b998)) - -## [5.10.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.10.1-dev.1...v5.10.1-dev.2) (2025-02-03) - - -### Bug Fixes - -* **YouTube - Enable slide to seek:** Change patch to default include ([50358cd](https://github.com/ReVanced/revanced-patches/commit/50358cddea3eef4051d248040d23f774521dce00)) - -## [5.10.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.10.0...v5.10.1-dev.1) (2025-02-02) - - -### Bug Fixes - -* **YouTube - Theme:** Use custom seekbar color for cairo startup animation ([#4399](https://github.com/ReVanced/revanced-patches/issues/4399)) ([1cba294](https://github.com/ReVanced/revanced-patches/commit/1cba2948a6787118eb380ffcec35ee4fb99447ea)) - -# [5.10.0](https://github.com/ReVanced/revanced-patches/compare/v5.9.0...v5.10.0) (2025-01-31) - - -### Bug Fixes - -* **SwissId - Play integrity Removal:** Add recommended app version ([#4370](https://github.com/ReVanced/revanced-patches/issues/4370)) ([d8ed474](https://github.com/ReVanced/revanced-patches/commit/d8ed474b165f094fdedc32caaae1f82ebc99eb3d)) -* Use correct path to fix invalid file paths ([5ff4ee8](https://github.com/ReVanced/revanced-patches/commit/5ff4ee823da55c7b135eab8b62e07be465612b55)) -* **YouTube - Hide ads:** fix 'Hide the Visit store button on channel pages' not working ([#4364](https://github.com/ReVanced/revanced-patches/issues/4364)) ([9d63ea9](https://github.com/ReVanced/revanced-patches/commit/9d63ea9a10ab5128ce18a1f53a946e84550da258)) -* **YouTube - Hide Ads:** Hide end screen store banner without leaving empty space ([#4367](https://github.com/ReVanced/revanced-patches/issues/4367)) ([7e68390](https://github.com/ReVanced/revanced-patches/commit/7e683906418434dd4e2104337d73a2292415c615)) -* **YouTube - Hide ads:** Hide new types of tablet ads ([574bcc8](https://github.com/ReVanced/revanced-patches/commit/574bcc844746b7445ec3e93b47daceafefad85e7)) -* **YouTube - Hide layout components:** Hide new kind of community post ([#4341](https://github.com/ReVanced/revanced-patches/issues/4341)) ([02685c4](https://github.com/ReVanced/revanced-patches/commit/02685c4567aca55f22d45dc238a7d1f0ea264143)) -* **YouTube - Hide seekbar:** Do not hide player seekbar if hide feed seekbar is enabled ([#4333](https://github.com/ReVanced/revanced-patches/issues/4333)) ([f5cf6f2](https://github.com/ReVanced/revanced-patches/commit/f5cf6f2a445492d33815a9772f49deac2d70eba9)) -* **YouTube - Hide video description components:** Use correct string key names ([0f28c2b](https://github.com/ReVanced/revanced-patches/commit/0f28c2b44c0051ea7ab3136433b84c73321cf5bd)) -* **YouTube - Spoof video streams:** Update settings side effects summary text ([#4369](https://github.com/ReVanced/revanced-patches/issues/4369)) ([e5b3aa1](https://github.com/ReVanced/revanced-patches/commit/e5b3aa1cc6a2465cb006487d528de888bc7cd430)) -* **YouTube - Theme:** Fix 19.25 - 19.45 patch error ([5b47a5f](https://github.com/ReVanced/revanced-patches/commit/5b47a5f0f6299daaae209341064fd85f16ca18a6)) -* **YouTube - Theme:** Replace custom seekbar gradient colors instead of disabling ([#4329](https://github.com/ReVanced/revanced-patches/issues/4329)) ([f03da98](https://github.com/ReVanced/revanced-patches/commit/f03da983051021e0c372557a5354d5d967409564)) - - -### Features - -* **YouTube - Hide ads:** Add `Hide end screen store banner` ([#4351](https://github.com/ReVanced/revanced-patches/issues/4351)) ([5505087](https://github.com/ReVanced/revanced-patches/commit/55050878028fed82b0f583a9f7ba06b8f267f8ec)) -* **YouTube - Hide video description components:** Add `Hide How this content was made section` ([#4355](https://github.com/ReVanced/revanced-patches/issues/4355)) ([68ec54e](https://github.com/ReVanced/revanced-patches/commit/68ec54ef850ae8d6461dd0ef2846e6efbb59e482)) -* **YouTube - Theme:** Add option to use custom seekbar accent color ([#4337](https://github.com/ReVanced/revanced-patches/issues/4337)) ([952b4fc](https://github.com/ReVanced/revanced-patches/commit/952b4fc4c9291e1a3e71437b503857763c973dd4)) -* **YouTube:** Add patch `Disable HDR video` ([#4347](https://github.com/ReVanced/revanced-patches/issues/4347)) ([0528f7c](https://github.com/ReVanced/revanced-patches/commit/0528f7cad856a2b1347e41944167b0583fc4a3d9)) - -# [5.10.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.10...v5.10.0-dev.11) (2025-01-30) - - -### Bug Fixes - -* Use correct path to fix invalid file paths ([5ff4ee8](https://github.com/ReVanced/revanced-patches/commit/5ff4ee823da55c7b135eab8b62e07be465612b55)) - -# [5.10.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.9...v5.10.0-dev.10) (2025-01-29) - - -### Bug Fixes - -* **YouTube - Hide ads:** Hide new types of tablet ads ([574bcc8](https://github.com/ReVanced/revanced-patches/commit/574bcc844746b7445ec3e93b47daceafefad85e7)) - -# [5.10.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.8...v5.10.0-dev.9) (2025-01-29) - - -### Bug Fixes - -* **SwissId - Play integrity Removal:** Add recommended app version ([#4370](https://github.com/ReVanced/revanced-patches/issues/4370)) ([d8ed474](https://github.com/ReVanced/revanced-patches/commit/d8ed474b165f094fdedc32caaae1f82ebc99eb3d)) - -# [5.10.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.7...v5.10.0-dev.8) (2025-01-29) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Update settings side effects summary text ([#4369](https://github.com/ReVanced/revanced-patches/issues/4369)) ([e5b3aa1](https://github.com/ReVanced/revanced-patches/commit/e5b3aa1cc6a2465cb006487d528de888bc7cd430)) - -# [5.10.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.6...v5.10.0-dev.7) (2025-01-29) - - -### Bug Fixes - -* **YouTube - Hide ads:** fix 'Hide the Visit store button on channel pages' not working ([#4364](https://github.com/ReVanced/revanced-patches/issues/4364)) ([9d63ea9](https://github.com/ReVanced/revanced-patches/commit/9d63ea9a10ab5128ce18a1f53a946e84550da258)) - -# [5.10.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.5...v5.10.0-dev.6) (2025-01-29) - - -### Bug Fixes - -* **YouTube - Hide Ads:** Hide end screen store banner without leaving empty space ([#4367](https://github.com/ReVanced/revanced-patches/issues/4367)) ([7e68390](https://github.com/ReVanced/revanced-patches/commit/7e683906418434dd4e2104337d73a2292415c615)) - -# [5.10.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.4...v5.10.0-dev.5) (2025-01-27) - - -### Bug Fixes - -* **YouTube - Hide video description components:** Use correct string key names ([0f28c2b](https://github.com/ReVanced/revanced-patches/commit/0f28c2b44c0051ea7ab3136433b84c73321cf5bd)) - -# [5.10.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.3...v5.10.0-dev.4) (2025-01-27) - - -### Features - -* **YouTube - Hide video description components:** Add `Hide How this content was made section` ([#4355](https://github.com/ReVanced/revanced-patches/issues/4355)) ([68ec54e](https://github.com/ReVanced/revanced-patches/commit/68ec54ef850ae8d6461dd0ef2846e6efbb59e482)) - -# [5.10.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.2...v5.10.0-dev.3) (2025-01-27) - - -### Features - -* **YouTube - Hide ads:** Add `Hide end screen store banner` ([#4351](https://github.com/ReVanced/revanced-patches/issues/4351)) ([5505087](https://github.com/ReVanced/revanced-patches/commit/55050878028fed82b0f583a9f7ba06b8f267f8ec)) - -# [5.10.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.1...v5.10.0-dev.2) (2025-01-25) - - -### Features - -* **YouTube:** Add patch `Disable HDR video` ([#4347](https://github.com/ReVanced/revanced-patches/issues/4347)) ([0528f7c](https://github.com/ReVanced/revanced-patches/commit/0528f7cad856a2b1347e41944167b0583fc4a3d9)) - -# [5.10.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.9.1-dev.4...v5.10.0-dev.1) (2025-01-23) - - -### Features - -* **YouTube - Theme:** Add option to use custom seekbar accent color ([#4337](https://github.com/ReVanced/revanced-patches/issues/4337)) ([952b4fc](https://github.com/ReVanced/revanced-patches/commit/952b4fc4c9291e1a3e71437b503857763c973dd4)) - -## [5.9.1-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.9.1-dev.3...v5.9.1-dev.4) (2025-01-22) - - -### Bug Fixes - -* **YouTube - Hide layout components:** Hide new kind of community post ([#4341](https://github.com/ReVanced/revanced-patches/issues/4341)) ([02685c4](https://github.com/ReVanced/revanced-patches/commit/02685c4567aca55f22d45dc238a7d1f0ea264143)) - -## [5.9.1-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.9.1-dev.2...v5.9.1-dev.3) (2025-01-22) - - -### Bug Fixes - -* **YouTube - Hide seekbar:** Do not hide player seekbar if hide feed seekbar is enabled ([#4333](https://github.com/ReVanced/revanced-patches/issues/4333)) ([f5cf6f2](https://github.com/ReVanced/revanced-patches/commit/f5cf6f2a445492d33815a9772f49deac2d70eba9)) - -## [5.9.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.9.1-dev.1...v5.9.1-dev.2) (2025-01-22) - - -### Bug Fixes - -* **YouTube - Theme:** Fix 19.25 - 19.45 patch error ([5b47a5f](https://github.com/ReVanced/revanced-patches/commit/5b47a5f0f6299daaae209341064fd85f16ca18a6)) - -## [5.9.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.9.0...v5.9.1-dev.1) (2025-01-21) - - -### Bug Fixes - -* **YouTube - Theme:** Replace custom seekbar gradient colors instead of disabling ([#4329](https://github.com/ReVanced/revanced-patches/issues/4329)) ([f03da98](https://github.com/ReVanced/revanced-patches/commit/f03da983051021e0c372557a5354d5d967409564)) - -# [5.9.0](https://github.com/ReVanced/revanced-patches/compare/v5.8.1...v5.9.0) (2025-01-20) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Resolve playback issues after changing from cellular to wifi ([#4277](https://github.com/ReVanced/revanced-patches/issues/4277)) ([e93e1c8](https://github.com/ReVanced/revanced-patches/commit/e93e1c8ec3367e941034e9c4e3725ec1db429a60)) -* **YouTube - Spoof video streams:** Update client user-agent ([#4304](https://github.com/ReVanced/revanced-patches/issues/4304)) ([7917871](https://github.com/ReVanced/revanced-patches/commit/7917871f510b6b805370ef98a0cf8a4e2df0e900)) - - -### Features - -* **YouTube - Hide feed components:** Handle new type of surveys ([#4295](https://github.com/ReVanced/revanced-patches/issues/4295)) ([c770e03](https://github.com/ReVanced/revanced-patches/commit/c770e03f3801367cb531af860fbdfa43dca89af0)) -* **YouTube - Playback speed:** Add option to change 2x tap and hold speed ([#4307](https://github.com/ReVanced/revanced-patches/issues/4307)) ([02fb26e](https://github.com/ReVanced/revanced-patches/commit/02fb26e9458fb8635d497e6e78f964055244d738)) -* **YouTube - Settings:** Add option to use new Cairo settings menus ([#4305](https://github.com/ReVanced/revanced-patches/issues/4305)) ([7b8a2a2](https://github.com/ReVanced/revanced-patches/commit/7b8a2a2721ab5351f8c0251401aceddf0c5327df)) - -# [5.9.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.9.0-dev.3...v5.9.0-dev.4) (2025-01-20) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Update client user-agent ([#4304](https://github.com/ReVanced/revanced-patches/issues/4304)) ([7917871](https://github.com/ReVanced/revanced-patches/commit/7917871f510b6b805370ef98a0cf8a4e2df0e900)) - -# [5.9.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.9.0-dev.2...v5.9.0-dev.3) (2025-01-19) - - -### Features - -* **YouTube - Settings:** Add option to use new Cairo settings menus ([#4305](https://github.com/ReVanced/revanced-patches/issues/4305)) ([7b8a2a2](https://github.com/ReVanced/revanced-patches/commit/7b8a2a2721ab5351f8c0251401aceddf0c5327df)) - -# [5.9.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.9.0-dev.1...v5.9.0-dev.2) (2025-01-18) - - -### Features - -* **YouTube - Playback speed:** Add option to change 2x tap and hold speed ([#4307](https://github.com/ReVanced/revanced-patches/issues/4307)) ([02fb26e](https://github.com/ReVanced/revanced-patches/commit/02fb26e9458fb8635d497e6e78f964055244d738)) - -# [5.9.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.8.2-dev.1...v5.9.0-dev.1) (2025-01-17) - - -### Features - -* **YouTube - Hide feed components:** Handle new type of surveys ([#4295](https://github.com/ReVanced/revanced-patches/issues/4295)) ([c770e03](https://github.com/ReVanced/revanced-patches/commit/c770e03f3801367cb531af860fbdfa43dca89af0)) - -## [5.8.2-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.8.1...v5.8.2-dev.1) (2025-01-09) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Resolve playback issues after changing from cellular to wifi ([#4277](https://github.com/ReVanced/revanced-patches/issues/4277)) ([e93e1c8](https://github.com/ReVanced/revanced-patches/commit/e93e1c8ec3367e941034e9c4e3725ec1db429a60)) - -## [5.8.1](https://github.com/ReVanced/revanced-patches/compare/v5.8.0...v5.8.1) (2025-01-07) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Add 'Android Creator' ([#4262](https://github.com/ReVanced/revanced-patches/issues/4262)) ([0479dd2](https://github.com/ReVanced/revanced-patches/commit/0479dd265e09b0accdf6ff6b00c8e938dc5b96c7)) - -## [5.8.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.8.0...v5.8.1-dev.1) (2025-01-06) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Add 'Android Creator' ([#4262](https://github.com/ReVanced/revanced-patches/issues/4262)) ([0479dd2](https://github.com/ReVanced/revanced-patches/commit/0479dd265e09b0accdf6ff6b00c8e938dc5b96c7)) - -# [5.8.0](https://github.com/ReVanced/revanced-patches/compare/v5.7.2...v5.8.0) (2024-12-30) - - -### Bug Fixes - -* **GmsCore support:** Do not show battery optimization error on Android Automotive devices (Google built-in) ([#4218](https://github.com/ReVanced/revanced-patches/issues/4218)) ([d6e389c](https://github.com/ReVanced/revanced-patches/commit/d6e389cc43bc40724f032b230f70048276349a19)) -* **YouTube - Exit fullscreen mode:** Exit fullscreen mode of first video opened after cold start ([be5cf2e](https://github.com/ReVanced/revanced-patches/commit/be5cf2e834d87d51b5d3061d46bd7154d6306787)) -* **YouTube - Force original audio:** If stream spoofing to Android then show a summary text why force audio is not available ([#4220](https://github.com/ReVanced/revanced-patches/issues/4220)) ([029aee8](https://github.com/ReVanced/revanced-patches/commit/029aee8023f096413fc80a2c583b4fe55ecb10ac)) -* **YouTube - Spoof video streams:** Ignore harmless error toast if hide ads is disabled ([c3423bb](https://github.com/ReVanced/revanced-patches/commit/c3423bb9e531cfa52f6d28e0b98bbe8ab8684c30)) - - -### Features - -* **Swipe controls:** Add option to enable/disable fullscreen swipe to next video ([#4222](https://github.com/ReVanced/revanced-patches/issues/4222)) ([119092f](https://github.com/ReVanced/revanced-patches/commit/119092fafa4129849246df15fe8076ed3b491b85)) -* **YouTube - Hide Shorts components:** Add option to hide Shorts in watch history ([#4214](https://github.com/ReVanced/revanced-patches/issues/4214)) ([19c2742](https://github.com/ReVanced/revanced-patches/commit/19c2742aa367367c77bb50ddad6f8a20fef8ea0a)) -* **YouTube - Spoof app version:** Add 'Restore old navigation and toolbar icons' ([f84e459](https://github.com/ReVanced/revanced-patches/commit/f84e459d3d54b3001586796ab4e114ebadf09043)) -* **YouTube:** Add `Change form factor` patch ([#4217](https://github.com/ReVanced/revanced-patches/issues/4217)) ([644ac5b](https://github.com/ReVanced/revanced-patches/commit/644ac5baa68b209a32300149a2efa009b776f9a7)) -* **YouTube:** Add `Exit fullscreen mode` patch ([#4223](https://github.com/ReVanced/revanced-patches/issues/4223)) ([bb5d03b](https://github.com/ReVanced/revanced-patches/commit/bb5d03bd89a3f932c77e4e9de90174c374933688)) -* **YouTube:** Add in app option to select a preferred language for ReVanced specific text ([#4231](https://github.com/ReVanced/revanced-patches/issues/4231)) ([3932af3](https://github.com/ReVanced/revanced-patches/commit/3932af397ae89a0b30191cd870bd6cddb7a078db)) - -# [5.8.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.8.0-dev.7...v5.8.0-dev.8) (2024-12-28) - - -### Features - -* **YouTube:** Add in app option to select a preferred language for ReVanced specific text ([#4231](https://github.com/ReVanced/revanced-patches/issues/4231)) ([3932af3](https://github.com/ReVanced/revanced-patches/commit/3932af397ae89a0b30191cd870bd6cddb7a078db)) - -# [5.8.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.8.0-dev.6...v5.8.0-dev.7) (2024-12-27) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Ignore harmless error toast if hide ads is disabled ([c3423bb](https://github.com/ReVanced/revanced-patches/commit/c3423bb9e531cfa52f6d28e0b98bbe8ab8684c30)) - -# [5.8.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.8.0-dev.5...v5.8.0-dev.6) (2024-12-27) - - -### Bug Fixes - -* **YouTube - Exit fullscreen mode:** Exit fullscreen mode of first video opened after cold start ([be5cf2e](https://github.com/ReVanced/revanced-patches/commit/be5cf2e834d87d51b5d3061d46bd7154d6306787)) - -# [5.8.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.8.0-dev.4...v5.8.0-dev.5) (2024-12-27) - - -### Features - -* **YouTube:** Add `Change form factor` patch ([#4217](https://github.com/ReVanced/revanced-patches/issues/4217)) ([644ac5b](https://github.com/ReVanced/revanced-patches/commit/644ac5baa68b209a32300149a2efa009b776f9a7)) - -# [5.8.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.8.0-dev.3...v5.8.0-dev.4) (2024-12-27) - - -### Bug Fixes - -* **GmsCore support:** Do not show battery optimization error on Android Automotive devices (Google built-in) ([#4218](https://github.com/ReVanced/revanced-patches/issues/4218)) ([d6e389c](https://github.com/ReVanced/revanced-patches/commit/d6e389cc43bc40724f032b230f70048276349a19)) - - -### Features - -* **Swipe controls:** Add option to enable/disable fullscreen swipe to next video ([#4222](https://github.com/ReVanced/revanced-patches/issues/4222)) ([119092f](https://github.com/ReVanced/revanced-patches/commit/119092fafa4129849246df15fe8076ed3b491b85)) -* **YouTube:** Add `Exit fullscreen mode` patch ([#4223](https://github.com/ReVanced/revanced-patches/issues/4223)) ([bb5d03b](https://github.com/ReVanced/revanced-patches/commit/bb5d03bd89a3f932c77e4e9de90174c374933688)) - -# [5.8.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.8.0-dev.2...v5.8.0-dev.3) (2024-12-26) - - -### Bug Fixes - -* **YouTube - Force original audio:** If stream spoofing to Android then show a summary text why force audio is not available ([#4220](https://github.com/ReVanced/revanced-patches/issues/4220)) ([029aee8](https://github.com/ReVanced/revanced-patches/commit/029aee8023f096413fc80a2c583b4fe55ecb10ac)) - -# [5.8.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.8.0-dev.1...v5.8.0-dev.2) (2024-12-24) - - -### Features - -* **YouTube - Spoof app version:** Add 'Restore old navigation and toolbar icons' ([f84e459](https://github.com/ReVanced/revanced-patches/commit/f84e459d3d54b3001586796ab4e114ebadf09043)) - -# [5.8.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.7.2...v5.8.0-dev.1) (2024-12-24) - - -### Features - -* **YouTube - Hide Shorts components:** Add option to hide Shorts in watch history ([#4214](https://github.com/ReVanced/revanced-patches/issues/4214)) ([19c2742](https://github.com/ReVanced/revanced-patches/commit/19c2742aa367367c77bb50ddad6f8a20fef8ea0a)) - -## [5.7.2](https://github.com/ReVanced/revanced-patches/compare/v5.7.1...v5.7.2) (2024-12-24) - - -### Bug Fixes - -* **YouTube - Hide layout components:** Don't hide Shorts channel bar when toggling for video player ([9af6412](https://github.com/ReVanced/revanced-patches/commit/9af6412d92ec31e612eaabba6578453da0fc61d6)) -* **YouTube - Spoof video streams:** Add iOS TV client, restore iOS 'force AVC', show client type in stats for nerds ([#4202](https://github.com/ReVanced/revanced-patches/issues/4202)) ([ab29f80](https://github.com/ReVanced/revanced-patches/commit/ab29f808a9f55b5ab0055533c1a6de549b0631a6)) - -## [5.7.2-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.7.2-dev.1...v5.7.2-dev.2) (2024-12-23) - - -### Bug Fixes - -* **YouTube - Hide layout components:** Don't hide Shorts channel bar when toggling for video player ([9af6412](https://github.com/ReVanced/revanced-patches/commit/9af6412d92ec31e612eaabba6578453da0fc61d6)) - -## [5.7.2-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.7.1...v5.7.2-dev.1) (2024-12-23) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Add iOS TV client, restore iOS 'force AVC', show client type in stats for nerds ([#4202](https://github.com/ReVanced/revanced-patches/issues/4202)) ([ab29f80](https://github.com/ReVanced/revanced-patches/commit/ab29f808a9f55b5ab0055533c1a6de549b0631a6)) - -## [5.7.1](https://github.com/ReVanced/revanced-patches/compare/v5.7.0...v5.7.1) (2024-12-23) - - -### Bug Fixes - -* **YouTube - SponsorBlock:** Show a toast and not a dialog if segment submitted successfully ([134b189](https://github.com/ReVanced/revanced-patches/commit/134b189791113dcf1a1cb7c87b8a0954f432730c)) -* **YouTube - Spoof video streams:** Use 2 letter device language code ([33ff997](https://github.com/ReVanced/revanced-patches/commit/33ff9972000581aca92262f984efb114eeeb9537)) -* **YouTube - Spoof video streams:** Use Android VR authentication if using default audio language ([#4191](https://github.com/ReVanced/revanced-patches/issues/4191)) ([98773cc](https://github.com/ReVanced/revanced-patches/commit/98773cc7d46e5c9c7715b82c8006f1ccbcc5443c)) -* **YouTube - Theme:** Use dark theme color for status and navigation bar ([0240efe](https://github.com/ReVanced/revanced-patches/commit/0240efe33e5444625ca2b760c861c9046d3dc836)) -* **YouTube:** Do not reset playback speed to 1.0x after closing comment thread (Fixes stock YouTube bug) ([#4195](https://github.com/ReVanced/revanced-patches/issues/4195)) ([dda788c](https://github.com/ReVanced/revanced-patches/commit/dda788c58c789d4f91646ea8e8a8077f590ab6b3)) - -## [5.7.1-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.7.1-dev.4...v5.7.1-dev.5) (2024-12-22) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Use 2 letter device language code ([33ff997](https://github.com/ReVanced/revanced-patches/commit/33ff9972000581aca92262f984efb114eeeb9537)) - -## [5.7.1-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.7.1-dev.3...v5.7.1-dev.4) (2024-12-22) - - -### Bug Fixes - -* **YouTube:** Do not reset playback speed to 1.0x after closing comment thread (Fixes stock YouTube bug) ([#4195](https://github.com/ReVanced/revanced-patches/issues/4195)) ([dda788c](https://github.com/ReVanced/revanced-patches/commit/dda788c58c789d4f91646ea8e8a8077f590ab6b3)) - -## [5.7.1-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.7.1-dev.2...v5.7.1-dev.3) (2024-12-22) - - -### Bug Fixes - -* **YouTube - SponsorBlock:** Show a toast and not a dialog if segment submitted successfully ([134b189](https://github.com/ReVanced/revanced-patches/commit/134b189791113dcf1a1cb7c87b8a0954f432730c)) - -## [5.7.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.7.1-dev.1...v5.7.1-dev.2) (2024-12-22) - - -### Bug Fixes - -* **YouTube - Theme:** Use dark theme color for status and navigation bar ([0240efe](https://github.com/ReVanced/revanced-patches/commit/0240efe33e5444625ca2b760c861c9046d3dc836)) - -## [5.7.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.7.0...v5.7.1-dev.1) (2024-12-22) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Use Android VR authentication if using default audio language ([#4191](https://github.com/ReVanced/revanced-patches/issues/4191)) ([98773cc](https://github.com/ReVanced/revanced-patches/commit/98773cc7d46e5c9c7715b82c8006f1ccbcc5443c)) - -# [5.7.0](https://github.com/ReVanced/revanced-patches/compare/v5.6.0...v5.7.0) (2024-12-22) - - -### Bug Fixes - -* **YouTube - Force original audio:** Use correct availability for settings UI ([a7eedcb](https://github.com/ReVanced/revanced-patches/commit/a7eedcb4cca6b7b12629c478c24c0899c80e3615)) -* **YouTube - Spoof video stream:** Remove UI client type setting. Allow setting default audio language. ([#4184](https://github.com/ReVanced/revanced-patches/issues/4184)) ([99f3f29](https://github.com/ReVanced/revanced-patches/commit/99f3f29c649bf7693c05bbce2bb49bd53e05f050)) -* **YouTube - Spoof video streams:** Remove iOS, add clients Android TV and Android Creator ([#4180](https://github.com/ReVanced/revanced-patches/issues/4180)) ([86abfb2](https://github.com/ReVanced/revanced-patches/commit/86abfb2b0d4675f0a1cb9ab244783075bfe89281)) -* **YouTube:** Change fingerprints to support a wider range of target versions ([8a09174](https://github.com/ReVanced/revanced-patches/commit/8a09174def205a26ce49cb7815097e235069526a)) - - -### Features - -* **YouTube:** Support version `19.47.53` ([#4182](https://github.com/ReVanced/revanced-patches/issues/4182)) ([2089e61](https://github.com/ReVanced/revanced-patches/commit/2089e613d36c45352db7d852aaee0087b1c3e1a4)) - -# [5.7.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.6.1-dev.4...v5.7.0-dev.1) (2024-12-21) - - -### Features - -* **YouTube:** Support version `19.47.53` ([#4182](https://github.com/ReVanced/revanced-patches/issues/4182)) ([2089e61](https://github.com/ReVanced/revanced-patches/commit/2089e613d36c45352db7d852aaee0087b1c3e1a4)) - -## [5.6.1-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.6.1-dev.3...v5.6.1-dev.4) (2024-12-21) - - -### Bug Fixes - -* **YouTube - Spoof video stream:** Remove UI client type setting. Allow setting default audio language. ([#4184](https://github.com/ReVanced/revanced-patches/issues/4184)) ([99f3f29](https://github.com/ReVanced/revanced-patches/commit/99f3f29c649bf7693c05bbce2bb49bd53e05f050)) - -## [5.6.1-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.6.1-dev.2...v5.6.1-dev.3) (2024-12-21) - - -### Bug Fixes - -* **YouTube - Force original audio:** Use correct availability for settings UI ([a7eedcb](https://github.com/ReVanced/revanced-patches/commit/a7eedcb4cca6b7b12629c478c24c0899c80e3615)) - -## [5.6.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.6.1-dev.1...v5.6.1-dev.2) (2024-12-21) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Remove iOS, add clients Android TV and Android Creator ([#4180](https://github.com/ReVanced/revanced-patches/issues/4180)) ([86abfb2](https://github.com/ReVanced/revanced-patches/commit/86abfb2b0d4675f0a1cb9ab244783075bfe89281)) - -## [5.6.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.6.0...v5.6.1-dev.1) (2024-12-21) - - -### Bug Fixes - -* **YouTube:** Change fingerprints to support a wider range of target versions ([8a09174](https://github.com/ReVanced/revanced-patches/commit/8a09174def205a26ce49cb7815097e235069526a)) - -# [5.6.0](https://github.com/ReVanced/revanced-patches/compare/v5.5.1...v5.6.0) (2024-12-20) - - -### Bug Fixes - -* **Twitter - Change link sharing domain:** Use correct extension package ([ad7fab6](https://github.com/ReVanced/revanced-patches/commit/ad7fab67319ba23f267d27da9b74266965fc4be3)) -* **YouTube - Force original audio:** Use correct original audio stream if app language is not English ([0d20171](https://github.com/ReVanced/revanced-patches/commit/0d2017133efac230887b5c2a331d87159df8af11)) -* **YouTube - Hide layout components:** Hide new kind of community post ([#4155](https://github.com/ReVanced/revanced-patches/issues/4155)) ([08f68cb](https://github.com/ReVanced/revanced-patches/commit/08f68cb5d33f2cfe656d2f93d159c69981f31418)) -* **YouTube - Miniplayer:** Use estimated maximum on screen size for devices with low density screens ([#4150](https://github.com/ReVanced/revanced-patches/issues/4150)) ([2694158](https://github.com/ReVanced/revanced-patches/commit/2694158c3c9935ede21c96832533222f850068df)) -* **YouTube - Open Shorts in regular player:** Do not show the miniplayer after opening a Short while a video is playing ([894e366](https://github.com/ReVanced/revanced-patches/commit/894e36665d17d5a3a5728961d424dffc55faa50b)) -* **YouTube - SponsorBlock:** Show create new segment error messages using a dialog ([#4148](https://github.com/ReVanced/revanced-patches/issues/4148)) ([5870906](https://github.com/ReVanced/revanced-patches/commit/587090636dfff0b358b15026cf7d47c65a4296dc)) -* **YouTube - Spoof video streams:** Change default spoofing to iOS, allow setting a default language with Android VR ([#4171](https://github.com/ReVanced/revanced-patches/issues/4171)) ([171b4e7](https://github.com/ReVanced/revanced-patches/commit/171b4e7e40066e38fba773b7a6525e9a038779ef)) -* **YouTube - Spoof video streams:** Update iOS client version ([df3aeed](https://github.com/ReVanced/revanced-patches/commit/df3aeed3b173e408fad80197a89ec5d003a2b328)) - - -### Features - -* **YouTube:** Add `Open Shorts in regular player` patch ([#4153](https://github.com/ReVanced/revanced-patches/issues/4153)) ([c7c5e5b](https://github.com/ReVanced/revanced-patches/commit/c7c5e5b2b9cf63d8225bb6bd5e735ddf945b6c29)) - -# [5.6.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.6.0-dev.5...v5.6.0-dev.6) (2024-12-20) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Update iOS client version ([df3aeed](https://github.com/ReVanced/revanced-patches/commit/df3aeed3b173e408fad80197a89ec5d003a2b328)) - -# [5.6.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.6.0-dev.4...v5.6.0-dev.5) (2024-12-20) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Change default spoofing to iOS, allow setting a default language with Android VR ([#4171](https://github.com/ReVanced/revanced-patches/issues/4171)) ([171b4e7](https://github.com/ReVanced/revanced-patches/commit/171b4e7e40066e38fba773b7a6525e9a038779ef)) - -# [5.6.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.6.0-dev.3...v5.6.0-dev.4) (2024-12-20) - - -### Bug Fixes - -* **YouTube - Force original audio:** Use correct original audio stream if app language is not English ([0d20171](https://github.com/ReVanced/revanced-patches/commit/0d2017133efac230887b5c2a331d87159df8af11)) - -# [5.6.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.6.0-dev.2...v5.6.0-dev.3) (2024-12-20) - - -### Bug Fixes - -* **Twitter - Change link sharing domain:** Use correct extension package ([ad7fab6](https://github.com/ReVanced/revanced-patches/commit/ad7fab67319ba23f267d27da9b74266965fc4be3)) - -# [5.6.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.6.0-dev.1...v5.6.0-dev.2) (2024-12-19) - - -### Bug Fixes - -* **YouTube - Open Shorts in regular player:** Do not show the miniplayer after opening a Short while a video is playing ([894e366](https://github.com/ReVanced/revanced-patches/commit/894e36665d17d5a3a5728961d424dffc55faa50b)) - -# [5.6.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.5.2-dev.2...v5.6.0-dev.1) (2024-12-19) - - -### Features - -* **YouTube:** Add `Open Shorts in regular player` patch ([#4153](https://github.com/ReVanced/revanced-patches/issues/4153)) ([c7c5e5b](https://github.com/ReVanced/revanced-patches/commit/c7c5e5b2b9cf63d8225bb6bd5e735ddf945b6c29)) - -## [5.5.2-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.5.2-dev.1...v5.5.2-dev.2) (2024-12-17) - - -### Bug Fixes - -* **YouTube - Hide layout components:** Hide new kind of community post ([#4155](https://github.com/ReVanced/revanced-patches/issues/4155)) ([08f68cb](https://github.com/ReVanced/revanced-patches/commit/08f68cb5d33f2cfe656d2f93d159c69981f31418)) - -## [5.5.2-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.5.1...v5.5.2-dev.1) (2024-12-17) - - -### Bug Fixes - -* **YouTube - Miniplayer:** Use estimated maximum on screen size for devices with low density screens ([#4150](https://github.com/ReVanced/revanced-patches/issues/4150)) ([2694158](https://github.com/ReVanced/revanced-patches/commit/2694158c3c9935ede21c96832533222f850068df)) -* **YouTube - SponsorBlock:** Show create new segment error messages using a dialog ([#4148](https://github.com/ReVanced/revanced-patches/issues/4148)) ([5870906](https://github.com/ReVanced/revanced-patches/commit/587090636dfff0b358b15026cf7d47c65a4296dc)) - -## [5.5.1](https://github.com/ReVanced/revanced-patches/compare/v5.5.0...v5.5.1) (2024-12-16) - - -### Bug Fixes - -* **YouTube:** Fix string translations ([52e04d3](https://github.com/ReVanced/revanced-patches/commit/52e04d340c1a85f3d683c67a15ae96529432d5fe)) - -## [5.5.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.5.0...v5.5.1-dev.1) (2024-12-16) - - -### Bug Fixes - -* **YouTube:** Fix string translations ([52e04d3](https://github.com/ReVanced/revanced-patches/commit/52e04d340c1a85f3d683c67a15ae96529432d5fe)) - -# [5.5.0](https://github.com/ReVanced/revanced-patches/compare/v5.4.0...v5.5.0) (2024-12-16) - - -### Bug Fixes - -* **Twitch:** Change recommended target to the latest app version ([fb32972](https://github.com/ReVanced/revanced-patches/commit/fb32972f4de92dac1fc5d73f56a392a671c4e94b)) -* **YouTube - Spoof video streams:** Make livestreams start at the current time when using iOS client ([#4137](https://github.com/ReVanced/revanced-patches/issues/4137)) ([140f484](https://github.com/ReVanced/revanced-patches/commit/140f484b4b251b0dfa94163a63f61f45f5302052)) -* **YouTube Music:** Add `Spoof client patch` to fix playback ([#4132](https://github.com/ReVanced/revanced-patches/issues/4132)) ([b092508](https://github.com/ReVanced/revanced-patches/commit/b0925088e8b41636e285cb234593d545604ce461)) - - -### Features - -* **YouTube - Hide feed components:** Remove obsolete `Hide search result shelf header` option ([#4134](https://github.com/ReVanced/revanced-patches/issues/4134)) ([c71443a](https://github.com/ReVanced/revanced-patches/commit/c71443a08883ab10ef2553213c03b00e7c580a43)) -* **YouTube - Navigation buttons:** Add options to disable translucent status bar and navigation bar ([#4133](https://github.com/ReVanced/revanced-patches/issues/4133)) ([a2d2141](https://github.com/ReVanced/revanced-patches/commit/a2d2141cec9b0b4929e07a8010889b21c324b229)) -* **YouTube:** Add `Force original audio` patch ([#4122](https://github.com/ReVanced/revanced-patches/issues/4122)) ([f4aa440](https://github.com/ReVanced/revanced-patches/commit/f4aa4406080b91f01d623e54b11b99ea849ddcdf)) - -# [5.5.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.5.0-dev.4...v5.5.0-dev.5) (2024-12-16) - - -### Features - -* **YouTube - Navigation buttons:** Add options to disable translucent status bar and navigation bar ([#4133](https://github.com/ReVanced/revanced-patches/issues/4133)) ([a2d2141](https://github.com/ReVanced/revanced-patches/commit/a2d2141cec9b0b4929e07a8010889b21c324b229)) - -# [5.5.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.5.0-dev.3...v5.5.0-dev.4) (2024-12-16) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Make livestreams start at the current time when using iOS client ([#4137](https://github.com/ReVanced/revanced-patches/issues/4137)) ([140f484](https://github.com/ReVanced/revanced-patches/commit/140f484b4b251b0dfa94163a63f61f45f5302052)) - -# [5.5.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.5.0-dev.2...v5.5.0-dev.3) (2024-12-16) - - -### Features - -* **YouTube - Hide feed components:** Remove obsolete `Hide search result shelf header` option ([#4134](https://github.com/ReVanced/revanced-patches/issues/4134)) ([c71443a](https://github.com/ReVanced/revanced-patches/commit/c71443a08883ab10ef2553213c03b00e7c580a43)) - -# [5.5.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.5.0-dev.1...v5.5.0-dev.2) (2024-12-16) - - -### Bug Fixes - -* **YouTube Music:** Add `Spoof client patch` to fix playback ([#4132](https://github.com/ReVanced/revanced-patches/issues/4132)) ([b092508](https://github.com/ReVanced/revanced-patches/commit/b0925088e8b41636e285cb234593d545604ce461)) - -# [5.5.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.4.1-dev.1...v5.5.0-dev.1) (2024-12-15) - - -### Features - -* **YouTube:** Add `Force original audio` patch ([#4122](https://github.com/ReVanced/revanced-patches/issues/4122)) ([f4aa440](https://github.com/ReVanced/revanced-patches/commit/f4aa4406080b91f01d623e54b11b99ea849ddcdf)) - -## [5.4.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.4.0...v5.4.1-dev.1) (2024-12-14) - - -### Bug Fixes - -* **Twitch:** Change recommended target to the latest app version ([fb32972](https://github.com/ReVanced/revanced-patches/commit/fb32972f4de92dac1fc5d73f56a392a671c4e94b)) - -# [5.4.0](https://github.com/ReVanced/revanced-patches/compare/v5.3.0...v5.4.0) (2024-12-14) - - -### Bug Fixes - -* **GmsCore support:** Adjust presentation of battery optimization dialog ([#4091](https://github.com/ReVanced/revanced-patches/issues/4091)) ([5d8fc1b](https://github.com/ReVanced/revanced-patches/commit/5d8fc1bcd4e453298cfac086cdbdf279612bfb63)) -* **TikTok - Settings:** Use correct colors for dark mode ([#4087](https://github.com/ReVanced/revanced-patches/issues/4087)) ([6bd22ff](https://github.com/ReVanced/revanced-patches/commit/6bd22ffa7e8af4d8f5d2d3b1711bd92c44b4e4aa)) -* **TikTok - SIM Spoof:** Change patch to default off to fix login ([#4084](https://github.com/ReVanced/revanced-patches/issues/4084)) ([f4659a3](https://github.com/ReVanced/revanced-patches/commit/f4659a328eaf600e1e5f02a66fa2af4b6d8dc7c1)) -* **YouTube - Hide ads:** Hide new type of featured promotions ([#4113](https://github.com/ReVanced/revanced-patches/issues/4113)) ([13c7592](https://github.com/ReVanced/revanced-patches/commit/13c7592b21defd27e3a7aa9b219ffc0247bb5914)) -* **YouTube - Spoof video streams:** Fix error toast that is sometimes shown ([#4090](https://github.com/ReVanced/revanced-patches/issues/4090)) ([4c46cb2](https://github.com/ReVanced/revanced-patches/commit/4c46cb27a02c6f29626cd769b6a8e825645d5b16)) -* **YouTube - Spoof video streams:** Resolve playback of age restricted videos ([#4096](https://github.com/ReVanced/revanced-patches/issues/4096)) ([839a404](https://github.com/ReVanced/revanced-patches/commit/839a4045f1bb1759d89047834e0b7695781e82a3)) -* **YouTube Music - Bypass certificate checks:** Add a recommended target version ([#4104](https://github.com/ReVanced/revanced-patches/issues/4104)) ([17a5a6c](https://github.com/ReVanced/revanced-patches/commit/17a5a6c1691b0c23f601d3355b72f122c2bd5dcb)) -* **YouTube Music - Spoof video streams:** Disable stable volume ([#4097](https://github.com/ReVanced/revanced-patches/issues/4097)) ([16bb9df](https://github.com/ReVanced/revanced-patches/commit/16bb9dfc299612f3922724c136878606987ab132)) - - -### Features - -* Add Internal data documents provider patch ([#3830](https://github.com/ReVanced/revanced-patches/issues/3830)) ([cb22f65](https://github.com/ReVanced/revanced-patches/commit/cb22f652ed678d81ffda9ece659b3971225d6931)) -* **Change package name:** Add options to change provider and permission package names to handle installation conflicts ([75c740c](https://github.com/ReVanced/revanced-patches/commit/75c740c6ba2e0c62e567f7dc90cdad368fc4f372)) -* **Twitch:** Make patches compatible with latest versions ([#4099](https://github.com/ReVanced/revanced-patches/issues/4099)) ([eecfbb7](https://github.com/ReVanced/revanced-patches/commit/eecfbb7122a9072e55e687f2c003f63108654888)) -* **YouTube - Comments:** Add `Hide 'Chat summary'` ([#4110](https://github.com/ReVanced/revanced-patches/issues/4110)) ([269493c](https://github.com/ReVanced/revanced-patches/commit/269493cd198604f1438ea2850fb68fe900d0e56f)) - -# [5.4.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.4.0-dev.10...v5.4.0-dev.11) (2024-12-14) - - -### Features - -* **Twitch:** Make patches compatible with latest versions ([#4099](https://github.com/ReVanced/revanced-patches/issues/4099)) ([eecfbb7](https://github.com/ReVanced/revanced-patches/commit/eecfbb7122a9072e55e687f2c003f63108654888)) - -# [5.4.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.4.0-dev.9...v5.4.0-dev.10) (2024-12-13) - - -### Bug Fixes - -* **YouTube - Hide ads:** Hide new type of featured promotions ([#4113](https://github.com/ReVanced/revanced-patches/issues/4113)) ([13c7592](https://github.com/ReVanced/revanced-patches/commit/13c7592b21defd27e3a7aa9b219ffc0247bb5914)) - -# [5.4.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.4.0-dev.8...v5.4.0-dev.9) (2024-12-12) - - -### Features - -* **YouTube - Comments:** Add `Hide 'Chat summary'` ([#4110](https://github.com/ReVanced/revanced-patches/issues/4110)) ([269493c](https://github.com/ReVanced/revanced-patches/commit/269493cd198604f1438ea2850fb68fe900d0e56f)) - -# [5.4.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.4.0-dev.7...v5.4.0-dev.8) (2024-12-11) - - -### Bug Fixes - -* **YouTube Music - Bypass certificate checks:** Add a recommended target version ([#4104](https://github.com/ReVanced/revanced-patches/issues/4104)) ([17a5a6c](https://github.com/ReVanced/revanced-patches/commit/17a5a6c1691b0c23f601d3355b72f122c2bd5dcb)) - -# [5.4.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.4.0-dev.6...v5.4.0-dev.7) (2024-12-10) - - -### Bug Fixes - -* **GmsCore support:** Adjust presentation of battery optimization dialog ([#4091](https://github.com/ReVanced/revanced-patches/issues/4091)) ([5d8fc1b](https://github.com/ReVanced/revanced-patches/commit/5d8fc1bcd4e453298cfac086cdbdf279612bfb63)) - -# [5.4.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.4.0-dev.5...v5.4.0-dev.6) (2024-12-10) - - -### Bug Fixes - -* **YouTube Music - Spoof video streams:** Disable stable volume ([#4097](https://github.com/ReVanced/revanced-patches/issues/4097)) ([16bb9df](https://github.com/ReVanced/revanced-patches/commit/16bb9dfc299612f3922724c136878606987ab132)) - -# [5.4.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.4.0-dev.4...v5.4.0-dev.5) (2024-12-10) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Resolve playback of age restricted videos ([#4096](https://github.com/ReVanced/revanced-patches/issues/4096)) ([839a404](https://github.com/ReVanced/revanced-patches/commit/839a4045f1bb1759d89047834e0b7695781e82a3)) - -# [5.4.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.4.0-dev.3...v5.4.0-dev.4) (2024-12-10) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Fix error toast that is sometimes shown ([#4090](https://github.com/ReVanced/revanced-patches/issues/4090)) ([4c46cb2](https://github.com/ReVanced/revanced-patches/commit/4c46cb27a02c6f29626cd769b6a8e825645d5b16)) - -# [5.4.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.4.0-dev.2...v5.4.0-dev.3) (2024-12-09) - - -### Bug Fixes - -* **TikTok - Settings:** Use correct colors for dark mode ([#4087](https://github.com/ReVanced/revanced-patches/issues/4087)) ([6bd22ff](https://github.com/ReVanced/revanced-patches/commit/6bd22ffa7e8af4d8f5d2d3b1711bd92c44b4e4aa)) - -# [5.4.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.4.0-dev.1...v5.4.0-dev.2) (2024-12-09) - - -### Bug Fixes - -* **TikTok - SIM Spoof:** Change patch to default off to fix login ([#4084](https://github.com/ReVanced/revanced-patches/issues/4084)) ([f4659a3](https://github.com/ReVanced/revanced-patches/commit/f4659a328eaf600e1e5f02a66fa2af4b6d8dc7c1)) - - -### Features - -* Add Internal data documents provider patch ([#3830](https://github.com/ReVanced/revanced-patches/issues/3830)) ([cb22f65](https://github.com/ReVanced/revanced-patches/commit/cb22f652ed678d81ffda9ece659b3971225d6931)) - -# [5.4.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.3.0...v5.4.0-dev.1) (2024-12-09) - - -### Features - -* **Change package name:** Add options to change provider and permission package names to handle installation conflicts ([75c740c](https://github.com/ReVanced/revanced-patches/commit/75c740c6ba2e0c62e567f7dc90cdad368fc4f372)) - -# [5.3.0](https://github.com/ReVanced/revanced-patches/compare/v5.2.3...v5.3.0) (2024-12-09) - - -### Bug Fixes - -* **Change package name:** Prevent applying the patch to known incompatible apps ([#3943](https://github.com/ReVanced/revanced-patches/issues/3943)) ([44936e7](https://github.com/ReVanced/revanced-patches/commit/44936e71e846f72f7279950232a5dba37765ceb3)) -* **Reddit:** Fix patches by using correct extension class ([70bdc68](https://github.com/ReVanced/revanced-patches/commit/70bdc6840d465399625aa1ae0259f49e72711955)) -* **Sync for Reddit:** Fix patches by using correct extension name ([030093e](https://github.com/ReVanced/revanced-patches/commit/030093e913aab3fab43935eedbaeba0f6c0491bb)) -* **Twitter:** Merge correct extension by depending on correct extension patch ([8281cf6](https://github.com/ReVanced/revanced-patches/commit/8281cf6a3eead8cc25a277371e0b0ab2be982497)) -* **YouTube - Spoof video streams:** Add missing preferred language preference to the settings ([630633c](https://github.com/ReVanced/revanced-patches/commit/630633cf57c65c65e5578046413e17670ae336e8)) -* **YouTube - Spoof video streams:** Enable opus codec by updating iOS client version ([#4063](https://github.com/ReVanced/revanced-patches/issues/4063)) ([0af156f](https://github.com/ReVanced/revanced-patches/commit/0af156f18972c5f089af4bb69824968d2a47d18f)) -* **YouTube - Spoof video streams:** Update `Force AVC` client data ([#4064](https://github.com/ReVanced/revanced-patches/issues/4064)) ([7d537dd](https://github.com/ReVanced/revanced-patches/commit/7d537ddff4bb5421fa320741275131a66ef5c7bb)) -* **YouTube Music - Permanent shuffle:** Remove obsolete and non functional patch ([#4073](https://github.com/ReVanced/revanced-patches/issues/4073)) ([fbc6ab6](https://github.com/ReVanced/revanced-patches/commit/fbc6ab6a357b351f02d4d486ddc2072cf53199c3)) - - -### Features - -* **Nyx:** Remove broken `Unlock pro` patch ([1fe8b16](https://github.com/ReVanced/revanced-patches/commit/1fe8b164eab0c4fa80ab2da2581977f5111a2858)) -* **YouTube - Spoof video streams:** Allow picking a default audio language track ([#4050](https://github.com/ReVanced/revanced-patches/issues/4050)) ([ede666b](https://github.com/ReVanced/revanced-patches/commit/ede666b5cb64fcbaa1334ad8bef79e2634ced113)) -* **YouTube Music:** Add `Spoof video streams` patch to fix playback ([#4065](https://github.com/ReVanced/revanced-patches/issues/4065)) ([cf3116a](https://github.com/ReVanced/revanced-patches/commit/cf3116a7583d09c25c798a85687a056f143656f0)) -* **YouTube:** Add `Open videos fullscreen` patch ([#4069](https://github.com/ReVanced/revanced-patches/issues/4069)) ([296d63b](https://github.com/ReVanced/revanced-patches/commit/296d63bd42c338a01efbcb2df702e5822d05a5f1)) - -# [5.3.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.3.0-dev.6...v5.3.0-dev.7) (2024-12-09) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Add missing preferred language preference to the settings ([630633c](https://github.com/ReVanced/revanced-patches/commit/630633cf57c65c65e5578046413e17670ae336e8)) - -# [5.3.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.3.0-dev.5...v5.3.0-dev.6) (2024-12-09) - - -### Features - -* **YouTube - Spoof video streams:** Allow picking a default audio language track ([#4050](https://github.com/ReVanced/revanced-patches/issues/4050)) ([ede666b](https://github.com/ReVanced/revanced-patches/commit/ede666b5cb64fcbaa1334ad8bef79e2634ced113)) - -# [5.3.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.3.0-dev.4...v5.3.0-dev.5) (2024-12-09) - - -### Bug Fixes - -* **Change package name:** Prevent applying the patch to known incompatible apps ([#3943](https://github.com/ReVanced/revanced-patches/issues/3943)) ([44936e7](https://github.com/ReVanced/revanced-patches/commit/44936e71e846f72f7279950232a5dba37765ceb3)) -* **YouTube Music - Permanent shuffle:** Remove obsolete and non functional patch ([#4073](https://github.com/ReVanced/revanced-patches/issues/4073)) ([fbc6ab6](https://github.com/ReVanced/revanced-patches/commit/fbc6ab6a357b351f02d4d486ddc2072cf53199c3)) - - -### Features - -* **YouTube:** Add `Open videos fullscreen` patch ([#4069](https://github.com/ReVanced/revanced-patches/issues/4069)) ([296d63b](https://github.com/ReVanced/revanced-patches/commit/296d63bd42c338a01efbcb2df702e5822d05a5f1)) - -# [5.3.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.3.0-dev.3...v5.3.0-dev.4) (2024-12-09) - - -### Features - -* **Nyx:** Remove broken `Unlock pro` patch ([1fe8b16](https://github.com/ReVanced/revanced-patches/commit/1fe8b164eab0c4fa80ab2da2581977f5111a2858)) - -# [5.3.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.3.0-dev.2...v5.3.0-dev.3) (2024-12-09) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Update `Force AVC` client data ([#4064](https://github.com/ReVanced/revanced-patches/issues/4064)) ([7d537dd](https://github.com/ReVanced/revanced-patches/commit/7d537ddff4bb5421fa320741275131a66ef5c7bb)) - -# [5.3.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.3.0-dev.1...v5.3.0-dev.2) (2024-12-08) - - -### Bug Fixes - -* **Reddit:** Fix patches by using correct extension class ([70bdc68](https://github.com/ReVanced/revanced-patches/commit/70bdc6840d465399625aa1ae0259f49e72711955)) - -# [5.3.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.2.4-dev.3...v5.3.0-dev.1) (2024-12-08) - - -### Features - -* **YouTube Music:** Add `Spoof video streams` patch to fix playback ([#4065](https://github.com/ReVanced/revanced-patches/issues/4065)) ([cf3116a](https://github.com/ReVanced/revanced-patches/commit/cf3116a7583d09c25c798a85687a056f143656f0)) - -## [5.2.4-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.2.4-dev.2...v5.2.4-dev.3) (2024-12-07) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Enable opus codec by updating iOS client version ([#4063](https://github.com/ReVanced/revanced-patches/issues/4063)) ([0af156f](https://github.com/ReVanced/revanced-patches/commit/0af156f18972c5f089af4bb69824968d2a47d18f)) - -## [5.2.4-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.2.4-dev.1...v5.2.4-dev.2) (2024-12-07) - - -### Bug Fixes - -* **Sync for Reddit:** Fix patches by using correct extension name ([030093e](https://github.com/ReVanced/revanced-patches/commit/030093e913aab3fab43935eedbaeba0f6c0491bb)) - -## [5.2.4-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.2.3...v5.2.4-dev.1) (2024-12-07) - - -### Bug Fixes - -* **Twitter:** Merge correct extension by depending on correct extension patch ([8281cf6](https://github.com/ReVanced/revanced-patches/commit/8281cf6a3eead8cc25a277371e0b0ab2be982497)) - -## [5.2.3](https://github.com/ReVanced/revanced-patches/compare/v5.2.2...v5.2.3) (2024-12-06) - - -### Bug Fixes - -* **YouTube Music - GmsCore support:** Resolve patching errors ([#4056](https://github.com/ReVanced/revanced-patches/issues/4056)) ([38a4bad](https://github.com/ReVanced/revanced-patches/commit/38a4bad5b890e3906d77d22efeabd8f38653508b)) - -## [5.2.3-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.2.2...v5.2.3-dev.1) (2024-12-06) - - -### Bug Fixes - -* **YouTube Music - GmsCore support:** Resolve patching errors ([#4056](https://github.com/ReVanced/revanced-patches/issues/4056)) ([38a4bad](https://github.com/ReVanced/revanced-patches/commit/38a4bad5b890e3906d77d22efeabd8f38653508b)) - -## [5.2.2](https://github.com/ReVanced/revanced-patches/compare/v5.2.1...v5.2.2) (2024-12-06) - - -### Bug Fixes - -* **YouTube - Spoof video streams:** Use system language as default iOS audio stream ([#4042](https://github.com/ReVanced/revanced-patches/issues/4042)) ([4017185](https://github.com/ReVanced/revanced-patches/commit/4017185e760c0569e6644b94bbe66a84fa245b4b)) - ## [5.2.2-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.2.1...v5.2.2-dev.1) (2024-12-05) diff --git a/README.md b/README.md index a7a9db8855..a7ebabe533 100644 --- a/README.md +++ b/README.md @@ -97,9 +97,9 @@ Thank you for considering contributing to ReVanced Patches. You can find the con To build ReVanced Patches, you can follow the [ReVanced documentation](https://github.com/ReVanced/revanced-documentation). -## 📜 License +## 📜 Licence ReVanced Patches is licensed under the GPLv3 license. Please see the [license file](LICENSE) for more information. [tl;dr](https://www.tldrlegal.com/license/gnu-general-public-license-v3-gpl-3) you may copy, distribute and modify ReVanced Patches as long as you track changes/dates in source files. Any modifications to ReVanced Patches must also be made available under the GPL, -along with build & install instructions. +along with build & install instructions. \ No newline at end of file diff --git a/adsfund.json b/adsfund.json deleted file mode 100644 index f451581bb5..0000000000 --- a/adsfund.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "info": "This is verification file for ads.fund project", - "project": { - "name": "Revanced Patches", - "walletAddress": "0x7ab4091e00363654bf84B34151225742cd92FCE5", - "tokenAddress": "0xadf325f255083a3f3d9a9d01ffb3db52a148d802" - } -} diff --git a/build.gradle.kts b/build.gradle.kts deleted file mode 100644 index fcaeeb54cd..0000000000 --- a/build.gradle.kts +++ /dev/null @@ -1,3 +0,0 @@ -plugins { - alias(libs.plugins.android.library) apply false -} diff --git a/crowdin.yml b/crowdin.yml index 81022c88c4..148f321cd2 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -1,9 +1,8 @@ project_id_env: "CROWDIN_PROJECT_ID" api_token_env: "CROWDIN_PERSONAL_TOKEN" -preserve_hierarchy: true +preserve_hierarchy: false files: - source: patches/src/main/resources/addresources/values/strings.xml - dest: patches.xml translation: patches/src/main/resources/addresources/values-%android_code%/strings.xml skip_untranslated_strings: true diff --git a/extensions/all/misc/adb/hide-adb/build.gradle.kts b/extensions/all/misc/adb/hide-adb/build.gradle.kts deleted file mode 100644 index 42eb9984c0..0000000000 --- a/extensions/all/misc/adb/hide-adb/build.gradle.kts +++ /dev/null @@ -1,9 +0,0 @@ -android { - defaultConfig { - minSdk = 21 - } -} - -dependencies { - compileOnly(libs.annotation) -} diff --git a/extensions/all/misc/adb/hide-adb/src/main/AndroidManifest.xml b/extensions/all/misc/adb/hide-adb/src/main/AndroidManifest.xml deleted file mode 100644 index 15e7c2ae67..0000000000 --- a/extensions/all/misc/adb/hide-adb/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/extensions/all/misc/adb/hide-adb/src/main/java/app/revanced/extension/all/misc/hide/adb/HideAdbPatch.java b/extensions/all/misc/adb/hide-adb/src/main/java/app/revanced/extension/all/misc/hide/adb/HideAdbPatch.java deleted file mode 100644 index f0b4f458be..0000000000 --- a/extensions/all/misc/adb/hide-adb/src/main/java/app/revanced/extension/all/misc/hide/adb/HideAdbPatch.java +++ /dev/null @@ -1,28 +0,0 @@ -package app.revanced.extension.all.misc.hide.adb; - -import android.content.ContentResolver; -import android.provider.Settings; - -import java.util.Arrays; -import java.util.List; - -@SuppressWarnings("unused") -public final class HideAdbPatch { - private static final List SPOOF_SETTINGS = Arrays.asList("adb_enabled", "adb_wifi_enabled", "development_settings_enabled"); - - public static int getInt(ContentResolver cr, String name) throws Settings.SettingNotFoundException { - if (SPOOF_SETTINGS.contains(name)) { - return 0; - } - - return Settings.Global.getInt(cr, name); - } - - public static int getInt(ContentResolver cr, String name, int def) { - if (SPOOF_SETTINGS.contains(name)) { - return 0; - } - - return Settings.Global.getInt(cr, name, def); - } -} diff --git a/extensions/all/misc/connectivity/wifi/spoof/spoof-wifi/build.gradle.kts b/extensions/all/misc/connectivity/wifi/spoof/spoof-wifi/build.gradle.kts index c269c9862f..bc416f685c 100644 --- a/extensions/all/misc/connectivity/wifi/spoof/spoof-wifi/build.gradle.kts +++ b/extensions/all/misc/connectivity/wifi/spoof/spoof-wifi/build.gradle.kts @@ -1,8 +1,4 @@ -android { - defaultConfig { - minSdk = 23 - } -} +android.namespace = "app.revanced.extension" dependencies { compileOnly(libs.annotation) diff --git a/extensions/all/misc/connectivity/wifi/spoof/spoof-wifi/src/main/java/app/revanced/extension/all/misc/connectivity/wifi/spoof/SpoofWifiPatch.java b/extensions/all/misc/connectivity/wifi/spoof/spoof-wifi/src/main/java/app/revanced/extension/all/connectivity/wifi/spoof/SpoofWifiPatch.java similarity index 99% rename from extensions/all/misc/connectivity/wifi/spoof/spoof-wifi/src/main/java/app/revanced/extension/all/misc/connectivity/wifi/spoof/SpoofWifiPatch.java rename to extensions/all/misc/connectivity/wifi/spoof/spoof-wifi/src/main/java/app/revanced/extension/all/connectivity/wifi/spoof/SpoofWifiPatch.java index 9c1702f1bc..5f00bd730f 100644 --- a/extensions/all/misc/connectivity/wifi/spoof/spoof-wifi/src/main/java/app/revanced/extension/all/misc/connectivity/wifi/spoof/SpoofWifiPatch.java +++ b/extensions/all/misc/connectivity/wifi/spoof/spoof-wifi/src/main/java/app/revanced/extension/all/connectivity/wifi/spoof/SpoofWifiPatch.java @@ -1,4 +1,4 @@ -package app.revanced.extension.all.misc.connectivity.wifi.spoof; +package app.revanced.extension.all.connectivity.wifi.spoof; import android.app.PendingIntent; import android.content.Context; @@ -12,7 +12,7 @@ import android.os.Handler; import androidx.annotation.RequiresApi; -@SuppressWarnings({"deprecation", "unused"}) +/** @noinspection deprecation, unused */ public class SpoofWifiPatch { // Used to check what the (real or fake) active network is (take a look at `hasTransport`). diff --git a/extensions/all/misc/directory/documentsprovider/export-internal-data-documents-provider/build.gradle.kts b/extensions/all/misc/directory/documentsprovider/export-internal-data-documents-provider/build.gradle.kts deleted file mode 100644 index 42eb9984c0..0000000000 --- a/extensions/all/misc/directory/documentsprovider/export-internal-data-documents-provider/build.gradle.kts +++ /dev/null @@ -1,9 +0,0 @@ -android { - defaultConfig { - minSdk = 21 - } -} - -dependencies { - compileOnly(libs.annotation) -} diff --git a/extensions/all/misc/directory/documentsprovider/export-internal-data-documents-provider/src/main/AndroidManifest.xml b/extensions/all/misc/directory/documentsprovider/export-internal-data-documents-provider/src/main/AndroidManifest.xml deleted file mode 100644 index 15e7c2ae67..0000000000 --- a/extensions/all/misc/directory/documentsprovider/export-internal-data-documents-provider/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/extensions/all/misc/directory/documentsprovider/export-internal-data-documents-provider/src/main/java/app/revanced/extension/all/misc/directory/documentsprovider/InternalDataDocumentsProvider.java b/extensions/all/misc/directory/documentsprovider/export-internal-data-documents-provider/src/main/java/app/revanced/extension/all/misc/directory/documentsprovider/InternalDataDocumentsProvider.java deleted file mode 100644 index ad9d48f6ec..0000000000 --- a/extensions/all/misc/directory/documentsprovider/export-internal-data-documents-provider/src/main/java/app/revanced/extension/all/misc/directory/documentsprovider/InternalDataDocumentsProvider.java +++ /dev/null @@ -1,339 +0,0 @@ -package app.revanced.extension.all.misc.directory.documentsprovider; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.ProviderInfo; -import android.database.Cursor; -import android.database.MatrixCursor; -import android.os.CancellationSignal; -import android.os.ParcelFileDescriptor; -import android.provider.DocumentsContract; -import android.provider.DocumentsProvider; -import android.system.ErrnoException; -import android.system.Os; -import android.system.StructStat; -import android.util.Log; -import android.webkit.MimeTypeMap; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.Objects; - -/** - * A DocumentsProvider that allows access to the app's internal data directory. - */ -@SuppressLint("LongLogTag") -public class InternalDataDocumentsProvider extends DocumentsProvider { - private static final String[] rootColumns = - {"root_id", "mime_types", "flags", "icon", "title", "summary", "document_id"}; - private static final String[] directoryColumns = - {"document_id", "mime_type", "_display_name", "last_modified", "flags", - "_size", "full_path", "lstat_info"}; - @SuppressWarnings("OctalInteger") - private static final int S_IFMT = 0170000; - @SuppressWarnings("OctalInteger") - private static final int S_IFLNK = 0120000; - - private String packageName; - private File dataDirectory; - - /** - * Recursively delete a file or directory and all its children. - * - * @param root The file or directory to delete. - * @return True if the file or directory and all its children were successfully deleted. - */ - private static boolean deleteRecursively(File root) { - // If root is a directory, delete all children first - if (root.isDirectory()) { - try { - // Only delete recursively if the directory is not a symlink - if ((Os.lstat(root.getPath()).st_mode & S_IFMT) != S_IFLNK) { - File[] files = root.listFiles(); - if (files != null) { - for (File file : files) { - if (!deleteRecursively(file)) { - return false; - } - } - } - } - } catch (ErrnoException e) { - Log.e("InternalDocumentsProvider", "Failed to lstat " + root.getPath(), e); - } - } - - // Delete file or empty directory - return root.delete(); - } - - /** - * Resolve the MIME type of a file based on its extension. - * - * @param file The file to resolve the MIME type for. - * @return The MIME type of the file. - */ - private static String resolveMimeType(File file) { - if (file.isDirectory()) { - return DocumentsContract.Document.MIME_TYPE_DIR; - } - - String name = file.getName(); - int indexOfExtDot = name.lastIndexOf('.'); - if (indexOfExtDot < 0) { - // No extension - return "application/octet-stream"; - } - - String extension = name.substring(indexOfExtDot + 1).toLowerCase(); - String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); - return mimeType != null ? mimeType : "application/octet-stream"; - } - - @Override - public final boolean onCreate() { - return true; - } - - @Override - public final void attachInfo(Context context, ProviderInfo providerInfo) { - super.attachInfo(context, providerInfo); - - this.packageName = context.getPackageName(); - this.dataDirectory = context.getFilesDir().getParentFile(); - } - - @Override - public final String createDocument(String parentDocumentId, String mimeType, String displayName) throws FileNotFoundException { - File directory = resolveDocumentId(parentDocumentId); - File file = new File(directory, displayName); - - // If file already exists, append a number to the name - int i = 2; - while (file.exists()) { - file = new File(directory, displayName + " (" + i + ")"); - i++; - } - - try { - // Create the file or directory - if (mimeType.equals(DocumentsContract.Document.MIME_TYPE_DIR) ? file.mkdir() : file.createNewFile()) { - // Return the document ID of the new entity - if (!parentDocumentId.endsWith("/")) { - parentDocumentId = parentDocumentId + "/"; - } - return parentDocumentId + file.getName(); - } - } catch (IOException e) { - // Do nothing. We are throwing a FileNotFoundException later if the file could not be created. - } - throw new FileNotFoundException("Failed to create document in " + parentDocumentId + " with name " + displayName); - } - - @Override - public final void deleteDocument(String documentId) throws FileNotFoundException { - File file = resolveDocumentId(documentId); - if (!deleteRecursively(file)) { - throw new FileNotFoundException("Failed to delete document " + documentId); - } - } - - @Override - public final String getDocumentType(String documentId) throws FileNotFoundException { - return resolveMimeType(resolveDocumentId(documentId)); - } - - @Override - public final boolean isChildDocument(String parentDocumentId, String documentId) { - return documentId.startsWith(parentDocumentId); - } - - @Override - public final String moveDocument(String sourceDocumentId, String sourceParentDocumentId, String targetParentDocumentId) throws FileNotFoundException { - File source = resolveDocumentId(sourceDocumentId); - File dest = resolveDocumentId(targetParentDocumentId); - - File file = new File(dest, source.getName()); - if (!file.exists() && source.renameTo(file)) { - // Return the new document ID - if (targetParentDocumentId.endsWith("/")) { - return targetParentDocumentId + file.getName(); - } - return targetParentDocumentId + "/" + file.getName(); - } - - throw new FileNotFoundException("Failed to move document from " + sourceDocumentId + " to " + targetParentDocumentId); - } - - @Override - public final ParcelFileDescriptor openDocument(String documentId, String mode, CancellationSignal signal) throws FileNotFoundException { - File file = resolveDocumentId(documentId); - return ParcelFileDescriptor.open(file, ParcelFileDescriptor.parseMode(mode)); - } - - @Override - public final Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException { - if (parentDocumentId.endsWith("/")) { - parentDocumentId = parentDocumentId.substring(0, parentDocumentId.length() - 1); - } - - if (projection == null) { - projection = directoryColumns; - } - - MatrixCursor cursor = new MatrixCursor(projection); - File children = resolveDocumentId(parentDocumentId); - - // Collect all children - File[] files = children.listFiles(); - if (files != null) { - for (File file : files) { - addRowForDocument(cursor, parentDocumentId + "/" + file.getName(), file); - } - } - return cursor; - } - - @Override - public final Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException { - if (projection == null) { - projection = directoryColumns; - } - - MatrixCursor cursor = new MatrixCursor(projection); - addRowForDocument(cursor, documentId, null); - return cursor; - } - - @Override - public final Cursor queryRoots(String[] projection) { - ApplicationInfo info = Objects.requireNonNull(getContext()).getApplicationInfo(); - String appName = info.loadLabel(getContext().getPackageManager()).toString(); - - if (projection == null) { - projection = rootColumns; - } - - MatrixCursor cursor = new MatrixCursor(projection); - MatrixCursor.RowBuilder row = cursor.newRow(); - row.add(DocumentsContract.Root.COLUMN_ROOT_ID, this.packageName); - row.add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, this.packageName); - row.add(DocumentsContract.Root.COLUMN_SUMMARY, this.packageName); - row.add(DocumentsContract.Root.COLUMN_FLAGS, - DocumentsContract.Root.FLAG_LOCAL_ONLY | - DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD); - row.add(DocumentsContract.Root.COLUMN_TITLE, appName); - row.add(DocumentsContract.Root.COLUMN_MIME_TYPES, "*/*"); - row.add(DocumentsContract.Root.COLUMN_ICON, info.icon); - return cursor; - } - - @Override - public final void removeDocument(String documentId, String parentDocumentId) throws FileNotFoundException { - deleteDocument(documentId); - } - - @Override - public final String renameDocument(String documentId, String displayName) throws FileNotFoundException { - File file = resolveDocumentId(documentId); - if (!file.renameTo(new File(file.getParentFile(), displayName))) { - throw new FileNotFoundException("Failed to rename document from " + documentId + " to " + displayName); - } - - // Return the new document ID - return documentId.substring(0, documentId.lastIndexOf('/', documentId.length() - 2)) + "/" + displayName; - } - - /** - * Resolve a file instance for a given document ID. - * - * @param fullContentPath The document ID to resolve. - * @return File object for the given document ID. - * @throws FileNotFoundException If the document ID is invalid or the file does not exist. - */ - private File resolveDocumentId(String fullContentPath) throws FileNotFoundException { - if (!fullContentPath.startsWith(this.packageName)) { - throw new FileNotFoundException(fullContentPath + " not found"); - } - String path = fullContentPath.substring(this.packageName.length()); - - // Resolve the relative path within /data/data/{PKG} - File file; - if (path.equals("/") || path.isEmpty()) { - file = this.dataDirectory; - } else { - // Remove leading slash - String relativePath = path.substring(1); - file = new File(this.dataDirectory, relativePath); - } - - if (!file.exists()) { - throw new FileNotFoundException(fullContentPath + " not found"); - } - return file; - } - - /** - * Add a row containing all file properties to a MatrixCursor for a given document ID. - * - * @param cursor The cursor to add the row to. - * @param documentId The document ID to add the row for. - * @param file The file to add the row for. If null, the file will be resolved from the document ID. - * @throws FileNotFoundException If the file does not exist. - */ - private void addRowForDocument(MatrixCursor cursor, String documentId, File file) throws FileNotFoundException { - if (file == null) { - file = resolveDocumentId(documentId); - } - - int flags = 0; - if (file.isDirectory()) { - // Prefer list view for directories - flags = flags | DocumentsContract.Document.FLAG_DIR_PREFERS_LAST_MODIFIED; - } - - if (file.canWrite()) { - if (file.isDirectory()) { - flags = flags | DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE; - } - - flags = flags | DocumentsContract.Document.FLAG_SUPPORTS_WRITE | - DocumentsContract.Document.FLAG_SUPPORTS_DELETE | - DocumentsContract.Document.FLAG_SUPPORTS_RENAME | - DocumentsContract.Document.FLAG_SUPPORTS_MOVE; - } - - MatrixCursor.RowBuilder row = cursor.newRow(); - row.add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, documentId); - row.add(DocumentsContract.Document.COLUMN_DISPLAY_NAME, file.getName()); - row.add(DocumentsContract.Document.COLUMN_SIZE, file.length()); - row.add(DocumentsContract.Document.COLUMN_MIME_TYPE, resolveMimeType(file)); - row.add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, file.lastModified()); - row.add(DocumentsContract.Document.COLUMN_FLAGS, flags); - - // Custom columns - row.add("full_path", file.getAbsolutePath()); - - // Add lstat column - String path = file.getPath(); - try { - StringBuilder sb = new StringBuilder(); - StructStat lstat = Os.lstat(path); - sb.append(lstat.st_mode); - sb.append(";"); - sb.append(lstat.st_uid); - sb.append(";"); - sb.append(lstat.st_gid); - // Append symlink target if it is a symlink - if ((lstat.st_mode & S_IFMT) == S_IFLNK) { - sb.append(";"); - sb.append(Os.readlink(path)); - } - row.add("lstat_info", sb.toString()); - } catch (Exception ex) { - Log.e("InternalDocumentsProvider", "Failed to get lstat info for " + path, ex); - } - } -} diff --git a/extensions/all/misc/disable-play-integrity/build.gradle.kts b/extensions/all/misc/disable-play-integrity/build.gradle.kts deleted file mode 100644 index b3a57874e5..0000000000 --- a/extensions/all/misc/disable-play-integrity/build.gradle.kts +++ /dev/null @@ -1,13 +0,0 @@ -android { - defaultConfig { - minSdk = 21 - } - - buildFeatures { - aidl = true - } -} - -dependencies { - compileOnly(libs.annotation) -} diff --git a/extensions/all/misc/disable-play-integrity/src/main/AndroidManifest.xml b/extensions/all/misc/disable-play-integrity/src/main/AndroidManifest.xml deleted file mode 100644 index 9b65eb06cf..0000000000 --- a/extensions/all/misc/disable-play-integrity/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IExpressIntegrityService.aidl b/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IExpressIntegrityService.aidl deleted file mode 100644 index 7b8f59f1d1..0000000000 --- a/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IExpressIntegrityService.aidl +++ /dev/null @@ -1,8 +0,0 @@ -package com.google.android.play.core.integrity.protocol; - -import android.os.Bundle; -import com.google.android.play.core.integrity.protocol.IExpressIntegrityServiceCallback; - -interface IExpressIntegrityService { - oneway void requestIntegrityToken(in Bundle request, IExpressIntegrityServiceCallback callback) = 2; -} diff --git a/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IExpressIntegrityServiceCallback.aidl b/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IExpressIntegrityServiceCallback.aidl deleted file mode 100644 index 624167afb0..0000000000 --- a/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IExpressIntegrityServiceCallback.aidl +++ /dev/null @@ -1,5 +0,0 @@ -package com.google.android.play.core.integrity.protocol; - -interface IExpressIntegrityServiceCallback { - oneway void onRequestExpressIntegrityTokenResult(in Bundle result) = 2; -} diff --git a/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IIntegrityService.aidl b/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IIntegrityService.aidl deleted file mode 100644 index bb1bcd5518..0000000000 --- a/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IIntegrityService.aidl +++ /dev/null @@ -1,8 +0,0 @@ -package com.google.android.play.core.integrity.protocol; - -import android.os.Bundle; -import com.google.android.play.core.integrity.protocol.IIntegrityServiceCallback; - -interface IIntegrityService { - oneway void requestIntegrityToken(in Bundle request, IIntegrityServiceCallback callback) = 1; -} diff --git a/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IIntegrityServiceCallback.aidl b/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IIntegrityServiceCallback.aidl deleted file mode 100644 index 9485ec1694..0000000000 --- a/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IIntegrityServiceCallback.aidl +++ /dev/null @@ -1,7 +0,0 @@ -package com.google.android.play.core.integrity.protocol; - -import android.os.Bundle; - -interface IIntegrityServiceCallback { - oneway void onResult(in Bundle result) = 1; -} diff --git a/extensions/all/misc/disable-play-integrity/src/main/java/android/ext/PackageId.java b/extensions/all/misc/disable-play-integrity/src/main/java/android/ext/PackageId.java deleted file mode 100644 index 31c2ca6dba..0000000000 --- a/extensions/all/misc/disable-play-integrity/src/main/java/android/ext/PackageId.java +++ /dev/null @@ -1,10 +0,0 @@ -package android.ext; -/** @hide */ -// Int values that are assigned to packages in this interface can be retrieved at runtime from -// ApplicationInfo.ext().getPackageId() or from AndroidPackage.ext().getPackageId() (in system_server). -// -// PackageIds are assigned to parsed APKs only after they are verified, either by a certificate check -// or by a check that the APK is stored on an immutable OS partition. -public interface PackageId { - String PLAY_STORE_NAME = "com.android.vending"; -} diff --git a/extensions/all/misc/disable-play-integrity/src/main/java/android/os/BinderWrapper.java b/extensions/all/misc/disable-play-integrity/src/main/java/android/os/BinderWrapper.java deleted file mode 100644 index a01806441a..0000000000 --- a/extensions/all/misc/disable-play-integrity/src/main/java/android/os/BinderWrapper.java +++ /dev/null @@ -1,62 +0,0 @@ -package android.os; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.io.FileDescriptor; - -/** @hide */ -public class BinderWrapper implements IBinder { - protected final IBinder base; - - public BinderWrapper(IBinder base) { - this.base = base; - } - - @Override - public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException { - return base.transact(code, data, reply, flags); - } - - @Nullable - @Override - public IInterface queryLocalInterface(@NonNull String descriptor) { - return base.queryLocalInterface(descriptor); - } - - @Nullable - @Override - public String getInterfaceDescriptor() throws RemoteException { - return base.getInterfaceDescriptor(); - } - - @Override - public boolean pingBinder() { - return base.pingBinder(); - } - - @Override - public boolean isBinderAlive() { - return base.isBinderAlive(); - } - - @Override - public void dump(@NonNull FileDescriptor fd, @Nullable String[] args) throws RemoteException { - base.dump(fd, args); - } - - @Override - public void dumpAsync(@NonNull FileDescriptor fd, @Nullable String[] args) throws RemoteException { - base.dumpAsync(fd, args); - } - - @Override - public void linkToDeath(@NonNull DeathRecipient recipient, int flags) throws RemoteException { - base.linkToDeath(recipient, flags); - } - - @Override - public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) { - return base.unlinkToDeath(recipient, flags); - } -} diff --git a/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/ClassicPlayIntegrityServiceWrapper.java b/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/ClassicPlayIntegrityServiceWrapper.java deleted file mode 100644 index 3bd88d2a65..0000000000 --- a/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/ClassicPlayIntegrityServiceWrapper.java +++ /dev/null @@ -1,41 +0,0 @@ -package app.grapheneos.gmscompat.lib.playintegrity; - -import android.os.Binder; -import android.os.Bundle; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import com.android.internal.os.FakeBackgroundHandler; -import com.google.android.play.core.integrity.protocol.IIntegrityService; -import com.google.android.play.core.integrity.protocol.IIntegrityServiceCallback; - -class ClassicPlayIntegrityServiceWrapper extends PlayIntegrityServiceWrapper { - - ClassicPlayIntegrityServiceWrapper(IBinder base) { - super(base); - requestIntegrityTokenTxnCode = 2; // IIntegrityService.Stub.TRANSACTION_requestIntegrityToken - } - - static class TokenRequestStub extends IIntegrityService.Stub { - public void requestIntegrityToken(Bundle request, IIntegrityServiceCallback callback) { - Runnable r = () -> { - var result = new Bundle(); - // https://developer.android.com/google/play/integrity/reference/com/google/android/play/core/integrity/model/IntegrityErrorCode.html#API_NOT_AVAILABLE - final int API_NOT_AVAILABLE = -1; - result.putInt("error", API_NOT_AVAILABLE); - try { - callback.onResult(result); - } catch (RemoteException e) { - Log.e("IIntegrityService.Stub", "", e); - } - }; - FakeBackgroundHandler.getHandler().postDelayed(r, getTokenRequestResultDelay()); - } - }; - - @Override - protected Binder createTokenRequestStub() { - return new TokenRequestStub(); - } -} diff --git a/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/PlayIntegrityServiceWrapper.java b/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/PlayIntegrityServiceWrapper.java deleted file mode 100644 index 0418b4fe73..0000000000 --- a/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/PlayIntegrityServiceWrapper.java +++ /dev/null @@ -1,48 +0,0 @@ -package app.grapheneos.gmscompat.lib.playintegrity; - -import android.os.Binder; -import android.os.BinderWrapper; -import android.os.IBinder; -import android.os.Parcel; -import android.os.RemoteException; -import android.util.Log; - -import androidx.annotation.Nullable; - -abstract class PlayIntegrityServiceWrapper extends BinderWrapper { - final String TAG; - protected int requestIntegrityTokenTxnCode; - - public PlayIntegrityServiceWrapper(IBinder base) { - super(base); - TAG = getClass().getSimpleName(); - } - - protected abstract Binder createTokenRequestStub(); - - @Override - public boolean transact(int code, Parcel data, @Nullable Parcel reply, int flags) throws RemoteException { - if (code == requestIntegrityTokenTxnCode) { - if (maybeStubOutIntegrityTokenRequest(code, data, reply, flags)) { - return true; - } - } - return super.transact(code, data, reply, flags); - } - - private boolean maybeStubOutIntegrityTokenRequest(int code, Parcel data, @Nullable Parcel reply, int flags) { - Log.d(TAG, "integrity token request detected"); - - try { - createTokenRequestStub().transact(code, data, reply, flags); - } catch (RemoteException e) { - // this is a local call - throw new IllegalStateException(e); - } - return true; - } - - protected static long getTokenRequestResultDelay() { - return 500L; - } -} diff --git a/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/PlayIntegrityUtils.java b/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/PlayIntegrityUtils.java deleted file mode 100644 index 6ff4720cce..0000000000 --- a/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/PlayIntegrityUtils.java +++ /dev/null @@ -1,35 +0,0 @@ -package app.grapheneos.gmscompat.lib.playintegrity; - -import android.content.Intent; -import android.content.ServiceConnection; -import android.ext.PackageId; -import android.os.IBinder; -import androidx.annotation.Nullable; -import app.grapheneos.gmscompat.lib.util.ServiceConnectionWrapper; -import java.util.function.UnaryOperator; - -public class PlayIntegrityUtils { - - public static @Nullable ServiceConnection maybeReplaceServiceConnection(Intent service, ServiceConnection orig) { - if (PackageId.PLAY_STORE_NAME.equals(service.getPackage())) { - UnaryOperator binderOverride = null; - - final String CLASSIC_SERVICE = - "com.google.android.play.core.integrityservice.BIND_INTEGRITY_SERVICE"; - final String STANDARD_SERVICE = - "com.google.android.play.core.expressintegrityservice.BIND_EXPRESS_INTEGRITY_SERVICE"; - - String action = service.getAction(); - if (STANDARD_SERVICE.equals(action)) { - binderOverride = StandardPlayIntegrityServiceWrapper::new; - } else if (CLASSIC_SERVICE.equals(action)) { - binderOverride = ClassicPlayIntegrityServiceWrapper::new; - } - - if (binderOverride != null) { - return new ServiceConnectionWrapper(orig, binderOverride); - } - } - return null; - } -} diff --git a/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/StandardPlayIntegrityServiceWrapper.java b/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/StandardPlayIntegrityServiceWrapper.java deleted file mode 100644 index c1c4937f0c..0000000000 --- a/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/StandardPlayIntegrityServiceWrapper.java +++ /dev/null @@ -1,42 +0,0 @@ -package app.grapheneos.gmscompat.lib.playintegrity; - -import android.annotation.SuppressLint; -import android.os.Binder; -import android.os.Bundle; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; -import com.android.internal.os.FakeBackgroundHandler; -import com.google.android.play.core.integrity.protocol.IExpressIntegrityService; -import com.google.android.play.core.integrity.protocol.IExpressIntegrityServiceCallback; - -@SuppressLint("LongLogTag") -class StandardPlayIntegrityServiceWrapper extends PlayIntegrityServiceWrapper { - - StandardPlayIntegrityServiceWrapper(IBinder base) { - super(base); - requestIntegrityTokenTxnCode = 3; // IExpressIntegrityService.Stub.TRANSACTION_requestIntegrityToken - } - - static class TokenRequestStub extends IExpressIntegrityService.Stub { - public void requestIntegrityToken(Bundle request, IExpressIntegrityServiceCallback callback) { - Runnable r = () -> { - var result = new Bundle(); - // https://developer.android.com/google/play/integrity/reference/com/google/android/play/core/integrity/model/StandardIntegrityErrorCode.html#API_NOT_AVAILABLE - final int API_NOT_AVAILABLE = -1; - result.putInt("error", API_NOT_AVAILABLE); - try { - callback.onRequestExpressIntegrityTokenResult(result); - } catch (RemoteException e) { - Log.e("IExpressIntegrityService.Stub", "", e); - } - }; - FakeBackgroundHandler.getHandler().postDelayed(r, getTokenRequestResultDelay()); - } - }; - - @Override - protected Binder createTokenRequestStub() { - return new TokenRequestStub(); - } -} diff --git a/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/util/ServiceConnectionWrapper.java b/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/util/ServiceConnectionWrapper.java deleted file mode 100644 index 9edfc39f85..0000000000 --- a/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/util/ServiceConnectionWrapper.java +++ /dev/null @@ -1,49 +0,0 @@ -package app.grapheneos.gmscompat.lib.util; - -import android.content.ComponentName; -import android.content.ServiceConnection; -import android.os.Build; -import android.os.IBinder; - -import java.util.function.UnaryOperator; - -public class ServiceConnectionWrapper implements ServiceConnection { - private final ServiceConnection base; - private final UnaryOperator binderOverride; - - public ServiceConnectionWrapper(ServiceConnection base, UnaryOperator binderOverride) { - this.base = base; - this.binderOverride = binderOverride; - } - - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - IBinder override = binderOverride.apply(service); - if (override != null) { - service = override; - } - } - - base.onServiceConnected(name, service); - } - - @Override - public void onServiceDisconnected(ComponentName name) { - base.onServiceDisconnected(name); - } - - @Override - public void onBindingDied(ComponentName name) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - base.onBindingDied(name); - } - } - - @Override - public void onNullBinding(ComponentName name) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - base.onNullBinding(name); - } - } -} diff --git a/extensions/all/misc/disable-play-integrity/src/main/java/app/revanced/extension/play/DisablePlayIntegrityPatch.java b/extensions/all/misc/disable-play-integrity/src/main/java/app/revanced/extension/play/DisablePlayIntegrityPatch.java deleted file mode 100644 index 4dd09f693f..0000000000 --- a/extensions/all/misc/disable-play-integrity/src/main/java/app/revanced/extension/play/DisablePlayIntegrityPatch.java +++ /dev/null @@ -1,17 +0,0 @@ -package app.revanced.extension.play; - -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import app.grapheneos.gmscompat.lib.playintegrity.PlayIntegrityUtils; - -public class DisablePlayIntegrityPatch { - public static boolean bindService(Context context, Intent service, ServiceConnection conn, int flags) { - ServiceConnection override = PlayIntegrityUtils.maybeReplaceServiceConnection(service, conn); - if (override != null) { - conn = override; - } - - return context.bindService(service, conn, flags); - } -} diff --git a/extensions/all/misc/disable-play-integrity/src/main/java/com/android/internal/os/FakeBackgroundHandler.java b/extensions/all/misc/disable-play-integrity/src/main/java/com/android/internal/os/FakeBackgroundHandler.java deleted file mode 100644 index 6b4cb92b45..0000000000 --- a/extensions/all/misc/disable-play-integrity/src/main/java/com/android/internal/os/FakeBackgroundHandler.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.android.internal.os; - -import android.os.Handler; -import android.os.Looper; - -public class FakeBackgroundHandler { - - public static Handler getHandler() { - return new Handler(Looper.getMainLooper()); - } -} diff --git a/extensions/all/misc/screencapture/remove-screen-capture-restriction/build.gradle.kts b/extensions/all/misc/screencapture/remove-screen-capture-restriction/build.gradle.kts index 42eb9984c0..bc416f685c 100644 --- a/extensions/all/misc/screencapture/remove-screen-capture-restriction/build.gradle.kts +++ b/extensions/all/misc/screencapture/remove-screen-capture-restriction/build.gradle.kts @@ -1,8 +1,4 @@ -android { - defaultConfig { - minSdk = 21 - } -} +android.namespace = "app.revanced.extension" dependencies { compileOnly(libs.annotation) diff --git a/extensions/all/misc/screencapture/remove-screen-capture-restriction/src/main/java/app/revanced/extension/all/misc/screencapture/removerestriction/RemoveScreenCaptureRestrictionPatch.java b/extensions/all/misc/screencapture/remove-screen-capture-restriction/src/main/java/app/revanced/extension/all/screencapture/removerestriction/RemoveScreencaptureRestrictionPatch.java similarity index 78% rename from extensions/all/misc/screencapture/remove-screen-capture-restriction/src/main/java/app/revanced/extension/all/misc/screencapture/removerestriction/RemoveScreenCaptureRestrictionPatch.java rename to extensions/all/misc/screencapture/remove-screen-capture-restriction/src/main/java/app/revanced/extension/all/screencapture/removerestriction/RemoveScreencaptureRestrictionPatch.java index 653d4d3942..1dac341441 100644 --- a/extensions/all/misc/screencapture/remove-screen-capture-restriction/src/main/java/app/revanced/extension/all/misc/screencapture/removerestriction/RemoveScreenCaptureRestrictionPatch.java +++ b/extensions/all/misc/screencapture/remove-screen-capture-restriction/src/main/java/app/revanced/extension/all/screencapture/removerestriction/RemoveScreencaptureRestrictionPatch.java @@ -1,12 +1,11 @@ -package app.revanced.extension.all.misc.screencapture.removerestriction; +package app.revanced.extension.all.screencapture.removerestriction; import android.media.AudioAttributes; import android.os.Build; import androidx.annotation.RequiresApi; -@SuppressWarnings("unused") -public final class RemoveScreenCaptureRestrictionPatch { +public final class RemoveScreencaptureRestrictionPatch { // Member of AudioAttributes.Builder @RequiresApi(api = Build.VERSION_CODES.Q) public static AudioAttributes.Builder setAllowedCapturePolicy(final AudioAttributes.Builder builder, final int capturePolicy) { diff --git a/extensions/all/misc/screenshot/remove-screenshot-restriction/build.gradle.kts b/extensions/all/misc/screenshot/remove-screenshot-restriction/build.gradle.kts index 42eb9984c0..88c859b78f 100644 --- a/extensions/all/misc/screenshot/remove-screenshot-restriction/build.gradle.kts +++ b/extensions/all/misc/screenshot/remove-screenshot-restriction/build.gradle.kts @@ -1,9 +1 @@ -android { - defaultConfig { - minSdk = 21 - } -} - -dependencies { - compileOnly(libs.annotation) -} +android.namespace = "app.revanced.extension" diff --git a/extensions/all/misc/screenshot/remove-screenshot-restriction/src/main/java/app/revanced/extension/all/misc/screenshot/removerestriction/RemoveScreenshotRestrictionPatch.java b/extensions/all/misc/screenshot/remove-screenshot-restriction/src/main/java/app/revanced/extension/all/screenshot/removerestriction/RemoveScreenshotRestrictionPatch.java similarity index 82% rename from extensions/all/misc/screenshot/remove-screenshot-restriction/src/main/java/app/revanced/extension/all/misc/screenshot/removerestriction/RemoveScreenshotRestrictionPatch.java rename to extensions/all/misc/screenshot/remove-screenshot-restriction/src/main/java/app/revanced/extension/all/screenshot/removerestriction/RemoveScreenshotRestrictionPatch.java index 22254f6285..fd5c427d37 100644 --- a/extensions/all/misc/screenshot/remove-screenshot-restriction/src/main/java/app/revanced/extension/all/misc/screenshot/removerestriction/RemoveScreenshotRestrictionPatch.java +++ b/extensions/all/misc/screenshot/remove-screenshot-restriction/src/main/java/app/revanced/extension/all/screenshot/removerestriction/RemoveScreenshotRestrictionPatch.java @@ -1,9 +1,8 @@ -package app.revanced.extension.all.misc.screenshot.removerestriction; +package app.revanced.extension.all.screenshot.removerestriction; import android.view.Window; import android.view.WindowManager; -@SuppressWarnings("unused") public class RemoveScreenshotRestrictionPatch { public static void addFlags(Window window, int flags) { diff --git a/extensions/baconreader/build.gradle.kts b/extensions/baconreader/build.gradle.kts deleted file mode 100644 index 843fd12cc9..0000000000 --- a/extensions/baconreader/build.gradle.kts +++ /dev/null @@ -1,11 +0,0 @@ -dependencies { - compileOnly(project(":extensions:shared:library")) - compileOnly(libs.annotation) - compileOnly(libs.okhttp) -} - -android { - defaultConfig { - minSdk = 22 - } -} diff --git a/extensions/baconreader/src/main/AndroidManifest.xml b/extensions/baconreader/src/main/AndroidManifest.xml deleted file mode 100644 index 9b65eb06cf..0000000000 --- a/extensions/baconreader/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/extensions/baconreader/src/main/java/app/revanced/extension/baconreader/FixRedgifsApiPatch.java b/extensions/baconreader/src/main/java/app/revanced/extension/baconreader/FixRedgifsApiPatch.java deleted file mode 100644 index 943da63fae..0000000000 --- a/extensions/baconreader/src/main/java/app/revanced/extension/baconreader/FixRedgifsApiPatch.java +++ /dev/null @@ -1,22 +0,0 @@ -package app.revanced.extension.baconreader; - -import app.revanced.extension.shared.fixes.redgifs.BaseFixRedgifsApiPatch; -import okhttp3.OkHttpClient; - -/** - * @noinspection unused - */ -public class FixRedgifsApiPatch extends BaseFixRedgifsApiPatch { - static { - INSTANCE = new FixRedgifsApiPatch(); - } - - public String getDefaultUserAgent() { - // BaconReader uses a static user agent for Redgifs API calls - return "BaconReader"; - } - - public static OkHttpClient install(OkHttpClient.Builder builder) { - return builder.addInterceptor(INSTANCE).build(); - } -} diff --git a/extensions/boostforreddit/build.gradle.kts b/extensions/boostforreddit/build.gradle.kts index d84b488441..54c06871b9 100644 --- a/extensions/boostforreddit/build.gradle.kts +++ b/extensions/boostforreddit/build.gradle.kts @@ -1,12 +1,4 @@ dependencies { compileOnly(project(":extensions:shared:library")) compileOnly(project(":extensions:boostforreddit:stub")) - compileOnly(libs.annotation) - compileOnly(libs.okhttp) -} - -android { - defaultConfig { - minSdk = 21 - } } diff --git a/extensions/boostforreddit/src/main/java/app/revanced/extension/boostforreddit/FixRedgifsApiPatch.java b/extensions/boostforreddit/src/main/java/app/revanced/extension/boostforreddit/FixRedgifsApiPatch.java deleted file mode 100644 index 92757dabf4..0000000000 --- a/extensions/boostforreddit/src/main/java/app/revanced/extension/boostforreddit/FixRedgifsApiPatch.java +++ /dev/null @@ -1,22 +0,0 @@ -package app.revanced.extension.boostforreddit; - -import app.revanced.extension.shared.fixes.redgifs.BaseFixRedgifsApiPatch; -import okhttp3.OkHttpClient; - -/** - * @noinspection unused - */ -public class FixRedgifsApiPatch extends BaseFixRedgifsApiPatch { - static { - INSTANCE = new FixRedgifsApiPatch(); - } - - public String getDefaultUserAgent() { - // Boost uses a static user agent for Redgifs API calls - return "Boost"; - } - - public static OkHttpClient createClient() { - return new OkHttpClient.Builder().addInterceptor(INSTANCE).build(); - } -} diff --git a/extensions/boostforreddit/stub/build.gradle.kts b/extensions/boostforreddit/stub/build.gradle.kts index b4bee8809f..c1cc5794c0 100644 --- a/extensions/boostforreddit/stub/build.gradle.kts +++ b/extensions/boostforreddit/stub/build.gradle.kts @@ -1,10 +1,10 @@ plugins { - alias(libs.plugins.android.library) + id(libs.plugins.android.library.get().pluginId) } android { namespace = "app.revanced.extension" - compileSdk = 34 + compileSdk = 33 defaultConfig { minSdk = 24 diff --git a/extensions/cricbuzz/build.gradle.kts b/extensions/cricbuzz/build.gradle.kts deleted file mode 100644 index b09ca9effe..0000000000 --- a/extensions/cricbuzz/build.gradle.kts +++ /dev/null @@ -1,10 +0,0 @@ -dependencies { - compileOnly(project(":extensions:shared:library")) - compileOnly(project(":extensions:cricbuzz:stub")) -} - -android { - defaultConfig { - minSdk = 21 - } -} diff --git a/extensions/cricbuzz/src/main/AndroidManifest.xml b/extensions/cricbuzz/src/main/AndroidManifest.xml deleted file mode 100644 index 9b65eb06cf..0000000000 --- a/extensions/cricbuzz/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/extensions/cricbuzz/src/main/java/app/revanced/extension/cricbuzz/HideAdsPatch.java b/extensions/cricbuzz/src/main/java/app/revanced/extension/cricbuzz/HideAdsPatch.java deleted file mode 100644 index 9338e2978f..0000000000 --- a/extensions/cricbuzz/src/main/java/app/revanced/extension/cricbuzz/HideAdsPatch.java +++ /dev/null @@ -1,28 +0,0 @@ -package app.revanced.extension.cricbuzz.ads; - -import com.cricbuzz.android.data.rest.model.BottomBar; -import java.util.List; -import java.util.Iterator; -import app.revanced.extension.shared.Logger; - -@SuppressWarnings("unused") -public class HideAdsPatch { - - /** - * Injection point. - */ - public static void filterCb11(List list) { - try { - Iterator iterator = list.iterator(); - while (iterator.hasNext()) { - BottomBar bar = iterator.next(); - if (bar.getName().equals("Cricbuzz11")) { - Logger.printInfo(() -> "Removing Cricbuzz11 bar: " + bar); - iterator.remove(); - } - } - } catch (Exception ex) { - Logger.printException(() -> "filterCb11 failure", ex); - } - } -} \ No newline at end of file diff --git a/extensions/cricbuzz/stub/build.gradle.kts b/extensions/cricbuzz/stub/build.gradle.kts deleted file mode 100644 index 7744c0eaac..0000000000 --- a/extensions/cricbuzz/stub/build.gradle.kts +++ /dev/null @@ -1,17 +0,0 @@ -plugins { - alias(libs.plugins.android.library) -} - -android { - namespace = "app.revanced.extension" - compileSdk = 34 - - defaultConfig { - minSdk = 21 - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } -} diff --git a/extensions/cricbuzz/stub/src/main/AndroidManifest.xml b/extensions/cricbuzz/stub/src/main/AndroidManifest.xml deleted file mode 100644 index 9b65eb06cf..0000000000 --- a/extensions/cricbuzz/stub/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/extensions/cricbuzz/stub/src/main/java/com/cricbuzz/android/data/rest/model/BottomBar.java b/extensions/cricbuzz/stub/src/main/java/com/cricbuzz/android/data/rest/model/BottomBar.java deleted file mode 100644 index 2b2660c320..0000000000 --- a/extensions/cricbuzz/stub/src/main/java/com/cricbuzz/android/data/rest/model/BottomBar.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.cricbuzz.android.data.rest.model; - -public final class BottomBar { - public final String getName() { throw new UnsupportedOperationException(); } -} \ No newline at end of file diff --git a/extensions/instagram/build.gradle.kts b/extensions/instagram/build.gradle.kts deleted file mode 100644 index 9b476b1c81..0000000000 --- a/extensions/instagram/build.gradle.kts +++ /dev/null @@ -1,9 +0,0 @@ -dependencies { - compileOnly(project(":extensions:shared:library")) -} - -android { - defaultConfig { - minSdk = 26 - } -} diff --git a/extensions/instagram/src/main/AndroidManifest.xml b/extensions/instagram/src/main/AndroidManifest.xml deleted file mode 100644 index 9b65eb06cf..0000000000 --- a/extensions/instagram/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/extensions/instagram/src/main/java/app/revanced/extension/instagram/feed/LimitFeedToFollowedProfiles.java b/extensions/instagram/src/main/java/app/revanced/extension/instagram/feed/LimitFeedToFollowedProfiles.java deleted file mode 100644 index 3154dd9f7a..0000000000 --- a/extensions/instagram/src/main/java/app/revanced/extension/instagram/feed/LimitFeedToFollowedProfiles.java +++ /dev/null @@ -1,26 +0,0 @@ -package app.revanced.extension.instagram.feed; - -import java.util.HashMap; -import java.util.Map; - -@SuppressWarnings("unused") -public class LimitFeedToFollowedProfiles { - - /** - * Injection point. - */ - public static Map setFollowingHeader(Map requestHeaderMap) { - String paginationHeaderName = "pagination_source"; - - // Patch the header only if it's trying to fetch the default feed - String currentHeader = requestHeaderMap.get(paginationHeaderName); - if (currentHeader != null && !currentHeader.equals("feed_recs")) { - return requestHeaderMap; - } - - // Create new map as original is unmodifiable. - Map patchedRequestHeaderMap = new HashMap<>(requestHeaderMap); - patchedRequestHeaderMap.put(paginationHeaderName, "following"); - return patchedRequestHeaderMap; - } -} diff --git a/extensions/instagram/src/main/java/app/revanced/extension/instagram/hide/navigation/HideNavigationButtonsPatch.java b/extensions/instagram/src/main/java/app/revanced/extension/instagram/hide/navigation/HideNavigationButtonsPatch.java deleted file mode 100644 index 1dd99e204f..0000000000 --- a/extensions/instagram/src/main/java/app/revanced/extension/instagram/hide/navigation/HideNavigationButtonsPatch.java +++ /dev/null @@ -1,33 +0,0 @@ -package app.revanced.extension.instagram.hide.navigation; - -import java.lang.reflect.Field; -import java.util.List; - -@SuppressWarnings("unused") -public class HideNavigationButtonsPatch { - - /** - * Injection point. - * @param navigationButtonsList the list of navigation buttons, as an (obfuscated) Enum type - * @param buttonNameToRemove the name of the button we want to remove - * @param enumNameField the field in the nav button enum class which contains the name of the button - * @return the patched list of navigation buttons - */ - public static List removeNavigationButtonByName( - List navigationButtonsList, - String buttonNameToRemove, - String enumNameField - ) - throws IllegalAccessException, NoSuchFieldException { - for (Object button : navigationButtonsList) { - Field f = button.getClass().getDeclaredField(enumNameField); - String currentButtonEnumName = (String) f.get(button); - - if (buttonNameToRemove.equals(currentButtonEnumName)) { - navigationButtonsList.remove(button); - break; - } - } - return navigationButtonsList; - } -} diff --git a/extensions/instagram/src/main/java/app/revanced/extension/instagram/misc/links/OpenLinksExternallyPatch.java b/extensions/instagram/src/main/java/app/revanced/extension/instagram/misc/links/OpenLinksExternallyPatch.java deleted file mode 100644 index 49db896c23..0000000000 --- a/extensions/instagram/src/main/java/app/revanced/extension/instagram/misc/links/OpenLinksExternallyPatch.java +++ /dev/null @@ -1,30 +0,0 @@ -package app.revanced.extension.instagram.misc.links; - -import android.net.Uri; - -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.Utils; - -@SuppressWarnings("unused") -public final class OpenLinksExternallyPatch { - - /** - * Injection point. - */ - public static boolean openExternally(String url) { - try { - // The "url" parameter to this function will be of the form. - // https://l.instagram.com/?u=&e= - String actualUrl = Uri.parse(url).getQueryParameter("u"); - if (actualUrl != null) { - Utils.openLink(actualUrl); - return true; - } - - } catch (Exception ex) { - Logger.printException(() -> "openExternally failure", ex); - } - - return false; - } -} diff --git a/extensions/instagram/src/main/java/app/revanced/extension/instagram/misc/privacy/SanitizeSharingLinksPatch.java b/extensions/instagram/src/main/java/app/revanced/extension/instagram/misc/privacy/SanitizeSharingLinksPatch.java deleted file mode 100644 index 058ee19f90..0000000000 --- a/extensions/instagram/src/main/java/app/revanced/extension/instagram/misc/privacy/SanitizeSharingLinksPatch.java +++ /dev/null @@ -1,15 +0,0 @@ -package app.revanced.extension.instagram.misc.privacy; - -import app.revanced.extension.shared.privacy.LinkSanitizer; - -@SuppressWarnings("unused") -public final class SanitizeSharingLinksPatch { - private static final LinkSanitizer sanitizer = new LinkSanitizer("igsh"); - - /** - * Injection point. - */ - public static String sanitizeSharingLink(String url) { - return sanitizer.sanitizeURLString(url); - } -} diff --git a/extensions/instagram/src/main/java/app/revanced/extension/instagram/misc/share/domain/ChangeLinkSharingDomainPatch.java b/extensions/instagram/src/main/java/app/revanced/extension/instagram/misc/share/domain/ChangeLinkSharingDomainPatch.java deleted file mode 100644 index 77eea7e847..0000000000 --- a/extensions/instagram/src/main/java/app/revanced/extension/instagram/misc/share/domain/ChangeLinkSharingDomainPatch.java +++ /dev/null @@ -1,33 +0,0 @@ -package app.revanced.extension.instagram.misc.share.domain; - -import android.net.Uri; -import app.revanced.extension.shared.Logger; - -@SuppressWarnings("unused") -public final class ChangeLinkSharingDomainPatch { - - private static String getCustomShareDomain() { - // Method is modified during patching. - throw new IllegalStateException(); - } - - /** - * Injection point. - */ - public static String setCustomShareDomain(String url) { - try { - Uri uri = Uri.parse(url); - Uri.Builder builder = uri - .buildUpon() - .authority(getCustomShareDomain()) - .clearQuery(); - - String patchedUrl = builder.build().toString(); - Logger.printInfo(() -> "Domain change from : " + url + " to: " + patchedUrl); - return patchedUrl; - } catch (Exception ex) { - Logger.printException(() -> "setCustomShareDomain failure with " + url, ex); - return url; - } - } -} diff --git a/extensions/instagram/src/main/java/app/revanced/extension/instagram/misc/share/privacy/SanitizeSharingLinksPatch.java b/extensions/instagram/src/main/java/app/revanced/extension/instagram/misc/share/privacy/SanitizeSharingLinksPatch.java deleted file mode 100644 index 0566f68acb..0000000000 --- a/extensions/instagram/src/main/java/app/revanced/extension/instagram/misc/share/privacy/SanitizeSharingLinksPatch.java +++ /dev/null @@ -1,15 +0,0 @@ -package app.revanced.extension.instagram.misc.share.privacy; - -import app.revanced.extension.shared.privacy.LinkSanitizer; - -@SuppressWarnings("unused") -public final class SanitizeSharingLinksPatch { - private static final LinkSanitizer sanitizer = new LinkSanitizer("igsh"); - - /** - * Injection point. - */ - public static String sanitizeSharingLink(String url) { - return sanitizer.sanitizeURLString(url); - } -} diff --git a/extensions/messenger/build.gradle.kts b/extensions/messenger/build.gradle.kts deleted file mode 100644 index 36b080b27b..0000000000 --- a/extensions/messenger/build.gradle.kts +++ /dev/null @@ -1,9 +0,0 @@ -dependencies { - compileOnly(project(":extensions:shared:library")) -} - -android { - defaultConfig { - minSdk = 24 - } -} diff --git a/extensions/messenger/src/main/AndroidManifest.xml b/extensions/messenger/src/main/AndroidManifest.xml deleted file mode 100644 index 9b65eb06cf..0000000000 --- a/extensions/messenger/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/extensions/messenger/src/main/java/app/revanced/extension/messenger/metaai/RemoveMetaAIPatch.java b/extensions/messenger/src/main/java/app/revanced/extension/messenger/metaai/RemoveMetaAIPatch.java deleted file mode 100644 index c09fe7edc9..0000000000 --- a/extensions/messenger/src/main/java/app/revanced/extension/messenger/metaai/RemoveMetaAIPatch.java +++ /dev/null @@ -1,25 +0,0 @@ -package app.revanced.extension.messenger.metaai; - -import java.util.*; - -import app.revanced.extension.shared.Logger; - -@SuppressWarnings("unused") -public class RemoveMetaAIPatch { - private static final Set loggedIDs = Collections.synchronizedSet(new HashSet<>()); - - public static boolean overrideBooleanFlag(long id, boolean value) { - try { - if (Long.toString(id).startsWith("REPLACED_BY_PATCH")) { - if (loggedIDs.add(id)) - Logger.printInfo(() -> "Overriding " + id + " from " + value + " to false"); - - return false; - } - } catch (Exception ex) { - Logger.printException(() -> "overrideBooleanFlag failure", ex); - } - - return value; - } -} diff --git a/extensions/music/build.gradle.kts b/extensions/music/build.gradle.kts deleted file mode 100644 index f84a54a0d3..0000000000 --- a/extensions/music/build.gradle.kts +++ /dev/null @@ -1,11 +0,0 @@ -dependencies { - compileOnly(project(":extensions:shared:library")) - compileOnly(project(":extensions:youtube:stub")) - compileOnly(libs.annotation) -} - -android { - defaultConfig { - minSdk = 26 - } -} diff --git a/extensions/music/src/main/AndroidManifest.xml b/extensions/music/src/main/AndroidManifest.xml deleted file mode 100644 index 9b65eb06cf..0000000000 --- a/extensions/music/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/extensions/music/src/main/java/app/revanced/extension/music/VersionCheckUtils.java b/extensions/music/src/main/java/app/revanced/extension/music/VersionCheckUtils.java deleted file mode 100644 index 76331a720b..0000000000 --- a/extensions/music/src/main/java/app/revanced/extension/music/VersionCheckUtils.java +++ /dev/null @@ -1,12 +0,0 @@ -package app.revanced.extension.music; - -import app.revanced.extension.shared.Utils; - -public class VersionCheckUtils { - private static boolean isVersionOrGreater(String version) { - return Utils.getAppVersionName().compareTo(version) >= 0; - } - - public static final boolean IS_8_40_OR_GREATER = isVersionOrGreater("8.40.00"); -} - diff --git a/extensions/music/src/main/java/app/revanced/extension/music/patches/ChangeMiniplayerColorPatch.java b/extensions/music/src/main/java/app/revanced/extension/music/patches/ChangeMiniplayerColorPatch.java deleted file mode 100644 index ec941f7f3b..0000000000 --- a/extensions/music/src/main/java/app/revanced/extension/music/patches/ChangeMiniplayerColorPatch.java +++ /dev/null @@ -1,14 +0,0 @@ -package app.revanced.extension.music.patches; - -import app.revanced.extension.music.settings.Settings; - -@SuppressWarnings("unused") -public class ChangeMiniplayerColorPatch { - - /** - * Injection point - */ - public static boolean changeMiniplayerColor() { - return Settings.CHANGE_MINIPLAYER_COLOR.get(); - } -} diff --git a/extensions/music/src/main/java/app/revanced/extension/music/patches/ForceOriginalAudioPatch.java b/extensions/music/src/main/java/app/revanced/extension/music/patches/ForceOriginalAudioPatch.java deleted file mode 100644 index 26589623e3..0000000000 --- a/extensions/music/src/main/java/app/revanced/extension/music/patches/ForceOriginalAudioPatch.java +++ /dev/null @@ -1,17 +0,0 @@ -package app.revanced.extension.music.patches; - -import app.revanced.extension.music.settings.Settings; - -@SuppressWarnings("unused") -public class ForceOriginalAudioPatch { - - /** - * Injection point. - */ - public static void setEnabled() { - app.revanced.extension.shared.patches.ForceOriginalAudioPatch.setEnabled( - Settings.FORCE_ORIGINAL_AUDIO.get(), - Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() - ); - } -} diff --git a/extensions/music/src/main/java/app/revanced/extension/music/patches/HideButtonsPatch.java b/extensions/music/src/main/java/app/revanced/extension/music/patches/HideButtonsPatch.java deleted file mode 100644 index 5794baa9b1..0000000000 --- a/extensions/music/src/main/java/app/revanced/extension/music/patches/HideButtonsPatch.java +++ /dev/null @@ -1,49 +0,0 @@ -package app.revanced.extension.music.patches; - -import static app.revanced.extension.shared.Utils.hideViewBy0dpUnderCondition; - -import android.view.View; -import android.view.ViewGroup; - -import app.revanced.extension.music.settings.Settings; - -@SuppressWarnings("unused") -public class HideButtonsPatch { - - /** - * Injection point - */ - public static int hideCastButton(int original) { - return Settings.HIDE_CAST_BUTTON.get() ? View.GONE : original; - } - - /** - * Injection point - */ - public static void hideCastButton(View view) { - hideViewBy0dpUnderCondition(Settings.HIDE_CAST_BUTTON, view); - } - - /** - * Injection point - */ - public static boolean hideHistoryButton(boolean original) { - return original && !Settings.HIDE_HISTORY_BUTTON.get(); - } - - /** - * Injection point - */ - public static void hideNotificationButton(View view) { - if (view.getParent() instanceof ViewGroup viewGroup) { - hideViewBy0dpUnderCondition(Settings.HIDE_NOTIFICATION_BUTTON, viewGroup); - } - } - - /** - * Injection point - */ - public static void hideSearchButton(View view) { - hideViewBy0dpUnderCondition(Settings.HIDE_SEARCH_BUTTON, view); - } -} diff --git a/extensions/music/src/main/java/app/revanced/extension/music/patches/HideCategoryBarPatch.java b/extensions/music/src/main/java/app/revanced/extension/music/patches/HideCategoryBarPatch.java deleted file mode 100644 index f0b0901555..0000000000 --- a/extensions/music/src/main/java/app/revanced/extension/music/patches/HideCategoryBarPatch.java +++ /dev/null @@ -1,18 +0,0 @@ -package app.revanced.extension.music.patches; - -import static app.revanced.extension.shared.Utils.hideViewBy0dpUnderCondition; - -import android.view.View; - -import app.revanced.extension.music.settings.Settings; - -@SuppressWarnings("unused") -public class HideCategoryBarPatch { - - /** - * Injection point - */ - public static void hideCategoryBar(View view) { - hideViewBy0dpUnderCondition(Settings.HIDE_CATEGORY_BAR, view); - } -} diff --git a/extensions/music/src/main/java/app/revanced/extension/music/patches/HideGetPremiumPatch.java b/extensions/music/src/main/java/app/revanced/extension/music/patches/HideGetPremiumPatch.java deleted file mode 100644 index 658c0e59af..0000000000 --- a/extensions/music/src/main/java/app/revanced/extension/music/patches/HideGetPremiumPatch.java +++ /dev/null @@ -1,14 +0,0 @@ -package app.revanced.extension.music.patches; - -import app.revanced.extension.music.settings.Settings; - -@SuppressWarnings("unused") -public class HideGetPremiumPatch { - - /** - * Injection point - */ - public static boolean hideGetPremiumLabel() { - return Settings.HIDE_GET_PREMIUM_LABEL.get(); - } -} diff --git a/extensions/music/src/main/java/app/revanced/extension/music/patches/HideVideoAdsPatch.java b/extensions/music/src/main/java/app/revanced/extension/music/patches/HideVideoAdsPatch.java deleted file mode 100644 index 9c4d51ee37..0000000000 --- a/extensions/music/src/main/java/app/revanced/extension/music/patches/HideVideoAdsPatch.java +++ /dev/null @@ -1,17 +0,0 @@ -package app.revanced.extension.music.patches; - -import app.revanced.extension.music.settings.Settings; - -@SuppressWarnings("unused") -public class HideVideoAdsPatch { - - /** - * Injection point - */ - public static boolean showVideoAds(boolean original) { - if (Settings.HIDE_VIDEO_ADS.get()) { - return false; - } - return original; - } -} diff --git a/extensions/music/src/main/java/app/revanced/extension/music/patches/NavigationBarPatch.java b/extensions/music/src/main/java/app/revanced/extension/music/patches/NavigationBarPatch.java deleted file mode 100644 index 6131401ce2..0000000000 --- a/extensions/music/src/main/java/app/revanced/extension/music/patches/NavigationBarPatch.java +++ /dev/null @@ -1,86 +0,0 @@ -package app.revanced.extension.music.patches; - -import static app.revanced.extension.shared.Utils.hideViewUnderCondition; - -import android.view.View; -import android.widget.TextView; - -import androidx.annotation.Nullable; - -import java.util.Arrays; -import java.util.List; - -import app.revanced.extension.music.settings.Settings; - -@SuppressWarnings("unused") -public class NavigationBarPatch { - private static String lastYTNavigationEnumName = ""; - - public static void setLastAppNavigationEnum(@Nullable Enum ytNavigationEnumName) { - if (ytNavigationEnumName != null) { - lastYTNavigationEnumName = ytNavigationEnumName.name(); - } - } - - public static void hideNavigationLabel(TextView textview) { - hideViewUnderCondition(Settings.HIDE_NAVIGATION_BAR_LABEL.get(), textview); - } - - public static void hideNavigationButton(View view) { - // Hide entire navigation bar. - if (Settings.HIDE_NAVIGATION_BAR.get() && view.getParent() != null) { - hideViewUnderCondition(true, (View) view.getParent()); - return; - } - - // Hide navigation buttons based on their type. - for (NavigationButton button : NavigationButton.values()) { - if (button.ytEnumNames.contains(lastYTNavigationEnumName)) { - hideViewUnderCondition(button.hidden, view); - break; - } - } - } - - private enum NavigationButton { - HOME( - Arrays.asList( - "TAB_HOME" - ), - Settings.HIDE_NAVIGATION_BAR_HOME_BUTTON.get() - ), - SAMPLES( - Arrays.asList( - "TAB_SAMPLES" - ), - Settings.HIDE_NAVIGATION_BAR_SAMPLES_BUTTON.get() - ), - EXPLORE( - Arrays.asList( - "TAB_EXPLORE" - ), - Settings.HIDE_NAVIGATION_BAR_EXPLORE_BUTTON.get() - ), - LIBRARY( - Arrays.asList( - "LIBRARY_MUSIC", - "TAB_BOOKMARK" // YouTube Music 8.24+ - ), - Settings.HIDE_NAVIGATION_BAR_LIBRARY_BUTTON.get() - ), - UPGRADE( - Arrays.asList( - "TAB_MUSIC_PREMIUM" - ), - Settings.HIDE_NAVIGATION_BAR_UPGRADE_BUTTON.get() - ); - - private final List ytEnumNames; - private final boolean hidden; - - NavigationButton(List ytEnumNames, boolean hidden) { - this.ytEnumNames = ytEnumNames; - this.hidden = hidden; - } - } -} diff --git a/extensions/music/src/main/java/app/revanced/extension/music/patches/PermanentRepeatPatch.java b/extensions/music/src/main/java/app/revanced/extension/music/patches/PermanentRepeatPatch.java deleted file mode 100644 index b44b0a3f1c..0000000000 --- a/extensions/music/src/main/java/app/revanced/extension/music/patches/PermanentRepeatPatch.java +++ /dev/null @@ -1,14 +0,0 @@ -package app.revanced.extension.music.patches; - -import app.revanced.extension.music.settings.Settings; - -@SuppressWarnings("unused") -public class PermanentRepeatPatch { - - /** - * Injection point - */ - public static boolean permanentRepeat() { - return Settings.PERMANENT_REPEAT.get(); - } -} diff --git a/extensions/music/src/main/java/app/revanced/extension/music/patches/spoof/SpoofVideoStreamsPatch.java b/extensions/music/src/main/java/app/revanced/extension/music/patches/spoof/SpoofVideoStreamsPatch.java deleted file mode 100644 index 46c85a8edd..0000000000 --- a/extensions/music/src/main/java/app/revanced/extension/music/patches/spoof/SpoofVideoStreamsPatch.java +++ /dev/null @@ -1,30 +0,0 @@ -package app.revanced.extension.music.patches.spoof; - -import static app.revanced.extension.music.settings.Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE; -import static app.revanced.extension.shared.spoof.ClientType.ANDROID_REEL; -import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_43_32; -import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_61_48; -import static app.revanced.extension.shared.spoof.ClientType.VISIONOS; - -import java.util.List; - -import app.revanced.extension.shared.spoof.ClientType; - -@SuppressWarnings("unused") -public class SpoofVideoStreamsPatch { - - /** - * Injection point. - */ - public static void setClientOrderToUse() { - List availableClients = List.of( - ANDROID_REEL, - ANDROID_VR_1_43_32, - VISIONOS, - ANDROID_VR_1_61_48 - ); - - app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.setClientsToUse( - availableClients, SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get()); - } -} diff --git a/extensions/music/src/main/java/app/revanced/extension/music/patches/theme/ThemePatch.java b/extensions/music/src/main/java/app/revanced/extension/music/patches/theme/ThemePatch.java deleted file mode 100644 index 3f4e396699..0000000000 --- a/extensions/music/src/main/java/app/revanced/extension/music/patches/theme/ThemePatch.java +++ /dev/null @@ -1,27 +0,0 @@ -package app.revanced.extension.music.patches.theme; - -import app.revanced.extension.shared.theme.BaseThemePatch; - -@SuppressWarnings("unused") -public class ThemePatch extends BaseThemePatch { - - // Color constants used in relation with litho components. - private static final int[] DARK_VALUES = { - 0xFF212121, // Comments box background. - 0xFF030303, // Button container background in album. - 0xFF000000, // Button container background in playlist. - }; - - /** - * Injection point. - *

- * Change the color of Litho components. - * If the color of the component matches one of the values, return the background color. - * - * @param originalValue The original color value. - * @return The new or original color value. - */ - public static int getValue(int originalValue) { - return processColorValue(originalValue, DARK_VALUES, null); - } -} diff --git a/extensions/music/src/main/java/app/revanced/extension/music/settings/MusicActivityHook.java b/extensions/music/src/main/java/app/revanced/extension/music/settings/MusicActivityHook.java deleted file mode 100644 index c3874f655c..0000000000 --- a/extensions/music/src/main/java/app/revanced/extension/music/settings/MusicActivityHook.java +++ /dev/null @@ -1,148 +0,0 @@ -package app.revanced.extension.music.settings; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.graphics.PorterDuff; -import android.graphics.drawable.Drawable; -import android.preference.PreferenceFragment; -import android.view.View; -import android.widget.Toolbar; - -import app.revanced.extension.music.VersionCheckUtils; -import app.revanced.extension.music.settings.preference.MusicPreferenceFragment; -import app.revanced.extension.music.settings.search.MusicSearchViewController; -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.ResourceType; -import app.revanced.extension.shared.Utils; -import app.revanced.extension.shared.settings.BaseActivityHook; - -/** - * Hooks GoogleApiActivity to inject a custom {@link MusicPreferenceFragment} with a toolbar and search. - */ -public class MusicActivityHook extends BaseActivityHook { - - @SuppressLint("StaticFieldLeak") - public static MusicSearchViewController searchViewController; - - /** - * How much time has passed since the first launch of the app. Simple check to prevent - * forcing bold icons on first launch where the settings menu is partially broken - * due to missing icon resources the client has not yet received. - * - * @see app.revanced.extension.youtube.settings.YouTubeActivityHook#MINIMUM_TIME_AFTER_FIRST_LAUNCH_BEFORE_ALLOWING_BOLD_ICONS - */ - private static final long MINIMUM_TIME_AFTER_FIRST_LAUNCH_BEFORE_ALLOWING_BOLD_ICONS = 30 * 1000; // 30 seconds. - - static { - final boolean useBoldIcons = VersionCheckUtils.IS_8_40_OR_GREATER - && !Settings.SETTINGS_DISABLE_BOLD_ICONS.get() - && (System.currentTimeMillis() - Settings.FIRST_TIME_APP_LAUNCHED.get()) - > MINIMUM_TIME_AFTER_FIRST_LAUNCH_BEFORE_ALLOWING_BOLD_ICONS; - - Utils.setAppIsUsingBoldIcons(useBoldIcons); - } - - /** - * Injection point. - */ - @SuppressWarnings("unused") - public static void initialize(Activity parentActivity) { - // Must touch the Music settings to ensure the class is loaded and - // the values can be found when setting the UI preferences. - // Logging anything under non debug ensures this is set. - Logger.printInfo(() -> "Permanent repeat enabled: " + Settings.PERMANENT_REPEAT.get()); - - // YT Music always uses dark mode. - Utils.setIsDarkModeEnabled(true); - - BaseActivityHook.initialize(new MusicActivityHook(), parentActivity); - } - - /** - * Sets the fixed theme for the activity. - */ - @Override - protected void customizeActivityTheme(Activity activity) { - // Override the default YouTube Music theme to increase start padding of list items. - // Custom style located in resources/music/values/style.xml - activity.setTheme(Utils.getResourceIdentifierOrThrow( - ResourceType.STYLE, "Theme.ReVanced.YouTubeMusic.Settings")); - } - - /** - * Returns the fixed background color for the toolbar. - */ - @Override - protected int getToolbarBackgroundColor() { - return Utils.getResourceColor("ytm_color_black"); - } - - /** - * Returns the navigation icon with a color filter applied. - */ - @Override - protected Drawable getNavigationIcon() { - Drawable navigationIcon = MusicPreferenceFragment.getBackButtonDrawable(); - navigationIcon.setColorFilter(Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN); - return navigationIcon; - } - - /** - * Returns the click listener that finishes the activity when the navigation icon is clicked. - */ - @Override - protected View.OnClickListener getNavigationClickListener(Activity activity) { - return view -> { - if (searchViewController != null && searchViewController.isSearchActive()) { - searchViewController.closeSearch(); - } else { - activity.finish(); - } - }; - } - - /** - * Adds search view components to the toolbar for {@link MusicPreferenceFragment}. - * - * @param activity The activity hosting the toolbar. - * @param toolbar The configured toolbar. - * @param fragment The PreferenceFragment associated with the activity. - */ - @Override - protected void onPostToolbarSetup(Activity activity, Toolbar toolbar, PreferenceFragment fragment) { - if (fragment instanceof MusicPreferenceFragment) { - searchViewController = MusicSearchViewController.addSearchViewComponents( - activity, toolbar, (MusicPreferenceFragment) fragment); - } - } - - /** - * Creates a new {@link MusicPreferenceFragment} for the activity. - */ - @Override - protected PreferenceFragment createPreferenceFragment() { - return new MusicPreferenceFragment(); - } - - /** - * Injection point. - *

- * Overrides {@link Activity#finish()} of the injection Activity. - * - * @return if the original activity finish method should be allowed to run. - */ - @SuppressWarnings("unused") - public static boolean handleFinish() { - return MusicSearchViewController.handleFinish(searchViewController); - } - - /** - * Injection point. - *

- * Decides whether to use bold icons. - */ - @SuppressWarnings("unused") - public static boolean useBoldIcons(boolean original) { - return Utils.appIsUsingBoldIcons(); - } -} diff --git a/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java b/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java deleted file mode 100644 index 7decd29b8a..0000000000 --- a/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java +++ /dev/null @@ -1,41 +0,0 @@ -package app.revanced.extension.music.settings; - -import static java.lang.Boolean.FALSE; -import static java.lang.Boolean.TRUE; -import static app.revanced.extension.shared.settings.Setting.parent; - -import app.revanced.extension.shared.settings.YouTubeAndMusicSettings; -import app.revanced.extension.shared.settings.BooleanSetting; -import app.revanced.extension.shared.settings.EnumSetting; -import app.revanced.extension.shared.spoof.ClientType; - -public class Settings extends YouTubeAndMusicSettings { - - // Ads - public static final BooleanSetting HIDE_VIDEO_ADS = new BooleanSetting("revanced_music_hide_video_ads", TRUE, true); - public static final BooleanSetting HIDE_GET_PREMIUM_LABEL = new BooleanSetting("revanced_music_hide_get_premium_label", TRUE, true); - - // General - public static final BooleanSetting HIDE_CAST_BUTTON = new BooleanSetting("revanced_music_hide_cast_button", TRUE, true); - public static final BooleanSetting HIDE_CATEGORY_BAR = new BooleanSetting("revanced_music_hide_category_bar", FALSE, true); - public static final BooleanSetting HIDE_HISTORY_BUTTON = new BooleanSetting("revanced_music_hide_history_button", FALSE, true); - public static final BooleanSetting HIDE_SEARCH_BUTTON = new BooleanSetting("revanced_music_hide_search_button", FALSE, true); - public static final BooleanSetting HIDE_NOTIFICATION_BUTTON = new BooleanSetting("revanced_music_hide_notification_button", FALSE, true); - public static final BooleanSetting HIDE_NAVIGATION_BAR_HOME_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_home_button", FALSE, true); - public static final BooleanSetting HIDE_NAVIGATION_BAR_SAMPLES_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_samples_button", FALSE, true); - public static final BooleanSetting HIDE_NAVIGATION_BAR_EXPLORE_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_explore_button", FALSE, true); - public static final BooleanSetting HIDE_NAVIGATION_BAR_LIBRARY_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_library_button", FALSE, true); - public static final BooleanSetting HIDE_NAVIGATION_BAR_UPGRADE_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_upgrade_button", TRUE, true); - public static final BooleanSetting HIDE_NAVIGATION_BAR = new BooleanSetting("revanced_music_hide_navigation_bar", FALSE, true); - public static final BooleanSetting HIDE_NAVIGATION_BAR_LABEL = new BooleanSetting("revanced_music_hide_navigation_bar_labels", FALSE, true); - - // Player - public static final BooleanSetting CHANGE_MINIPLAYER_COLOR = new BooleanSetting("revanced_music_change_miniplayer_color", FALSE, true); - public static final BooleanSetting PERMANENT_REPEAT = new BooleanSetting("revanced_music_play_permanent_repeat", FALSE, true); - - // Miscellaneous - public static final EnumSetting SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type", - ClientType.ANDROID_REEL, true, parent(SPOOF_VIDEO_STREAMS)); - - public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", TRUE, true); -} diff --git a/extensions/music/src/main/java/app/revanced/extension/music/settings/preference/MusicPreferenceFragment.java b/extensions/music/src/main/java/app/revanced/extension/music/settings/preference/MusicPreferenceFragment.java deleted file mode 100644 index 86e5173420..0000000000 --- a/extensions/music/src/main/java/app/revanced/extension/music/settings/preference/MusicPreferenceFragment.java +++ /dev/null @@ -1,93 +0,0 @@ -package app.revanced.extension.music.settings.preference; - -import android.app.Dialog; -import android.preference.PreferenceScreen; -import android.widget.Toolbar; - -import app.revanced.extension.music.settings.MusicActivityHook; -import app.revanced.extension.shared.GmsCoreSupport; -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.Utils; -import app.revanced.extension.shared.settings.BaseSettings; -import app.revanced.extension.shared.settings.preference.ToolbarPreferenceFragment; - -/** - * Preference fragment for ReVanced settings. - */ -@SuppressWarnings("deprecation") -public class MusicPreferenceFragment extends ToolbarPreferenceFragment { - /** - * The main PreferenceScreen used to display the current set of preferences. - */ - private PreferenceScreen preferenceScreen; - - /** - * Initializes the preference fragment. - */ - @Override - protected void initialize() { - super.initialize(); - - try { - preferenceScreen = getPreferenceScreen(); - Utils.sortPreferenceGroups(preferenceScreen); - setPreferenceScreenToolbar(preferenceScreen); - - // Clunky work around until preferences are custom classes that manage themselves. - // Custom branding only works with non-root install. But the preferences must be - // added during patched because of difficulties detecting during patching if it's - // a root install. So instead the non-functional preferences are removed during - // runtime if the app is mount (root) installation. - if (GmsCoreSupport.isPackageNameOriginal()) { - removePreferences( - BaseSettings.CUSTOM_BRANDING_ICON.key, - BaseSettings.CUSTOM_BRANDING_NAME.key); - } - } catch (Exception ex) { - Logger.printException(() -> "initialize failure", ex); - } - } - - /** - * Called when the fragment starts. - */ - @Override - public void onStart() { - super.onStart(); - try { - // Initialize search controller if needed - if (MusicActivityHook.searchViewController != null) { - // Trigger search data collection after fragment is ready. - MusicActivityHook.searchViewController.initializeSearchData(); - } - } catch (Exception ex) { - Logger.printException(() -> "onStart failure", ex); - } - } - - /** - * Sets toolbar for all nested preference screens. - */ - @Override - protected void customizeToolbar(Toolbar toolbar) { - MusicActivityHook.setToolbarLayoutParams(toolbar); - } - - /** - * Perform actions after toolbar setup. - */ - @Override - protected void onPostToolbarSetup(Toolbar toolbar, Dialog preferenceScreenDialog) { - if (MusicActivityHook.searchViewController != null - && MusicActivityHook.searchViewController.isSearchActive()) { - toolbar.post(() -> MusicActivityHook.searchViewController.closeSearch()); - } - } - - /** - * Returns the preference screen for external access by SearchViewController. - */ - public PreferenceScreen getPreferenceScreenForSearch() { - return preferenceScreen; - } -} diff --git a/extensions/music/src/main/java/app/revanced/extension/music/settings/search/MusicSearchResultsAdapter.java b/extensions/music/src/main/java/app/revanced/extension/music/settings/search/MusicSearchResultsAdapter.java deleted file mode 100644 index 65ccd4ea1a..0000000000 --- a/extensions/music/src/main/java/app/revanced/extension/music/settings/search/MusicSearchResultsAdapter.java +++ /dev/null @@ -1,28 +0,0 @@ -package app.revanced.extension.music.settings.search; - -import android.content.Context; -import android.preference.PreferenceScreen; - -import app.revanced.extension.shared.settings.search.BaseSearchResultsAdapter; -import app.revanced.extension.shared.settings.search.BaseSearchViewController; -import app.revanced.extension.shared.settings.search.BaseSearchResultItem; - -import java.util.List; - -/** - * Music-specific search results adapter. - */ -@SuppressWarnings("deprecation") -public class MusicSearchResultsAdapter extends BaseSearchResultsAdapter { - - public MusicSearchResultsAdapter(Context context, List items, - BaseSearchViewController.BasePreferenceFragment fragment, - BaseSearchViewController searchViewController) { - super(context, items, fragment, searchViewController); - } - - @Override - protected PreferenceScreen getMainPreferenceScreen() { - return fragment.getPreferenceScreenForSearch(); - } -} diff --git a/extensions/music/src/main/java/app/revanced/extension/music/settings/search/MusicSearchViewController.java b/extensions/music/src/main/java/app/revanced/extension/music/settings/search/MusicSearchViewController.java deleted file mode 100644 index 6681a2f027..0000000000 --- a/extensions/music/src/main/java/app/revanced/extension/music/settings/search/MusicSearchViewController.java +++ /dev/null @@ -1,71 +0,0 @@ -package app.revanced.extension.music.settings.search; - -import android.app.Activity; -import android.preference.Preference; -import android.preference.PreferenceScreen; -import android.view.View; -import android.widget.Toolbar; - -import app.revanced.extension.music.settings.preference.MusicPreferenceFragment; -import app.revanced.extension.shared.settings.search.*; - -/** - * Music-specific search view controller implementation. - */ -@SuppressWarnings("deprecation") -public class MusicSearchViewController extends BaseSearchViewController { - - public static MusicSearchViewController addSearchViewComponents(Activity activity, Toolbar toolbar, - MusicPreferenceFragment fragment) { - return new MusicSearchViewController(activity, toolbar, fragment); - } - - private MusicSearchViewController(Activity activity, Toolbar toolbar, MusicPreferenceFragment fragment) { - super(activity, toolbar, new PreferenceFragmentAdapter(fragment)); - } - - @Override - protected BaseSearchResultsAdapter createSearchResultsAdapter() { - return new MusicSearchResultsAdapter(activity, filteredSearchItems, fragment, this); - } - - @Override - protected boolean isSpecialPreferenceGroup(Preference preference) { - // Music doesn't have SponsorBlock, so no special groups. - return false; - } - - @Override - protected void setupSpecialPreferenceListeners(BaseSearchResultItem item) { - // Music doesn't have special preferences. - // This method can be empty or handle music-specific preferences if any. - } - - // Static method for handling Activity finish - public static boolean handleFinish(MusicSearchViewController searchViewController) { - if (searchViewController != null && searchViewController.isSearchActive()) { - searchViewController.closeSearch(); - return true; - } - return false; - } - - // Adapter to wrap MusicPreferenceFragment to BasePreferenceFragment interface. - private record PreferenceFragmentAdapter(MusicPreferenceFragment fragment) implements BasePreferenceFragment { - - @Override - public PreferenceScreen getPreferenceScreenForSearch() { - return fragment.getPreferenceScreenForSearch(); - } - - @Override - public View getView() { - return fragment.getView(); - } - - @Override - public Activity getActivity() { - return fragment.getActivity(); - } - } -} diff --git a/extensions/nothingx/build.gradle.kts b/extensions/nothingx/build.gradle.kts deleted file mode 100644 index ed2b78c5f6..0000000000 --- a/extensions/nothingx/build.gradle.kts +++ /dev/null @@ -1,10 +0,0 @@ -dependencies { - compileOnly(project(":extensions:shared:library")) - compileOnly(project(":extensions:nothingx:stub")) -} - -android { - defaultConfig { - minSdk = 23 - } -} \ No newline at end of file diff --git a/extensions/nothingx/src/main/AndroidManifest.xml b/extensions/nothingx/src/main/AndroidManifest.xml deleted file mode 100644 index 15e7c2ae67..0000000000 --- a/extensions/nothingx/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/extensions/nothingx/src/main/java/app/revanced/extension/nothingx/patches/ShowK1TokensPatch.java b/extensions/nothingx/src/main/java/app/revanced/extension/nothingx/patches/ShowK1TokensPatch.java deleted file mode 100644 index c301ae2fb3..0000000000 --- a/extensions/nothingx/src/main/java/app/revanced/extension/nothingx/patches/ShowK1TokensPatch.java +++ /dev/null @@ -1,590 +0,0 @@ -package app.revanced.extension.nothingx.patches; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Application; -import android.content.ClipboardManager; -import android.content.Context; -import android.content.SharedPreferences; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.graphics.Color; -import android.graphics.Typeface; -import android.os.Build; -import android.os.Bundle; -import android.util.Log; -import android.util.TypedValue; -import android.view.Gravity; -import android.widget.Button; -import android.widget.LinearLayout; -import android.widget.ScrollView; -import android.widget.TextView; -import android.widget.Toast; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Patches to expose the K1 token for Nothing X app to enable pairing with GadgetBridge. - */ -@SuppressWarnings("unused") -public class ShowK1TokensPatch { - - private static final String TAG = "ReVanced"; - private static final String PACKAGE_NAME = "com.nothing.smartcenter"; - private static final String EMPTY_MD5 = "d41d8cd98f00b204e9800998ecf8427e"; - private static final String PREFS_NAME = "revanced_nothingx_prefs"; - private static final String KEY_DONT_SHOW_DIALOG = "dont_show_k1_dialog"; - - // Colors - private static final int COLOR_BG = 0xFF1E1E1E; - private static final int COLOR_CARD = 0xFF2D2D2D; - private static final int COLOR_TEXT_PRIMARY = 0xFFFFFFFF; - private static final int COLOR_TEXT_SECONDARY = 0xFFB0B0B0; - private static final int COLOR_ACCENT = 0xFFFF9500; - private static final int COLOR_TOKEN_BG = 0xFF3A3A3A; - private static final int COLOR_BUTTON_POSITIVE = 0xFFFF9500; - private static final int COLOR_BUTTON_NEGATIVE = 0xFFFF6B6B; - - // Match standalone K1: k1:, K1:, k1>, etc. - private static final Pattern K1_STANDALONE_PATTERN = Pattern.compile("(?i)(?:k1\\s*[:>]\\s*)([0-9a-f]{32})"); - // Match combined r3+k1: format (64 chars = r3(32) + k1(32)) - private static final Pattern K1_COMBINED_PATTERN = Pattern.compile("(?i)r3\\+k1\\s*:\\s*([0-9a-f]{64})"); - - private static volatile boolean k1Logged = false; - private static volatile boolean lifecycleCallbacksRegistered = false; - private static Context appContext; - - /** - * Get K1 tokens from database and log files. - * Call this after the app initializes. - * - * @param context Application context - */ - public static void showK1Tokens(Context context) { - if (k1Logged) { - return; - } - - appContext = context.getApplicationContext(); - - Set allTokens = new LinkedHashSet<>(); - - // First try to get from database. - String dbToken = getK1TokensFromDatabase(); - if (dbToken != null) { - allTokens.add(dbToken); - } - - // Then get from log files. - Set logTokens = getK1TokensFromLogFiles(); - allTokens.addAll(logTokens); - - if (allTokens.isEmpty()) { - return; - } - - // Log all found tokens. - int index = 1; - for (String token : allTokens) { - Log.i(TAG, "#" + index++ + ": " + token.toUpperCase()); - } - - // Register lifecycle callbacks to show dialog when an Activity is ready. - registerLifecycleCallbacks(allTokens); - - k1Logged = true; - } - - /** - * Register ActivityLifecycleCallbacks to show dialog when first Activity resumes. - * - * @param tokens Set of K1 tokens to display - */ - private static void registerLifecycleCallbacks(Set tokens) { - if (lifecycleCallbacksRegistered || !(appContext instanceof Application)) { - return; - } - - Application application = (Application) appContext; - application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() { - @Override - public void onActivityCreated(Activity activity, Bundle savedInstanceState) { - } - - @Override - public void onActivityStarted(Activity activity) { - } - - @Override - public void onActivityResumed(Activity activity) { - // Check if user chose not to show dialog. - SharedPreferences prefs = activity.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); - if (prefs.getBoolean(KEY_DONT_SHOW_DIALOG, false)) { - application.unregisterActivityLifecycleCallbacks(this); - lifecycleCallbacksRegistered = false; - return; - } - - // Show dialog on first Activity resume. - if (tokens != null && !tokens.isEmpty()) { - activity.runOnUiThread(() -> showK1TokensDialog(activity, tokens)); - // Unregister after showing - application.unregisterActivityLifecycleCallbacks(this); - lifecycleCallbacksRegistered = false; - } - } - - @Override - public void onActivityPaused(Activity activity) { - } - - @Override - public void onActivityStopped(Activity activity) { - } - - @Override - public void onActivitySaveInstanceState(Activity activity, Bundle outState) { - } - - @Override - public void onActivityDestroyed(Activity activity) { - } - }); - - lifecycleCallbacksRegistered = true; - } - - /** - * Show dialog with K1 tokens. - * - * @param activity Activity context - * @param tokens Set of K1 tokens - */ - private static void showK1TokensDialog(Activity activity, Set tokens) { - try { - // Create main container. - LinearLayout mainLayout = new LinearLayout(activity); - mainLayout.setOrientation(LinearLayout.VERTICAL); - mainLayout.setBackgroundColor(COLOR_BG); - mainLayout.setPadding(dpToPx(activity, 24), dpToPx(activity, 16), - dpToPx(activity, 24), dpToPx(activity, 16)); - - // Title. - TextView titleView = new TextView(activity); - titleView.setText("K1 Token(s) Found"); - titleView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20); - titleView.setTypeface(Typeface.DEFAULT_BOLD); - titleView.setTextColor(COLOR_TEXT_PRIMARY); - titleView.setGravity(Gravity.CENTER); - mainLayout.addView(titleView, new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT - )); - - // Subtitle. - TextView subtitleView = new TextView(activity); - subtitleView.setText(tokens.size() == 1 ? "1 token found • Tap to copy" : tokens.size() + " tokens found • Tap to copy"); - subtitleView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); - subtitleView.setTextColor(COLOR_TEXT_SECONDARY); - subtitleView.setGravity(Gravity.CENTER); - LinearLayout.LayoutParams subtitleParams = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT - ); - subtitleParams.topMargin = dpToPx(activity, 4); - subtitleParams.bottomMargin = dpToPx(activity, 16); - mainLayout.addView(subtitleView, subtitleParams); - - // Scrollable content. - ScrollView scrollView = new ScrollView(activity); - scrollView.setVerticalScrollBarEnabled(false); - LinearLayout.LayoutParams scrollParams = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - 0, - 1.0f - ); - scrollParams.topMargin = dpToPx(activity, 8); - scrollParams.bottomMargin = dpToPx(activity, 16); - mainLayout.addView(scrollView, scrollParams); - - LinearLayout tokensContainer = new LinearLayout(activity); - tokensContainer.setOrientation(LinearLayout.VERTICAL); - scrollView.addView(tokensContainer); - - // Add each token as a card. - boolean singleToken = tokens.size() == 1; - int index = 1; - for (String token : tokens) { - LinearLayout tokenCard = createTokenCard(activity, token, index++, singleToken); - LinearLayout.LayoutParams cardParams = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT - ); - cardParams.bottomMargin = dpToPx(activity, 12); - tokensContainer.addView(tokenCard, cardParams); - } - - // Info text. - TextView infoView = new TextView(activity); - infoView.setText(tokens.size() == 1 ? "Tap the token to copy it" : "Tap any token to copy it"); - infoView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12); - infoView.setTextColor(COLOR_TEXT_SECONDARY); - infoView.setGravity(Gravity.CENTER); - infoView.setAlpha(0.7f); - LinearLayout.LayoutParams infoParams = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT - ); - infoParams.topMargin = dpToPx(activity, 8); - mainLayout.addView(infoView, infoParams); - - // Button row. - LinearLayout buttonRow = new LinearLayout(activity); - buttonRow.setOrientation(LinearLayout.HORIZONTAL); - buttonRow.setGravity(Gravity.END); - LinearLayout.LayoutParams buttonRowParams = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT - ); - buttonRowParams.topMargin = dpToPx(activity, 16); - mainLayout.addView(buttonRow, buttonRowParams); - - // "Don't show again" button. - Button dontShowButton = new Button(activity); - dontShowButton.setText("Don't show again"); - dontShowButton.setTextColor(Color.WHITE); - dontShowButton.setBackgroundColor(Color.TRANSPARENT); - dontShowButton.setAllCaps(false); - dontShowButton.setTypeface(Typeface.DEFAULT); - dontShowButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); - dontShowButton.setPadding(dpToPx(activity, 16), dpToPx(activity, 8), - dpToPx(activity, 16), dpToPx(activity, 8)); - LinearLayout.LayoutParams dontShowParams = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.WRAP_CONTENT - ); - dontShowParams.rightMargin = dpToPx(activity, 8); - buttonRow.addView(dontShowButton, dontShowParams); - - // "OK" button. - Button okButton = new Button(activity); - okButton.setText("OK"); - okButton.setTextColor(Color.BLACK); - okButton.setBackgroundColor(COLOR_BUTTON_POSITIVE); - okButton.setAllCaps(false); - okButton.setTypeface(Typeface.DEFAULT_BOLD); - okButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); - okButton.setPadding(dpToPx(activity, 24), dpToPx(activity, 12), - dpToPx(activity, 24), dpToPx(activity, 12)); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - okButton.setElevation(dpToPx(activity, 4)); - } - buttonRow.addView(okButton, new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.WRAP_CONTENT - )); - - // Build dialog. - AlertDialog.Builder builder = new AlertDialog.Builder(activity); - builder.setView(mainLayout); - - final AlertDialog dialog = builder.create(); - - // Style the dialog with dark background. - if (dialog.getWindow() != null) { - dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent); - } - - dialog.show(); - - // Set button click listeners after dialog is created. - dontShowButton.setOnClickListener(v -> { - SharedPreferences prefs = activity.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); - prefs.edit().putBoolean(KEY_DONT_SHOW_DIALOG, true).apply(); - Toast.makeText(activity, "Dialog disabled. Clear app data to re-enable.", - Toast.LENGTH_SHORT).show(); - dialog.dismiss(); - }); - - okButton.setOnClickListener(v -> { - dialog.dismiss(); - }); - - } catch (Exception e) { - Log.e(TAG, "Failed to show K1 dialog", e); - } - } - - /** - * Create a card view for a single token. - */ - private static LinearLayout createTokenCard(Activity activity, String token, int index, boolean singleToken) { - LinearLayout card = new LinearLayout(activity); - card.setOrientation(LinearLayout.VERTICAL); - card.setBackgroundColor(COLOR_TOKEN_BG); - card.setPadding(dpToPx(activity, 16), dpToPx(activity, 12), - dpToPx(activity, 16), dpToPx(activity, 12)); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - card.setElevation(dpToPx(activity, 2)); - } - card.setClickable(true); - card.setFocusable(true); - - // Token label (only show if multiple tokens). - if (!singleToken) { - TextView labelView = new TextView(activity); - labelView.setText("Token #" + index); - labelView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12); - labelView.setTextColor(COLOR_ACCENT); - labelView.setTypeface(Typeface.DEFAULT_BOLD); - card.addView(labelView); - } - - // Token value. - TextView tokenView = new TextView(activity); - tokenView.setText(token.toUpperCase()); - tokenView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); - tokenView.setTextColor(COLOR_TEXT_PRIMARY); - tokenView.setTypeface(Typeface.MONOSPACE); - tokenView.setLetterSpacing(0.05f); - LinearLayout.LayoutParams tokenParams = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT - ); - if (!singleToken) { - tokenParams.topMargin = dpToPx(activity, 8); - } - card.addView(tokenView, tokenParams); - - // Click to copy. - card.setOnClickListener(v -> { - ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); - if (clipboard != null) { - clipboard.setText(token.toUpperCase()); - Toast.makeText(activity, "Token copied!", Toast.LENGTH_SHORT).show(); - } - }); - - return card; - } - - /** - * Convert dp to pixels. - */ - private static int dpToPx(Context context, float dp) { - return (int) TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - dp, - context.getResources().getDisplayMetrics() - ); - } - - /** - * Get K1 tokens from log files. - * Prioritizes pairing K1 tokens over reconnect tokens. - */ - private static Set getK1TokensFromLogFiles() { - Set pairingTokens = new LinkedHashSet<>(); - Set reconnectTokens = new LinkedHashSet<>(); - try { - File logDir = new File("/data/data/" + PACKAGE_NAME + "/files/log"); - if (!logDir.exists() || !logDir.isDirectory()) { - return pairingTokens; - } - - File[] logFiles = logDir.listFiles((dir, name) -> - name.endsWith(".log") || name.endsWith(".log.") || name.matches(".*\\.log\\.\\d+")); - - if (logFiles == null || logFiles.length == 0) { - return pairingTokens; - } - - for (File logFile : logFiles) { - try (BufferedReader reader = new BufferedReader(new FileReader(logFile))) { - String line; - while ((line = reader.readLine()) != null) { - // Determine if this is a pairing or reconnect context. - boolean isPairingContext = line.toLowerCase().contains("watchbind"); - boolean isReconnectContext = line.toLowerCase().contains("watchreconnect"); - - String k1Token = null; - - // First check for combined r3+k1 format (priority). - Matcher combinedMatcher = K1_COMBINED_PATTERN.matcher(line); - if (combinedMatcher.find()) { - String combined = combinedMatcher.group(1); - if (combined.length() == 64) { - // Second half is the actual K1 - k1Token = combined.substring(32).toLowerCase(); - } - } - - // Then check for standalone K1 format (only if not found in combined). - if (k1Token == null) { - Matcher standaloneMatcher = K1_STANDALONE_PATTERN.matcher(line); - if (standaloneMatcher.find()) { - String token = standaloneMatcher.group(1); - if (token != null && token.length() == 32) { - k1Token = token.toLowerCase(); - } - } - } - - // Add to appropriate set. - if (k1Token != null) { - if (isPairingContext && !isReconnectContext) { - pairingTokens.add(k1Token); - } else { - reconnectTokens.add(k1Token); - } - } - } - } catch (Exception e) { - // Skip unreadable files. - } - } - } catch (Exception ex) { - // Fail silently. - } - - // Return pairing tokens first, add reconnect tokens if no pairing tokens found. - if (!pairingTokens.isEmpty()) { - Log.i(TAG, "Found " + pairingTokens.size() + " pairing K1 token(s)"); - return pairingTokens; - } - - if (!reconnectTokens.isEmpty()) { - Log.i(TAG, "Found " + reconnectTokens.size() + " reconnect K1 token(s) (may not work for initial pairing)"); - } - return reconnectTokens; - } - - /** - * Try to get K1 tokens from the database. - */ - private static String getK1TokensFromDatabase() { - try { - File dbDir = new File("/data/data/" + PACKAGE_NAME + "/databases"); - if (!dbDir.exists() || !dbDir.isDirectory()) { - return null; - } - - File[] dbFiles = dbDir.listFiles((dir, name) -> - name.endsWith(".db") && !name.startsWith("google_app_measurement") && !name.contains("firebase")); - - if (dbFiles == null || dbFiles.length == 0) { - return null; - } - - for (File dbFile : dbFiles) { - String token = getK1TokensFromDatabase(dbFile); - if (token != null) { - return token; - } - } - - return null; - } catch (Exception ex) { - return null; - } - } - - /** - * Extract K1 tokens from a database file. - */ - private static String getK1TokensFromDatabase(File dbFile) { - SQLiteDatabase db = null; - try { - db = SQLiteDatabase.openDatabase(dbFile.getPath(), null, SQLiteDatabase.OPEN_READONLY); - - // Get all tables. - Cursor cursor = db.rawQuery( - "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'", - null - ); - - List tables = new ArrayList<>(); - while (cursor.moveToNext()) { - tables.add(cursor.getString(0)); - } - cursor.close(); - - // Scan all columns for 32-char hex strings. - for (String table : tables) { - Cursor schemaCursor = null; - try { - schemaCursor = db.rawQuery("PRAGMA table_info(" + table + ")", null); - List columns = new ArrayList<>(); - while (schemaCursor.moveToNext()) { - columns.add(schemaCursor.getString(1)); - } - schemaCursor.close(); - - for (String column : columns) { - Cursor dataCursor = null; - try { - dataCursor = db.query(table, new String[]{column}, null, null, null, null, null); - while (dataCursor.moveToNext()) { - String value = dataCursor.getString(0); - if (value != null && value.length() == 32 && value.matches("[0-9a-fA-F]{32}")) { - // Skip obviously fake tokens (MD5 of empty string). - if (!value.equalsIgnoreCase(EMPTY_MD5)) { - dataCursor.close(); - db.close(); - return value.toLowerCase(); - } - } - } - } catch (Exception e) { - // Skip non-string columns. - } finally { - if (dataCursor != null) { - dataCursor.close(); - } - } - } - } catch (Exception e) { - // Continue to next table. - } finally { - if (schemaCursor != null && !schemaCursor.isClosed()) { - schemaCursor.close(); - } - } - } - - return null; - } catch (Exception ex) { - return null; - } finally { - if (db != null && db.isOpen()) { - db.close(); - } - } - } - - /** - * Reset the logged flag (useful for testing or re-pairing). - */ - public static void resetK1Logged() { - k1Logged = false; - lifecycleCallbacksRegistered = false; - } - - /** - * Reset the "don't show again" preference. - */ - public static void resetDontShowPreference() { - if (appContext != null) { - SharedPreferences prefs = appContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); - prefs.edit().putBoolean(KEY_DONT_SHOW_DIALOG, false).apply(); - } - } -} diff --git a/extensions/nothingx/stub/build.gradle.kts b/extensions/nothingx/stub/build.gradle.kts deleted file mode 100644 index fcadc678c4..0000000000 --- a/extensions/nothingx/stub/build.gradle.kts +++ /dev/null @@ -1,17 +0,0 @@ -plugins { - alias(libs.plugins.android.library) -} - -android { - namespace = "app.revanced.extension" - compileSdk = 34 - - defaultConfig { - minSdk = 26 - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } -} \ No newline at end of file diff --git a/extensions/nothingx/stub/src/main/AndroidManifest.xml b/extensions/nothingx/stub/src/main/AndroidManifest.xml deleted file mode 100644 index 15e7c2ae67..0000000000 --- a/extensions/nothingx/stub/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/extensions/nunl/build.gradle.kts b/extensions/nunl/build.gradle.kts deleted file mode 100644 index ab48531bba..0000000000 --- a/extensions/nunl/build.gradle.kts +++ /dev/null @@ -1,10 +0,0 @@ -dependencies { - compileOnly(project(":extensions:shared:library")) - compileOnly(project(":extensions:nunl:stub")) -} - -android { - defaultConfig { - minSdk = 26 - } -} diff --git a/extensions/nunl/src/main/AndroidManifest.xml b/extensions/nunl/src/main/AndroidManifest.xml deleted file mode 100644 index 9b65eb06cf..0000000000 --- a/extensions/nunl/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/extensions/nunl/src/main/java/app/revanced/extension/nunl/ads/HideAdsPatch.java b/extensions/nunl/src/main/java/app/revanced/extension/nunl/ads/HideAdsPatch.java deleted file mode 100644 index 2e4ab5b069..0000000000 --- a/extensions/nunl/src/main/java/app/revanced/extension/nunl/ads/HideAdsPatch.java +++ /dev/null @@ -1,114 +0,0 @@ -package app.revanced.extension.nunl.ads; - -import nl.nu.performance.api.client.interfaces.Block; -import nl.nu.performance.api.client.unions.SmallArticleLinkFlavor; -import nl.nu.performance.api.client.objects.*; - -import java.util.ArrayList; -import java.util.List; - -import app.revanced.extension.shared.Logger; - -@SuppressWarnings("unused") -public class HideAdsPatch { - private static final String[] blockedHeaderBlocks = { - "Aanbiedingen (Adverteerders)", - "Aangeboden door NUshop" - }; - - // "Rubrieken" menu links to ads. - private static final String[] blockedLinkBlocks = { - "Van onze adverteerders" - }; - - public static void filterAds(List blocks) { - try { - ArrayList cleanedList = new ArrayList<>(); - - boolean skipFullHeader = false; - boolean skipUntilDivider = false; - - int index = 0; - while (index < blocks.size()) { - Block currentBlock = blocks.get(index); - - // Because of pagination, we might not see the Divider in front of it. - // Just remove it as is and leave potential extra spacing visible on the screen. - if (currentBlock instanceof DpgBannerBlock) { - index++; - continue; - } - - if (index + 1 < blocks.size()) { - // Filter Divider -> DpgMediaBanner -> Divider. - if (currentBlock instanceof DividerBlock - && blocks.get(index + 1) instanceof DpgBannerBlock) { - index += 2; - continue; - } - - // Filter Divider -> LinkBlock (... -> LinkBlock -> LinkBlock-> LinkBlock -> Divider). - if (currentBlock instanceof DividerBlock - && blocks.get(index + 1) instanceof LinkBlock linkBlock) { - Link link = linkBlock.getLink(); - if (link != null && link.getTitle() != null) { - for (String blockedLinkBlock : blockedLinkBlocks) { - if (blockedLinkBlock.equals(link.getTitle().getText())) { - skipUntilDivider = true; - break; - } - } - if (skipUntilDivider) { - index++; - continue; - } - } - } - } - - // Skip LinkBlocks with a "flavor" claiming to be "isPartner" (sponsored inline ads). - if (currentBlock instanceof LinkBlock linkBlock - && linkBlock.getLink() != null - && linkBlock.getLink().getLinkFlavor() instanceof SmallArticleLinkFlavor smallArticleLinkFlavor - && smallArticleLinkFlavor.isPartner() != null - && smallArticleLinkFlavor.isPartner()) { - index++; - continue; - } - - if (currentBlock instanceof DividerBlock) { - skipUntilDivider = false; - } - - // Filter HeaderBlock with known ads until next HeaderBlock. - if (currentBlock instanceof HeaderBlock headerBlock) { - StyledText headerText = headerBlock.getTitle(); - if (headerText != null) { - skipFullHeader = false; - for (String blockedHeaderBlock : blockedHeaderBlocks) { - if (blockedHeaderBlock.equals(headerText.getText())) { - skipFullHeader = true; - break; - } - } - if (skipFullHeader) { - index++; - continue; - } - } - } - - if (!skipFullHeader && !skipUntilDivider) { - cleanedList.add(currentBlock); - } - index++; - } - - // Replace list in-place to not deal with moving the result to the correct register in smali. - blocks.clear(); - blocks.addAll(cleanedList); - } catch (Exception ex) { - Logger.printException(() -> "filterAds failure", ex); - } - } -} diff --git a/extensions/nunl/stub/build.gradle.kts b/extensions/nunl/stub/build.gradle.kts deleted file mode 100644 index 7905271b26..0000000000 --- a/extensions/nunl/stub/build.gradle.kts +++ /dev/null @@ -1,17 +0,0 @@ -plugins { - alias(libs.plugins.android.library) -} - -android { - namespace = "app.revanced.extension" - compileSdk = 34 - - defaultConfig { - minSdk = 26 - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } -} diff --git a/extensions/nunl/stub/src/main/AndroidManifest.xml b/extensions/nunl/stub/src/main/AndroidManifest.xml deleted file mode 100644 index 9b65eb06cf..0000000000 --- a/extensions/nunl/stub/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/extensions/nunl/stub/src/main/java/nl/nu/performance/api/client/interfaces/Block.java b/extensions/nunl/stub/src/main/java/nl/nu/performance/api/client/interfaces/Block.java deleted file mode 100644 index 3514f360cb..0000000000 --- a/extensions/nunl/stub/src/main/java/nl/nu/performance/api/client/interfaces/Block.java +++ /dev/null @@ -1,5 +0,0 @@ -package nl.nu.performance.api.client.interfaces; - -public class Block { - -} diff --git a/extensions/nunl/stub/src/main/java/nl/nu/performance/api/client/objects/DividerBlock.java b/extensions/nunl/stub/src/main/java/nl/nu/performance/api/client/objects/DividerBlock.java deleted file mode 100644 index 0351aec049..0000000000 --- a/extensions/nunl/stub/src/main/java/nl/nu/performance/api/client/objects/DividerBlock.java +++ /dev/null @@ -1,7 +0,0 @@ -package nl.nu.performance.api.client.objects; - -import nl.nu.performance.api.client.interfaces.Block; - -public class DividerBlock extends Block { - -} diff --git a/extensions/nunl/stub/src/main/java/nl/nu/performance/api/client/objects/DpgBannerBlock.java b/extensions/nunl/stub/src/main/java/nl/nu/performance/api/client/objects/DpgBannerBlock.java deleted file mode 100644 index ac300b0539..0000000000 --- a/extensions/nunl/stub/src/main/java/nl/nu/performance/api/client/objects/DpgBannerBlock.java +++ /dev/null @@ -1,7 +0,0 @@ -package nl.nu.performance.api.client.objects; - -import nl.nu.performance.api.client.interfaces.Block; - -public class DpgBannerBlock extends Block { - -} diff --git a/extensions/nunl/stub/src/main/java/nl/nu/performance/api/client/objects/HeaderBlock.java b/extensions/nunl/stub/src/main/java/nl/nu/performance/api/client/objects/HeaderBlock.java deleted file mode 100644 index 7b1f7ad192..0000000000 --- a/extensions/nunl/stub/src/main/java/nl/nu/performance/api/client/objects/HeaderBlock.java +++ /dev/null @@ -1,9 +0,0 @@ -package nl.nu.performance.api.client.objects; - -import nl.nu.performance.api.client.interfaces.Block; - -public class HeaderBlock extends Block { - public final StyledText getTitle() { - throw new UnsupportedOperationException("Stub"); - } -} diff --git a/extensions/nunl/stub/src/main/java/nl/nu/performance/api/client/objects/Link.java b/extensions/nunl/stub/src/main/java/nl/nu/performance/api/client/objects/Link.java deleted file mode 100644 index 771d11dad1..0000000000 --- a/extensions/nunl/stub/src/main/java/nl/nu/performance/api/client/objects/Link.java +++ /dev/null @@ -1,13 +0,0 @@ -package nl.nu.performance.api.client.objects; - -import nl.nu.performance.api.client.unions.LinkFlavor; - -public class Link { - public final StyledText getTitle() { - throw new UnsupportedOperationException("Stub"); - } - - public final LinkFlavor getLinkFlavor() { - throw new UnsupportedOperationException("Stub"); - } -} diff --git a/extensions/nunl/stub/src/main/java/nl/nu/performance/api/client/objects/LinkBlock.java b/extensions/nunl/stub/src/main/java/nl/nu/performance/api/client/objects/LinkBlock.java deleted file mode 100644 index dea1950573..0000000000 --- a/extensions/nunl/stub/src/main/java/nl/nu/performance/api/client/objects/LinkBlock.java +++ /dev/null @@ -1,10 +0,0 @@ -package nl.nu.performance.api.client.objects; - -import android.os.Parcelable; -import nl.nu.performance.api.client.interfaces.Block; - -public abstract class LinkBlock extends Block implements Parcelable { - public final Link getLink() { - throw new UnsupportedOperationException("Stub"); - } -} diff --git a/extensions/nunl/stub/src/main/java/nl/nu/performance/api/client/objects/StyledText.java b/extensions/nunl/stub/src/main/java/nl/nu/performance/api/client/objects/StyledText.java deleted file mode 100644 index 719403eb4e..0000000000 --- a/extensions/nunl/stub/src/main/java/nl/nu/performance/api/client/objects/StyledText.java +++ /dev/null @@ -1,7 +0,0 @@ -package nl.nu.performance.api.client.objects; - -public class StyledText { - public final String getText() { - throw new UnsupportedOperationException("Stub"); - } -} diff --git a/extensions/nunl/stub/src/main/java/nl/nu/performance/api/client/unions/LinkFlavor.java b/extensions/nunl/stub/src/main/java/nl/nu/performance/api/client/unions/LinkFlavor.java deleted file mode 100644 index 08413d3fd9..0000000000 --- a/extensions/nunl/stub/src/main/java/nl/nu/performance/api/client/unions/LinkFlavor.java +++ /dev/null @@ -1,4 +0,0 @@ -package nl.nu.performance.api.client.unions; - -public interface LinkFlavor { -} diff --git a/extensions/nunl/stub/src/main/java/nl/nu/performance/api/client/unions/SmallArticleLinkFlavor.java b/extensions/nunl/stub/src/main/java/nl/nu/performance/api/client/unions/SmallArticleLinkFlavor.java deleted file mode 100644 index 4dcbf23cb9..0000000000 --- a/extensions/nunl/stub/src/main/java/nl/nu/performance/api/client/unions/SmallArticleLinkFlavor.java +++ /dev/null @@ -1,7 +0,0 @@ -package nl.nu.performance.api.client.unions; - -public class SmallArticleLinkFlavor implements LinkFlavor { - public final Boolean isPartner() { - throw new UnsupportedOperationException("Stub"); - } -} diff --git a/extensions/primevideo/build.gradle.kts b/extensions/primevideo/build.gradle.kts deleted file mode 100644 index 17a3c31a21..0000000000 --- a/extensions/primevideo/build.gradle.kts +++ /dev/null @@ -1,10 +0,0 @@ -dependencies { - compileOnly(project(":extensions:shared:library")) - compileOnly(project(":extensions:primevideo:stub")) -} - -android { - defaultConfig { - minSdk = 21 - } -} diff --git a/extensions/primevideo/src/main/AndroidManifest.xml b/extensions/primevideo/src/main/AndroidManifest.xml deleted file mode 100644 index 9b65eb06cf..0000000000 --- a/extensions/primevideo/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/extensions/primevideo/src/main/java/app/revanced/extension/primevideo/ads/SkipAdsPatch.java b/extensions/primevideo/src/main/java/app/revanced/extension/primevideo/ads/SkipAdsPatch.java deleted file mode 100644 index d0a97810a2..0000000000 --- a/extensions/primevideo/src/main/java/app/revanced/extension/primevideo/ads/SkipAdsPatch.java +++ /dev/null @@ -1,36 +0,0 @@ -package app.revanced.extension.primevideo.ads; - -import com.amazon.avod.fsm.SimpleTrigger; -import com.amazon.avod.media.ads.AdBreak; -import com.amazon.avod.media.ads.internal.state.AdBreakTrigger; -import com.amazon.avod.media.ads.internal.state.AdEnabledPlayerTriggerType; -import com.amazon.avod.media.playback.VideoPlayer; -import com.amazon.avod.media.ads.internal.state.ServerInsertedAdBreakState; - -import app.revanced.extension.shared.Logger; - -@SuppressWarnings("unused") -public final class SkipAdsPatch { - public static void enterServerInsertedAdBreakState(ServerInsertedAdBreakState state, AdBreakTrigger trigger, VideoPlayer player) { - try { - AdBreak adBreak = trigger.getBreak(); - - // There are two scenarios when entering the original method: - // 1. Player naturally entered an ad break while watching a video. - // 2. User is skipped/scrubbed to a position on the timeline. If seek position is past an ad break, - // user is forced to watch an ad before continuing. - // - // Scenario 2 is indicated by trigger.getSeekStartPosition() != null, so skip directly to the scrubbing - // target. Otherwise, just calculate when the ad break should end and skip to there. - if (trigger.getSeekStartPosition() != null) - player.seekTo(trigger.getSeekTarget().getTotalMilliseconds()); - else - player.seekTo(player.getCurrentPosition() + adBreak.getDurationExcludingAux().getTotalMilliseconds()); - - // Send "end of ads" trigger to state machine so everything doesn't get whacky. - state.doTrigger(new SimpleTrigger(AdEnabledPlayerTriggerType.NO_MORE_ADS_SKIP_TRANSITION)); - } catch (Exception ex) { - Logger.printException(() -> "Failed skipping ads", ex); - } - } -} \ No newline at end of file diff --git a/extensions/primevideo/src/main/java/app/revanced/extension/primevideo/videoplayer/PlaybackSpeedPatch.java b/extensions/primevideo/src/main/java/app/revanced/extension/primevideo/videoplayer/PlaybackSpeedPatch.java deleted file mode 100644 index b11ec0875d..0000000000 --- a/extensions/primevideo/src/main/java/app/revanced/extension/primevideo/videoplayer/PlaybackSpeedPatch.java +++ /dev/null @@ -1,207 +0,0 @@ -package app.revanced.extension.primevideo.videoplayer; - -import android.app.AlertDialog; -import android.content.Context; -import android.graphics.RectF; -import android.view.View; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.graphics.Color; -import android.graphics.drawable.Drawable; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.ColorFilter; -import android.graphics.PixelFormat; -import java.util.Arrays; - -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.Utils; -import app.revanced.extension.shared.ui.Dim; - -import com.amazon.video.sdk.player.Player; - -public class PlaybackSpeedPatch { - private static Player player; - private static final float[] SPEED_VALUES = {0.5f, 0.7f, 0.8f, 0.9f, 0.95f, 1.0f, 1.05f, 1.1f, 1.2f, 1.3f, 1.5f, 2.0f}; - private static final String SPEED_BUTTON_TAG = "speed_overlay"; - - public static void setPlayer(Player playerInstance) { - player = playerInstance; - if (player != null) { - // Reset playback rate when switching between episodes to ensure correct display. - player.setPlaybackRate(1.0f); - } - } - - public static void initializeSpeedOverlay(View userControlsView) { - try { - LinearLayout buttonContainer = Utils.getChildViewByResourceName(userControlsView, "ButtonContainerPlayerTop"); - - // If the speed overlay exists we should return early. - if (Utils.getChildView(buttonContainer, false, child -> - child instanceof ImageView && SPEED_BUTTON_TAG.equals(child.getTag())) != null) { - return; - } - - ImageView speedButton = createSpeedButton(userControlsView.getContext()); - speedButton.setOnClickListener(v -> changePlaybackSpeed(speedButton)); - buttonContainer.addView(speedButton, 0); - - } catch (IllegalArgumentException e) { - Logger.printException(() -> "initializeSpeedOverlay, no button container found", e); - } catch (Exception e) { - Logger.printException(() -> "initializeSpeedOverlay failure", e); - } - } - - private static ImageView createSpeedButton(Context context) { - ImageView speedButton = new ImageView(context); - speedButton.setContentDescription("Playback Speed"); - speedButton.setTag(SPEED_BUTTON_TAG); - speedButton.setClickable(true); - speedButton.setFocusable(true); - speedButton.setScaleType(ImageView.ScaleType.CENTER); - - SpeedIconDrawable speedIcon = new SpeedIconDrawable(); - speedButton.setImageDrawable(speedIcon); - - speedButton.setMinimumWidth(Dim.dp48); - speedButton.setMinimumHeight(Dim.dp48); - - return speedButton; - } - - private static String[] getSpeedOptions() { - String[] options = new String[SPEED_VALUES.length]; - for (int i = 0; i < SPEED_VALUES.length; i++) { - options[i] = SPEED_VALUES[i] + "x"; - } - return options; - } - - private static void changePlaybackSpeed(ImageView imageView) { - if (player == null) { - Logger.printException(() -> "Player not available"); - return; - } - - try { - player.pause(); - AlertDialog dialog = createSpeedPlaybackDialog(imageView); - dialog.setOnDismissListener(dialogInterface -> player.play()); - dialog.show(); - - } catch (Exception e) { - Logger.printException(() -> "changePlaybackSpeed", e); - } - } - - private static AlertDialog createSpeedPlaybackDialog(ImageView imageView) { - Context context = imageView.getContext(); - int currentSelection = getCurrentSpeedSelection(); - - return new AlertDialog.Builder(context) - .setTitle("Select Playback Speed") - .setSingleChoiceItems(getSpeedOptions(), currentSelection, - PlaybackSpeedPatch::handleSpeedSelection) - .create(); - } - - private static int getCurrentSpeedSelection() { - try { - float currentRate = player.getPlaybackRate(); - int index = Arrays.binarySearch(SPEED_VALUES, currentRate); - return Math.max(index, 0); // Use slowest speed if not found. - } catch (Exception e) { - Logger.printException(() -> "getCurrentSpeedSelection error getting current playback speed", e); - return 0; - } - } - - private static void handleSpeedSelection(android.content.DialogInterface dialog, int selectedIndex) { - try { - float selectedSpeed = SPEED_VALUES[selectedIndex]; - player.setPlaybackRate(selectedSpeed); - player.play(); - } catch (Exception e) { - Logger.printException(() -> "handleSpeedSelection error setting playback speed", e); - } finally { - dialog.dismiss(); - } - } -} - -class SpeedIconDrawable extends Drawable { - private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); - - @Override - public void draw(Canvas canvas) { - int w = getBounds().width(); - int h = getBounds().height(); - float centerX = w / 2f; - // Position gauge in lower portion. - float centerY = h * 0.7f; - float radius = Math.min(w, h) / 2f * 0.8f; - - paint.setColor(Color.WHITE); - paint.setStyle(Paint.Style.STROKE); - paint.setStrokeWidth(radius * 0.1f); - - // Draw semicircle. - RectF oval = new RectF(centerX - radius, centerY - radius, centerX + radius, centerY + radius); - canvas.drawArc(oval, 180, 180, false, paint); - - // Draw three tick marks. - paint.setStrokeWidth(radius * 0.06f); - for (int i = 0; i < 3; i++) { - float angle = 180 + (i * 45); // 180°, 225°, 270°. - float angleRad = (float) Math.toRadians(angle); - - float startX = centerX + (radius * 0.8f) * (float) Math.cos(angleRad); - float startY = centerY + (radius * 0.8f) * (float) Math.sin(angleRad); - float endX = centerX + radius * (float) Math.cos(angleRad); - float endY = centerY + radius * (float) Math.sin(angleRad); - - canvas.drawLine(startX, startY, endX, endY, paint); - } - - // Draw needle. - paint.setStrokeWidth(radius * 0.08f); - float needleAngle = 200; // Slightly right of center. - float needleAngleRad = (float) Math.toRadians(needleAngle); - - float needleEndX = centerX + (radius * 0.6f) * (float) Math.cos(needleAngleRad); - float needleEndY = centerY + (radius * 0.6f) * (float) Math.sin(needleAngleRad); - - canvas.drawLine(centerX, centerY, needleEndX, needleEndY, paint); - - // Center dot. - paint.setStyle(Paint.Style.FILL); - canvas.drawCircle(centerX, centerY, radius * 0.06f, paint); - } - - @Override - public void setAlpha(int alpha) { - paint.setAlpha(alpha); - } - - @Override - public void setColorFilter(ColorFilter colorFilter) { - paint.setColorFilter(colorFilter); - } - - @Override - public int getOpacity() { - return PixelFormat.TRANSLUCENT; - } - - @Override - public int getIntrinsicWidth() { - return Dim.dp32; - } - - @Override - public int getIntrinsicHeight() { - return Dim.dp32; - } -} diff --git a/extensions/primevideo/stub/build.gradle.kts b/extensions/primevideo/stub/build.gradle.kts deleted file mode 100644 index 7744c0eaac..0000000000 --- a/extensions/primevideo/stub/build.gradle.kts +++ /dev/null @@ -1,17 +0,0 @@ -plugins { - alias(libs.plugins.android.library) -} - -android { - namespace = "app.revanced.extension" - compileSdk = 34 - - defaultConfig { - minSdk = 21 - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } -} diff --git a/extensions/primevideo/stub/src/main/AndroidManifest.xml b/extensions/primevideo/stub/src/main/AndroidManifest.xml deleted file mode 100644 index 9b65eb06cf..0000000000 --- a/extensions/primevideo/stub/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/extensions/primevideo/stub/src/main/java/com/amazon/avod/fsm/SimpleTrigger.java b/extensions/primevideo/stub/src/main/java/com/amazon/avod/fsm/SimpleTrigger.java deleted file mode 100644 index b537fe0402..0000000000 --- a/extensions/primevideo/stub/src/main/java/com/amazon/avod/fsm/SimpleTrigger.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.amazon.avod.fsm; - -public final class SimpleTrigger implements Trigger { - public SimpleTrigger(T triggerType) { - } -} \ No newline at end of file diff --git a/extensions/primevideo/stub/src/main/java/com/amazon/avod/fsm/StateBase.java b/extensions/primevideo/stub/src/main/java/com/amazon/avod/fsm/StateBase.java deleted file mode 100644 index 95741308c3..0000000000 --- a/extensions/primevideo/stub/src/main/java/com/amazon/avod/fsm/StateBase.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.amazon.avod.fsm; - -public abstract class StateBase { - // This method orginally has protected access (modified in patch code). - public void doTrigger(Trigger trigger) { - } -} \ No newline at end of file diff --git a/extensions/primevideo/stub/src/main/java/com/amazon/avod/fsm/Trigger.java b/extensions/primevideo/stub/src/main/java/com/amazon/avod/fsm/Trigger.java deleted file mode 100644 index 282f0f2004..0000000000 --- a/extensions/primevideo/stub/src/main/java/com/amazon/avod/fsm/Trigger.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.amazon.avod.fsm; - -public interface Trigger { -} \ No newline at end of file diff --git a/extensions/primevideo/stub/src/main/java/com/amazon/avod/media/TimeSpan.java b/extensions/primevideo/stub/src/main/java/com/amazon/avod/media/TimeSpan.java deleted file mode 100644 index cc90e43cdc..0000000000 --- a/extensions/primevideo/stub/src/main/java/com/amazon/avod/media/TimeSpan.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.amazon.avod.media; - -public final class TimeSpan { - public long getTotalMilliseconds() { - throw new UnsupportedOperationException(); - } -} \ No newline at end of file diff --git a/extensions/primevideo/stub/src/main/java/com/amazon/avod/media/ads/AdBreak.java b/extensions/primevideo/stub/src/main/java/com/amazon/avod/media/ads/AdBreak.java deleted file mode 100644 index 9a950434dc..0000000000 --- a/extensions/primevideo/stub/src/main/java/com/amazon/avod/media/ads/AdBreak.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.amazon.avod.media.ads; - -import com.amazon.avod.media.TimeSpan; - -public interface AdBreak { - TimeSpan getDurationExcludingAux(); -} \ No newline at end of file diff --git a/extensions/primevideo/stub/src/main/java/com/amazon/avod/media/ads/internal/state/AdBreakState.java b/extensions/primevideo/stub/src/main/java/com/amazon/avod/media/ads/internal/state/AdBreakState.java deleted file mode 100644 index f417660ed7..0000000000 --- a/extensions/primevideo/stub/src/main/java/com/amazon/avod/media/ads/internal/state/AdBreakState.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.amazon.avod.media.ads.internal.state; - -public abstract class AdBreakState extends AdEnabledPlaybackState { -} \ No newline at end of file diff --git a/extensions/primevideo/stub/src/main/java/com/amazon/avod/media/ads/internal/state/AdBreakTrigger.java b/extensions/primevideo/stub/src/main/java/com/amazon/avod/media/ads/internal/state/AdBreakTrigger.java deleted file mode 100644 index f8b3995650..0000000000 --- a/extensions/primevideo/stub/src/main/java/com/amazon/avod/media/ads/internal/state/AdBreakTrigger.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.amazon.avod.media.ads.internal.state; - -import com.amazon.avod.media.ads.AdBreak; -import com.amazon.avod.media.TimeSpan; - -public class AdBreakTrigger { - public AdBreak getBreak() { - throw new UnsupportedOperationException(); - } - - public TimeSpan getSeekTarget() { - throw new UnsupportedOperationException(); - } - - public TimeSpan getSeekStartPosition() { - throw new UnsupportedOperationException(); - } -} \ No newline at end of file diff --git a/extensions/primevideo/stub/src/main/java/com/amazon/avod/media/ads/internal/state/AdEnabledPlaybackState.java b/extensions/primevideo/stub/src/main/java/com/amazon/avod/media/ads/internal/state/AdEnabledPlaybackState.java deleted file mode 100644 index 445aad580a..0000000000 --- a/extensions/primevideo/stub/src/main/java/com/amazon/avod/media/ads/internal/state/AdEnabledPlaybackState.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.amazon.avod.media.ads.internal.state; - -import com.amazon.avod.fsm.StateBase; -import com.amazon.avod.media.playback.state.PlayerStateType; -import com.amazon.avod.media.playback.state.trigger.PlayerTriggerType; - -public class AdEnabledPlaybackState extends StateBase { -} \ No newline at end of file diff --git a/extensions/primevideo/stub/src/main/java/com/amazon/avod/media/ads/internal/state/AdEnabledPlayerTriggerType.java b/extensions/primevideo/stub/src/main/java/com/amazon/avod/media/ads/internal/state/AdEnabledPlayerTriggerType.java deleted file mode 100644 index e7951e9342..0000000000 --- a/extensions/primevideo/stub/src/main/java/com/amazon/avod/media/ads/internal/state/AdEnabledPlayerTriggerType.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.amazon.avod.media.ads.internal.state; - -public enum AdEnabledPlayerTriggerType { - NO_MORE_ADS_SKIP_TRANSITION -} \ No newline at end of file diff --git a/extensions/primevideo/stub/src/main/java/com/amazon/avod/media/ads/internal/state/ServerInsertedAdBreakState.java b/extensions/primevideo/stub/src/main/java/com/amazon/avod/media/ads/internal/state/ServerInsertedAdBreakState.java deleted file mode 100644 index 07c198013f..0000000000 --- a/extensions/primevideo/stub/src/main/java/com/amazon/avod/media/ads/internal/state/ServerInsertedAdBreakState.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.amazon.avod.media.ads.internal.state; - -public class ServerInsertedAdBreakState extends AdBreakState { -} \ No newline at end of file diff --git a/extensions/primevideo/stub/src/main/java/com/amazon/avod/media/playback/VideoPlayer.java b/extensions/primevideo/stub/src/main/java/com/amazon/avod/media/playback/VideoPlayer.java deleted file mode 100644 index 4f82e98727..0000000000 --- a/extensions/primevideo/stub/src/main/java/com/amazon/avod/media/playback/VideoPlayer.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.amazon.avod.media.playback; - -public interface VideoPlayer { - long getCurrentPosition(); - - void seekTo(long positionMs); - - void pause(); - - void play(); - - boolean isPlaying(); -} \ No newline at end of file diff --git a/extensions/primevideo/stub/src/main/java/com/amazon/avod/media/playback/state/PlayerStateType.java b/extensions/primevideo/stub/src/main/java/com/amazon/avod/media/playback/state/PlayerStateType.java deleted file mode 100644 index 202723285e..0000000000 --- a/extensions/primevideo/stub/src/main/java/com/amazon/avod/media/playback/state/PlayerStateType.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.amazon.avod.media.playback.state; - -public interface PlayerStateType { -} \ No newline at end of file diff --git a/extensions/primevideo/stub/src/main/java/com/amazon/avod/media/playback/state/trigger/PlayerTriggerType.java b/extensions/primevideo/stub/src/main/java/com/amazon/avod/media/playback/state/trigger/PlayerTriggerType.java deleted file mode 100644 index eac139f9bf..0000000000 --- a/extensions/primevideo/stub/src/main/java/com/amazon/avod/media/playback/state/trigger/PlayerTriggerType.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.amazon.avod.media.playback.state.trigger; - -public interface PlayerTriggerType { -} \ No newline at end of file diff --git a/extensions/primevideo/stub/src/main/java/com/amazon/video/sdk/player/Player.java b/extensions/primevideo/stub/src/main/java/com/amazon/video/sdk/player/Player.java deleted file mode 100644 index bd609e1964..0000000000 --- a/extensions/primevideo/stub/src/main/java/com/amazon/video/sdk/player/Player.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.amazon.video.sdk.player; - -public interface Player { - float getPlaybackRate(); - - void setPlaybackRate(float rate); - - void play(); - - void pause(); -} \ No newline at end of file diff --git a/extensions/reddit/build.gradle.kts b/extensions/reddit/build.gradle.kts index 75c8d7a179..8693f97f53 100644 --- a/extensions/reddit/build.gradle.kts +++ b/extensions/reddit/build.gradle.kts @@ -1,9 +1,3 @@ dependencies { compileOnly(project(":extensions:reddit:stub")) } - -android { - defaultConfig { - minSdk = 28 - } -} diff --git a/extensions/reddit/src/main/java/app/revanced/extension/reddit/patches/FilterPromotedLinksPatch.java b/extensions/reddit/src/main/java/app/revanced/extension/patches/FilterPromotedLinksPatch.java similarity index 83% rename from extensions/reddit/src/main/java/app/revanced/extension/reddit/patches/FilterPromotedLinksPatch.java rename to extensions/reddit/src/main/java/app/revanced/extension/patches/FilterPromotedLinksPatch.java index 12cdc88345..5b3e61b2ae 100644 --- a/extensions/reddit/src/main/java/app/revanced/extension/reddit/patches/FilterPromotedLinksPatch.java +++ b/extensions/reddit/src/main/java/app/revanced/extension/patches/FilterPromotedLinksPatch.java @@ -1,16 +1,12 @@ -package app.revanced.extension.reddit.patches; +package app.revanced.extension.patches; import com.reddit.domain.model.ILink; import java.util.ArrayList; import java.util.List; -@SuppressWarnings("unused") public final class FilterPromotedLinksPatch { - /** - * Injection point. - * * Filters list from promoted links. **/ public static List filterChildren(final Iterable links) { diff --git a/extensions/reddit/stub/build.gradle.kts b/extensions/reddit/stub/build.gradle.kts index b4bee8809f..c1cc5794c0 100644 --- a/extensions/reddit/stub/build.gradle.kts +++ b/extensions/reddit/stub/build.gradle.kts @@ -1,10 +1,10 @@ plugins { - alias(libs.plugins.android.library) + id(libs.plugins.android.library.get().pluginId) } android { namespace = "app.revanced.extension" - compileSdk = 34 + compileSdk = 33 defaultConfig { minSdk = 24 diff --git a/extensions/samsung/radio/build.gradle.kts b/extensions/samsung/radio/build.gradle.kts deleted file mode 100644 index 15d386efb3..0000000000 --- a/extensions/samsung/radio/build.gradle.kts +++ /dev/null @@ -1,10 +0,0 @@ -dependencies { - compileOnly(project(":extensions:shared:library")) - compileOnly(project(":extensions:samsung:radio:stub")) -} - -android { - defaultConfig { - minSdk = 26 - } -} diff --git a/extensions/samsung/radio/src/main/AndroidManifest.xml b/extensions/samsung/radio/src/main/AndroidManifest.xml deleted file mode 100644 index 9b65eb06cf..0000000000 --- a/extensions/samsung/radio/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/extensions/samsung/radio/src/main/java/app/revanced/extension/samsung/radio/misc/fix/crash/FixCrashPatch.java b/extensions/samsung/radio/src/main/java/app/revanced/extension/samsung/radio/misc/fix/crash/FixCrashPatch.java deleted file mode 100644 index 72c5addc4c..0000000000 --- a/extensions/samsung/radio/src/main/java/app/revanced/extension/samsung/radio/misc/fix/crash/FixCrashPatch.java +++ /dev/null @@ -1,24 +0,0 @@ -package app.revanced.extension.samsung.radio.misc.fix.crash; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -@SuppressWarnings("unused") -public final class FixCrashPatch { - /** - * Injection point. - *

- * Add the required permissions to the request list to avoid crashes on API 34+. - **/ - public static final String[] fixPermissionRequestList(String[] perms) { - List permsList = new ArrayList<>(Arrays.asList(perms)); - if (permsList.contains("android.permission.POST_NOTIFICATIONS")) { - permsList.addAll(Arrays.asList("android.permission.RECORD_AUDIO", "android.permission.READ_PHONE_STATE", "android.permission.FOREGROUND_SERVICE_MICROPHONE")); - } - if (permsList.contains("android.permission.RECORD_AUDIO")) { - permsList.add("android.permission.FOREGROUND_SERVICE_MICROPHONE"); - } - return permsList.toArray(new String[0]); - } -} diff --git a/extensions/samsung/radio/src/main/java/app/revanced/extension/samsung/radio/restrictions/device/BypassDeviceChecksPatch.java b/extensions/samsung/radio/src/main/java/app/revanced/extension/samsung/radio/restrictions/device/BypassDeviceChecksPatch.java deleted file mode 100644 index 19b6c3e822..0000000000 --- a/extensions/samsung/radio/src/main/java/app/revanced/extension/samsung/radio/restrictions/device/BypassDeviceChecksPatch.java +++ /dev/null @@ -1,19 +0,0 @@ -package app.revanced.extension.samsung.radio.restrictions.device; - -import android.os.SemSystemProperties; - -import java.util.Arrays; - -@SuppressWarnings("unused") -public final class BypassDeviceChecksPatch { - - /** - * Injection point. - *

- * Check if the device has the required hardware - **/ - public static final boolean checkIfDeviceIsIncompatible(String[] deviceList) { - String currentDevice = SemSystemProperties.getSalesCode(); - return Arrays.asList(deviceList).contains(currentDevice); - } -} diff --git a/extensions/samsung/radio/stub/build.gradle.kts b/extensions/samsung/radio/stub/build.gradle.kts deleted file mode 100644 index b4bee8809f..0000000000 --- a/extensions/samsung/radio/stub/build.gradle.kts +++ /dev/null @@ -1,17 +0,0 @@ -plugins { - alias(libs.plugins.android.library) -} - -android { - namespace = "app.revanced.extension" - compileSdk = 34 - - defaultConfig { - minSdk = 24 - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } -} diff --git a/extensions/samsung/radio/stub/src/main/AndroidManifest.xml b/extensions/samsung/radio/stub/src/main/AndroidManifest.xml deleted file mode 100644 index 15e7c2ae67..0000000000 --- a/extensions/samsung/radio/stub/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/extensions/samsung/radio/stub/src/main/java/android/os/SemSystemProperties.java b/extensions/samsung/radio/stub/src/main/java/android/os/SemSystemProperties.java deleted file mode 100644 index 33a4b4400c..0000000000 --- a/extensions/samsung/radio/stub/src/main/java/android/os/SemSystemProperties.java +++ /dev/null @@ -1,7 +0,0 @@ -package android.os; - -public class SemSystemProperties { - public static String getSalesCode() { - throw new UnsupportedOperationException("Stub"); - } -} \ No newline at end of file diff --git a/extensions/shared/build.gradle.kts b/extensions/shared/build.gradle.kts index 3eb6ff48c7..2da2e1e89c 100644 --- a/extensions/shared/build.gradle.kts +++ b/extensions/shared/build.gradle.kts @@ -1,10 +1,3 @@ dependencies { implementation(project(":extensions:shared:library")) - compileOnly(libs.okhttp) -} - -android { - defaultConfig { - minSdk = 23 - } } diff --git a/extensions/shared/library/build.gradle.kts b/extensions/shared/library/build.gradle.kts index 8215e513ad..3cbb560695 100644 --- a/extensions/shared/library/build.gradle.kts +++ b/extensions/shared/library/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - alias(libs.plugins.android.library) + id("com.android.library") } android { @@ -18,7 +18,4 @@ android { dependencies { compileOnly(libs.annotation) - compileOnly(libs.okhttp) - compileOnly(libs.protobuf.javalite) - implementation(project(":extensions:shared:protobuf", configuration = "shadowRuntimeElements")) } diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/GmsCoreSupport.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/GmsCoreSupport.java index fb7e68963a..1e2586b2bc 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/GmsCoreSupport.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/GmsCoreSupport.java @@ -1,8 +1,10 @@ package app.revanced.extension.shared; +import static app.revanced.extension.shared.StringRef.str; + import android.annotation.SuppressLint; import android.app.Activity; -import android.app.Dialog; +import android.app.AlertDialog; import android.app.SearchManager; import android.content.Context; import android.content.DialogInterface; @@ -12,401 +14,145 @@ import android.net.Uri; import android.os.Build; import android.os.PowerManager; import android.provider.Settings; -import android.util.Pair; -import android.widget.LinearLayout; -import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; -import app.revanced.extension.shared.requests.Requester; -import app.revanced.extension.shared.requests.Route; -import app.revanced.extension.shared.settings.BaseSettings; -import app.revanced.extension.shared.ui.CustomDialog; - -import org.json.JSONObject; - -import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; -import java.util.Locale; -import static app.revanced.extension.shared.StringRef.str; -import static app.revanced.extension.shared.requests.Route.Method.GET; - -@SuppressWarnings("unused") +/** + * @noinspection unused + */ public class GmsCoreSupport { - private static GmsCore gmsCore = GmsCore.UNKNOWN; + public static final String ORIGINAL_UNPATCHED_PACKAGE_NAME = "com.google.android.youtube"; + private static final String GMS_CORE_PACKAGE_NAME + = getGmsCoreVendorGroupId() + ".android.gms"; + private static final Uri GMS_CORE_PROVIDER + = Uri.parse("content://" + getGmsCoreVendorGroupId() + ".android.gsf.gservices/prefix"); + private static final String DONT_KILL_MY_APP_LINK + = "https://dontkillmyapp.com"; - static { - for (GmsCore core : GmsCore.values()) { - if (core.getGroupId().equals(getGmsCoreVendorGroupId())) { - GmsCoreSupport.gmsCore = core; - break; - } + private static void open(String queryOrLink) { + Intent intent; + try { + // Check if queryOrLink is a valid URL. + new URL(queryOrLink); + + intent = new Intent(Intent.ACTION_VIEW, Uri.parse(queryOrLink)); + } catch (MalformedURLException e) { + intent = new Intent(Intent.ACTION_WEB_SEARCH); + intent.putExtra(SearchManager.QUERY, queryOrLink); } + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + Utils.getContext().startActivity(intent); + + // Gracefully exit, otherwise the broken app will continue to run. + System.exit(0); + } + + private static void showBatteryOptimizationDialog(Activity context, + String dialogMessageRef, + String positiveButtonStringRef, + DialogInterface.OnClickListener onPositiveClickListener) { + // Do not set cancelable to false, to allow using back button to skip the action, + // just in case the check can never be satisfied. + var dialog = new AlertDialog.Builder(context) + .setIconAttribute(android.R.attr.alertDialogIcon) + .setTitle(str("gms_core_dialog_title")) + .setMessage(str(dialogMessageRef)) + .setPositiveButton(str(positiveButtonStringRef), onPositiveClickListener) + .create(); + Utils.showDialog(context, dialog); } /** * Injection point. */ + @RequiresApi(api = Build.VERSION_CODES.N) public static void checkGmsCore(Activity context) { - gmsCore.check(context); - } + try { + // Verify the user has not included GmsCore for a root installation. + // GmsCore Support changes the package name, but with a mounted installation + // all manifest changes are ignored and the original package name is used. + if (context.getPackageName().equals(ORIGINAL_UNPATCHED_PACKAGE_NAME)) { + Logger.printInfo(() -> "App is mounted with root, but GmsCore patch was included"); + // Cannot use localize text here, since the app will load + // resources from the unpatched app and all patch strings are missing. + Utils.showToastLong("The 'GmsCore support' patch breaks mount installations"); - private static String getOriginalPackageName() { - return null; // Modified during patching. - } - - private static String getGmsCoreVendorGroupId() { - return "app.revanced"; // Modified during patching. - } - - - /** - * @return If the current package name is the same as the original unpatched app. - * If `GmsCore support` was not included during patching, this returns true; - */ - public static boolean isPackageNameOriginal() { - String originalPackageName = getOriginalPackageName(); - return originalPackageName == null - || originalPackageName.equals(Utils.getContext().getPackageName()); - } - - private enum GmsCore { - REVANCED("app.revanced", "https://github.com/revanced/gmscore/releases/latest", () -> { - try { - HttpURLConnection connection = Requester.getConnectionFromRoute( - "https://api.github.com", - new Route(GET, "/repos/revanced/gmscore/releases/latest") - ); - connection.setConnectTimeout(5000); - connection.setReadTimeout(5000); - - int responseCode = connection.getResponseCode(); - if (responseCode != 200) { - Logger.printDebug(() -> "GitHub API returned status code: " + responseCode); - return null; - } - - // Parse the response - JSONObject releaseData = Requester.parseJSONObject(connection); - String tagName = releaseData.optString("tag_name", ""); - connection.disconnect(); - - if (tagName.isEmpty()) { - Logger.printDebug(() -> "No tag_name found in GitHub release data"); - return null; - } - - if (tagName.startsWith("v")) tagName = tagName.substring(1); - - return tagName; - } catch (Exception ex) { - Logger.printInfo(() -> "Failed to fetch latest GmsCore version from GitHub", ex); - return null; + // Do not exit. If the app exits before launch completes (and without + // opening another activity), then on some devices such as Pixel phone Android 10 + // no toast will be shown and the app will continually be relaunched + // with the appearance of a hung app. } - }), - UNKNOWN(getGmsCoreVendorGroupId(), getGmsCoreVendorGroupId() + "android.gms", () -> null); - private static final String DONT_KILL_MY_APP_URL - = "https://dontkillmyapp.com/"; - private static final Route DONT_KILL_MY_APP_MANUFACTURER_API - = new Route(GET, "/api/v2/{manufacturer}.json"); - private static final String DONT_KILL_MY_APP_NAME_PARAMETER - = "?app=MicroG"; - private static final String BUILD_MANUFACTURER - = Build.MANUFACTURER.toLowerCase(Locale.ROOT).replace(" ", "-"); - - /** - * If a manufacturer specific page exists on DontKillMyApp. - */ - @Nullable - private volatile Boolean dontKillMyAppManufacturerSupported; - - private final String groupId; - private final String packageName; - private final String downloadQuery; - private final GetLatestVersion getLatestVersion; - private final Uri gmsCoreProvider; - - GmsCore(String groupId, String downloadQuery, GetLatestVersion getLatestVersion) { - this.groupId = groupId; - this.packageName = groupId + ".android.gms"; - this.gmsCoreProvider = Uri.parse("content://" + groupId + ".android.gsf.gservices/prefix"); - - this.downloadQuery = downloadQuery; - this.getLatestVersion = getLatestVersion; - } - - String getGroupId() { - return groupId; - } - - void check(Activity context) { - checkInstallation(context); - checkUpdates(context); - } - - private void checkInstallation(Activity context) { + // Verify GmsCore is installed. try { - // Verify the user has not included GmsCore for a root installation. - // GmsCore Support changes the package name, but with a mounted installation - // all manifest changes are ignored and the original package name is used. - if (isPackageNameOriginal()) { - Logger.printInfo(() -> "App is mounted with root, but GmsCore patch was included"); - // Cannot use localize text here, since the app will load resources - // from the unpatched app and all patch strings are missing. - Utils.showToastLong("The 'GmsCore support' patch breaks mount installations"); - - // Do not exit. If the app exits before launch completes (and without - // opening another activity), then on some devices such as Pixel phone Android 10 - // no toast will be shown and the app will continually relaunch - // with the appearance of a hung app. - } - - // Verify GmsCore is installed. - try { - PackageManager manager = context.getPackageManager(); - manager.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES); - } catch (PackageManager.NameNotFoundException exception) { - Logger.printInfo(() -> "GmsCore was not found"); - // Cannot show a dialog and must show a toast, - // because on some installations the app crashes before a dialog can be displayed. - Utils.showToastLong(str("revanced_gms_core_toast_not_installed_message")); - - open(downloadQuery); - return; - } - - // Check if GmsCore is whitelisted from battery optimizations. - if (isAndroidAutomotive(context)) { - // Ignore Android Automotive devices (Google built-in), - // as there is no way to disable battery optimizations. - Logger.printDebug(() -> "Device is Android Automotive"); - } else if (batteryOptimizationsEnabled(context)) { - Logger.printInfo(() -> "GmsCore is not whitelisted from battery optimizations"); - - showBatteryOptimizationDialog(context, - "revanced_gms_core_dialog_not_whitelisted_using_battery_optimizations_message", - "revanced_gms_core_dialog_continue_text", - (dialog, id) -> openGmsCoreDisableBatteryOptimizationsIntent(context)); - return; - } - - // Check if GmsCore is currently running in the background. - var client = context.getContentResolver().acquireContentProviderClient(gmsCoreProvider); - //noinspection TryFinallyCanBeTryWithResources - try { - if (client == null) { - Logger.printInfo(() -> "GmsCore is not running in the background"); - checkIfDontKillMyAppSupportsManufacturer(); - - showBatteryOptimizationDialog(context, - "revanced_gms_core_dialog_not_whitelisted_not_allowed_in_background_message", - "gmsrevanced_gms_core_log_open_website_text", - (dialog, id) -> openDontKillMyApp()); - } - } finally { - if (client != null) client.close(); - } - } catch (Exception ex) { - Logger.printException(() -> "checkGmsCore failure", ex); - } - } - - private void checkUpdates(Activity context) { - if (!BaseSettings.GMS_CORE_CHECK_UPDATES.get()) { - Logger.printDebug(() -> "GmsCore update check is disabled in settings"); + PackageManager manager = context.getPackageManager(); + manager.getPackageInfo(GMS_CORE_PACKAGE_NAME, PackageManager.GET_ACTIVITIES); + } catch (PackageManager.NameNotFoundException exception) { + Logger.printInfo(() -> "GmsCore was not found"); + // Cannot show a dialog and must show a toast, + // because on some installations the app crashes before a dialog can be displayed. + Utils.showToastLong(str("gms_core_toast_not_installed_message")); + open(getGmsCoreDownload()); return; } - Utils.runOnBackgroundThread(() -> { - try { - PackageManager manager = context.getPackageManager(); - var installedVersion = manager.getPackageInfo(packageName, 0).versionName; + // Check if GmsCore is running in the background. + try (var client = context.getContentResolver().acquireContentProviderClient(GMS_CORE_PROVIDER)) { + if (client == null) { + Logger.printInfo(() -> "GmsCore is not running in the background"); - // GmsCore adds suffixes for flavor builds. Remove the suffix for version comparison. - int suffixIndex = installedVersion.indexOf('-'); - if (suffixIndex != -1) - installedVersion = installedVersion.substring(0, suffixIndex); - String finalInstalledVersion = installedVersion; - - Logger.printDebug(() -> "Installed GmsCore version: " + finalInstalledVersion); - - var latestVersion = getLatestVersion.get(); - - if (latestVersion == null || latestVersion.isEmpty()) { - Logger.printDebug(() -> "Could not get latest GmsCore version"); - Utils.showToastLong(str("revanced_gms_core_toast_update_check_failed_message")); - return; - } - - Logger.printDebug(() -> "Latest GmsCore version on GitHub: " + latestVersion); - - // Compare versions - if (!installedVersion.equals(latestVersion)) { - Logger.printInfo(() -> "GmsCore update available. Installed: " + finalInstalledVersion - + ", Latest: " + latestVersion); - - showUpdateDialog(context, installedVersion, latestVersion); - } else { - Logger.printDebug(() -> "GmsCore is up to date"); - } - } catch (Exception ex) { - Logger.printInfo(() -> "Could not check GmsCore updates", ex); - Utils.showToastLong(str("revanced_gms_core_toast_update_check_failed_message")); + showBatteryOptimizationDialog(context, + "gms_core_dialog_not_whitelisted_not_allowed_in_background_message", + "gms_core_dialog_open_website_text", + (dialog, id) -> open(DONT_KILL_MY_APP_LINK)); + return; } - }); - } - - private void open(String queryOrLink) { - Logger.printInfo(() -> "Opening link: " + queryOrLink); - - Intent intent; - try { - // Check if queryOrLink is a valid URL. - new URL(queryOrLink); - - intent = new Intent(Intent.ACTION_VIEW, Uri.parse(queryOrLink)); - } catch (MalformedURLException e) { - intent = new Intent(Intent.ACTION_WEB_SEARCH); - intent.putExtra(SearchManager.QUERY, queryOrLink); - } - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - Utils.getContext().startActivity(intent); - - // Gracefully exit, otherwise the broken app will continue to run. - System.exit(0); - } - - private void showUpdateDialog(Activity context, String installedVersion, String latestVersion) { - // Use a delay to allow the activity to finish initializing. - // Otherwise, if device is in dark mode the dialog is shown with wrong color scheme. - Utils.runOnMainThreadDelayed(() -> { - try { - Pair dialogPair = CustomDialog.create( - context, - str("revanced_gms_core_dialog_title"), - String.format(str("revanced_gms_core_update_available_message"), latestVersion, installedVersion), - null, - str("revanced_gms_core_dialog_open_website_text"), - () -> open(downloadQuery), - () -> { - }, - str("revanced_gms_core_dialog_cancel_text"), - null, - true - ); - - Dialog dialog = dialogPair.first; - dialog.setCancelable(true); - Utils.showDialog(context, dialog); - } catch (Exception ex) { - Logger.printException(() -> "Failed to show GmsCore update dialog", ex); - } - }, 100); - } - - private static void showBatteryOptimizationDialog(Activity context, - String dialogMessageRef, - String positiveButtonTextRef, - DialogInterface.OnClickListener onPositiveClickListener) { - // Use a delay to allow the activity to finish initializing. - // Otherwise, if device is in dark mode the dialog is shown with wrong color scheme. - Utils.runOnMainThreadDelayed(() -> { - // Create the custom dialog. - Pair dialogPair = CustomDialog.create( - context, - str("revanced_gms_core_dialog_title"), // Title. - str(dialogMessageRef), // Message. - null, // No EditText. - str(positiveButtonTextRef), // OK button text. - () -> onPositiveClickListener.onClick(null, 0), // Convert DialogInterface.OnClickListener to Runnable. - null, // No Cancel button action. - null, // No Neutral button text. - null, // No Neutral button action. - true // Dismiss dialog when onNeutralClick. - ); - - Dialog dialog = dialogPair.first; - - // Do not set cancelable to false to allow using back button to skip the action, - // just in case the battery change can never be satisfied. - dialog.setCancelable(true); - - // Show the dialog - Utils.showDialog(context, dialog); - }, 100); - } - - @SuppressLint("BatteryLife") // Permission is part of GmsCore - private void openGmsCoreDisableBatteryOptimizationsIntent(Activity activity) { - Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); - intent.setData(Uri.fromParts("package", packageName, null)); - activity.startActivityForResult(intent, 0); - } - - private void checkIfDontKillMyAppSupportsManufacturer() { - Utils.runOnBackgroundThread(() -> { - try { - final long start = System.currentTimeMillis(); - HttpURLConnection connection = Requester.getConnectionFromRoute( - DONT_KILL_MY_APP_URL, DONT_KILL_MY_APP_MANUFACTURER_API, BUILD_MANUFACTURER); - connection.setConnectTimeout(5000); - connection.setReadTimeout(5000); - - final boolean supported = connection.getResponseCode() == 200; - Logger.printInfo(() -> "Manufacturer is " + (supported ? "" : "NOT ") - + "listed on DontKillMyApp: " + BUILD_MANUFACTURER - + " fetch took: " + (System.currentTimeMillis() - start) + "ms"); - dontKillMyAppManufacturerSupported = supported; - } catch (Exception ex) { - Logger.printInfo(() -> "Could not check if manufacturer is listed on DontKillMyApp: " - + BUILD_MANUFACTURER, ex); - dontKillMyAppManufacturerSupported = null; - } - }); - } - - private void openDontKillMyApp() { - final Boolean manufacturerSupported = dontKillMyAppManufacturerSupported; - - String manufacturerPageToOpen; - if (manufacturerSupported == null) { - // Fetch has not completed yet. Only happens on extremely slow internet connections - // and the user spends less than 1 second reading what's on screen. - // Instead of waiting for the fetch (which may timeout), - // open the website without a vendor. - manufacturerPageToOpen = ""; - } else if (manufacturerSupported) { - manufacturerPageToOpen = BUILD_MANUFACTURER; - } else { - // No manufacturer specific page exists. Open the general page. - manufacturerPageToOpen = "general"; } - open(DONT_KILL_MY_APP_URL + manufacturerPageToOpen + DONT_KILL_MY_APP_NAME_PARAMETER); - } - - /** - * @return If GmsCore is not whitelisted from battery optimizations. - */ - private boolean batteryOptimizationsEnabled(Context context) { - //noinspection ObsoleteSdkInt - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - // Android 5.0 does not have battery optimization settings. - return false; + // Check if GmsCore is whitelisted from battery optimizations. + if (batteryOptimizationsEnabled(context)) { + Logger.printInfo(() -> "GmsCore is not whitelisted from battery optimizations"); + showBatteryOptimizationDialog(context, + "gms_core_dialog_not_whitelisted_using_battery_optimizations_message", + "gms_core_dialog_continue_text", + (dialog, id) -> openGmsCoreDisableBatteryOptimizationsIntent(context)); } - var powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); - return !powerManager.isIgnoringBatteryOptimizations(packageName); - } - - private boolean isAndroidAutomotive(Context context) { - return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); + } catch (Exception ex) { + Logger.printException(() -> "checkGmsCore failure", ex); } } - @FunctionalInterface - private interface GetLatestVersion { - String get(); + @SuppressLint("BatteryLife") // Permission is part of GmsCore + private static void openGmsCoreDisableBatteryOptimizationsIntent(Activity activity) { + Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); + intent.setData(Uri.fromParts("package", GMS_CORE_PACKAGE_NAME, null)); + activity.startActivityForResult(intent, 0); + } + + /** + * @return If GmsCore is not whitelisted from battery optimizations. + */ + private static boolean batteryOptimizationsEnabled(Context context) { + var powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + return !powerManager.isIgnoringBatteryOptimizations(GMS_CORE_PACKAGE_NAME); + } + + private static String getGmsCoreDownload() { + final var vendorGroupId = getGmsCoreVendorGroupId(); + //noinspection SwitchStatementWithTooFewBranches + switch (vendorGroupId) { + case "app.revanced": + return "https://github.com/revanced/gmscore/releases/latest"; + default: + return vendorGroupId + ".android.gms"; + } + } + + // Modified by a patch. Do not touch. + private static String getGmsCoreVendorGroupId() { + return "app.revanced"; } } diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/Logger.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/Logger.java index 610cd3414f..9df38ac99b 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/Logger.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/Logger.java @@ -1,27 +1,15 @@ package app.revanced.extension.shared; -import static app.revanced.extension.shared.settings.BaseSettings.DEBUG; -import static app.revanced.extension.shared.settings.BaseSettings.DEBUG_STACKTRACE; -import static app.revanced.extension.shared.settings.BaseSettings.DEBUG_TOAST_ON_ERROR; - import android.util.Log; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import app.revanced.extension.shared.settings.BaseSettings; import java.io.PrintWriter; import java.io.StringWriter; -import app.revanced.extension.shared.settings.BaseSettings; -import app.revanced.extension.shared.settings.preference.LogBufferManager; +import static app.revanced.extension.shared.settings.BaseSettings.*; -/** - * ReVanced specific logger. Logging is done to standard device log (accessible through ADB), - * and additionally accessible through {@link LogBufferManager}. - * - * All methods are thread safe, and are safe to call even - * if {@link Utils#getContext()} is not available. - */ public class Logger { /** @@ -29,186 +17,140 @@ public class Logger { */ @FunctionalInterface public interface LogMessage { - /** - * @return Logger string message. This method is only called if logging is enabled. - */ @NonNull String buildMessageString(); - } - private enum LogLevel { - DEBUG, - INFO, - ERROR - } + /** + * @return For outer classes, this returns {@link Class#getSimpleName()}. + * For static, inner, or anonymous classes, this returns the simple name of the enclosing class. + *
+ * For example, each of these classes return 'SomethingView': + * + * com.company.SomethingView + * com.company.SomethingView$StaticClass + * com.company.SomethingView$1 + * + */ + private String findOuterClassSimpleName() { + var selfClass = this.getClass(); - /** - * Log tag prefix. Only used for system logging. - */ - private static final String REVANCED_LOG_TAG_PREFIX = "revanced: "; - - private static final String LOGGER_CLASS_NAME = Logger.class.getName(); - - /** - * @return For outer classes, this returns {@link Class#getSimpleName()}. - * For static, inner, or anonymous classes, this returns the simple name of the enclosing class. - *
- * For example, each of these classes returns 'SomethingView': - * - * com.company.SomethingView - * com.company.SomethingView$StaticClass - * com.company.SomethingView$1 - * - */ - private static String getOuterClassSimpleName(Object obj) { - Class logClass = obj.getClass(); - String fullClassName = logClass.getName(); - final int dollarSignIndex = fullClassName.indexOf('$'); - if (dollarSignIndex < 0) { - return logClass.getSimpleName(); // Already an outer class. - } - - // Class is inner, static, or anonymous. - // Parse the simple name full name. - // A class with no package returns index of -1, but incrementing gives index zero which is correct. - final int simpleClassNameStartIndex = fullClassName.lastIndexOf('.') + 1; - return fullClassName.substring(simpleClassNameStartIndex, dollarSignIndex); - } - - /** - * Internal method to handle logging to Android Log and {@link LogBufferManager}. - * Appends the log message, stack trace (if enabled), and exception (if present) to logBuffer - * with class name but without 'revanced:' prefix. - * - * @param logLevel The log level. - * @param message Log message object. - * @param ex Optional exception. - * @param includeStackTrace If the current stack should be included. - * @param showToast If a toast is to be shown. - */ - private static void logInternal(LogLevel logLevel, LogMessage message, @Nullable Throwable ex, - boolean includeStackTrace, boolean showToast) { - // It's very important that no Settings are used in this method, - // as this code is used when a context is not set and thus referencing - // a setting will crash the app. - String messageString = message.buildMessageString(); - String className = getOuterClassSimpleName(message); - - String logText = messageString; - - // Append exception message if present. - if (ex != null) { - var exceptionMessage = ex.getMessage(); - if (exceptionMessage != null) { - logText += "\nException: " + exceptionMessage; + String fullClassName = selfClass.getName(); + final int dollarSignIndex = fullClassName.indexOf('$'); + if (dollarSignIndex < 0) { + return selfClass.getSimpleName(); // Already an outer class. } - } - if (includeStackTrace) { - var sw = new StringWriter(); - new Throwable().printStackTrace(new PrintWriter(sw)); - String stackTrace = sw.toString(); - // Remove the stacktrace elements of this class. - final int loggerIndex = stackTrace.lastIndexOf(LOGGER_CLASS_NAME); - final int loggerBegins = stackTrace.indexOf('\n', loggerIndex); - logText += stackTrace.substring(loggerBegins); - } - - // Do not include "revanced:" prefix in clipboard logs. - String managerToastString = className + ": " + logText; - LogBufferManager.appendToLogBuffer(managerToastString); - - String logTag = REVANCED_LOG_TAG_PREFIX + className; - switch (logLevel) { - case DEBUG: - if (ex == null) Log.d(logTag, logText); - else Log.d(logTag, logText, ex); - break; - case INFO: - if (ex == null) Log.i(logTag, logText); - else Log.i(logTag, logText, ex); - break; - case ERROR: - if (ex == null) Log.e(logTag, logText); - else Log.e(logTag, logText, ex); - break; - } - - if (showToast) { - Utils.showToastLong(managerToastString); + // Class is inner, static, or anonymous. + // Parse the simple name full name. + // A class with no package returns index of -1, but incrementing gives index zero which is correct. + final int simpleClassNameStartIndex = fullClassName.lastIndexOf('.') + 1; + return fullClassName.substring(simpleClassNameStartIndex, dollarSignIndex); } } - private static boolean shouldLogDebug() { - // If the app is still starting up and the context is not yet set, - // then allow debug logging regardless what the debug setting actually is. - return Utils.context == null || DEBUG.get(); - } - - private static boolean shouldShowErrorToast() { - return Utils.context != null && DEBUG_TOAST_ON_ERROR.get(); - } - - private static boolean includeStackTrace() { - return Utils.context != null && DEBUG_STACKTRACE.get(); - } + private static final String REVANCED_LOG_PREFIX = "revanced: "; /** * Logs debug messages under the outer class name of the code calling this method. - *

- * Whenever possible, the log string should be constructed entirely inside - * {@link LogMessage#buildMessageString()} so the performance cost of - * building strings is paid only if {@link BaseSettings#DEBUG} is enabled. + * Whenever possible, the log string should be constructed entirely inside {@link LogMessage#buildMessageString()} + * so the performance cost of building strings is paid only if {@link BaseSettings#DEBUG} is enabled. */ - public static void printDebug(LogMessage message) { + public static void printDebug(@NonNull LogMessage message) { printDebug(message, null); } /** * Logs debug messages under the outer class name of the code calling this method. - *

- * Whenever possible, the log string should be constructed entirely inside - * {@link LogMessage#buildMessageString()} so the performance cost of - * building strings is paid only if {@link BaseSettings#DEBUG} is enabled. + * Whenever possible, the log string should be constructed entirely inside {@link LogMessage#buildMessageString()} + * so the performance cost of building strings is paid only if {@link BaseSettings#DEBUG} is enabled. */ - public static void printDebug(LogMessage message, @Nullable Exception ex) { - if (shouldLogDebug()) { - logInternal(LogLevel.DEBUG, message, ex, includeStackTrace(), false); + public static void printDebug(@NonNull LogMessage message, @Nullable Exception ex) { + if (DEBUG.get()) { + String logMessage = message.buildMessageString(); + String logTag = REVANCED_LOG_PREFIX + message.findOuterClassSimpleName(); + + if (DEBUG_STACKTRACE.get()) { + var builder = new StringBuilder(logMessage); + var sw = new StringWriter(); + new Throwable().printStackTrace(new PrintWriter(sw)); + + builder.append('\n').append(sw); + logMessage = builder.toString(); + } + + if (ex == null) { + Log.d(logTag, logMessage); + } else { + Log.d(logTag, logMessage, ex); + } } } /** * Logs information messages using the outer class name of the code calling this method. */ - public static void printInfo(LogMessage message) { + public static void printInfo(@NonNull LogMessage message) { printInfo(message, null); } /** * Logs information messages using the outer class name of the code calling this method. */ - public static void printInfo(LogMessage message, @Nullable Exception ex) { - logInternal(LogLevel.INFO, message, ex, includeStackTrace(), false); + public static void printInfo(@NonNull LogMessage message, @Nullable Exception ex) { + String logTag = REVANCED_LOG_PREFIX + message.findOuterClassSimpleName(); + String logMessage = message.buildMessageString(); + if (ex == null) { + Log.i(logTag, logMessage); + } else { + Log.i(logTag, logMessage, ex); + } } /** * Logs exceptions under the outer class name of the code calling this method. - * Appends the log message, exception (if present), and toast message (if enabled) to logBuffer. */ - public static void printException(LogMessage message) { + public static void printException(@NonNull LogMessage message) { printException(message, null); } /** * Logs exceptions under the outer class name of the code calling this method. *

- * If the calling code is showing its own error toast, + * If the calling code is showing it's own error toast, * instead use {@link #printInfo(LogMessage, Exception)} * * @param message log message * @param ex exception (optional) */ - public static void printException(LogMessage message, @Nullable Throwable ex) { - logInternal(LogLevel.ERROR, message, ex, includeStackTrace(), shouldShowErrorToast()); + public static void printException(@NonNull LogMessage message, @Nullable Throwable ex) { + String messageString = message.buildMessageString(); + String outerClassSimpleName = message.findOuterClassSimpleName(); + String logMessage = REVANCED_LOG_PREFIX + outerClassSimpleName; + if (ex == null) { + Log.e(logMessage, messageString); + } else { + Log.e(logMessage, messageString, ex); + } + if (DEBUG_TOAST_ON_ERROR.get()) { + Utils.showToastLong(outerClassSimpleName + ": " + messageString); + } } -} + + /** + * Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#getContext()} may not be initialized. + * Normally this method should not be used. + */ + public static void initializationInfo(@NonNull Class callingClass, @NonNull String message) { + Log.i(REVANCED_LOG_PREFIX + callingClass.getSimpleName(), message); + } + + /** + * Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#getContext()} may not be initialized. + * Normally this method should not be used. + */ + public static void initializationException(@NonNull Class callingClass, @NonNull String message, + @Nullable Exception ex) { + Log.e(REVANCED_LOG_PREFIX + callingClass.getSimpleName(), message, ex); + } + +} \ No newline at end of file diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/ResourceType.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/ResourceType.java deleted file mode 100644 index 48032017a4..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/ResourceType.java +++ /dev/null @@ -1,57 +0,0 @@ -package app.revanced.extension.shared; - -import java.util.HashMap; -import java.util.Map; - -public enum ResourceType { - ANIM("anim"), - ANIMATOR("animator"), - ARRAY("array"), - ATTR("attr"), - BOOL("bool"), - COLOR("color"), - DIMEN("dimen"), - DRAWABLE("drawable"), - FONT("font"), - FRACTION("fraction"), - ID("id"), - INTEGER("integer"), - INTERPOLATOR("interpolator"), - LAYOUT("layout"), - MENU("menu"), - MIPMAP("mipmap"), - NAVIGATION("navigation"), - PLURALS("plurals"), - RAW("raw"), - STRING("string"), - STYLE("style"), - STYLEABLE("styleable"), - TRANSITION("transition"), - VALUES("values"), - XML("xml"); - - private static final Map VALUE_MAP; - - static { - ResourceType[] values = values(); - VALUE_MAP = new HashMap<>(2 * values.length); - - for (ResourceType type : values) { - VALUE_MAP.put(type.value, type); - } - } - - public final String value; - - public static ResourceType fromValue(String value) { - ResourceType type = VALUE_MAP.get(value); - if (type == null) { - throw new IllegalArgumentException("Unknown resource type: " + value); - } - return type; - } - - ResourceType(String value) { - this.value = value; - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/StringRef.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/StringRef.java index c1c2c90d14..4390137de7 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/StringRef.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/StringRef.java @@ -70,7 +70,7 @@ public class StringRef { } /** - * Creates a StringRef object that'll not change its value + * Creates a StringRef object that'll not change it's value * * @param value value which toString() method returns when invoked on returned object * @return Unique StringRef instance, its value will never change @@ -102,7 +102,7 @@ public class StringRef { public String toString() { if (!resolved) { if (resources == null || packageName == null) { - var context = Utils.getContext(); + Context context = Utils.getContext(); resources = context.getResources(); packageName = context.getPackageName(); } diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java index cf65db8a4c..aed89670ce 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java @@ -1,21 +1,13 @@ package app.revanced.extension.shared; import android.annotation.SuppressLint; -import android.app.Activity; -import android.app.Dialog; -import android.app.DialogFragment; -import android.content.ClipData; -import android.content.ClipboardManager; +import android.app.*; import android.content.Context; import android.content.Intent; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.res.Configuration; import android.content.res.Resources; -import android.graphics.Color; import android.net.ConnectivityManager; -import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -23,70 +15,38 @@ import android.os.Looper; import android.preference.Preference; import android.preference.PreferenceGroup; import android.preference.PreferenceScreen; -import android.util.Pair; -import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; -import android.view.Window; -import android.view.WindowManager; import android.view.animation.Animation; import android.view.animation.AnimationUtils; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; import android.widget.Toast; +import android.widget.Toolbar; -import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import java.text.Bidi; -import java.text.Collator; -import java.text.Normalizer; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; +import java.util.*; +import java.util.regex.Pattern; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import java.util.regex.Pattern; -import app.revanced.extension.shared.settings.AppLanguage; -import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BooleanSetting; import app.revanced.extension.shared.settings.preference.ReVancedAboutPreference; -import app.revanced.extension.shared.ui.Dim; public class Utils { @SuppressLint("StaticFieldLeak") - static volatile Context context; + private static Context context; private static String versionName; - private static String applicationLabel; - - @ColorInt - private static int darkColor = Color.BLACK; - @ColorInt - private static int lightColor = Color.WHITE; - - @Nullable - private static Boolean isDarkModeEnabled; - - private static boolean appIsUsingBoldIcons; - - // Cached Collator instance with its locale. - @Nullable - private static Locale cachedCollatorLocale; - @Nullable - private static Collator cachedCollator; - - private static final Pattern PUNCTUATION_PATTERN = Pattern.compile("\\p{P}+"); - private static final Pattern DIACRITICS_PATTERN = Pattern.compile("\\p{M}"); private Utils() { } // utility class @@ -101,30 +61,28 @@ public class Utils { return ""; // Value is replaced during patching. } - private static PackageInfo getPackageInfo() throws PackageManager.NameNotFoundException { - final var packageName = Objects.requireNonNull(getContext()).getPackageName(); - - PackageManager packageManager = context.getPackageManager(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - return packageManager.getPackageInfo( - packageName, - PackageManager.PackageInfoFlags.of(0) - ); - } - - return packageManager.getPackageInfo( - packageName, - 0 - ); - } - /** - * @return The version name of the app, such as 20.13.41 + * @return The version name of the app, such as 19.11.43 */ public static String getAppVersionName() { if (versionName == null) { try { - versionName = getPackageInfo().versionName; + final var packageName = Objects.requireNonNull(getContext()).getPackageName(); + + PackageManager packageManager = context.getPackageManager(); + PackageInfo packageInfo; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + packageInfo = packageManager.getPackageInfo( + packageName, + PackageManager.PackageInfoFlags.of(0) + ); + } else { + packageInfo = packageManager.getPackageInfo( + packageName, + 0 + ); + } + versionName = packageInfo.versionName; } catch (Exception ex) { Logger.printException(() -> "Failed to get package info", ex); versionName = "Unknown"; @@ -134,30 +92,16 @@ public class Utils { return versionName; } - @SuppressWarnings("unused") - public static String getApplicationName() { - if (applicationLabel == null) { - try { - ApplicationInfo applicationInfo = getPackageInfo().applicationInfo; - applicationLabel = (String) applicationInfo.loadLabel(context.getPackageManager()); - } catch (Exception ex) { - Logger.printException(() -> "Failed to get application name", ex); - applicationLabel = "Unknown"; - } - } - - return applicationLabel; - } /** * Hide a view by setting its layout height and width to 1dp. * - * @param setting The setting to check for hiding the view. + * @param condition The setting to check for hiding the view. * @param view The view to hide. */ - public static void hideViewBy0dpUnderCondition(BooleanSetting setting, View view) { - if (hideViewBy0dpUnderCondition(setting.get(), view)) { - Logger.printDebug(() -> "View hidden by setting: " + setting); + public static void hideViewBy0dpUnderCondition(BooleanSetting condition, View view) { + if (hideViewBy0dpUnderCondition(condition.get(), view)) { + Logger.printDebug(() -> "View hidden by setting: " + condition); } } @@ -169,7 +113,7 @@ public class Utils { */ public static boolean hideViewBy0dpUnderCondition(boolean condition, View view) { if (condition) { - hideViewBy0dp(view); + hideViewByLayoutParams(view); return true; } @@ -177,33 +121,19 @@ public class Utils { } /** - * Hide a view by setting its layout params to 0x0 - * @param view The view to hide. - */ - public static void hideViewBy0dp(View view) { - ViewGroup.LayoutParams params = view.getLayoutParams(); - if (params == null) - params = new ViewGroup.LayoutParams(0, 0); - - params.width = 0; - params.height = 0; - view.setLayoutParams(params); - } - - /** - * Hide a view by setting its visibility as GONE. + * Hide a view by setting its visibility to GONE. * - * @param setting The setting to check for hiding the view. + * @param condition The setting to check for hiding the view. * @param view The view to hide. */ - public static void hideViewUnderCondition(BooleanSetting setting, View view) { - if (hideViewUnderCondition(setting.get(), view)) { - Logger.printDebug(() -> "View hidden by setting: " + setting); + public static void hideViewUnderCondition(BooleanSetting condition, View view) { + if (hideViewUnderCondition(condition.get(), view)) { + Logger.printDebug(() -> "View hidden by setting: " + condition); } } /** - * Hide a view by setting its visibility as GONE. + * Hide a view by setting its visibility to GONE. * * @param condition The setting to check for hiding the view. * @param view The view to hide. @@ -217,17 +147,17 @@ public class Utils { return false; } - public static void hideViewByRemovingFromParentUnderCondition(BooleanSetting setting, View view) { - if (hideViewByRemovingFromParentUnderCondition(setting.get(), view)) { - Logger.printDebug(() -> "View hidden by setting: " + setting); + public static void hideViewByRemovingFromParentUnderCondition(BooleanSetting condition, View view) { + if (hideViewByRemovingFromParentUnderCondition(condition.get(), view)) { + Logger.printDebug(() -> "View hidden by setting: " + condition); } } - public static boolean hideViewByRemovingFromParentUnderCondition(boolean condition, View view) { - if (condition) { + public static boolean hideViewByRemovingFromParentUnderCondition(boolean setting, View view) { + if (setting) { ViewParent parent = view.getParent(); - if (parent instanceof ViewGroup parentGroup) { - parentGroup.removeView(view); + if (parent instanceof ViewGroup) { + ((ViewGroup) parent).removeView(view); return true; } } @@ -240,22 +170,23 @@ public class Utils { * All tasks run at max thread priority. */ private static final ThreadPoolExecutor backgroundThreadPool = new ThreadPoolExecutor( - 3, // 3 threads always ready to go. + 3, // 3 threads always ready to go Integer.MAX_VALUE, - 10, // For any threads over the minimum, keep them alive 10 seconds after they go idle. + 10, // For any threads over the minimum, keep them alive 10 seconds after they go idle TimeUnit.SECONDS, new SynchronousQueue<>(), r -> { // ThreadFactory Thread t = new Thread(r); - t.setPriority(Thread.MAX_PRIORITY); // Run at max priority. + t.setPriority(Thread.MAX_PRIORITY); // run at max priority return t; }); - public static void runOnBackgroundThread(Runnable task) { + public static void runOnBackgroundThread(@NonNull Runnable task) { backgroundThreadPool.execute(task); } - public static Future submitOnBackgroundThread(Callable call) { + @NonNull + public static Future submitOnBackgroundThread(@NonNull Callable call) { return backgroundThreadPool.submit(call); } @@ -270,92 +201,64 @@ public class Utils { long meaninglessValue = 0; while (System.currentTimeMillis() - timeCalculationStarted < amountOfTimeToWaste) { - // Could do a thread sleep, but that will trigger an exception if the thread is interrupted. + // could do a thread sleep, but that will trigger an exception if the thread is interrupted meaninglessValue += Long.numberOfLeadingZeros((long) Math.exp(Math.random())); } - // Return the value, otherwise the compiler or VM might optimize and remove the meaningless time-wasting work, - // leaving an empty loop that hammers on the System.currentTimeMillis native call. + // return the value, otherwise the compiler or VM might optimize and remove the meaningless time wasting work, + // leaving an empty loop that hammers on the System.currentTimeMillis native call return meaninglessValue; } - public static boolean containsAny(String value, String... targets) { + + public static boolean containsAny(@NonNull String value, @NonNull String... targets) { return indexOfFirstFound(value, targets) >= 0; } - public static int indexOfFirstFound(String value, String... targets) { - if (isNotEmpty(value)) { - for (String string : targets) { - if (!string.isEmpty()) { - final int indexOf = value.indexOf(string); - if (indexOf >= 0) return indexOf; - } + public static int indexOfFirstFound(@NonNull String value, @NonNull String... targets) { + for (String string : targets) { + if (!string.isEmpty()) { + final int indexOf = value.indexOf(string); + if (indexOf >= 0) return indexOf; } } return -1; } /** - * @return zero, if the resource is not found. + * @return zero, if the resource is not found */ @SuppressLint("DiscouragedApi") - public static int getResourceIdentifier(Context context, @Nullable ResourceType type, String resourceIdentifierName) { - return context.getResources().getIdentifier(resourceIdentifierName, - type == null ? null : type.value, context.getPackageName()); - } - - public static int getResourceIdentifierOrThrow(Context context, @Nullable ResourceType type, String resourceIdentifierName) { - final int resourceId = getResourceIdentifier(context, type, resourceIdentifierName); - if (resourceId == 0) { - throw new Resources.NotFoundException("No resource id exists with name: " + resourceIdentifierName - + " type: " + type); - } - return resourceId; + public static int getResourceIdentifier(@NonNull Context context, @NonNull String resourceIdentifierName, @NonNull String type) { + return context.getResources().getIdentifier(resourceIdentifierName, type, context.getPackageName()); } /** - * @return zero, if the resource is not found. - * @see #getResourceIdentifierOrThrow(ResourceType, String) + * @return zero, if the resource is not found */ - public static int getResourceIdentifier(@Nullable ResourceType type, String resourceIdentifierName) { - return getResourceIdentifier(getContext(), type, resourceIdentifierName); + public static int getResourceIdentifier(@NonNull String resourceIdentifierName, @NonNull String type) { + return getResourceIdentifier(getContext(), resourceIdentifierName, type); } - /** - * @return zero, if the resource is not found. - * @see #getResourceIdentifier(ResourceType, String) - */ - public static int getResourceIdentifierOrThrow(@Nullable ResourceType type, String resourceIdentifierName) { - return getResourceIdentifierOrThrow(getContext(), type, resourceIdentifierName); + public static int getResourceInteger(@NonNull String resourceIdentifierName) throws Resources.NotFoundException { + return getContext().getResources().getInteger(getResourceIdentifier(resourceIdentifierName, "integer")); } - public static String getResourceString(int id) throws Resources.NotFoundException { - return getContext().getResources().getString(id); + @NonNull + public static Animation getResourceAnimation(@NonNull String resourceIdentifierName) throws Resources.NotFoundException { + return AnimationUtils.loadAnimation(getContext(), getResourceIdentifier(resourceIdentifierName, "anim")); } - public static int getResourceInteger(String resourceIdentifierName) throws Resources.NotFoundException { - return getContext().getResources().getInteger(getResourceIdentifierOrThrow(ResourceType.INTEGER, resourceIdentifierName)); - } - - public static Animation getResourceAnimation(String resourceIdentifierName) throws Resources.NotFoundException { - return AnimationUtils.loadAnimation(getContext(), getResourceIdentifierOrThrow(ResourceType.ANIM, resourceIdentifierName)); - } - - @ColorInt - public static int getResourceColor(String resourceIdentifierName) throws Resources.NotFoundException { + public static int getResourceColor(@NonNull String resourceIdentifierName) throws Resources.NotFoundException { //noinspection deprecation - return getContext().getResources().getColor(getResourceIdentifierOrThrow(ResourceType.COLOR, resourceIdentifierName)); + return getContext().getResources().getColor(getResourceIdentifier(resourceIdentifierName, "color")); } - public static int getResourceDimensionPixelSize(String resourceIdentifierName) throws Resources.NotFoundException { - return getContext().getResources().getDimensionPixelSize(getResourceIdentifierOrThrow(ResourceType.DIMEN, resourceIdentifierName)); + public static int getResourceDimensionPixelSize(@NonNull String resourceIdentifierName) throws Resources.NotFoundException { + return getContext().getResources().getDimensionPixelSize(getResourceIdentifier(resourceIdentifierName, "dimen")); } - public static float getResourceDimension(String resourceIdentifierName) throws Resources.NotFoundException { - return getContext().getResources().getDimension(getResourceIdentifierOrThrow(ResourceType.DIMEN, resourceIdentifierName)); - } - - public static String[] getResourceStringArray(String resourceIdentifierName) throws Resources.NotFoundException { - return getContext().getResources().getStringArray(getResourceIdentifierOrThrow(ResourceType.ARRAY, resourceIdentifierName)); + public static float getResourceDimension(@NonNull String resourceIdentifierName) throws Resources.NotFoundException { + return getContext().getResources().getDimension(getResourceIdentifier(resourceIdentifierName, "dimen")); } public interface MatchFilter { @@ -364,11 +267,16 @@ public class Utils { /** * Includes sub children. + * + * @noinspection unchecked */ - public static R getChildViewByResourceName(View view, String str) { - var child = view.findViewById(Utils.getResourceIdentifierOrThrow(ResourceType.ID, str)); - //noinspection unchecked - return (R) child; + public static R getChildViewByResourceName(@NonNull View view, @NonNull String str) { + var child = view.findViewById(Utils.getResourceIdentifier(str, "id")); + if (child != null) { + return (R) child; + } + + throw new IllegalArgumentException("View with resource name '" + str + "' not found"); } /** @@ -377,8 +285,8 @@ public class Utils { * @return The first child view that matches the filter. */ @Nullable - public static T getChildView(ViewGroup viewGroup, boolean searchRecursively, - MatchFilter filter) { + public static T getChildView(@NonNull ViewGroup viewGroup, boolean searchRecursively, + @NonNull MatchFilter filter) { for (int i = 0, childCount = viewGroup.getChildCount(); i < childCount; i++) { View childAt = viewGroup.getChildAt(i); @@ -397,7 +305,7 @@ public class Utils { } @Nullable - public static ViewParent getParentView(View view, int nthParent) { + public static ViewParent getParentView(@NonNull View view, int nthParent) { ViewParent parent = view.getParent(); int currentDepth = 0; @@ -415,9 +323,9 @@ public class Utils { return null; } - public static void restartApp(Context context) { + public static void restartApp(@NonNull Context context) { String packageName = context.getPackageName(); - Intent intent = Objects.requireNonNull(context.getPackageManager().getLaunchIntentForPackage(packageName)); + Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName); Intent mainIntent = Intent.makeRestartActivityTask(intent.getComponent()); // Required for API 34 and later // Ref: https://developer.android.com/about/versions/14/behavior-changes-14#safer-intents @@ -428,43 +336,31 @@ public class Utils { public static Context getContext() { if (context == null) { - Logger.printException(() -> "Context is not set by extension hook, returning null", null); + Logger.initializationException(Utils.class, "Context is null, returning null!", null); } return context; } public static void setContext(Context appContext) { - // Intentionally use logger before context is set, - // to expose any bugs in the 'no context available' logger code. - Logger.printInfo(() -> "Set context: " + appContext); - // Must initially set context to check the app language. context = appContext; - - AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get(); - if (language != AppLanguage.DEFAULT) { - // Create a new context with the desired language. - Logger.printDebug(() -> "Using app language: " + language); - Configuration config = new Configuration(appContext.getResources().getConfiguration()); - config.setLocale(language.getLocale()); - context = appContext.createConfigurationContext(config); - } - - setThemeLightColor(getThemeColor(getThemeLightColorResourceName(), Color.WHITE)); - setThemeDarkColor(getThemeColor(getThemeDarkColorResourceName(), Color.BLACK)); + // In some apps like TikTok, the Setting classes can load in weird orders due to cyclic class dependencies. + // Calling the regular printDebug method here can cause a Settings context null pointer exception, + // even though the context is already set before the call. + // + // The initialization logger methods do not directly or indirectly + // reference the Context or any Settings and are unaffected by this problem. + // + // Info level also helps debug if a patch hook is called before + // the context is set since debug logging is off by default. + Logger.initializationInfo(Utils.class, "Set context: " + appContext); } - public static void setClipboard(CharSequence text) { - ClipboardManager clipboard = (ClipboardManager) context - .getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText("ReVanced", text); + public static void setClipboard(@NonNull String text) { + android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + android.content.ClipData clip = android.content.ClipData.newPlainText("ReVanced", text); clipboard.setPrimaryClip(clip); } - public static boolean isNotEmpty(@Nullable String str) { - return str != null && !str.isEmpty(); - } - - @SuppressWarnings("unused") public static boolean isTablet() { return context.getResources().getConfiguration().smallestScreenWidthDp >= 600; } @@ -473,54 +369,22 @@ public class Utils { private static Boolean isRightToLeftTextLayout; /** - * @return If the device language uses right to left text layout (Hebrew, Arabic, etc.). - * If this should match any ReVanced language override then instead use - * {@link #isRightToLeftLocale(Locale)} with {@link BaseSettings#REVANCED_LANGUAGE}. - * This is the default locale of the device, which may differ if - * {@link BaseSettings#REVANCED_LANGUAGE} is set to a different language. + * If the device language uses right to left text layout (hebrew, arabic, etc) */ - public static boolean isRightToLeftLocale() { + public static boolean isRightToLeftTextLayout() { if (isRightToLeftTextLayout == null) { - isRightToLeftTextLayout = isRightToLeftLocale(Locale.getDefault()); + String displayLanguage = Locale.getDefault().getDisplayLanguage(); + isRightToLeftTextLayout = new Bidi(displayLanguage, Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT).isRightToLeft(); } return isRightToLeftTextLayout; } - /** - * @return If the locale uses right to left text layout (Hebrew, Arabic, etc.). - */ - public static boolean isRightToLeftLocale(Locale locale) { - String displayLanguage = locale.getDisplayLanguage(); - return new Bidi(displayLanguage, Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT).isRightToLeft(); - } - - /** - * @return A UTF8 string containing a left-to-right or right-to-left - * character of the device locale. If this should match any ReVanced language - * override then instead use {@link #getTextDirectionString(Locale)} with - * {@link BaseSettings#REVANCED_LANGUAGE}. - */ - public static String getTextDirectionString() { - return getTextDirectionString(isRightToLeftLocale()); - } - - @SuppressWarnings("unused") - public static String getTextDirectionString(Locale locale) { - return getTextDirectionString(isRightToLeftLocale(locale)); - } - - private static String getTextDirectionString(boolean isRightToLeft) { - return isRightToLeft - ? "\u200F" // u200F = right to left character. - : "\u200E"; // u200E = left to right character. - } - /** * @return if the text contains at least 1 number character, - * including any Unicode numbers such as Arabic. + * including any unicode numbers such as Arabic. */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") - public static boolean containsNumber(CharSequence text) { + public static boolean containsNumber(@NonNull CharSequence text) { for (int index = 0, length = text.length(); index < length;) { final int codePoint = Character.codePointAt(text, index); if (Character.isDigit(codePoint)) { @@ -559,7 +423,7 @@ public class Utils { super.onStart(); if (onStartAction != null) { - onStartAction.onStart(dialog); + onStartAction.onStart((AlertDialog) getDialog()); } } catch (Exception ex) { Logger.printException(() -> "onStart failure: " + dialog.getClass().getSimpleName(), ex); @@ -568,34 +432,34 @@ public class Utils { } /** - * Interface for {@link #showDialog(Activity, Dialog, boolean, DialogFragmentOnStartAction)}. + * Interface for {@link #showDialog(Activity, AlertDialog, boolean, DialogFragmentOnStartAction)}. */ @FunctionalInterface public interface DialogFragmentOnStartAction { - void onStart(Dialog dialog); + void onStart(AlertDialog dialog); } - public static void showDialog(Activity activity, Dialog dialog) { + public static void showDialog(Activity activity, AlertDialog dialog) { showDialog(activity, dialog, true, null); } /** - * Utility method to allow showing a Dialog on top of other dialogs. + * Utility method to allow showing an AlertDialog on top of other alert dialogs. * Calling this will always display the dialog on top of all other dialogs * previously called using this method. - *

+ *
* Be aware the on start action can be called multiple times for some situations, * such as the user switching apps without dismissing the dialog then switching back to this app. - *

+ *
* This method is only useful during app startup and multiple patches may show their own dialog, * and the most important dialog can be called last (using a delay) so it's always on top. - *

+ *
* For all other situations it's better to not use this method and - * call {@link Dialog#show()} on the dialog. + * call {@link AlertDialog#show()} on the dialog. */ @SuppressWarnings("deprecation") public static void showDialog(Activity activity, - Dialog dialog, + AlertDialog dialog, boolean isCancelable, @Nullable DialogFragmentOnStartAction onStartAction) { verifyOnMainThread(); @@ -609,65 +473,30 @@ public class Utils { } /** - * Safe to call from any thread. + * Safe to call from any thread */ - public static void showToastShort(String messageToToast) { + public static void showToastShort(@NonNull String messageToToast) { showToast(messageToToast, Toast.LENGTH_SHORT); } /** - * Safe to call from any thread. + * Safe to call from any thread */ - public static void showToastLong(String messageToToast) { + public static void showToastLong(@NonNull String messageToToast) { showToast(messageToToast, Toast.LENGTH_LONG); } - /** - * Safe to call from any thread. - * - * @param messageToToast Message to show. - * @param toastDuration Either {@link Toast#LENGTH_SHORT} or {@link Toast#LENGTH_LONG}. - */ - public static void showToast(String messageToToast, int toastDuration) { + private static void showToast(@NonNull String messageToToast, int toastDuration) { Objects.requireNonNull(messageToToast); runOnMainThreadNowOrLater(() -> { - Context currentContext = context; - - if (currentContext == null) { - Logger.printException(() -> "Cannot show toast (context is null): " + messageToToast); - } else { - Logger.printDebug(() -> "Showing toast: " + messageToToast); - Toast.makeText(currentContext, messageToToast, toastDuration).show(); - } - }); - } - - /** - * @return The current dark mode as set by any patch. - * Or if none is set, then the system dark mode status is returned. - */ - public static boolean isDarkModeEnabled() { - Boolean isDarkMode = isDarkModeEnabled; - if (isDarkMode != null) { - return isDarkMode; - } - - Configuration config = Resources.getSystem().getConfiguration(); - final int currentNightMode = config.uiMode & Configuration.UI_MODE_NIGHT_MASK; - return currentNightMode == Configuration.UI_MODE_NIGHT_YES; - } - - /** - * Overrides dark mode status as returned by {@link #isDarkModeEnabled()}. - */ - public static void setIsDarkModeEnabled(boolean isDarkMode) { - isDarkModeEnabled = isDarkMode; - Logger.printDebug(() -> "Dark mode status: " + isDarkMode); - } - - public static boolean isLandscapeOrientation() { - final int orientation = Resources.getSystem().getConfiguration().orientation; - return orientation == Configuration.ORIENTATION_LANDSCAPE; + if (context == null) { + Logger.initializationException(Utils.class, "Cannot show toast (context is null): " + messageToToast, null); + } else { + Logger.printDebug(() -> "Showing toast: " + messageToToast); + Toast.makeText(context, messageToToast, toastDuration).show(); + } + } + ); } /** @@ -675,14 +504,14 @@ public class Utils { * * @see #runOnMainThreadNowOrLater(Runnable) */ - public static void runOnMainThread(Runnable runnable) { + public static void runOnMainThread(@NonNull Runnable runnable) { runOnMainThreadDelayed(runnable, 0); } /** - * Automatically logs any exceptions the runnable throws. + * Automatically logs any exceptions the runnable throws */ - public static void runOnMainThreadDelayed(Runnable runnable, long delayMillis) { + public static void runOnMainThreadDelayed(@NonNull Runnable runnable, long delayMillis) { Runnable loggingRunnable = () -> { try { runnable.run(); @@ -694,10 +523,10 @@ public class Utils { } /** - * If called from the main thread, the code is run immediately. + * If called from the main thread, the code is run immediately.

* If called off the main thread, this is the same as {@link #runOnMainThread(Runnable)}. */ - public static void runOnMainThreadNowOrLater(Runnable runnable) { + public static void runOnMainThreadNowOrLater(@NonNull Runnable runnable) { if (isCurrentlyOnMainThread()) { runnable.run(); } else { @@ -706,14 +535,14 @@ public class Utils { } /** - * @return if the calling thread is on the main thread. + * @return if the calling thread is on the main thread */ public static boolean isCurrentlyOnMainThread() { return Looper.getMainLooper().isCurrentThread(); } /** - * @throws IllegalStateException if the calling thread is _off_ the main thread. + * @throws IllegalStateException if the calling thread is _off_ the main thread */ public static void verifyOnMainThread() throws IllegalStateException { if (!isCurrentlyOnMainThread()) { @@ -722,7 +551,7 @@ public class Utils { } /** - * @throws IllegalStateException if the calling thread is _on_ the main thread. + * @throws IllegalStateException if the calling thread is _on_ the main thread */ public static void verifyOffMainThread() throws IllegalStateException { if (isCurrentlyOnMainThread()) { @@ -730,41 +559,19 @@ public class Utils { } } - public static void openLink(String url) { - try { - Intent intent = new Intent("android.intent.action.VIEW", Uri.parse(url)); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - Logger.printInfo(() -> "Opening link with external browser: " + intent); - getContext().startActivity(intent); - } catch (Exception ex) { - Logger.printException(() -> "openLink failure", ex); - } - } - public enum NetworkType { NONE, MOBILE, OTHER, } - /** - * Calling extension code must ensure the un-patched app has the permission - * android.permission.ACCESS_NETWORK_STATE, - * otherwise the app will crash if this method is used. - */ public static boolean isNetworkConnected() { NetworkType networkType = getNetworkType(); return networkType == NetworkType.MOBILE || networkType == NetworkType.OTHER; } - /** - * Calling extension code must ensure the un-patched app has the permission - * android.permission.ACCESS_NETWORK_STATE, - * otherwise the app will crash if this method is used. - */ - @SuppressWarnings({"MissingPermission", "deprecation"}) + @SuppressLint("MissingPermission") // permission already included in YouTube public static NetworkType getNetworkType() { Context networkContext = getContext(); if (networkContext == null) { @@ -782,188 +589,31 @@ public class Utils { } /** - * Hides a view by setting its layout width and height to 0dp. - * Handles null layout params safely. - * - * @param view The view to hide. If null, does nothing. + * Hide a view by setting its layout params to 0x0 + * @param view The view to hide. */ - public static void hideViewByLayoutParams(@Nullable View view) { - if (view == null) return; - - ViewGroup.LayoutParams params = view.getLayoutParams(); - - if (params == null) { - // Create generic 0x0 layout params accepted by all ViewGroups. - params = new ViewGroup.LayoutParams(0, 0); + public static void hideViewByLayoutParams(View view) { + if (view instanceof LinearLayout) { + LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(0, 0); + view.setLayoutParams(layoutParams); + } else if (view instanceof FrameLayout) { + FrameLayout.LayoutParams layoutParams2 = new FrameLayout.LayoutParams(0, 0); + view.setLayoutParams(layoutParams2); + } else if (view instanceof RelativeLayout) { + RelativeLayout.LayoutParams layoutParams3 = new RelativeLayout.LayoutParams(0, 0); + view.setLayoutParams(layoutParams3); + } else if (view instanceof Toolbar) { + Toolbar.LayoutParams layoutParams4 = new Toolbar.LayoutParams(0, 0); + view.setLayoutParams(layoutParams4); + } else if (view instanceof ViewGroup) { + ViewGroup.LayoutParams layoutParams5 = new ViewGroup.LayoutParams(0, 0); + view.setLayoutParams(layoutParams5); } else { + ViewGroup.LayoutParams params = view.getLayoutParams(); params.width = 0; params.height = 0; + view.setLayoutParams(params); } - - view.setLayoutParams(params); - } - - /** - * Configures the parameters of a dialog window, including its width, gravity, vertical offset and background dimming. - * The width is calculated as a percentage of the screen's portrait width and the vertical offset is specified in DIP. - * The default dialog background is removed to allow for custom styling. - * - * @param window The {@link Window} object to configure. - * @param gravity The gravity for positioning the dialog (e.g., {@link Gravity#BOTTOM}). - * @param yOffsetDip The vertical offset from the gravity position in DIP. - * @param widthPercentage The width of the dialog as a percentage of the screen's portrait width (0-100). - * @param dimAmount If true, sets the background dim amount to 0 (no dimming); if false, leaves the default dim amount. - */ - public static void setDialogWindowParameters(Window window, int gravity, int yOffsetDip, int widthPercentage, boolean dimAmount) { - WindowManager.LayoutParams params = window.getAttributes(); - - params.width = Dim.pctPortraitWidth(widthPercentage); - params.height = WindowManager.LayoutParams.WRAP_CONTENT; - params.gravity = gravity; - params.y = yOffsetDip > 0 ? Dim.dp(yOffsetDip) : 0; - if (dimAmount) { - params.dimAmount = 0f; - } - - window.setAttributes(params); // Apply window attributes. - window.setBackgroundDrawable(null); // Remove default dialog background - } - - /** - * @return If the unpatched app is currently using bold icons. - */ - public static boolean appIsUsingBoldIcons() { - return appIsUsingBoldIcons; - } - - /** - * Controls if ReVanced bold icons are shown in various places. - * @param boldIcons If the app is currently using bold icons. - */ - public static void setAppIsUsingBoldIcons(boolean boldIcons) { - appIsUsingBoldIcons = boldIcons; - } - - /** - * Sets the theme light color used by the app. - */ - public static void setThemeLightColor(@ColorInt int color) { - Logger.printDebug(() -> "Setting theme light color: " + getColorHexString(color)); - lightColor = color; - } - - /** - * Sets the theme dark used by the app. - */ - public static void setThemeDarkColor(@ColorInt int color) { - Logger.printDebug(() -> "Setting theme dark color: " + getColorHexString(color)); - darkColor = color; - } - - /** - * Returns the themed light color, or {@link Color#WHITE} if no theme was set using - * {@link #setThemeLightColor(int). - */ - @ColorInt - public static int getThemeLightColor() { - return lightColor; - } - - /** - * Returns the themed dark color, or {@link Color#BLACK} if no theme was set using - * {@link #setThemeDarkColor(int)}. - */ - @ColorInt - public static int getThemeDarkColor() { - return darkColor; - } - - /** - * Injection point. - */ - @SuppressWarnings("SameReturnValue") - private static String getThemeLightColorResourceName() { - // Value is changed by Settings patch. - return "#FFFFFFFF"; - } - - /** - * Injection point. - */ - @SuppressWarnings("SameReturnValue") - private static String getThemeDarkColorResourceName() { - // Value is changed by Settings patch. - return "#FF000000"; - } - - @ColorInt - private static int getThemeColor(String resourceName, int defaultColor) { - try { - return getColorFromString(resourceName); - } catch (Exception ex) { - // This code can never be reached since a bad custom color will - // fail during resource compilation. So no localized strings are needed here. - Logger.printException(() -> "Invalid custom theme color: " + resourceName, ex); - return defaultColor; - } - } - - - @ColorInt - public static int getDialogBackgroundColor() { - if (isDarkModeEnabled()) { - final int darkColor = getThemeDarkColor(); - return darkColor == Color.BLACK - // Lighten the background a little if using AMOLED dark theme - // as the dialogs are almost invisible. - ? 0xFF080808 // 3% - : darkColor; - } - return getThemeLightColor(); - } - - /** - * @return The current app background color. - */ - @ColorInt - public static int getAppBackgroundColor() { - return isDarkModeEnabled() ? getThemeDarkColor() : getThemeLightColor(); - } - - /** - * @return The current app foreground color. - */ - @ColorInt - public static int getAppForegroundColor() { - return isDarkModeEnabled() - ? getThemeLightColor() - : getThemeDarkColor(); - } - - @ColorInt - public static int getOkButtonBackgroundColor() { - return isDarkModeEnabled() - // Must be inverted color. - ? Color.WHITE - : Color.BLACK; - } - - @ColorInt - public static int getCancelOrNeutralButtonBackgroundColor() { - return isDarkModeEnabled() - ? adjustColorBrightness(getDialogBackgroundColor(), 1.10f) - : adjustColorBrightness(getThemeLightColor(), 0.95f); - } - - @ColorInt - public static int getEditTextBackground() { - return isDarkModeEnabled() - ? adjustColorBrightness(getDialogBackgroundColor(), 1.05f) - : adjustColorBrightness(getThemeLightColor(), 0.97f); - } - - public static String getColorHexString(@ColorInt int color) { - return String.format("#%06X", (0x00FFFFFF & color)); } /** @@ -991,7 +641,8 @@ public class Utils { this.keySuffix = keySuffix; } - static Sort fromKey(@Nullable String key, Sort defaultSort) { + @NonNull + static Sort fromKey(@Nullable String key, @NonNull Sort defaultSort) { if (key != null) { for (Sort sort : values()) { if (key.endsWith(sort.keySuffix)) { @@ -1003,66 +654,35 @@ public class Utils { } } + private static final Pattern punctuationPattern = Pattern.compile("\\p{P}+"); + /** - * Removes punctuation and converts text to lowercase. Returns an empty string if input is null. + * Strips all punctuation and converts to lower case. A null parameter returns an empty string. */ - public static String removePunctuationToLowercase(@Nullable CharSequence original) { + public static String removePunctuationConvertToLowercase(@Nullable CharSequence original) { if (original == null) return ""; - return PUNCTUATION_PATTERN.matcher(original).replaceAll("") - .toLowerCase(BaseSettings.REVANCED_LANGUAGE.get().getLocale()); + return punctuationPattern.matcher(original).replaceAll("").toLowerCase(); } /** - * Normalizes text for search: applies NFD, removes diacritics, and lowercases (locale-neutral). - * Returns an empty string if input is null. - */ - public static String normalizeTextToLowercase(@Nullable CharSequence original) { - if (original == null) return ""; - return DIACRITICS_PATTERN.matcher(Normalizer.normalize(original, Normalizer.Form.NFD)) - .replaceAll("").toLowerCase(Locale.ROOT); - } - - /** - * Returns a cached Collator for the current locale, or creates a new one if locale changed. - */ - private static Collator getCollator() { - Locale currentLocale = BaseSettings.REVANCED_LANGUAGE.get().getLocale(); - - if (cachedCollator == null || !currentLocale.equals(cachedCollatorLocale)) { - cachedCollatorLocale = currentLocale; - cachedCollator = Collator.getInstance(currentLocale); - cachedCollator.setStrength(Collator.SECONDARY); // Case-insensitive, diacritic-insensitive. - } - - return cachedCollator; - } - - /** - * Sorts a {@link PreferenceGroup} and all nested subgroups by title or key. - *

- * The sort order is controlled by the {@link Sort} suffix present in the preference key. - * Preferences without a key or without a {@link Sort} suffix remain in their original order. - *

- * Sorting is performed using {@link Collator} with the current user locale, - * ensuring correct alphabetical ordering for all supported languages - * (e.g., Ukrainian "і", German "ß", French accented characters, etc.). + * Sort a PreferenceGroup and all it's sub groups by title or key. * - * @param group the {@link PreferenceGroup} to sort + * Sort order is determined by the preferences key {@link Sort} suffix. + * + * If a preference has no key or no {@link Sort} suffix, + * then the preferences are left unsorted. */ @SuppressWarnings("deprecation") - public static void sortPreferenceGroups(PreferenceGroup group) { + public static void sortPreferenceGroups(@NonNull PreferenceGroup group) { Sort groupSort = Sort.fromKey(group.getKey(), Sort.UNSORTED); - List> preferences = new ArrayList<>(); - - // Get cached Collator for locale-aware string comparison. - Collator collator = getCollator(); + SortedMap preferences = new TreeMap<>(); for (int i = 0, prefCount = group.getPreferenceCount(); i < prefCount; i++) { Preference preference = group.getPreference(i); final Sort preferenceSort; - if (preference instanceof PreferenceGroup subGroup) { - sortPreferenceGroups(subGroup); + if (preference instanceof PreferenceGroup) { + sortPreferenceGroups((PreferenceGroup) preference); preferenceSort = groupSort; // Sort value for groups is for it's content, not itself. } else { // Allow individual preferences to set a key sorting. @@ -1073,7 +693,7 @@ public class Utils { final String sortValue; switch (preferenceSort) { case BY_TITLE: - sortValue = removePunctuationToLowercase(preference.getTitle()); + sortValue = removePunctuationConvertToLowercase(preference.getTitle()); break; case BY_KEY: sortValue = preference.getKey(); @@ -1084,23 +704,17 @@ public class Utils { throw new IllegalStateException(); } - preferences.add(new Pair<>(sortValue, preference)); + preferences.put(sortValue, preference); } - // Sort the list using locale-specific collation rules. - Collections.sort(preferences, (pair1, pair2) - -> collator.compare(pair1.first, pair2.first)); - - // Reassign order values to reflect the new sorted sequence int index = 0; - for (Pair pair : preferences) { + for (Preference pref : preferences.values()) { int order = index++; - Preference pref = pair.second; // Move any screens, intents, and the one off About preference to the top. if (pref instanceof PreferenceScreen || pref instanceof ReVancedAboutPreference || pref.getIntent() != null) { - // Any arbitrary large number. + // Arbitrary high number. order -= 1000; } @@ -1112,7 +726,7 @@ public class Utils { * Set all preferences to multiline titles if the device is not using an English variant. * The English strings are heavily scrutinized and all titles fit on screen * except 2 or 3 preference strings and those do not affect readability. - *

+ * * Allowing multiline for those 2 or 3 English preferences looks weird and out of place, * and visually it looks better to clip the text and keep all titles 1 line. */ @@ -1122,8 +736,8 @@ public class Utils { return; } - String revancedLocale = Utils.getContext().getResources().getConfiguration().locale.getLanguage(); - if (revancedLocale.equals(Locale.ENGLISH.getLanguage())) { + String deviceLanguage = Utils.getContext().getResources().getConfiguration().locale.getLanguage(); + if (deviceLanguage.equals("en")) { return; } @@ -1131,92 +745,26 @@ public class Utils { Preference pref = group.getPreference(i); pref.setSingleLineTitle(false); - if (pref instanceof PreferenceGroup subGroup) { - setPreferenceTitlesToMultiLineIfNeeded(subGroup); + if (pref instanceof PreferenceGroup) { + setPreferenceTitlesToMultiLineIfNeeded((PreferenceGroup) pref); } } } /** - * Parse a color resource or hex code to an int representation of the color. - */ - @ColorInt - public static int getColorFromString(String colorString) throws IllegalArgumentException, Resources.NotFoundException { - if (colorString.startsWith("#")) { - return Color.parseColor(colorString); - } - return getResourceColor(colorString); - } - - /** - * Uses {@link #adjustColorBrightness(int, float)} depending on if light or dark mode is active. - */ - @ColorInt - public static int adjustColorBrightness(@ColorInt int baseColor, float lightThemeFactor, float darkThemeFactor) { - return isDarkModeEnabled() - ? adjustColorBrightness(baseColor, darkThemeFactor) - : adjustColorBrightness(baseColor, lightThemeFactor); - } - - /** - * Adjusts the brightness of a color by lightening or darkening it based on the given factor. + * If {@link Fragment} uses [Android library] rather than [AndroidX library], + * the Dialog theme corresponding to [Android library] should be used. *

- * If the factor is greater than 1, the color is lightened by interpolating toward white (#FFFFFF). - * If the factor is less than or equal to 1, the color is darkened by scaling its RGB components toward black (#000000). - * The alpha channel remains unchanged. - * - * @param color The input color to adjust, in ARGB format. - * @param factor The adjustment factor. Use values > 1.0f to lighten (e.g., 1.11f for slight lightening) - * or values <= 1.0f to darken (e.g., 0.95f for slight darkening). - * @return The adjusted color in ARGB format. + * If not, the following issues will occur: + * ReVanced/revanced-patches#3061 + *

+ * To prevent these issues, apply the Dialog theme corresponding to [Android library]. */ - @ColorInt - public static int adjustColorBrightness(@ColorInt int color, float factor) { - final int alpha = Color.alpha(color); - int red = Color.red(color); - int green = Color.green(color); - int blue = Color.blue(color); - - if (factor > 1.0f) { - // Lighten: Interpolate toward white (255). - final float t = 1.0f - (1.0f / factor); // Interpolation parameter. - red = Math.round(red + (255 - red) * t); - green = Math.round(green + (255 - green) * t); - blue = Math.round(blue + (255 - blue) * t); - } else { - // Darken or no change: Scale toward black. - red = Math.round(red * factor); - green = Math.round(green * factor); - blue = Math.round(blue * factor); + public static void setEditTextDialogTheme(AlertDialog.Builder builder) { + final int editTextDialogStyle = getResourceIdentifier( + "revanced_edit_text_dialog_style", "style"); + if (editTextDialogStyle != 0) { + builder.getContext().setTheme(editTextDialogStyle); } - - // Ensure values are within [0, 255]. - red = clamp(red, 0, 255); - green = clamp(green, 0, 255); - blue = clamp(blue, 0, 255); - - return Color.argb(alpha, red, green, blue); - } - - public static int clamp(int value, int lower, int upper) { - return Math.max(lower, Math.min(value, upper)); - } - - public static float clamp(float value, float lower, float upper) { - return Math.max(lower, Math.min(value, upper)); - } - - /** - * @param maxSize The maximum number of elements to keep in the map. - * @return A {@link LinkedHashMap} that automatically evicts the oldest entry - * when the size exceeds {@code maxSize}. - */ - public static Map createSizeRestrictedMap(int maxSize) { - return new LinkedHashMap<>(2 * maxSize) { - @Override - protected boolean removeEldestEntry(Entry eldest) { - return size() > maxSize; - } - }; } } diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/checks/Check.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/checks/Check.java index bde66a043c..6d4db14e57 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/checks/Check.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/checks/Check.java @@ -4,32 +4,23 @@ import static android.text.Html.FROM_HTML_MODE_COMPACT; import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.Utils.DialogFragmentOnStartAction; +import android.annotation.SuppressLint; import android.app.Activity; -import android.app.Dialog; +import android.app.AlertDialog; +import android.content.DialogInterface; import android.content.Intent; -import android.graphics.PorterDuff; import android.net.Uri; -import android.os.Build; import android.text.Html; -import android.util.Pair; -import android.view.Gravity; -import android.view.View; import android.widget.Button; -import android.widget.ImageView; -import android.widget.LinearLayout; import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; import java.util.Collection; import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.ResourceType; import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.settings.BaseSettings; -import app.revanced.extension.shared.ui.CustomDialog; -@RequiresApi(api = Build.VERSION_CODES.N) abstract class Check { private static final int NUMBER_OF_TIMES_TO_IGNORE_WARNING_BEFORE_DISABLING = 2; @@ -78,6 +69,7 @@ abstract class Check { BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.save(Integer.MAX_VALUE); } + @SuppressLint("NewApi") static void issueWarning(Activity activity, Collection failedChecks) { final var reasons = new StringBuilder(); @@ -94,59 +86,38 @@ abstract class Check { ); Utils.runOnMainThreadDelayed(() -> { - // Create the custom dialog. - Pair dialogPair = CustomDialog.create( - activity, - str("revanced_check_environment_failed_title"), // Title. - message, // Message. - null, // No EditText. - str("revanced_check_environment_dialog_open_official_source_button"), // OK button text. - () -> { - // Action for the OK (website) button. - final var intent = new Intent(Intent.ACTION_VIEW, GOOD_SOURCE); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - activity.startActivity(intent); + AlertDialog alert = new AlertDialog.Builder(activity) + .setCancelable(false) + .setIconAttribute(android.R.attr.alertDialogIcon) + .setTitle(str("revanced_check_environment_failed_title")) + .setMessage(message) + .setPositiveButton( + " ", + (dialog, which) -> { + final var intent = new Intent(Intent.ACTION_VIEW, GOOD_SOURCE); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + activity.startActivity(intent); - // Shutdown to prevent the user from navigating back to this app, - // which is no longer showing a warning dialog. - activity.finishAffinity(); - System.exit(0); - }, - null, // No cancel button. - str("revanced_check_environment_dialog_ignore_button"), // Neutral button text. - () -> { - // Neutral button action. - // Cleanup data if the user incorrectly imported a huge negative number. - final int current = Math.max(0, BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.get()); - BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.save(current + 1); - }, - true // Dismiss dialog when onNeutralClick. - ); + // Shutdown to prevent the user from navigating back to this app, + // which is no longer showing a warning dialog. + activity.finishAffinity(); + System.exit(0); + } + ).setNegativeButton( + " ", + (dialog, which) -> { + // Cleanup data if the user incorrectly imported a huge negative number. + final int current = Math.max(0, BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.get()); + BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.save(current + 1); - // Get the dialog and main layout. - Dialog dialog = dialogPair.first; - LinearLayout mainLayout = dialogPair.second; + dialog.dismiss(); + } + ).create(); - // Add icon to the dialog. - ImageView iconView = new ImageView(activity); - iconView.setImageResource(Utils.getResourceIdentifierOrThrow( - ResourceType.DRAWABLE, "revanced_ic_dialog_alert")); - iconView.setColorFilter(Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN); - iconView.setPadding(0, 0, 0, 0); - LinearLayout.LayoutParams iconParams = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.WRAP_CONTENT - ); - iconParams.gravity = Gravity.CENTER; - mainLayout.addView(iconView, 0); // Add icon at the top. - - dialog.setCancelable(false); - - // Show the dialog. - Utils.showDialog(activity, dialog, false, new DialogFragmentOnStartAction() { + Utils.showDialog(activity, alert, false, new DialogFragmentOnStartAction() { boolean hasRun; @Override - public void onStart(Dialog dialog) { + public void onStart(AlertDialog dialog) { // Only run this once, otherwise if the user changes to a different app // then changes back, this handler will run again and disable the buttons. if (hasRun) { @@ -154,43 +125,19 @@ abstract class Check { } hasRun = true; - // Get the button container to access buttons. - LinearLayout buttonContainer = (LinearLayout) mainLayout.getChildAt(mainLayout.getChildCount() - 1); - - Button openWebsiteButton; - Button ignoreButton; - - // Check if buttons are in a single-row layout (buttonContainer has one child: rowContainer). - if (buttonContainer.getChildCount() == 1 - && buttonContainer.getChildAt(0) instanceof LinearLayout rowContainer) { - // Neutral button is the first child (index 0). - ignoreButton = (Button) rowContainer.getChildAt(0); - // OK button is the last child. - openWebsiteButton = (Button) rowContainer.getChildAt(rowContainer.getChildCount() - 1); - } else { - // Multi-row layout: buttons are in separate containers, ordered OK, Cancel, Neutral. - LinearLayout okContainer = - (LinearLayout) buttonContainer.getChildAt(0); // OK is first. - openWebsiteButton = (Button) okContainer.getChildAt(0); - LinearLayout neutralContainer = - (LinearLayout)buttonContainer.getChildAt(buttonContainer.getChildCount() - 1); // Neutral is last. - ignoreButton = (Button) neutralContainer.getChildAt(0); - } - - // Initially set buttons to INVISIBLE and disabled. - openWebsiteButton.setVisibility(View.INVISIBLE); + var openWebsiteButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); openWebsiteButton.setEnabled(false); - ignoreButton.setVisibility(View.INVISIBLE); - ignoreButton.setEnabled(false); - // Start the countdown for showing and enabling buttons. - getCountdownRunnable(ignoreButton, openWebsiteButton).run(); + var dismissButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE); + dismissButton.setEnabled(false); + + getCountdownRunnable(dismissButton, openWebsiteButton).run(); } }); }, 1000); // Use a delay, so this dialog is shown on top of any other startup dialogs. } - private static Runnable getCountdownRunnable(Button ignoreButton, Button openWebsiteButton) { + private static Runnable getCountdownRunnable(Button dismissButton, Button openWebsiteButton) { return new Runnable() { private int secondsRemaining = SECONDS_BEFORE_SHOWING_IGNORE_BUTTON; @@ -199,15 +146,17 @@ abstract class Check { Utils.verifyOnMainThread(); if (secondsRemaining > 0) { - if (secondsRemaining - SECONDS_BEFORE_SHOWING_WEBSITE_BUTTON <= 0) { - openWebsiteButton.setVisibility(View.VISIBLE); + if (secondsRemaining - SECONDS_BEFORE_SHOWING_WEBSITE_BUTTON == 0) { + openWebsiteButton.setText(str("revanced_check_environment_dialog_open_official_source_button")); openWebsiteButton.setEnabled(true); } + secondsRemaining--; + Utils.runOnMainThreadDelayed(this, 1000); } else { - ignoreButton.setVisibility(View.VISIBLE); - ignoreButton.setEnabled(true); + dismissButton.setText(str("revanced_check_environment_dialog_ignore_button")); + dismissButton.setEnabled(true); } } }; diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/checks/CheckEnvironmentPatch.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/checks/CheckEnvironmentPatch.java index e54ab27f74..d63f8b7e3f 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/checks/CheckEnvironmentPatch.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/checks/CheckEnvironmentPatch.java @@ -7,12 +7,8 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Build; import android.util.Base64; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; - - import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Utils; @@ -31,7 +27,6 @@ import static app.revanced.extension.shared.checks.PatchInfo.Build.*; *
* Various indicators help to detect if the app was patched by the user. */ -@RequiresApi(api = Build.VERSION_CODES.N) @SuppressWarnings("unused") public final class CheckEnvironmentPatch { private static final boolean DEBUG_ALWAYS_SHOW_CHECK_FAILED_DIALOG = debugAlwaysShowWarning(); @@ -44,7 +39,6 @@ public final class CheckEnvironmentPatch { ADB((String) null), ROOT_MOUNT_ON_APP_STORE("com.android.vending"), MANAGER("app.revanced.manager.flutter", - "app.revanced.manager.flutter.debug", "app.revanced.manager", "app.revanced.manager.debug"); @@ -124,7 +118,7 @@ public final class CheckEnvironmentPatch { * If the build properties are different, the app was likely downloaded pre-patched or patched on another device. */ private static class CheckWasPatchedOnSameDevice extends Check { - @SuppressLint("HardwareIds") + @SuppressLint({"NewApi", "HardwareIds"}) @Override protected Boolean check() { if (PATCH_BOARD.isEmpty()) { @@ -198,7 +192,7 @@ public final class CheckEnvironmentPatch { PackageManager packageManager = context.getPackageManager(); PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0); - // Duration since initial install or last update, whichever is sooner. + // Duration since initial install or last update, which ever is sooner. durationBetweenPatchingAndInstallation = packageInfo.lastUpdateTime - PatchInfo.PATCH_TIME; Logger.printInfo(() -> "App was installed/updated: " + (durationBetweenPatchingAndInstallation / (60 * 1000) + " minutes after patching")); @@ -294,8 +288,8 @@ public final class CheckEnvironmentPatch { CheckIsNearPatchTime nearPatchTime = new CheckIsNearPatchTime(); Boolean timeCheckPassed = nearPatchTime.check(); if (timeCheckPassed && !DEBUG_ALWAYS_SHOW_CHECK_FAILED_DIALOG) { - // Allow installing recently patched APKs, - // even if the installation source is not Manager or ADB. + // Allow installing recently patched apks, + // even if the install source is not Manager or ADB. Check.disableForever(); return; } else { diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/fixes/redgifs/BaseFixRedgifsApiPatch.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/fixes/redgifs/BaseFixRedgifsApiPatch.java deleted file mode 100644 index 00ee6def3b..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/fixes/redgifs/BaseFixRedgifsApiPatch.java +++ /dev/null @@ -1,70 +0,0 @@ -package app.revanced.extension.shared.fixes.redgifs; - -import androidx.annotation.NonNull; - -import org.json.JSONException; - -import java.io.IOException; -import java.net.HttpURLConnection; - -import app.revanced.extension.shared.Logger; -import okhttp3.Interceptor; -import okhttp3.MediaType; -import okhttp3.Protocol; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.ResponseBody; - -public abstract class BaseFixRedgifsApiPatch implements Interceptor { - protected static BaseFixRedgifsApiPatch INSTANCE; - public abstract String getDefaultUserAgent(); - - @NonNull - @Override - public Response intercept(@NonNull Chain chain) throws IOException { - Request request = chain.request(); - if (!request.url().host().equals("api.redgifs.com")) { - return chain.proceed(request); - } - - String userAgent = getDefaultUserAgent(); - - if (request.header("Authorization") != null) { - Response response = chain.proceed(request.newBuilder().header("User-Agent", userAgent).build()); - if (response.isSuccessful()) { - return response; - } - // It's possible that the user agent is being overwritten later down in the interceptor - // chain, so make sure we grab the new user agent from the request headers. - userAgent = response.request().header("User-Agent"); - response.close(); - } - - try { - RedgifsTokenManager.RedgifsToken token = RedgifsTokenManager.refreshToken(userAgent); - - // Emulate response for old OAuth endpoint - if (request.url().encodedPath().equals("/v2/oauth/client")) { - String responseBody = RedgifsTokenManager.getEmulatedOAuthResponseBody(token); - return new Response.Builder() - .message("OK") - .code(HttpURLConnection.HTTP_OK) - .protocol(Protocol.HTTP_1_1) - .request(request) - .header("Content-Type", "application/json") - .body(ResponseBody.create( - responseBody, MediaType.get("application/json"))) - .build(); - } - - Request modifiedRequest = request.newBuilder() - .header("Authorization", "Bearer " + token.getAccessToken()) - .header("User-Agent", userAgent) - .build(); - return chain.proceed(modifiedRequest); - } catch (JSONException ex) { - Logger.printException(() -> "Could not parse Redgifs response", ex); - throw new IOException(ex); - } - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/fixes/redgifs/RedgifsTokenManager.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/fixes/redgifs/RedgifsTokenManager.java deleted file mode 100644 index 792465a89f..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/fixes/redgifs/RedgifsTokenManager.java +++ /dev/null @@ -1,94 +0,0 @@ -package app.revanced.extension.shared.fixes.redgifs; - -import static app.revanced.extension.shared.requests.Route.Method.GET; - -import androidx.annotation.GuardedBy; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import app.revanced.extension.shared.requests.Requester; - - -/** - * Manages Redgifs token lifecycle. - */ -public class RedgifsTokenManager { - public static class RedgifsToken { - // Expire after 23 hours to provide some breathing room - private static final long EXPIRY_SECONDS = 23 * 60 * 60; - - private final String accessToken; - private final long refreshTimeInSeconds; - - public RedgifsToken(String accessToken, long refreshTime) { - this.accessToken = accessToken; - this.refreshTimeInSeconds = refreshTime; - } - - public String getAccessToken() { - return accessToken; - } - - public long getExpiryTimeInSeconds() { - return refreshTimeInSeconds + EXPIRY_SECONDS; - } - - public boolean isValid() { - if (accessToken == null) return false; - return getExpiryTimeInSeconds() >= System.currentTimeMillis() / 1000; - } - } - public static final String REDGIFS_API_HOST = "https://api.redgifs.com"; - private static final String GET_TEMPORARY_TOKEN = REDGIFS_API_HOST + "/v2/auth/temporary"; - @GuardedBy("itself") - private static final Map tokenMap = new HashMap<>(); - - private static String getToken(String userAgent) throws IOException, JSONException { - HttpURLConnection connection = (HttpURLConnection) new URL(GET_TEMPORARY_TOKEN).openConnection(); - connection.setFixedLengthStreamingMode(0); - connection.setRequestMethod(GET.name()); - connection.setRequestProperty("User-Agent", userAgent); - connection.setRequestProperty("Content-Type", "application/json"); - connection.setRequestProperty("Accept", "application/json"); - connection.setUseCaches(false); - - JSONObject responseObject = Requester.parseJSONObject(connection); - return responseObject.getString("token"); - } - - public static RedgifsToken refreshToken(String userAgent) throws IOException, JSONException { - synchronized(tokenMap) { - // Reference: https://github.com/JeffreyCA/Apollo-ImprovedCustomApi/pull/67 - RedgifsToken token = tokenMap.get(userAgent); - if (token != null && token.isValid()) { - return token; - } - - // Copy user agent from original request if present because Redgifs verifies - // that the user agent in subsequent requests matches the one in the OAuth token. - String accessToken = getToken(userAgent); - long refreshTime = System.currentTimeMillis() / 1000; - token = new RedgifsToken(accessToken, refreshTime); - tokenMap.put(userAgent, token); - return token; - } - } - - public static String getEmulatedOAuthResponseBody(RedgifsToken token) throws JSONException { - // Reference: https://github.com/JeffreyCA/Apollo-ImprovedCustomApi/pull/67 - JSONObject responseObject = new JSONObject(); - responseObject.put("access_token", token.accessToken); - responseObject.put("expiry_time", token.getExpiryTimeInSeconds() - (System.currentTimeMillis() / 1000)); - responseObject.put("scope", "read"); - responseObject.put("token_type", "Bearer"); - return responseObject.toString(); - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/CustomBrandingPatch.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/CustomBrandingPatch.java deleted file mode 100644 index d13513e2df..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/CustomBrandingPatch.java +++ /dev/null @@ -1,227 +0,0 @@ -package app.revanced.extension.shared.patches; - -import android.app.Notification; -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.PackageManager; -import android.graphics.Color; -import android.view.View; - -import androidx.annotation.Nullable; - -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; - -import app.revanced.extension.shared.GmsCoreSupport; -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.ResourceType; -import app.revanced.extension.shared.Utils; -import app.revanced.extension.shared.settings.BaseSettings; - -/** - * Patch shared by YouTube and YT Music. - */ -@SuppressWarnings("unused") -public class CustomBrandingPatch { - - // Important: In the future, additional branding themes can be added but all existing and prior - // themes cannot be removed or renamed. - // - // This is because if a user has a branding theme selected, then only that launch alias is enabled. - // If a future update removes or renames that alias, then after updating the app is effectively - // broken and it cannot be opened and not even clearing the app data will fix it. - // In that situation the only fix is to completely uninstall and reinstall again. - // - // The most that can be done is to hide a theme from the UI and keep the alias with dummy data. - public enum BrandingTheme { - /** - * Original unpatched icon. - */ - ORIGINAL, - ROUNDED, - MINIMAL, - SCALED, - /** - * User provided custom icon. - */ - CUSTOM; - - private String packageAndNameIndexToClassAlias(String packageName, int appIndex) { - if (appIndex <= 0) { - throw new IllegalArgumentException("App index starts at index 1"); - } - return packageName + ".revanced_" + name().toLowerCase(Locale.US) + '_' + appIndex; - } - } - - @Nullable - private static Integer notificationSmallIcon; - - private static int getNotificationSmallIcon() { - // Cannot use static initialization block otherwise cyclic references exist - // between Settings initialization and this class. - if (notificationSmallIcon == null) { - if (GmsCoreSupport.isPackageNameOriginal()) { - Logger.printDebug(() -> "App is root mounted. Not overriding small notification icon"); - return notificationSmallIcon = 0; - } - - BrandingTheme branding = BaseSettings.CUSTOM_BRANDING_ICON.get(); - if (branding == BrandingTheme.ORIGINAL) { - notificationSmallIcon = 0; - } else { - // Original icon is quantum_ic_video_youtube_white_24 - String iconName = "revanced_notification_icon"; - if (branding == BrandingTheme.CUSTOM) { - iconName += "_custom"; - } - - notificationSmallIcon = Utils.getResourceIdentifier(ResourceType.DRAWABLE, iconName); - if (notificationSmallIcon == 0) { - Logger.printException(() -> "Could not load notification small icon"); - } - } - } - return notificationSmallIcon; - } - - /** - * Injection point. - */ - public static View getLottieViewOrNull(View lottieStartupView) { - if (BaseSettings.CUSTOM_BRANDING_ICON.get() == BrandingTheme.ORIGINAL) { - return lottieStartupView; - } - - return null; - } - - /** - * Injection point. - */ - public static void setNotificationIcon(Notification.Builder builder) { - try { - final int smallIcon = getNotificationSmallIcon(); - if (smallIcon != 0) { - builder.setSmallIcon(smallIcon) - .setColor(Color.TRANSPARENT); // Remove YT red tint. - } - } catch (Exception ex) { - Logger.printException(() -> "setNotificationIcon failure", ex); - } - } - - /** - * Injection point. - *

- * The total number of app name aliases, including dummy aliases. - */ - private static int numberOfPresetAppNames() { - // Modified during patching, but requires a default if custom branding is excluded. - return 1; - } - - - /** - * Injection point. - *

- * If a custom icon was provided during patching. - */ - private static boolean userProvidedCustomIcon() { - // Modified during patching, but requires a default if custom branding is excluded. - return false; - } - - /** - * Injection point. - *

- * If a custom name was provided during patching. - */ - private static boolean userProvidedCustomName() { - // Modified during patching, but requires a default if custom branding is excluded.. - return false; - } - - public static int getDefaultAppNameIndex() { - return userProvidedCustomName() - ? numberOfPresetAppNames() - : 2; - } - - public static BrandingTheme getDefaultIconStyle() { - return userProvidedCustomIcon() - ? BrandingTheme.CUSTOM - : BrandingTheme.ROUNDED; - } - - /** - * Injection point. - */ - @SuppressWarnings("ConstantConditions") - public static void setBranding() { - try { - if (GmsCoreSupport.isPackageNameOriginal()) { - Logger.printInfo(() -> "App is root mounted. Cannot dynamically change app icon"); - return; - } - - Context context = Utils.getContext(); - PackageManager pm = context.getPackageManager(); - String packageName = context.getPackageName(); - - BrandingTheme selectedBranding = BaseSettings.CUSTOM_BRANDING_ICON.get(); - final int selectedNameIndex = BaseSettings.CUSTOM_BRANDING_NAME.get(); - ComponentName componentToEnable = null; - ComponentName defaultComponent = null; - List componentsToDisable = new ArrayList<>(); - - for (BrandingTheme theme : BrandingTheme.values()) { - // Must always update all aliases including custom alias (last index). - final int numberOfPresetAppNames = numberOfPresetAppNames(); - - // App name indices starts at 1. - for (int index = 1; index <= numberOfPresetAppNames; index++) { - String aliasClass = theme.packageAndNameIndexToClassAlias(packageName, index); - ComponentName component = new ComponentName(packageName, aliasClass); - if (defaultComponent == null) { - // Default is always the first alias. - defaultComponent = component; - } - - if (index == selectedNameIndex && theme == selectedBranding) { - componentToEnable = component; - } else { - componentsToDisable.add(component); - } - } - } - - if (componentToEnable == null) { - // User imported a bad app name index value. Either the imported data - // was corrupted, or they previously had custom name enabled and the app - // no longer has a custom name specified. - Utils.showToastLong("Custom branding reset"); - BaseSettings.CUSTOM_BRANDING_ICON.resetToDefault(); - BaseSettings.CUSTOM_BRANDING_NAME.resetToDefault(); - - componentToEnable = defaultComponent; - componentsToDisable.remove(defaultComponent); - } - - for (ComponentName disable : componentsToDisable) { - pm.setComponentEnabledSetting(disable, - PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); - } - - // Use info logging because if the alias status become corrupt the app cannot launch. - ComponentName componentToEnableFinal = componentToEnable; - Logger.printInfo(() -> "Enabling: " + componentToEnableFinal.getClassName()); - - pm.setComponentEnabledSetting(componentToEnable, - PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0); - } catch (Exception ex) { - Logger.printException(() -> "setBranding failure", ex); - } - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/EnableDebuggingPatch.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/EnableDebuggingPatch.java deleted file mode 100644 index b63f2c6049..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/EnableDebuggingPatch.java +++ /dev/null @@ -1,138 +0,0 @@ -package app.revanced.extension.shared.patches; - -import static java.lang.Boolean.TRUE; - -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.settings.BaseSettings; - -@SuppressWarnings("unused") -public final class EnableDebuggingPatch { - - /** - * Only log if debugging is enabled on startup. - * This prevents enabling debugging - * while the app is running then failing to restart - * resulting in an incomplete log. - */ - private static final boolean LOG_FEATURE_FLAGS = BaseSettings.DEBUG.get(); - - private static final ConcurrentMap featureFlags = LOG_FEATURE_FLAGS - ? new ConcurrentHashMap<>(800, 0.5f, 1) - : null; - - private static final Set DISABLED_FEATURE_FLAGS = parseFlags(BaseSettings.DISABLED_FEATURE_FLAGS.get()); - - // Log all disabled flags on app startup. - static { - if (LOG_FEATURE_FLAGS && !DISABLED_FEATURE_FLAGS.isEmpty()) { - StringBuilder sb = new StringBuilder("Disabled feature flags:\n"); - for (Long flag : DISABLED_FEATURE_FLAGS) { - sb.append(" ").append(flag).append('\n'); - } - Logger.printDebug(sb::toString); - } - } - - /** - * Injection point. - */ - public static boolean isBooleanFeatureFlagEnabled(boolean value, long flag) { - if (LOG_FEATURE_FLAGS && value) { - Long flagObj = flag; - if (DISABLED_FEATURE_FLAGS.contains(flagObj)) { - return false; - } - if (featureFlags.putIfAbsent(flagObj, TRUE) == null) { - Logger.printDebug(() -> "boolean feature is enabled: " + flag); - } - } - - return value; - } - - /** - * Injection point. - */ - public static double isDoubleFeatureFlagEnabled(double value, long flag, double defaultValue) { - if (LOG_FEATURE_FLAGS && defaultValue != value) { - if (DISABLED_FEATURE_FLAGS.contains(flag)) return defaultValue; - - if (featureFlags.putIfAbsent(flag, true) == null) { - // Align the log outputs to make post processing easier. - Logger.printDebug(() -> " double feature is enabled: " + flag - + " value: " + value + (defaultValue == 0 ? "" : " default: " + defaultValue)); - } - } - - return value; - } - - /** - * Injection point. - */ - public static long isLongFeatureFlagEnabled(long value, long flag, long defaultValue) { - if (LOG_FEATURE_FLAGS && defaultValue != value) { - if (DISABLED_FEATURE_FLAGS.contains(flag)) return defaultValue; - - if (featureFlags.putIfAbsent(flag, true) == null) { - Logger.printDebug(() -> " long feature is enabled: " + flag - + " value: " + value + (defaultValue == 0 ? "" : " default: " + defaultValue)); - } - } - - return value; - } - - /** - * Injection point. - */ - public static String isStringFeatureFlagEnabled(String value, long flag, String defaultValue) { - if (LOG_FEATURE_FLAGS && !defaultValue.equals(value)) { - if (featureFlags.putIfAbsent(flag, true) == null) { - Logger.printDebug(() -> " string feature is enabled: " + flag - + " value: " + value + (defaultValue.isEmpty() ? "" : " default: " + defaultValue)); - } - } - - return value; - } - - /** - * Get all logged feature flags. - * @return Set of all known flags - */ - public static Set getAllLoggedFlags() { - if (featureFlags != null) { - return new HashSet<>(featureFlags.keySet()); - } - - return new HashSet<>(); - } - - /** - * Public method for parsing flags. - * @param flags String containing newline-separated flag IDs - * @return Set of parsed flag IDs - */ - public static Set parseFlags(String flags) { - Set parsedFlags = new HashSet<>(); - if (!flags.isBlank()) { - for (String flag : flags.split("\n")) { - String trimmedFlag = flag.trim(); - if (trimmedFlag.isEmpty()) continue; // Skip empty lines. - try { - parsedFlags.add(Long.parseLong(trimmedFlag)); - } catch (NumberFormatException e) { - Logger.printException(() -> "Invalid flag ID: " + flag); - } - } - } - - return parsedFlags; - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/ForceOriginalAudioPatch.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/ForceOriginalAudioPatch.java deleted file mode 100644 index 8ae454e69a..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/ForceOriginalAudioPatch.java +++ /dev/null @@ -1,71 +0,0 @@ -package app.revanced.extension.shared.patches; - -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.settings.AppLanguage; -import app.revanced.extension.shared.spoof.ClientType; -import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch; - -@SuppressWarnings("unused") -public class ForceOriginalAudioPatch { - - private static final String DEFAULT_AUDIO_TRACKS_SUFFIX = ".4"; - - private static volatile boolean enabled; - - public static void setEnabled(boolean isEnabled, ClientType client) { - enabled = isEnabled; - - if (isEnabled && !client.useAuth && !client.supportsMultiAudioTracks) { - // If client spoofing does not use authentication and lacks multi-audio streams, - // then can use any language code for the request and if that requested language is - // not available YT uses the original audio language. Authenticated requests ignore - // the language code and always use the account language. Use a language that is - // not auto-dubbed by YouTube: https://support.google.com/youtube/answer/15569972 - // but the language is also supported natively by the Meta Quest device that - // Android VR is spoofing. - AppLanguage override = AppLanguage.NB; // Norwegian Bokmal. - Logger.printDebug(() -> "Setting language override: " + override); - SpoofVideoStreamsPatch.setLanguageOverride(override); - } - } - - /** - * Injection point. - */ - public static boolean ignoreDefaultAudioStream(boolean original) { - if (enabled) { - return false; - } - return original; - } - - /** - * Injection point. - */ - public static boolean isDefaultAudioStream(boolean isDefault, String audioTrackId, String audioTrackDisplayName) { - try { - if (!enabled) { - return isDefault; - } - - if (audioTrackId.isEmpty()) { - // Older app targets can have empty audio tracks and these might be placeholders. - // The real audio tracks are called after these. - return isDefault; - } - - Logger.printDebug(() -> "default: " + String.format("%-5s", isDefault) + " id: " - + String.format("%-8s", audioTrackId) + " name:" + audioTrackDisplayName); - - final boolean isOriginal = audioTrackId.endsWith(DEFAULT_AUDIO_TRACKS_SUFFIX); - if (isOriginal) { - Logger.printDebug(() -> "Using audio: " + audioTrackId); - } - - return isOriginal; - } catch (Exception ex) { - Logger.printException(() -> "isDefaultAudioStream failure", ex); - return isDefault; - } - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/SanitizeSharingLinksPatch.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/SanitizeSharingLinksPatch.java deleted file mode 100644 index b0bcbc6f04..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/SanitizeSharingLinksPatch.java +++ /dev/null @@ -1,31 +0,0 @@ -package app.revanced.extension.shared.patches; - -import app.revanced.extension.shared.privacy.LinkSanitizer; -import app.revanced.extension.shared.settings.BaseSettings; - -/** - * YouTube and YouTube Music. - */ -@SuppressWarnings("unused") -public final class SanitizeSharingLinksPatch { - - private static final LinkSanitizer sanitizer = new LinkSanitizer( - "si", - "feature" // Old tracking parameter name, and may be obsolete. - ); - - /** - * Injection point. - */ - public static String sanitize(String url) { - if (BaseSettings.SANITIZE_SHARING_LINKS.get()) { - url = sanitizer.sanitizeURLString(url); - } - - if (BaseSettings.REPLACE_MUSIC_LINKS_WITH_YOUTUBE.get()) { - url = url.replace("music.youtube.com", "youtube.com"); - } - - return url; - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/FilterGroup.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/FilterGroup.java deleted file mode 100644 index 212787f305..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/FilterGroup.java +++ /dev/null @@ -1,213 +0,0 @@ -package app.revanced.extension.shared.patches.litho; - -import androidx.annotation.NonNull; -import app.revanced.extension.shared.ByteTrieSearch; -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.settings.BooleanSetting; - -public abstract class FilterGroup { - public final static class FilterGroupResult { - private BooleanSetting setting; - private int matchedIndex; - private int matchedLength; - // In the future it might be useful to include which pattern matched, - // but for now that is not needed. - - FilterGroupResult() { - this(null, -1, 0); - } - - FilterGroupResult(BooleanSetting setting, int matchedIndex, int matchedLength) { - setValues(setting, matchedIndex, matchedLength); - } - - public void setValues(BooleanSetting setting, int matchedIndex, int matchedLength) { - this.setting = setting; - this.matchedIndex = matchedIndex; - this.matchedLength = matchedLength; - } - - /** - * A null value if the group has no setting, - * or if no match is returned from {@link FilterGroupList#check(Object)}. - */ - public BooleanSetting getSetting() { - return setting; - } - - public boolean isFiltered() { - return matchedIndex >= 0; - } - - /** - * Matched index of first pattern that matched, or -1 if nothing matched. - */ - public int getMatchedIndex() { - return matchedIndex; - } - - /** - * Length of the matched filter pattern. - */ - public int getMatchedLength() { - return matchedLength; - } - } - - protected final BooleanSetting setting; - public final T[] filters; - - /** - * Initialize a new filter group. - * - * @param setting The associated setting. - * @param filters The filters. - */ - @SafeVarargs - public FilterGroup(final BooleanSetting setting, final T... filters) { - this.setting = setting; - this.filters = filters; - if (filters.length == 0) { - throw new IllegalArgumentException("Must use one or more filter patterns (zero specified)"); - } - } - - public boolean isEnabled() { - return setting == null || setting.get(); - } - - /** - * @return If {@link FilterGroupList} should include this group when searching. - * By default, all filters are included except non enabled settings that require reboot. - */ - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - public boolean includeInSearch() { - return isEnabled() || !setting.rebootApp; - } - - @NonNull - @Override - public String toString() { - return getClass().getSimpleName() + ": " + (setting == null ? "(null setting)" : setting); - } - - public abstract FilterGroupResult check(final T stack); - - - public static class StringFilterGroup extends FilterGroup { - - public StringFilterGroup(final BooleanSetting setting, final String... filters) { - super(setting, filters); - } - - @Override - public FilterGroupResult check(final String string) { - int matchedIndex = -1; - int matchedLength = 0; - if (isEnabled()) { - for (String pattern : filters) { - if (!string.isEmpty()) { - final int indexOf = string.indexOf(pattern); - if (indexOf >= 0) { - matchedIndex = indexOf; - matchedLength = pattern.length(); - break; - } - } - } - } - return new FilterGroupResult(setting, matchedIndex, matchedLength); - } - } - - /** - * If you have more than 1 filter patterns, then all instances of - * this class should be filtered using {@link FilterGroupList.ByteArrayFilterGroupList#check(byte[])}, - * which uses a prefix tree to give better performance. - */ - public static class ByteArrayFilterGroup extends FilterGroup { - - private volatile int[][] failurePatterns; - - // Modified implementation from https://stackoverflow.com/a/1507813 - private static int indexOf(final byte[] data, final byte[] pattern, final int[] failure) { - // Finds the first occurrence of the pattern in the byte array using - // KMP matching algorithm. - int patternLength = pattern.length; - for (int i = 0, j = 0, dataLength = data.length; i < dataLength; i++) { - while (j > 0 && pattern[j] != data[i]) { - j = failure[j - 1]; - } - if (pattern[j] == data[i]) { - j++; - } - if (j == patternLength) { - return i - patternLength + 1; - } - } - return -1; - } - - private static int[] createFailurePattern(byte[] pattern) { - // Computes the failure function using a bootstrapping process, - // where the pattern is matched against itself. - final int patternLength = pattern.length; - final int[] failure = new int[patternLength]; - - for (int i = 1, j = 0; i < patternLength; i++) { - while (j > 0 && pattern[j] != pattern[i]) { - j = failure[j - 1]; - } - if (pattern[j] == pattern[i]) { - j++; - } - failure[i] = j; - } - return failure; - } - - public ByteArrayFilterGroup(BooleanSetting setting, byte[]... filters) { - super(setting, filters); - } - - /** - * Converts the Strings into byte arrays. Used to search for text in binary data. - */ - public ByteArrayFilterGroup(BooleanSetting setting, String... filters) { - super(setting, ByteTrieSearch.convertStringsToBytes(filters)); - } - - private synchronized void buildFailurePatterns() { - if (failurePatterns != null) return; // Thread race and another thread already initialized the search. - Logger.printDebug(() -> "Building failure array for: " + this); - int[][] failurePatterns = new int[filters.length][]; - int i = 0; - for (byte[] pattern : filters) { - failurePatterns[i++] = createFailurePattern(pattern); - } - this.failurePatterns = failurePatterns; // Must set after initialization finishes. - } - - @Override - public FilterGroupResult check(final byte[] bytes) { - int matchedLength = 0; - int matchedIndex = -1; - if (isEnabled()) { - int[][] failures = failurePatterns; - if (failures == null) { - buildFailurePatterns(); // Lazy load. - failures = failurePatterns; - } - for (int i = 0, length = filters.length; i < length; i++) { - byte[] filter = filters[i]; - matchedIndex = indexOf(bytes, filter, failures[i]); - if (matchedIndex >= 0) { - matchedLength = filter.length; - break; - } - } - } - return new FilterGroupResult(setting, matchedIndex, matchedLength); - } - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/FilterGroupList.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/FilterGroupList.java deleted file mode 100644 index da22ca9ff7..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/FilterGroupList.java +++ /dev/null @@ -1,72 +0,0 @@ -package app.revanced.extension.shared.patches.litho; - -import androidx.annotation.NonNull; -import app.revanced.extension.shared.ByteTrieSearch; -import app.revanced.extension.shared.StringTrieSearch; -import app.revanced.extension.shared.TrieSearch; -import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup; -import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; - -public abstract class FilterGroupList> implements Iterable { - - private final List filterGroups = new ArrayList<>(); - private final TrieSearch search = createSearchGraph(); - - @SafeVarargs - public final void addAll(final T... groups) { - filterGroups.addAll(Arrays.asList(groups)); - - for (T group : groups) { - if (!group.includeInSearch()) { - continue; - } - for (V pattern : group.filters) { - search.addPattern(pattern, (textSearched, matchedStartIndex, matchedLength, callbackParameter) -> { - if (group.isEnabled()) { - FilterGroup.FilterGroupResult result = (FilterGroup.FilterGroupResult) callbackParameter; - result.setValues(group.setting, matchedStartIndex, matchedLength); - return true; - } - return false; - }); - } - } - } - - @NonNull - @Override - public Iterator iterator() { - return filterGroups.iterator(); - } - - public FilterGroup.FilterGroupResult check(V stack) { - FilterGroup.FilterGroupResult result = new FilterGroup.FilterGroupResult(); - search.matches(stack, result); - return result; - - } - - protected abstract TrieSearch createSearchGraph(); - - public static final class StringFilterGroupList extends FilterGroupList { - protected StringTrieSearch createSearchGraph() { - return new StringTrieSearch(); - } - } - - /** - * If searching for a single byte pattern, then it is slightly better to use - * {@link ByteArrayFilterGroup#check(byte[])} as it uses KMP which is faster - * than a prefix tree to search for only 1 pattern. - */ - public static final class ByteArrayFilterGroupList extends FilterGroupList { - protected ByteTrieSearch createSearchGraph() { - return new ByteTrieSearch(); - } - } -} \ No newline at end of file diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/LithoFilterPatch.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/LithoFilterPatch.java deleted file mode 100644 index e1b329ee54..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/LithoFilterPatch.java +++ /dev/null @@ -1,439 +0,0 @@ -package app.revanced.extension.shared.patches.litho; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.Utils; -import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup; -import app.revanced.extension.shared.settings.BaseSettings; -import app.revanced.extension.shared.StringTrieSearch; -import app.revanced.extension.shared.settings.YouTubeAndMusicSettings; - -@SuppressWarnings("unused") -public final class LithoFilterPatch { - /** - * Simple wrapper to pass the litho parameters through the prefix search. - */ - private static final class LithoFilterParameters { - final String identifier; - final String path; - final String accessibility; - final byte[] buffer; - - LithoFilterParameters(String lithoIdentifier, String lithoPath, - String accessibility, byte[] buffer) { - this.identifier = lithoIdentifier; - this.path = lithoPath; - this.accessibility = accessibility; - this.buffer = buffer; - } - - @NonNull - @Override - public String toString() { - // Estimate the percentage of the buffer that are Strings. - StringBuilder builder = new StringBuilder(Math.max(100, buffer.length / 2)); - builder.append("ID: "); - builder.append(identifier); - if (!accessibility.isEmpty()) { - // AccessibilityId and AccessibilityText are pieces of BufferStrings. - builder.append(" Accessibility: "); - builder.append(accessibility); - } - builder.append(" Path: "); - builder.append(path); - if (YouTubeAndMusicSettings.DEBUG_PROTOCOLBUFFER.get()) { - builder.append(" BufferStrings: "); - findAsciiStrings(builder, buffer); - } - - return builder.toString(); - } - - /** - * Search through a byte array for all ASCII strings. - */ - static void findAsciiStrings(StringBuilder builder, byte[] buffer) { - // Valid ASCII values (ignore control characters). - final int minimumAscii = 32; // 32 = space character - final int maximumAscii = 126; // 127 = delete character - final int minimumAsciiStringLength = 4; // Minimum length of an ASCII string to include. - String delimitingCharacter = "❙"; // Non ascii character, to allow easier log filtering. - - final int length = buffer.length; - int start = 0; - int end = 0; - while (end < length) { - int value = buffer[end]; - if (value < minimumAscii || value > maximumAscii || end == length - 1) { - if (end - start >= minimumAsciiStringLength) { - for (int i = start; i < end; i++) { - builder.append((char) buffer[i]); - } - builder.append(delimitingCharacter); - } - start = end + 1; - } - end++; - } - } - } - - /** - * Placeholder for actual filters. - */ - private static final class DummyFilter extends Filter { - } - - private static final Filter[] filters = new Filter[]{ - new DummyFilter() // Replaced during patching, do not touch. - }; - - /** - * Litho layout fixed thread pool size override. - *

- * Unpatched YouTube uses a layout fixed thread pool between 1 and 3 threads: - *

-     * 1 thread - > Device has less than 6 cores
-     * 2 threads -> Device has over 6 cores and less than 6GB of memory
-     * 3 threads -> Device has over 6 cores and more than 6GB of memory
-     * 
- * - * Using more than 1 thread causes layout issues such as the You tab watch/playlist shelf - * that is sometimes incorrectly hidden (ReVanced is not hiding it), and seems to - * fix a race issue if using the active navigation tab status with litho filtering. - */ - private static final int LITHO_LAYOUT_THREAD_POOL_SIZE = 1; - - /** - * For YouTube 20.22+, this is set to true by a patch, - * because it cannot use the thread buffer due to the buffer frequently not being correct, - * especially for components that are recreated such as dragging off-screen then back on screen. - * Instead, parse the identifier found near the start of the buffer and use that to - * identify the correct buffer to use when filtering. - *

- * This is set during patching, do not change manually. - */ - private static final boolean EXTRACT_IDENTIFIER_FROM_BUFFER = false; - - /** - * Turns on additional logging, used for development purposes only. - */ - public static final boolean DEBUG_EXTRACT_IDENTIFIER_FROM_BUFFER = false; - - /** - * String suffix for components. - * Can be any of: ".eml", ".eml-fe", ".e-b", ".eml-js", "e-js-b" - */ - private static final byte[] LITHO_COMPONENT_EXTENSION_BYTES = ".e".getBytes(StandardCharsets.US_ASCII); - - /** - * Used as placeholder for litho id/path filters that do not use a buffer - */ - private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; - - /** - * Because litho filtering is multithreaded and the buffer is passed in from a different injection point, - * the buffer is saved to a ThreadLocal so each calling thread does not interfere with other threads. - * Used for 20.21 and lower. - */ - private static final ThreadLocal bufferThreadLocal = new ThreadLocal<>(); - - /** - * Identifier to protocol buffer mapping. Only used for 20.22+. - * Thread local is needed because filtering is multithreaded and each thread can load - * a different component with the same identifier. - */ - private static final ThreadLocal> identifierToBufferThread = new ThreadLocal<>(); - - /** - * Global shared buffer. Used only if the buffer is not found in the ThreadLocal. - */ - private static final Map identifierToBufferGlobal - = Collections.synchronizedMap(createIdentifierToBufferMap()); - - private static final StringTrieSearch pathSearchTree = new StringTrieSearch(); - private static final StringTrieSearch identifierSearchTree = new StringTrieSearch(); - - static { - - for (Filter filter : filters) { - filterUsingCallbacks(identifierSearchTree, filter, - filter.identifierCallbacks, Filter.FilterContentType.IDENTIFIER); - filterUsingCallbacks(pathSearchTree, filter, - filter.pathCallbacks, Filter.FilterContentType.PATH); - } - - Logger.printDebug(() -> "Using: " - + identifierSearchTree.numberOfPatterns() + " identifier filters" - + " (" + identifierSearchTree.getEstimatedMemorySize() + " KB), " - + pathSearchTree.numberOfPatterns() + " path filters" - + " (" + pathSearchTree.getEstimatedMemorySize() + " KB)"); - } - - private static void filterUsingCallbacks(StringTrieSearch pathSearchTree, - Filter filter, List groups, - Filter.FilterContentType type) { - String filterSimpleName = filter.getClass().getSimpleName(); - - for (StringFilterGroup group : groups) { - if (!group.includeInSearch()) { - continue; - } - - for (String pattern : group.filters) { - pathSearchTree.addPattern(pattern, (textSearched, matchedStartIndex, - matchedLength, callbackParameter) -> { - if (!group.isEnabled()) return false; - - LithoFilterParameters parameters = (LithoFilterParameters) callbackParameter; - final boolean isFiltered = filter.isFiltered(parameters.identifier, - parameters.accessibility, parameters.path, parameters.buffer, - group, type, matchedStartIndex); - - if (isFiltered && BaseSettings.DEBUG.get()) { - Logger.printDebug(() -> type == Filter.FilterContentType.IDENTIFIER - ? filterSimpleName + " filtered identifier: " + parameters.identifier - : filterSimpleName + " filtered path: " + parameters.path); - } - - return isFiltered; - } - ); - } - } - } - - private static Map createIdentifierToBufferMap() { - // It's unclear how many items should be cached. This is a guess. - return Utils.createSizeRestrictedMap(100); - } - - /** - * Helper function that differs from {@link Character#isDigit(char)} - * as this only matches ascii and not Unicode numbers. - */ - private static boolean isAsciiNumber(byte character) { - return '0' <= character && character <= '9'; - } - - private static boolean isAsciiLowerCaseLetter(byte character) { - return 'a' <= character && character <= 'z'; - } - - /** - * Injection point. Called off the main thread. - * Targets 20.22+ - */ - public static void setProtoBuffer(byte[] buffer) { - if (DEBUG_EXTRACT_IDENTIFIER_FROM_BUFFER) { - StringBuilder builder = new StringBuilder(); - LithoFilterParameters.findAsciiStrings(builder, buffer); - Logger.printDebug(() -> "New buffer: " + builder); - } - - // The identifier always seems to start very close to the buffer start. - // Highest identifier start index ever observed is 50, with most around 30 to 40. - // The buffer can be very large with up to 200kb has been observed, - // so the search is restricted to only the start. - final int maxBufferStartIndex = 500; // 10x expected upper bound. - - // Could use Boyer-Moore-Horspool since the string is ASCII and has a limited number of - // unique characters, but it seems to be slower since the extra overhead of checking the - // bad character array negates any performance gain of skipping a few extra subsearches. - int emlIndex = -1; - final int emlStringLength = LITHO_COMPONENT_EXTENSION_BYTES.length; - final int lastBufferIndexToCheckFrom = Math.min(maxBufferStartIndex, buffer.length - emlStringLength); - for (int i = 0; i < lastBufferIndexToCheckFrom; i++) { - boolean match = true; - for (int j = 0; j < emlStringLength; j++) { - if (buffer[i + j] != LITHO_COMPONENT_EXTENSION_BYTES[j]) { - match = false; - break; - } - } - if (match) { - emlIndex = i; - break; - } - } - - if (emlIndex < 0) { - // Buffer is not used for creating a new litho component. - if (DEBUG_EXTRACT_IDENTIFIER_FROM_BUFFER) { - Logger.printDebug(() -> "Could not find eml index"); - } - return; - } - - int startIndex = emlIndex - 1; - while (startIndex > 0) { - final byte character = buffer[startIndex]; - int startIndexFinal = startIndex; - if (isAsciiLowerCaseLetter(character) || isAsciiNumber(character) || character == '_') { - // Valid character for the first path element. - startIndex--; - } else { - startIndex++; - break; - } - } - - // Strip away any numbers on the start of the identifier, which can - // be from random data in the buffer before the identifier starts. - while (true) { - final byte character = buffer[startIndex]; - if (isAsciiNumber(character)) { - startIndex++; - } else { - break; - } - } - - // Find the pipe character after the identifier. - int endIndex = -1; - for (int i = emlIndex, length = buffer.length; i < length; i++) { - if (buffer[i] == '|') { - endIndex = i; - break; - } - } - if (endIndex < 0) { - if (BaseSettings.DEBUG.get()) { - Logger.printException(() -> "Debug: Could not find buffer identifier"); - } - return; - } - - String identifier = new String(buffer, startIndex, endIndex - startIndex, StandardCharsets.US_ASCII); - if (DEBUG_EXTRACT_IDENTIFIER_FROM_BUFFER) { - Logger.printDebug(() -> "Found buffer for identifier: " + identifier); - } - identifierToBufferGlobal.put(identifier, buffer); - - Map map = identifierToBufferThread.get(); - if (map == null) { - map = createIdentifierToBufferMap(); - identifierToBufferThread.set(map); - } - map.put(identifier, buffer); - } - - /** - * Injection point. Called off the main thread. - * Targets 20.21 and lower. - */ - public static void setProtoBuffer(@Nullable ByteBuffer buffer) { - if (buffer == null || !buffer.hasArray()) { - // It appears the buffer can be cleared out just before the call to #filter() - // Ignore this null value and retain the last buffer that was set. - Logger.printDebug(() -> "Ignoring null or empty buffer: " + buffer); - } else { - // Set the buffer to a thread local. The buffer will remain in memory, even after the call to #filter completes. - // This is intentional, as it appears the buffer can be set once and then filtered multiple times. - // The buffer will be cleared from memory after a new buffer is set by the same thread, - // or when the calling thread eventually dies. - bufferThreadLocal.set(buffer.array()); - } - } - - /** - * Injection point. - */ - public static boolean isFiltered(String identifier, @Nullable String accessibilityId, - @Nullable String accessibilityText, StringBuilder pathBuilder) { - try { - if (identifier.isEmpty() || pathBuilder.length() == 0) { - return false; - } - - byte[] buffer = null; - if (EXTRACT_IDENTIFIER_FROM_BUFFER) { - final int pipeIndex = identifier.indexOf('|'); - if (pipeIndex >= 0) { - // If the identifier contains no pipe, then it's not an ".eml" identifier - // and the buffer is not uniquely identified. Typically, this only happens - // for subcomponents where buffer filtering is not used. - String identifierKey = identifier.substring(0, pipeIndex); - - var map = identifierToBufferThread.get(); - if (map != null) { - buffer = map.get(identifierKey); - } - - if (buffer == null) { - // Buffer for thread local not found. Use the last buffer found from any thread. - buffer = identifierToBufferGlobal.get(identifierKey); - - if (DEBUG_EXTRACT_IDENTIFIER_FROM_BUFFER && buffer == null) { - // No buffer is found for some components, such as - // shorts_lockup_cell.eml on channel profiles. - // For now, just ignore this and filter without a buffer. - if (BaseSettings.DEBUG.get()) { - Logger.printException(() -> "Debug: Could not find buffer for identifier: " + identifier); - } - } - } - } - } else { - buffer = bufferThreadLocal.get(); - } - - // Potentially the buffer may have been null or never set up until now. - // Use an empty buffer so the litho id/path filters that do not use a buffer still work. - if (buffer == null) { - buffer = EMPTY_BYTE_ARRAY; - } - - String path = pathBuilder.toString(); - - String accessibility = ""; - if (accessibilityId != null && !accessibilityId.isBlank()) { - accessibility = accessibilityId; - } - if (accessibilityText != null && !accessibilityText.isBlank()) { - accessibility = accessibilityId + '|' + accessibilityText; - } - LithoFilterParameters parameter = new LithoFilterParameters(identifier, path, accessibility, buffer); - Logger.printDebug(() -> "Searching " + parameter); - - return identifierSearchTree.matches(identifier, parameter) - || pathSearchTree.matches(path, parameter); - } catch (Exception ex) { - Logger.printException(() -> "isFiltered failure", ex); - } - - return false; - } - - /** - * Injection point. - */ - public static int getExecutorCorePoolSize(int originalCorePoolSize) { - if (originalCorePoolSize != LITHO_LAYOUT_THREAD_POOL_SIZE) { - Logger.printDebug(() -> "Overriding core thread pool size from: " + originalCorePoolSize - + " to: " + LITHO_LAYOUT_THREAD_POOL_SIZE); - } - - return LITHO_LAYOUT_THREAD_POOL_SIZE; - } - - /** - * Injection point. - */ - public static int getExecutorMaxThreads(int originalMaxThreads) { - if (originalMaxThreads != LITHO_LAYOUT_THREAD_POOL_SIZE) { - Logger.printDebug(() -> "Overriding max thread pool size from: " + originalMaxThreads - + " to: " + LITHO_LAYOUT_THREAD_POOL_SIZE); - } - - return LITHO_LAYOUT_THREAD_POOL_SIZE; - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/privacy/LinkSanitizer.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/privacy/LinkSanitizer.java deleted file mode 100644 index 421761f7da..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/privacy/LinkSanitizer.java +++ /dev/null @@ -1,68 +0,0 @@ -package app.revanced.extension.shared.privacy; - -import android.net.Uri; - -import java.util.Collection; -import java.util.List; -import java.util.Set; - -import app.revanced.extension.shared.Logger; - -/** - * Strips away specific parameters from URLs. - */ -public class LinkSanitizer { - - private final Collection parametersToRemove; - - public LinkSanitizer(String ... parametersToRemove) { - final int parameterCount = parametersToRemove.length; - - // List is faster if only checking a few parameters. - this.parametersToRemove = parameterCount > 4 - ? Set.of(parametersToRemove) - : List.of(parametersToRemove); - } - - public String sanitizeURLString(String url) { - try { - return sanitizeURI(Uri.parse(url)).toString(); - } catch (Exception ex) { - Logger.printException(() -> "sanitizeURLString failure: " + url, ex); - return url; - } - } - - public Uri sanitizeURI(Uri uri) { - try { - String scheme = uri.getScheme(); - if (scheme == null || !(scheme.equals("http") || scheme.equals("https"))) { - // Opening YouTube share sheet 'other' option passes the video title as a URI. - // Checking !uri.isHierarchical() works for all cases, except if the - // video title starts with / and then it's hierarchical but still an invalid URI. - Logger.printDebug(() -> "Ignoring URI: " + uri); - return uri; - } - - Uri.Builder builder = uri.buildUpon().clearQuery(); - - if (!parametersToRemove.isEmpty()) { - for (String paramName : uri.getQueryParameterNames()) { - if (!parametersToRemove.contains(paramName)) { - for (String value : uri.getQueryParameters(paramName)) { - builder.appendQueryParameter(paramName, value); - } - } - } - } - - Uri sanitizedURL = builder.build(); - Logger.printInfo(() -> "Sanitized URL: " + uri + " to: " + sanitizedURL); - - return sanitizedURL; - } catch (Exception ex) { - Logger.printException(() -> "sanitizeURI failure: " + uri, ex); - return uri; - } - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/requests/Requester.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/requests/Requester.java index 2e5c457f7b..c25e71d78c 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/requests/Requester.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/requests/Requester.java @@ -23,8 +23,8 @@ public class Requester { public static HttpURLConnection getConnectionFromCompiledRoute(String apiUrl, Route.CompiledRoute route) throws IOException { String url = apiUrl + route.getCompiledRoute(); HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); - // This request sends data via URL query parameters. No request body is included. - // If a request body is added, the caller must set the appropriate Content-Length header. + // Request data is in the URL parameters and no body is sent. + // The calling code must set a length if using a request body. connection.setFixedLengthStreamingMode(0); connection.setRequestMethod(route.getMethod().name()); String agentString = System.getProperty("http.agent") diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/requests/Route.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/requests/Route.java index 74428224a7..9e6f2c5a71 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/requests/Route.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/requests/Route.java @@ -52,7 +52,7 @@ public class Route { private int countMatches(CharSequence seq, char c) { int count = 0; - for (int i = 0, length = seq.length(); i < length; i++) { + for (int i = 0; i < seq.length(); i++) { if (seq.charAt(i) == c) count++; } diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/AppLanguage.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/AppLanguage.java deleted file mode 100644 index fbc734a51d..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/AppLanguage.java +++ /dev/null @@ -1,119 +0,0 @@ -package app.revanced.extension.shared.settings; - -import java.util.Locale; - -public enum AppLanguage { - /** - * The current app language. - */ - DEFAULT, - - // Languages codes not included with YouTube, but are translated on Crowdin - GA, - - // Language codes found in locale_config.xml - // All region specific variants have been removed. - AF, - AM, - AR, - AS, - AZ, - BE, - BG, - BN, - BS, - CA, - CS, - DA, - DE, - EL, - EN, - ES, - ET, - EU, - FA, - FI, - FR, - GL, - GU, - HE, // App uses obsolete 'IW' and not the modern 'HE' ISO code. - HI, - HR, - HU, - HY, - ID, - IS, - IT, - JA, - KA, - KK, - KM, - KN, - KO, - KY, - LO, - LT, - LV, - MK, - ML, - MN, - MR, - MS, - MY, - NB, - NE, - NL, - OR, - PA, - PL, - PT, - RO, - RU, - SI, - SK, - SL, - SQ, - SR, - SV, - SW, - TA, - TE, - TH, - TL, - TR, - UK, - UR, - UZ, - VI, - ZH, - ZU; - - private final String language; - private final Locale locale; - - AppLanguage() { - language = name().toLowerCase(Locale.US); - locale = Locale.forLanguageTag(language); - } - - /** - * @return The 2 letter ISO 639_1 language code. - */ - public String getLanguage() { - // Changing the app language does not force the app to completely restart, - // so the default needs to be the current language and not a static field. - if (this == DEFAULT) { - return Locale.getDefault().getLanguage(); - } - - return language; - } - - public Locale getLocale() { - if (this == DEFAULT) { - return Locale.getDefault(); - } - - return locale; - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BaseActivityHook.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BaseActivityHook.java deleted file mode 100644 index 1a2bfe9a2b..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BaseActivityHook.java +++ /dev/null @@ -1,173 +0,0 @@ -package app.revanced.extension.shared.settings; - -import static app.revanced.extension.shared.Utils.getResourceIdentifierOrThrow; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.preference.PreferenceFragment; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; -import android.widget.Toolbar; - -import androidx.annotation.RequiresApi; - -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.ResourceType; -import app.revanced.extension.shared.Utils; -import app.revanced.extension.shared.settings.preference.ToolbarPreferenceFragment; -import app.revanced.extension.shared.ui.Dim; - -/** - * Base class for hooking activities to inject a custom PreferenceFragment with a toolbar. - * Provides common logic for initializing the activity and setting up the toolbar. - */ -@SuppressWarnings("deprecation") -@RequiresApi(api = Build.VERSION_CODES.O) -public abstract class BaseActivityHook extends Activity { - - private static final int ID_REVANCED_SETTINGS_FRAGMENTS = - getResourceIdentifierOrThrow(ResourceType.ID, "revanced_settings_fragments"); - private static final int ID_REVANCED_TOOLBAR_PARENT = - getResourceIdentifierOrThrow(ResourceType.ID, "revanced_toolbar_parent"); - public static final int LAYOUT_REVANCED_SETTINGS_WITH_TOOLBAR = - getResourceIdentifierOrThrow(ResourceType.LAYOUT, "revanced_settings_with_toolbar"); - private static final int STRING_REVANCED_SETTINGS_TITLE = - getResourceIdentifierOrThrow(ResourceType.STRING, "revanced_settings_title"); - - /** - * Layout parameters for the toolbar, extracted from the dummy toolbar. - */ - protected static ViewGroup.LayoutParams toolbarLayoutParams; - - /** - * Sets the layout parameters for the toolbar. - */ - public static void setToolbarLayoutParams(Toolbar toolbar) { - if (toolbarLayoutParams != null) { - toolbar.setLayoutParams(toolbarLayoutParams); - } - } - - /** - * Initializes the activity by setting the theme, content view and injecting a PreferenceFragment. - */ - public static void initialize(BaseActivityHook hook, Activity activity) { - try { - hook.customizeActivityTheme(activity); - activity.setContentView(hook.getContentViewResourceId()); - - // Sanity check. - String dataString = activity.getIntent().getDataString(); - if (!"revanced_settings_intent".equals(dataString)) { - Logger.printException(() -> "Unknown intent: " + dataString); - return; - } - - PreferenceFragment fragment = hook.createPreferenceFragment(); - hook.createToolbar(activity, fragment); - - activity.getFragmentManager() - .beginTransaction() - .replace(ID_REVANCED_SETTINGS_FRAGMENTS, fragment) - .commit(); - } catch (Exception ex) { - Logger.printException(() -> "initialize failure", ex); - } - } - - /** - * Injection point. - * Overrides the ReVanced settings language. - */ - @SuppressWarnings("unused") - public static Context getAttachBaseContext(Context original) { - AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get(); - if (language == AppLanguage.DEFAULT) { - return original; - } - - return Utils.getContext(); - } - - /** - * Creates and configures a toolbar for the activity, replacing a dummy placeholder. - */ - @SuppressLint("UseCompatLoadingForDrawables") - protected void createToolbar(Activity activity, PreferenceFragment fragment) { - // Replace dummy placeholder toolbar. - // This is required to fix submenu title alignment issue with Android ASOP 15+ - ViewGroup toolbarParent = activity.findViewById(ID_REVANCED_TOOLBAR_PARENT); - ViewGroup dummyToolbar = Utils.getChildViewByResourceName(toolbarParent, "revanced_toolbar"); - toolbarLayoutParams = dummyToolbar.getLayoutParams(); - toolbarParent.removeView(dummyToolbar); - - // Sets appropriate system navigation bar color for the activity. - ToolbarPreferenceFragment.setNavigationBarColor(activity.getWindow()); - - Toolbar toolbar = new Toolbar(toolbarParent.getContext()); - toolbar.setBackgroundColor(getToolbarBackgroundColor()); - toolbar.setNavigationIcon(getNavigationIcon()); - toolbar.setNavigationOnClickListener(getNavigationClickListener(activity)); - toolbar.setTitle(STRING_REVANCED_SETTINGS_TITLE); - - toolbar.setTitleMarginStart(Dim.dp16); - toolbar.setTitleMarginEnd(Dim.dp16); - TextView toolbarTextView = Utils.getChildView(toolbar, false, view -> view instanceof TextView); - if (toolbarTextView != null) { - toolbarTextView.setTextColor(Utils.getAppForegroundColor()); - toolbarTextView.setTextSize(20); - } - setToolbarLayoutParams(toolbar); - - onPostToolbarSetup(activity, toolbar, fragment); - - toolbarParent.addView(toolbar, 0); - } - - /** - * Returns the resource ID for the content view layout. - */ - protected int getContentViewResourceId() { - return LAYOUT_REVANCED_SETTINGS_WITH_TOOLBAR; - } - - /** - * Customizes the activity's theme. - */ - protected abstract void customizeActivityTheme(Activity activity); - - /** - * Returns the background color for the toolbar. - */ - protected abstract int getToolbarBackgroundColor(); - - /** - * Returns the navigation icon drawable for the toolbar. - */ - protected abstract Drawable getNavigationIcon(); - - /** - * Returns the click listener for the toolbar's navigation icon. - */ - protected abstract View.OnClickListener getNavigationClickListener(Activity activity); - - /** - * Creates the PreferenceFragment to be injected into the activity. - */ - protected PreferenceFragment createPreferenceFragment() { - return new ToolbarPreferenceFragment(); - } - - /** - * Performs additional setup after the toolbar is configured. - * - * @param activity The activity hosting the toolbar. - * @param toolbar The configured toolbar. - * @param fragment The PreferenceFragment associated with the activity. - */ - protected void onPostToolbarSetup(Activity activity, Toolbar toolbar, PreferenceFragment fragment) {} -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BaseSettings.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BaseSettings.java index 5fc4418366..13be9547ca 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BaseSettings.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BaseSettings.java @@ -2,15 +2,11 @@ package app.revanced.extension.shared.settings; import static java.lang.Boolean.FALSE; import static java.lang.Boolean.TRUE; -import static app.revanced.extension.shared.patches.CustomBrandingPatch.BrandingTheme; import static app.revanced.extension.shared.settings.Setting.parent; -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.patches.CustomBrandingPatch; - /** * Settings shared across multiple apps. - *

+ * * To ensure this class is loaded when the UI is created, app specific setting bundles should extend * or reference this class. */ @@ -20,51 +16,4 @@ public class BaseSettings { public static final BooleanSetting DEBUG_TOAST_ON_ERROR = new BooleanSetting("revanced_debug_toast_on_error", TRUE, "revanced_debug_toast_on_error_user_dialog_message"); public static final IntegerSetting CHECK_ENVIRONMENT_WARNINGS_ISSUED = new IntegerSetting("revanced_check_environment_warnings_issued", 0, true, false); - - public static final EnumSetting REVANCED_LANGUAGE = new EnumSetting<>("revanced_language", AppLanguage.DEFAULT, true, "revanced_language_user_dialog_message"); - - /** - * Use the icons declared in the preferences created during patching. If no icons or styles are declared then this setting does nothing. - */ - public static final BooleanSetting SHOW_MENU_ICONS = new BooleanSetting("revanced_show_menu_icons", TRUE, true); - /** - * Do not use this setting directly. Instead use {@link app.revanced.extension.shared.Utils#appIsUsingBoldIcons()} - */ - public static final BooleanSetting SETTINGS_DISABLE_BOLD_ICONS = new BooleanSetting("revanced_settings_disable_bold_icons", FALSE, true); - - public static final BooleanSetting SETTINGS_SEARCH_HISTORY = new BooleanSetting("revanced_settings_search_history", TRUE, true); - public static final StringSetting SETTINGS_SEARCH_ENTRIES = new StringSetting("revanced_settings_search_entries", ""); - - /** - * The first time the app was launched with no previous app data (either a clean install, or after wiping app data). - */ - public static final LongSetting FIRST_TIME_APP_LAUNCHED = new LongSetting("revanced_last_time_app_was_launched", -1L, false, false); - - public static final BooleanSetting GMS_CORE_CHECK_UPDATES = new BooleanSetting("revanced_gms_core_check_updates", true, true); - - // - // Settings shared by YouTube and YouTube Music. - // - - public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("revanced_spoof_video_streams", TRUE, true, "revanced_spoof_video_streams_user_dialog_message"); - public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_video_streams_stats_for_nerds", TRUE, parent(SPOOF_VIDEO_STREAMS)); - - public static final BooleanSetting SANITIZE_SHARING_LINKS = new BooleanSetting("revanced_sanitize_sharing_links", TRUE); - public static final BooleanSetting REPLACE_MUSIC_LINKS_WITH_YOUTUBE = new BooleanSetting("revanced_replace_music_with_youtube", FALSE); - - public static final BooleanSetting CHECK_WATCH_HISTORY_DOMAIN_NAME = new BooleanSetting("revanced_check_watch_history_domain_name", TRUE, false, false); - - public static final EnumSetting CUSTOM_BRANDING_ICON = new EnumSetting<>("revanced_custom_branding_icon", CustomBrandingPatch.getDefaultIconStyle(), true); - public static final IntegerSetting CUSTOM_BRANDING_NAME = new IntegerSetting("revanced_custom_branding_name", CustomBrandingPatch.getDefaultAppNameIndex(), true); - - public static final StringSetting DISABLED_FEATURE_FLAGS = new StringSetting("revanced_disabled_feature_flags", "", true, parent(DEBUG)); - - static { - final long now = System.currentTimeMillis(); - - if (FIRST_TIME_APP_LAUNCHED.get() < 0) { - Logger.printInfo(() -> "First launch of installation with no prior app data"); - FIRST_TIME_APP_LAUNCHED.save(now); - } - } } diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BooleanSetting.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BooleanSetting.java index c67ebabf96..7e84034d06 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BooleanSetting.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BooleanSetting.java @@ -43,14 +43,10 @@ public class BooleanSetting extends Setting { * This method is only to be used by the Settings preference code. * * This intentionally is a static method to deter - * accidental usage when {@link #save(Boolean)} was intended. + * accidental usage when {@link #save(Boolean)} was intnded. */ public static void privateSetValue(@NonNull BooleanSetting setting, @NonNull Boolean newValue) { setting.value = Objects.requireNonNull(newValue); - - if (setting.isSetToDefault()) { - setting.removeFromPreferences(); - } } @Override @@ -69,8 +65,10 @@ public class BooleanSetting extends Setting { } @Override - public void saveToPreferences() { - preferences.saveBoolean(key, value); + public void save(@NonNull Boolean newValue) { + // Must set before saving to preferences (otherwise importing fails to update UI correctly). + value = Objects.requireNonNull(newValue); + preferences.saveBoolean(key, newValue); } @NonNull diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/EnumSetting.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/EnumSetting.java index 2c2cb6a3a8..a2b82dd215 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/EnumSetting.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/EnumSetting.java @@ -71,20 +71,15 @@ public class EnumSetting> extends Setting { json.put(importExportKey, value.name().toLowerCase(Locale.ENGLISH)); } - /** - * @param enumName Enum name. Casing does not matter. - * @return Enum of this type with the same declared name. - * @throws IllegalArgumentException if the name is not a valid enum of this type. - */ - protected T getEnumFromString(String enumName) { + @NonNull + private T getEnumFromString(String enumName) { //noinspection ConstantConditions for (Enum value : defaultValue.getClass().getEnumConstants()) { if (value.name().equalsIgnoreCase(enumName)) { - //noinspection unchecked + // noinspection unchecked return (T) value; } } - throw new IllegalArgumentException("Unknown enum value: " + enumName); } @@ -94,8 +89,10 @@ public class EnumSetting> extends Setting { } @Override - public void saveToPreferences() { - preferences.saveEnumAsString(key, value); + public void save(@NonNull T newValue) { + // Must set before saving to preferences (otherwise importing fails to update UI correctly). + value = Objects.requireNonNull(newValue); + preferences.saveEnumAsString(key, newValue); } @NonNull @@ -108,9 +105,7 @@ public class EnumSetting> extends Setting { * Availability based on if this setting is currently set to any of the provided types. */ @SafeVarargs - public final Setting.Availability availability(T... types) { - Objects.requireNonNull(types); - + public final Setting.Availability availability(@NonNull T... types) { return () -> { T currentEnumType = get(); for (T enumType : types) { diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/FloatSetting.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/FloatSetting.java index 59846e037f..7419741e03 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/FloatSetting.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/FloatSetting.java @@ -55,8 +55,10 @@ public class FloatSetting extends Setting { } @Override - public void saveToPreferences() { - preferences.saveFloatString(key, value); + public void save(@NonNull Float newValue) { + // Must set before saving to preferences (otherwise importing fails to update UI correctly). + value = Objects.requireNonNull(newValue); + preferences.saveFloatString(key, newValue); } @NonNull diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/IntegerSetting.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/IntegerSetting.java index ccf128dfdd..58f39a9107 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/IntegerSetting.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/IntegerSetting.java @@ -55,8 +55,10 @@ public class IntegerSetting extends Setting { } @Override - public void saveToPreferences() { - preferences.saveIntegerString(key, value); + public void save(@NonNull Integer newValue) { + // Must set before saving to preferences (otherwise importing fails to update UI correctly). + value = Objects.requireNonNull(newValue); + preferences.saveIntegerString(key, newValue); } @NonNull diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/LongSetting.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/LongSetting.java index ea3adcebac..4d7f8114f2 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/LongSetting.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/LongSetting.java @@ -55,8 +55,10 @@ public class LongSetting extends Setting { } @Override - public void saveToPreferences() { - preferences.saveLongString(key, value); + public void save(@NonNull Long newValue) { + // Must set before saving to preferences (otherwise importing fails to update UI correctly). + value = Objects.requireNonNull(newValue); + preferences.saveLongString(key, newValue); } @NonNull diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/Setting.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/Setting.java index 53a980e3c2..db5ecc844b 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/Setting.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/Setting.java @@ -1,28 +1,21 @@ package app.revanced.extension.shared.settings; -import static app.revanced.extension.shared.StringRef.str; - import android.content.Context; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; - import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.StringRef; import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.settings.preference.SharedPrefCategory; +import org.jetbrains.annotations.NotNull; +import org.json.JSONException; +import org.json.JSONObject; +import java.util.*; + +import static app.revanced.extension.shared.StringRef.str; + +@SuppressWarnings("unused") public abstract class Setting { /** @@ -32,86 +25,39 @@ public abstract class Setting { */ public interface Availability { boolean isAvailable(); - - /** - * @return parent settings (dependencies) of this availability. - */ - default List> getParentSettings() { - return Collections.emptyList(); - } } /** * Availability based on a single parent setting being enabled. */ - public static Availability parent(BooleanSetting parent) { - return new Availability() { - @Override - public boolean isAvailable() { - return parent.get(); - } - - @Override - public List> getParentSettings() { - return Collections.singletonList(parent); - } - }; - } - - /** - * Availability based on a single parent setting being disabled. - */ - public static Availability parentNot(BooleanSetting parent) { - return new Availability() { - @Override - public boolean isAvailable() { - return !parent.get(); - } - - @Override - public List> getParentSettings() { - return Collections.singletonList(parent); - } - }; + @NonNull + public static Availability parent(@NonNull BooleanSetting parent) { + return parent::get; } /** * Availability based on all parents being enabled. */ - public static Availability parentsAll(BooleanSetting... parents) { - return new Availability() { - @Override - public boolean isAvailable() { - for (BooleanSetting parent : parents) { - if (!parent.get()) return false; - } - return true; - } - - @Override - public List> getParentSettings() { - return Collections.unmodifiableList(Arrays.asList(parents)); + @NonNull + public static Availability parentsAll(@NonNull BooleanSetting... parents) { + return () -> { + for (BooleanSetting parent : parents) { + if (!parent.get()) return false; } + return true; }; } /** * Availability based on any parent being enabled. */ - public static Availability parentsAny(BooleanSetting... parents) { - return new Availability() { - @Override - public boolean isAvailable() { - for (BooleanSetting parent : parents) { - if (parent.get()) return true; - } - return false; - } - - @Override - public List> getParentSettings() { - return Collections.unmodifiableList(Arrays.asList(parents)); + @NonNull + public static Availability parentsAny(@NonNull BooleanSetting... parents) { + return () -> { + for (BooleanSetting parent : parents) { + if (parent.get()) return true; } + return false; }; } @@ -135,7 +81,7 @@ public abstract class Setting { /** * Adds a callback for {@link #importFromJSON(Context, String)} and {@link #exportToJson(Context)}. */ - public static void addImportExportCallback(ImportExportCallback callback) { + public static void addImportExportCallback(@NonNull ImportExportCallback callback) { importExportCallbacks.add(Objects.requireNonNull(callback)); } @@ -156,13 +102,14 @@ public abstract class Setting { public static final SharedPrefCategory preferences = new SharedPrefCategory("revanced_prefs"); @Nullable - public static Setting getSettingFromPath(String str) { + public static Setting getSettingFromPath(@NonNull String str) { return PATH_TO_SETTINGS.get(str); } /** * @return All settings that have been created. */ + @NonNull public static List> allLoadedSettings() { return Collections.unmodifiableList(SETTINGS); } @@ -170,8 +117,8 @@ public abstract class Setting { /** * @return All settings that have been created, sorted by keys. */ + @NonNull private static List> allLoadedSettingsSorted() { - //noinspection ComparatorCombinators Collections.sort(SETTINGS, (Setting o1, Setting o2) -> o1.key.compareTo(o2.key)); return allLoadedSettings(); } @@ -179,11 +126,13 @@ public abstract class Setting { /** * The key used to store the value in the shared preferences. */ + @NonNull public final String key; /** * The default value of the setting. */ + @NonNull public final T defaultValue; /** @@ -205,6 +154,7 @@ public abstract class Setting { /** * Confirmation message to display, if the user tries to change the setting from the default value. + * Currently this works only for Boolean setting types. */ @Nullable public final StringRef userDialogMessage; @@ -214,6 +164,7 @@ public abstract class Setting { /** * The value of the setting. */ + @NonNull protected volatile T value; public Setting(String key, T defaultValue) { @@ -251,8 +202,8 @@ public abstract class Setting { * @param userDialogMessage Confirmation message to display, if the user tries to change the setting from the default value. * @param availability Condition that must be true, for this setting to be available to configure. */ - public Setting(String key, - T defaultValue, + public Setting(@NonNull String key, + @NonNull T defaultValue, boolean rebootApp, boolean includeWithImportExport, @Nullable String userDialogMessage, @@ -267,35 +218,83 @@ public abstract class Setting { SETTINGS.add(this); if (PATH_TO_SETTINGS.put(key, this) != null) { - Logger.printException(() -> this.getClass().getSimpleName() + // Debug setting may not be created yet so using Logger may cause an initialization crash. + // Show a toast instead. + Utils.showToastLong(this.getClass().getSimpleName() + " error: Duplicate Setting key found: " + key); } load(); } + /** + * Migrate a setting value if the path is renamed but otherwise the old and new settings are identical. + */ + public static void migrateOldSettingToNew(@NonNull Setting oldSetting, @NonNull Setting newSetting) { + if (oldSetting == newSetting) throw new IllegalArgumentException(); + + if (!oldSetting.isSetToDefault()) { + Logger.printInfo(() -> "Migrating old setting value: " + oldSetting + " into replacement setting: " + newSetting); + newSetting.save(oldSetting.value); + oldSetting.resetToDefault(); + } + } + + /** + * Migrate an old Setting value previously stored in a different SharedPreference. + * + * This method will be deleted in the future. + */ + public static void migrateFromOldPreferences(@NonNull SharedPrefCategory oldPrefs, @NonNull Setting setting, String settingKey) { + if (!oldPrefs.preferences.contains(settingKey)) { + return; // Nothing to do. + } + + Object newValue = setting.get(); + final Object migratedValue; + if (setting instanceof BooleanSetting) { + migratedValue = oldPrefs.getBoolean(settingKey, (Boolean) newValue); + } else if (setting instanceof IntegerSetting) { + migratedValue = oldPrefs.getIntegerString(settingKey, (Integer) newValue); + } else if (setting instanceof LongSetting) { + migratedValue = oldPrefs.getLongString(settingKey, (Long) newValue); + } else if (setting instanceof FloatSetting) { + migratedValue = oldPrefs.getFloatString(settingKey, (Float) newValue); + } else if (setting instanceof StringSetting) { + migratedValue = oldPrefs.getString(settingKey, (String) newValue); + } else { + Logger.printException(() -> "Unknown setting: " + setting); + // Remove otherwise it'll show a toast on every launch + oldPrefs.preferences.edit().remove(settingKey).apply(); + return; + } + + oldPrefs.preferences.edit().remove(settingKey).apply(); // Remove the old setting. + if (migratedValue.equals(newValue)) { + Logger.printDebug(() -> "Value does not need migrating: " + settingKey); + return; // Old value is already equal to the new setting value. + } + + Logger.printDebug(() -> "Migrating old preference value into current preference: " + settingKey); + //noinspection unchecked + setting.save(migratedValue); + } + /** * Sets, but does _not_ persistently save the value. * This method is only to be used by the Settings preference code. - *

+ * * This intentionally is a static method to deter * accidental usage when {@link #save(Object)} was intended. */ - public static void privateSetValueFromString(Setting setting, String newValue) { + public static void privateSetValueFromString(@NonNull Setting setting, @NonNull String newValue) { setting.setValueFromString(newValue); - - // Clear the preference value since default is used, to allow changing - // the changing the default for a future release. Without this after upgrading - // the saved value will be whatever was the default when the app was first installed. - if (setting.isSetToDefault()) { - setting.removeFromPreferences(); - } } /** * Sets the value of {@link #value}, but do not save to {@link #preferences}. */ - protected abstract void setValueFromString(String newValue); + protected abstract void setValueFromString(@NonNull String newValue); /** * Load and set the value of {@link #value}. @@ -305,45 +304,16 @@ public abstract class Setting { /** * Persistently saves the value. */ - public final void save(T newValue) { - if (value.equals(newValue)) { - return; - } - - // Must set before saving to preferences (otherwise importing fails to update UI correctly). - value = Objects.requireNonNull(newValue); - - if (defaultValue.equals(newValue)) { - removeFromPreferences(); - } else { - saveToPreferences(); - } - } - - /** - * Save {@link #value} to {@link #preferences}. - */ - protected abstract void saveToPreferences(); - - /** - * Remove {@link #value} from {@link #preferences}. - */ - protected final void removeFromPreferences() { - Logger.printDebug(() -> "Clearing stored preference value (reset to default): " + key); - preferences.removeKey(key); - } + public abstract void save(@NonNull T newValue); @NonNull public abstract T get(); /** * Identical to calling {@link #save(Object)} using {@link #defaultValue}. - * - * @return The newly saved default value. */ - public T resetToDefault() { + public void resetToDefault() { save(defaultValue); - return defaultValue; } /** @@ -354,24 +324,13 @@ public abstract class Setting { } /** - * Get the parent Settings that this setting depends on. - * @return List of parent Settings, or empty list if no dependencies exist. - * Defensive: handles null availability or missing getParentSettings() override. - */ - public List> getParentSettings() { - return availability == null - ? Collections.emptyList() - : Objects.requireNonNullElse(availability.getParentSettings(), Collections.emptyList()); - } - - /** - * @return if the currently set value is the same as {@link #defaultValue}. + * @return if the currently set value is the same as {@link #defaultValue} */ public boolean isSetToDefault() { return value.equals(defaultValue); } - @NonNull + @NotNull @Override public String toString() { return key + "=" + get(); @@ -396,7 +355,7 @@ public abstract class Setting { /** * @param importExportKey The JSON key. The JSONObject parameter will contain data for this key. - * @return the value stored using the import/export key. Do not set any values in this method. + * @return the value stored using the import/export key. Do not set any values in this method. */ protected abstract T readFromJSON(JSONObject json, String importExportKey) throws JSONException; @@ -413,6 +372,7 @@ public abstract class Setting { json.put(importExportKey, value); } + @NonNull public static String exportToJson(@Nullable Context alertDialogContext) { try { JSONObject json = new JSONObject(); @@ -451,7 +411,7 @@ public abstract class Setting { /** * @return if any settings that require a reboot were changed. */ - public static boolean importFromJSON(Context alertDialogContext, String settingsJsonString) { + public static boolean importFromJSON(@NonNull Context alertDialogContext, @NonNull String settingsJsonString) { try { if (!settingsJsonString.matches("[\\s\\S]*\\{")) { settingsJsonString = '{' + settingsJsonString + '}'; // Restore outer JSON braces @@ -460,7 +420,6 @@ public abstract class Setting { boolean rebootSettingChanged = false; int numberOfSettingsImported = 0; - //noinspection rawtypes for (Setting setting : SETTINGS) { String key = setting.getImportExportKey(); if (json.has(key)) { @@ -482,12 +441,9 @@ public abstract class Setting { callback.settingsImported(alertDialogContext); } - // Use a delay, otherwise the toast can move about on screen from the dismissing dialog. - final int numberOfSettingsImportedFinal = numberOfSettingsImported; - Utils.runOnMainThreadDelayed(() -> Utils.showToastLong(numberOfSettingsImportedFinal == 0 - ? str("revanced_settings_import_reset") - : str("revanced_settings_import_success", numberOfSettingsImportedFinal)), - 150); + Utils.showToastLong(numberOfSettingsImported == 0 + ? str("revanced_settings_import_reset") + : str("revanced_settings_import_success", numberOfSettingsImported)); return rebootSettingChanged; } catch (JSONException | IllegalArgumentException ex) { diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/StringSetting.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/StringSetting.java index adb9beaa18..0fa5e03fc1 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/StringSetting.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/StringSetting.java @@ -55,8 +55,10 @@ public class StringSetting extends Setting { } @Override - public void saveToPreferences() { - preferences.saveString(key, value); + public void save(@NonNull String newValue) { + // Must set before saving to preferences (otherwise importing fails to update UI correctly). + value = Objects.requireNonNull(newValue); + preferences.saveString(key, newValue); } @NonNull diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/YouTubeAndMusicSettings.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/YouTubeAndMusicSettings.java deleted file mode 100644 index 221ce00456..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/YouTubeAndMusicSettings.java +++ /dev/null @@ -1,14 +0,0 @@ -package app.revanced.extension.shared.settings; - -import static app.revanced.extension.shared.settings.Setting.parent; -import static java.lang.Boolean.FALSE; - -public class YouTubeAndMusicSettings extends BaseSettings { - // Custom filter - public static final BooleanSetting CUSTOM_FILTER = new BooleanSetting("revanced_custom_filter", FALSE); - public static final StringSetting CUSTOM_FILTER_STRINGS = new StringSetting("revanced_custom_filter_strings", "", true, parent(CUSTOM_FILTER)); - - // Miscellaneous - public static final BooleanSetting DEBUG_PROTOCOLBUFFER = new BooleanSetting("revanced_debug_protocolbuffer", FALSE, false, - "revanced_debug_protocolbuffer_user_dialog_message", parent(BaseSettings.DEBUG)); -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java index a515471a00..902b95897e 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java @@ -3,67 +3,46 @@ package app.revanced.extension.shared.settings.preference; import static app.revanced.extension.shared.StringRef.str; import android.annotation.SuppressLint; -import android.app.Dialog; +import android.app.AlertDialog; import android.content.Context; import android.content.SharedPreferences; import android.os.Bundle; -import android.preference.Preference; -import android.preference.PreferenceFragment; -import android.preference.PreferenceGroup; -import android.preference.PreferenceManager; -import android.preference.PreferenceScreen; -import android.preference.SwitchPreference; -import android.preference.EditTextPreference; -import android.preference.ListPreference; -import android.util.Pair; -import android.widget.LinearLayout; +import android.preference.*; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import java.util.Objects; import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.ResourceType; import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BooleanSetting; import app.revanced.extension.shared.settings.Setting; -import app.revanced.extension.shared.ui.CustomDialog; @SuppressWarnings("deprecation") public abstract class AbstractPreferenceFragment extends PreferenceFragment { - /** * Indicates that if a preference changes, * to apply the change from the Setting to the UI component. */ public static boolean settingImportInProgress; - /** - * Prevents recursive calls during preference <-> UI syncing from showing extra dialogs. - */ - private static boolean updatingPreference; - - /** - * Used to prevent showing reboot dialog, if user cancels a setting user dialog. - */ - private static boolean showingUserDialogMessage; - /** * Confirm and restart dialog button text and title. * Set by subclasses if Strings cannot be added as a resource. */ @Nullable - protected static CharSequence restartDialogTitle, restartDialogMessage, restartDialogButtonText, confirmDialogTitle; + protected static String restartDialogButtonText, restartDialogTitle, confirmDialogTitle; + + /** + * Used to prevent showing reboot dialog, if user cancels a setting user dialog. + */ + private boolean showingUserDialogMessage; private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> { try { - if (updatingPreference) { - Logger.printDebug(() -> "Ignoring preference change as sync is in progress"); - return; - } - - Setting setting = Setting.getSettingFromPath(Objects.requireNonNull(str)); + Setting setting = Setting.getSettingFromPath(str); if (setting == null) { return; } @@ -73,29 +52,29 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment { } Logger.printDebug(() -> "Preference changed: " + setting.key); - if (!settingImportInProgress && !showingUserDialogMessage) { - if (setting.userDialogMessage != null && !prefIsSetToDefault(pref, setting)) { - // Do not change the setting yet, to allow preserving whatever - // list/text value was previously set if it needs to be reverted. - showSettingUserDialogConfirmation(pref, setting); - return; + // Apply 'Setting <- Preference', unless during importing when it needs to be 'Setting -> Preference'. + updatePreference(pref, setting, true, settingImportInProgress); + // Update any other preference availability that may now be different. + updateUIAvailability(); + + if (settingImportInProgress) { + return; + } + + if (!showingUserDialogMessage) { + if (setting.userDialogMessage != null && ((SwitchPreference) pref).isChecked() != (Boolean) setting.defaultValue) { + showSettingUserDialogConfirmation((SwitchPreference) pref, (BooleanSetting) setting); } else if (setting.rebootApp) { showRestartDialog(getContext()); } } - updatingPreference = true; - // Apply 'Setting <- Preference', unless during importing when it needs to be 'Setting -> Preference'. - // Updating here can cause a recursive call back into this same method. - updatePreference(pref, setting, true, settingImportInProgress); - // Update any other preference availability that may now be different. - updateUIAvailability(); - updatingPreference = false; } catch (Exception ex) { Logger.printException(() -> "OnSharedPreferenceChangeListener failure", ex); } }; + /** * Initialize this instance, and do any custom behavior. *

@@ -104,16 +83,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment { * so all app specific {@link Setting} instances are loaded before this method returns. */ protected void initialize() { - String preferenceResourceName; - if (BaseSettings.SHOW_MENU_ICONS.get()) { - preferenceResourceName = Utils.appIsUsingBoldIcons() - ? "revanced_prefs_icons_bold" - : "revanced_prefs_icons"; - } else { - preferenceResourceName = "revanced_prefs"; - } - - final var identifier = Utils.getResourceIdentifier(ResourceType.XML, preferenceResourceName); + final var identifier = Utils.getResourceIdentifier("revanced_prefs", "xml"); if (identifier == 0) return; addPreferencesFromResource(identifier); @@ -122,57 +92,37 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment { Utils.setPreferenceTitlesToMultiLineIfNeeded(screen); } - private void showSettingUserDialogConfirmation(Preference pref, Setting setting) { + private void showSettingUserDialogConfirmation(SwitchPreference switchPref, BooleanSetting setting) { Utils.verifyOnMainThread(); final var context = getContext(); if (confirmDialogTitle == null) { confirmDialogTitle = str("revanced_settings_confirm_user_dialog_title"); } - showingUserDialogMessage = true; - - CharSequence message = BulletPointPreference.formatIntoBulletPoints( - Objects.requireNonNull(setting.userDialogMessage).toString()); - - Pair dialogPair = CustomDialog.create( - context, - confirmDialogTitle, // Title. - message, - null, // No EditText. - null, // OK button text. - () -> { - // OK button action. User confirmed, save to the Setting. - updatePreference(pref, setting, true, false); - - // Update availability of other preferences that may be changed. - updateUIAvailability(); - + new AlertDialog.Builder(context) + .setTitle(confirmDialogTitle) + .setMessage(Objects.requireNonNull(setting.userDialogMessage).toString()) + .setPositiveButton(android.R.string.ok, (dialog, id) -> { if (setting.rebootApp) { showRestartDialog(context); } - }, - () -> { - // Cancel button action. Restore whatever the setting was before the change. - updatePreference(pref, setting, true, true); - }, - null, // No Neutral button. - null, // No Neutral button action. - true // Dismiss dialog when onNeutralClick. - ); - - dialogPair.first.setOnDismissListener(d -> showingUserDialogMessage = false); - dialogPair.first.setCancelable(false); - - // Show the dialog. - dialogPair.first.show(); + }) + .setNegativeButton(android.R.string.cancel, (dialog, id) -> { + switchPref.setChecked(setting.defaultValue); // Recursive call that resets the Setting value. + }) + .setOnDismissListener(dialog -> { + showingUserDialogMessage = false; + }) + .setCancelable(false) + .show(); } /** * Updates all Preferences values and their availability using the current values in {@link Setting}. */ protected void updateUIToSettingValues() { - updatePreferenceScreen(getPreferenceScreen(), true, true); + updatePreferenceScreen(getPreferenceScreen(), true,true); } /** @@ -182,39 +132,19 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment { updatePreferenceScreen(getPreferenceScreen(), false, false); } - /** - * @return If the preference is currently set to the default value of the Setting. - */ - protected boolean prefIsSetToDefault(Preference pref, Setting setting) { - Object defaultValue = setting.defaultValue; - if (pref instanceof SwitchPreference switchPref) { - return switchPref.isChecked() == (Boolean) defaultValue; - } - String defaultValueString = defaultValue.toString(); - if (pref instanceof EditTextPreference editPreference) { - return editPreference.getText().equals(defaultValueString); - } - if (pref instanceof ListPreference listPref) { - return listPref.getValue().equals(defaultValueString); - } - - throw new IllegalStateException("Must override method to handle " - + "preference type: " + pref.getClass()); - } - /** * Syncs all UI Preferences to any {@link Setting} they represent. */ - private void updatePreferenceScreen(@NonNull PreferenceGroup group, + private void updatePreferenceScreen(@NonNull PreferenceScreen screen, boolean syncSettingValue, boolean applySettingToPreference) { - // Alternatively this could iterate through all Settings and check for any matching Preferences, + // Alternatively this could iterate thru all Settings and check for any matching Preferences, // but there are many more Settings than UI preferences so it's more efficient to only check // the Preferences. - for (int i = 0, prefCount = group.getPreferenceCount(); i < prefCount; i++) { - Preference pref = group.getPreference(i); - if (pref instanceof PreferenceGroup subGroup) { - updatePreferenceScreen(subGroup, syncSettingValue, applySettingToPreference); + for (int i = 0, prefCount = screen.getPreferenceCount(); i < prefCount; i++) { + Preference pref = screen.getPreference(i); + if (pref instanceof PreferenceScreen) { + updatePreferenceScreen((PreferenceScreen) pref, syncSettingValue, applySettingToPreference); } else if (pref.hasKey()) { String key = pref.getKey(); Setting setting = Setting.getSettingFromPath(key); @@ -240,28 +170,30 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment { protected void syncSettingWithPreference(@NonNull Preference pref, @NonNull Setting setting, boolean applySettingToPreference) { - if (pref instanceof SwitchPreference switchPref) { + if (pref instanceof SwitchPreference) { + SwitchPreference switchPref = (SwitchPreference) pref; BooleanSetting boolSetting = (BooleanSetting) setting; if (applySettingToPreference) { switchPref.setChecked(boolSetting.get()); } else { BooleanSetting.privateSetValue(boolSetting, switchPref.isChecked()); } - } else if (pref instanceof EditTextPreference editPreference) { + } else if (pref instanceof EditTextPreference) { + EditTextPreference editPreference = (EditTextPreference) pref; if (applySettingToPreference) { editPreference.setText(setting.get().toString()); } else { Setting.privateSetValueFromString(setting, editPreference.getText()); } - } else if (pref instanceof ListPreference listPref) { + } else if (pref instanceof ListPreference) { + ListPreference listPref = (ListPreference) pref; if (applySettingToPreference) { listPref.setValue(setting.get().toString()); } else { Setting.privateSetValueFromString(setting, listPref.getValue()); } updateListPreferenceSummary(listPref, setting); - } else if (!pref.getClass().equals(Preference.class)) { - // Ignore root preference class because there is no data to sync. + } else { Logger.printException(() -> "Setting cannot be handled: " + pref.getClass() + ": " + pref); } } @@ -303,33 +235,21 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment { } } - public static void showRestartDialog(Context context) { + public static void showRestartDialog(@NonNull final Context context) { Utils.verifyOnMainThread(); if (restartDialogTitle == null) { restartDialogTitle = str("revanced_settings_restart_title"); } - if (restartDialogMessage == null) { - restartDialogMessage = str("revanced_settings_restart_dialog_message"); - } if (restartDialogButtonText == null) { restartDialogButtonText = str("revanced_settings_restart"); } - - Pair dialogPair = CustomDialog.create( - context, - restartDialogTitle, // Title. - restartDialogMessage, // Message. - null, // No EditText. - restartDialogButtonText, // OK button text. - () -> Utils.restartApp(context), // OK button action. - () -> {}, // Cancel button action (dismiss only). - null, // No Neutral button text. - null, // No Neutral button action. - true // Dismiss dialog when onNeutralClick. - ); - - // Show the dialog. - dialogPair.first.show(); + new AlertDialog.Builder(context) + .setMessage(restartDialogTitle) + .setPositiveButton(restartDialogButtonText, (dialog, id) + -> Utils.restartApp(context)) + .setNegativeButton(android.R.string.cancel, null) + .setCancelable(false) + .show(); } @SuppressLint("ResourceType") diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/BulletPointPreference.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/BulletPointPreference.java deleted file mode 100644 index ee3f02fc8c..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/BulletPointPreference.java +++ /dev/null @@ -1,86 +0,0 @@ -package app.revanced.extension.shared.settings.preference; - -import android.content.Context; -import android.preference.Preference; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import android.text.SpannedString; -import android.text.TextUtils; -import android.text.style.BulletSpan; -import android.util.AttributeSet; - -/** - * Formats the summary text bullet points into Spanned text for better presentation. - */ -@SuppressWarnings({"unused", "deprecation"}) -public class BulletPointPreference extends Preference { - - /** - * Replaces bullet points with styled spans. - */ - public static CharSequence formatIntoBulletPoints(CharSequence source) { - final char bulletPoint = '•'; - if (TextUtils.indexOf(source, bulletPoint) < 0) { - return source; // Nothing to do. - } - - SpannableStringBuilder builder = new SpannableStringBuilder(source); - - int lineStart = 0; - int length = builder.length(); - - while (lineStart < length) { - int lineEnd = TextUtils.indexOf(builder, '\n', lineStart); - if (lineEnd < 0) lineEnd = length; - - // Apply BulletSpan only if the line starts with the '•' character. - if (lineEnd > lineStart && builder.charAt(lineStart) == bulletPoint) { - int deleteEnd = lineStart + 1; // remove the bullet itself - - // If there's a single space right after the bullet, remove that too. - if (deleteEnd < builder.length() && builder.charAt(deleteEnd) == ' ') { - deleteEnd++; - } - - builder.delete(lineStart, deleteEnd); - - // Apply the BulletSpan to the remainder of that line. - builder.setSpan(new BulletSpan(20), - lineStart, - lineEnd - (deleteEnd - lineStart), // adjust for deleted chars. - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE - ); - - // Update total length and lineEnd after deletion. - length = builder.length(); - final int removed = deleteEnd - lineStart; - lineEnd -= removed; - } - - lineStart = lineEnd + 1; - } - - return new SpannedString(builder); - } - - public BulletPointPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - public BulletPointPreference(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - public BulletPointPreference(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public BulletPointPreference(Context context) { - super(context); - } - - @Override - public void setSummary(CharSequence summary) { - super.setSummary(formatIntoBulletPoints(summary)); - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/BulletPointSwitchPreference.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/BulletPointSwitchPreference.java deleted file mode 100644 index ccbbf1eef9..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/BulletPointSwitchPreference.java +++ /dev/null @@ -1,45 +0,0 @@ -package app.revanced.extension.shared.settings.preference; - -import static app.revanced.extension.shared.settings.preference.BulletPointPreference.formatIntoBulletPoints; - -import android.content.Context; -import android.preference.SwitchPreference; -import android.util.AttributeSet; - -/** - * Formats the summary text bullet points into Spanned text for better presentation. - */ -@SuppressWarnings({"unused", "deprecation"}) -public class BulletPointSwitchPreference extends SwitchPreference { - - public BulletPointSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - public BulletPointSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - public BulletPointSwitchPreference(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public BulletPointSwitchPreference(Context context) { - super(context); - } - - @Override - public void setSummary(CharSequence summary) { - super.setSummary(formatIntoBulletPoints(summary)); - } - - @Override - public void setSummaryOn(CharSequence summaryOn) { - super.setSummaryOn(formatIntoBulletPoints(summaryOn)); - } - - @Override - public void setSummaryOff(CharSequence summaryOff) { - super.setSummaryOff(formatIntoBulletPoints(summaryOff)); - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ClearLogBufferPreference.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ClearLogBufferPreference.java deleted file mode 100644 index 7dbf0dd387..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ClearLogBufferPreference.java +++ /dev/null @@ -1,33 +0,0 @@ -package app.revanced.extension.shared.settings.preference; - -import android.content.Context; -import android.util.AttributeSet; -import android.preference.Preference; - -/** - * A custom preference that clears the ReVanced debug log buffer when clicked. - * Invokes the {@link LogBufferManager#clearLogBuffer} method. - */ -@SuppressWarnings("unused") -public class ClearLogBufferPreference extends Preference { - - { - setOnPreferenceClickListener(pref -> { - LogBufferManager.clearLogBuffer(); - return true; - }); - } - - public ClearLogBufferPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - public ClearLogBufferPreference(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - public ClearLogBufferPreference(Context context, AttributeSet attrs) { - super(context, attrs); - } - public ClearLogBufferPreference(Context context) { - super(context); - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ColorPickerPreference.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ColorPickerPreference.java deleted file mode 100644 index c9fc7b6da9..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ColorPickerPreference.java +++ /dev/null @@ -1,476 +0,0 @@ -package app.revanced.extension.shared.settings.preference; - -import static app.revanced.extension.shared.StringRef.str; -import static app.revanced.extension.shared.Utils.getResourceIdentifierOrThrow; - -import android.app.Dialog; -import android.content.Context; -import android.graphics.Color; -import android.graphics.Typeface; -import android.os.Build; -import android.os.Bundle; -import android.preference.EditTextPreference; -import android.text.Editable; -import android.text.InputType; -import android.text.TextWatcher; -import android.util.AttributeSet; -import android.util.Pair; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.widget.EditText; -import android.widget.LinearLayout; -import android.widget.ScrollView; - -import androidx.annotation.ColorInt; -import androidx.annotation.Nullable; - -import java.util.Locale; -import java.util.regex.Pattern; - -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.ResourceType; -import app.revanced.extension.shared.Utils; -import app.revanced.extension.shared.settings.Setting; -import app.revanced.extension.shared.settings.StringSetting; -import app.revanced.extension.shared.ui.ColorDot; -import app.revanced.extension.shared.ui.CustomDialog; -import app.revanced.extension.shared.ui.Dim; - -/** - * A custom preference for selecting a color via a hexadecimal code or a color picker dialog. - * Extends {@link EditTextPreference} to display a colored dot in the widget area, - * reflecting the currently selected color. The dot is dimmed when the preference is disabled. - */ -@SuppressWarnings({"unused", "deprecation"}) -public class ColorPickerPreference extends EditTextPreference { - /** Length of a valid color string of format #RRGGBB (without alpha) or #AARRGGBB (with alpha). */ - public static final int COLOR_STRING_LENGTH_WITHOUT_ALPHA = 7; - public static final int COLOR_STRING_LENGTH_WITH_ALPHA = 9; - - /** Matches everything that is not a hex number/letter. */ - private static final Pattern PATTERN_NOT_HEX = Pattern.compile("[^0-9A-Fa-f]"); - - /** Alpha for dimming when the preference is disabled. */ - public static final float DISABLED_ALPHA = 0.5f; // 50% - - /** View displaying a colored dot in the widget area. */ - private View widgetColorDot; - - /** Dialog View displaying a colored dot for the selected color preview in the dialog. */ - private View dialogColorDot; - - /** Current color, including alpha channel if opacity slider is enabled. */ - @ColorInt - private int currentColor; - - /** Associated setting for storing the color value. */ - private StringSetting colorSetting; - - /** Dialog TextWatcher for the EditText to monitor color input changes. */ - private TextWatcher colorTextWatcher; - - /** Dialog color picker view. */ - protected ColorPickerView dialogColorPickerView; - - /** Listener for color changes. */ - protected OnColorChangeListener colorChangeListener; - - /** Whether the opacity slider is enabled. */ - private boolean opacitySliderEnabled = false; - - public static final int ID_REVANCED_COLOR_PICKER_VIEW = - getResourceIdentifierOrThrow(ResourceType.ID, "revanced_color_picker_view"); - public static final int ID_PREFERENCE_COLOR_DOT = - getResourceIdentifierOrThrow(ResourceType.ID, "preference_color_dot"); - public static final int LAYOUT_REVANCED_COLOR_DOT_WIDGET = - getResourceIdentifierOrThrow(ResourceType.LAYOUT, "revanced_color_dot_widget"); - public static final int LAYOUT_REVANCED_COLOR_PICKER = - getResourceIdentifierOrThrow(ResourceType.LAYOUT, "revanced_color_picker"); - - /** - * Removes non valid hex characters, converts to all uppercase, - * and adds # character to the start if not present. - */ - public static String cleanupColorCodeString(String colorString, boolean includeAlpha) { - String result = "#" + PATTERN_NOT_HEX.matcher(colorString) - .replaceAll("").toUpperCase(Locale.ROOT); - - int maxLength = includeAlpha ? COLOR_STRING_LENGTH_WITH_ALPHA : COLOR_STRING_LENGTH_WITHOUT_ALPHA; - if (result.length() < maxLength) { - return result; - } - - return result.substring(0, maxLength); - } - - /** - * @param color Color, with or without alpha channel. - * @param includeAlpha Whether to include the alpha channel in the output string. - * @return #RRGGBB or #AARRGGBB hex color string - */ - public static String getColorString(@ColorInt int color, boolean includeAlpha) { - if (includeAlpha) { - return String.format("#%08X", color); - } - color = color & 0x00FFFFFF; // Mask to strip alpha. - return String.format("#%06X", color); - } - - /** - * Interface for notifying color changes. - */ - public interface OnColorChangeListener { - void onColorChanged(String key, int newColor); - } - - /** - * Sets the listener for color changes. - */ - public void setOnColorChangeListener(OnColorChangeListener listener) { - this.colorChangeListener = listener; - } - - /** - * Enables or disables the opacity slider in the color picker dialog. - */ - public void setOpacitySliderEnabled(boolean enabled) { - this.opacitySliderEnabled = enabled; - } - - public ColorPickerPreference(Context context) { - super(context); - init(); - } - - public ColorPickerPreference(Context context, AttributeSet attrs) { - super(context, attrs); - init(); - } - - public ColorPickerPreference(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(); - } - - /** - * Initializes the preference by setting up the EditText, loading the color, and set the widget layout. - */ - private void init() { - if (getKey() != null) { - colorSetting = (StringSetting) Setting.getSettingFromPath(getKey()); - if (colorSetting == null) { - Logger.printException(() -> "Could not find color setting for: " + getKey()); - } - } else { - Logger.printDebug(() -> "initialized without key, settings will be loaded later"); - } - - EditText editText = getEditText(); - editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS - | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - editText.setAutofillHints((String) null); - } - - // Set the widget layout to a custom layout containing the colored dot. - setWidgetLayoutResource(LAYOUT_REVANCED_COLOR_DOT_WIDGET); - } - - /** - * Sets the selected color and updates the UI and settings. - */ - @Override - public void setText(String colorString) { - try { - Logger.printDebug(() -> "setText: " + colorString); - super.setText(colorString); - - currentColor = Color.parseColor(colorString); - if (colorSetting != null) { - colorSetting.save(getColorString(currentColor, opacitySliderEnabled)); - } - updateDialogColorDot(); - updateWidgetColorDot(); - - // Notify the listener about the color change. - if (colorChangeListener != null) { - colorChangeListener.onColorChanged(getKey(), currentColor); - } - } catch (IllegalArgumentException ex) { - // This code is reached if the user pastes settings json with an invalid color - // since this preference is updated with the new setting text. - Logger.printDebug(() -> "Parse color error: " + colorString, ex); - Utils.showToastShort(str("revanced_settings_color_invalid")); - setText(colorSetting.resetToDefault()); - } catch (Exception ex) { - Logger.printException(() -> "setText failure: " + colorString, ex); - } - } - - /** - * Creates a TextWatcher to monitor changes in the EditText for color input. - */ - private TextWatcher createColorTextWatcher(ColorPickerView colorPickerView) { - return new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - - @Override - public void afterTextChanged(Editable edit) { - try { - String colorString = edit.toString(); - String sanitizedColorString = cleanupColorCodeString(colorString, opacitySliderEnabled); - if (!sanitizedColorString.equals(colorString)) { - edit.replace(0, colorString.length(), sanitizedColorString); - return; - } - - int expectedLength = opacitySliderEnabled - ? COLOR_STRING_LENGTH_WITH_ALPHA - : COLOR_STRING_LENGTH_WITHOUT_ALPHA; - if (sanitizedColorString.length() != expectedLength) { - return; - } - - final int newColor = Color.parseColor(colorString); - if (currentColor != newColor) { - Logger.printDebug(() -> "afterTextChanged: " + sanitizedColorString); - currentColor = newColor; - updateDialogColorDot(); - updateWidgetColorDot(); - colorPickerView.setColor(newColor); - } - } catch (Exception ex) { - // Should never be reached since input is validated before using. - Logger.printException(() -> "afterTextChanged failure", ex); - } - } - }; - } - - /** - * Hook for subclasses to add a custom view to the top of the dialog. - */ - @Nullable - protected View createExtraDialogContentView(Context context) { - return null; // Default implementation returns no extra view. - } - - /** - * Hook for subclasses to handle the OK button click. - */ - protected void onDialogOkClicked() { - // Default implementation does nothing. - } - - /** - * Hook for subclasses to handle the Neutral button click. - */ - protected void onDialogNeutralClicked() { - // Default implementation. - try { - final int defaultColor = Color.parseColor(colorSetting.defaultValue); - dialogColorPickerView.setColor(defaultColor); - } catch (Exception ex) { - Logger.printException(() -> "Reset button failure", ex); - } - } - - @Override - protected void showDialog(Bundle state) { - Context context = getContext(); - - // Create content container for all dialog views. - LinearLayout contentContainer = new LinearLayout(context); - contentContainer.setOrientation(LinearLayout.VERTICAL); - - // Add extra view from subclass if it exists. - View extraView = createExtraDialogContentView(context); - if (extraView != null) { - contentContainer.addView(extraView); - } - - // Inflate color picker view. - View colorPicker = LayoutInflater.from(context).inflate(LAYOUT_REVANCED_COLOR_PICKER, null); - dialogColorPickerView = colorPicker.findViewById(ID_REVANCED_COLOR_PICKER_VIEW); - dialogColorPickerView.setOpacitySliderEnabled(opacitySliderEnabled); - dialogColorPickerView.setColor(currentColor); - contentContainer.addView(colorPicker); - - // Horizontal layout for preview and EditText. - LinearLayout inputLayout = new LinearLayout(context); - inputLayout.setOrientation(LinearLayout.HORIZONTAL); - inputLayout.setGravity(Gravity.CENTER_VERTICAL); - - dialogColorDot = new View(context); - LinearLayout.LayoutParams previewParams = new LinearLayout.LayoutParams(Dim.dp20,Dim.dp20); - previewParams.setMargins(Dim.dp16, 0, Dim.dp10, 0); - dialogColorDot.setLayoutParams(previewParams); - inputLayout.addView(dialogColorDot); - updateDialogColorDot(); - - EditText editText = getEditText(); - ViewParent parent = editText.getParent(); - if (parent instanceof ViewGroup parentViewGroup) { - parentViewGroup.removeView(editText); - } - editText.setLayoutParams(new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.WRAP_CONTENT - )); - String currentColorString = getColorString(currentColor, opacitySliderEnabled); - editText.setText(currentColorString); - editText.setSelection(currentColorString.length()); - editText.setTypeface(Typeface.MONOSPACE); - colorTextWatcher = createColorTextWatcher(dialogColorPickerView); - editText.addTextChangedListener(colorTextWatcher); - inputLayout.addView(editText); - - // Add a dummy view to take up remaining horizontal space, - // otherwise it will show an oversize underlined text view. - View paddingView = new View(context); - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( - 0, - LinearLayout.LayoutParams.MATCH_PARENT, - 1f - ); - paddingView.setLayoutParams(params); - inputLayout.addView(paddingView); - - contentContainer.addView(inputLayout); - - // Create ScrollView to wrap the content container. - ScrollView contentScrollView = new ScrollView(context); - contentScrollView.setVerticalScrollBarEnabled(false); - contentScrollView.setOverScrollMode(View.OVER_SCROLL_NEVER); - LinearLayout.LayoutParams scrollViewParams = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - 0, - 1.0f - ); - contentScrollView.setLayoutParams(scrollViewParams); - contentScrollView.addView(contentContainer); - - final int originalColor = currentColor; - Pair dialogPair = CustomDialog.create( - context, - getTitle() != null ? getTitle().toString() : str("revanced_settings_color_picker_title"), - null, - null, - null, - () -> { // OK button action. - try { - String colorString = editText.getText().toString(); - int expectedLength = opacitySliderEnabled - ? COLOR_STRING_LENGTH_WITH_ALPHA - : COLOR_STRING_LENGTH_WITHOUT_ALPHA; - if (colorString.length() != expectedLength) { - Utils.showToastShort(str("revanced_settings_color_invalid")); - setText(getColorString(originalColor, opacitySliderEnabled)); - return; - } - setText(colorString); - - onDialogOkClicked(); - } catch (Exception ex) { - // Should never happen due to a bad color string, - // since the text is validated and fixed while the user types. - Logger.printException(() -> "OK button failure", ex); - } - }, - () -> { // Cancel button action. - try { - setText(getColorString(originalColor, opacitySliderEnabled)); - } catch (Exception ex) { - Logger.printException(() -> "Cancel button failure", ex); - } - }, - str("revanced_settings_reset_color"), // Neutral button text. - this::onDialogNeutralClicked, // Neutral button action. - false // Do not dismiss dialog. - ); - - // Add the ScrollView to the dialog's main layout. - LinearLayout dialogMainLayout = dialogPair.second; - dialogMainLayout.addView(contentScrollView, dialogMainLayout.getChildCount() - 1); - - // Set up color picker listener with debouncing. - // Add listener last to prevent callbacks from set calls above. - dialogColorPickerView.setOnColorChangedListener(color -> { - // Check if it actually changed, since this callback - // can be caused by updates in afterTextChanged(). - if (currentColor == color) { - return; - } - - String updatedColorString = getColorString(color, opacitySliderEnabled); - Logger.printDebug(() -> "onColorChanged: " + updatedColorString); - currentColor = color; - editText.setText(updatedColorString); - editText.setSelection(updatedColorString.length()); - - updateDialogColorDot(); - updateWidgetColorDot(); - }); - - // Configure and show the dialog. - Dialog dialog = dialogPair.first; - dialog.setCanceledOnTouchOutside(false); - dialog.show(); - } - - @Override - protected void onDialogClosed(boolean positiveResult) { - super.onDialogClosed(positiveResult); - - if (colorTextWatcher != null) { - getEditText().removeTextChangedListener(colorTextWatcher); - colorTextWatcher = null; - } - - dialogColorDot = null; - dialogColorPickerView = null; - } - - @Override - public void setEnabled(boolean enabled) { - super.setEnabled(enabled); - updateWidgetColorDot(); - } - - @Override - protected void onBindView(View view) { - super.onBindView(view); - - widgetColorDot = view.findViewById(ID_PREFERENCE_COLOR_DOT); - updateWidgetColorDot(); - } - - private void updateWidgetColorDot() { - if (widgetColorDot == null) return; - - ColorDot.applyColorDot( - widgetColorDot, - currentColor, - widgetColorDot.isEnabled() - ); - } - - private void updateDialogColorDot() { - if (dialogColorDot == null) return; - - ColorDot.applyColorDot( - dialogColorDot, - currentColor, - true - ); - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ColorPickerView.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ColorPickerView.java deleted file mode 100644 index b8c9577112..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ColorPickerView.java +++ /dev/null @@ -1,639 +0,0 @@ -package app.revanced.extension.shared.settings.preference; - -import static app.revanced.extension.shared.settings.preference.ColorPickerPreference.getColorString; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.ComposeShader; -import android.graphics.LinearGradient; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.RectF; -import android.graphics.Shader; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; - -import androidx.annotation.ColorInt; - -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.Utils; -import app.revanced.extension.shared.ui.Dim; - -/** - * A custom color picker view that allows the user to select a color using a hue slider, a saturation-value selector - * and an optional opacity slider. - * This implementation is density-independent and responsive across different screen sizes and DPIs. - *

- * This view displays three main components for color selection: - *

    - *
  • Hue Bar: A horizontal bar at the bottom that allows the user to select the hue component of the color. - *
  • Saturation-Value Selector: A rectangular area above the hue bar that allows the user to select the - * saturation and value (brightness) components of the color based on the selected hue. - *
  • Opacity Slider: An optional horizontal bar below the hue bar that allows the user to adjust - * the opacity (alpha channel) of the color. - *
- *

- * The view uses {@link LinearGradient} and {@link ComposeShader} to create the color gradients for the hue bar, - * opacity slider, and the saturation-value selector. It also uses {@link Paint} to draw the selectors (draggable handles). - *

- * The selected color can be retrieved using {@link #getColor()} and can be set using {@link #setColor(int)}. - * An {@link OnColorChangedListener} can be registered to receive notifications when the selected color changes. - */ -public class ColorPickerView extends View { - /** - * Interface definition for a callback to be invoked when the selected color changes. - */ - public interface OnColorChangedListener { - /** - * Called when the selected color has changed. - */ - void onColorChanged(@ColorInt int color); - } - - /** Expanded touch area for the hue and opacity bars to increase the touch-sensitive area. */ - public static final float TOUCH_EXPANSION = Dim.dp20; - - /** Margin between different areas of the view (saturation-value selector, hue bar, and opacity slider). */ - private static final float MARGIN_BETWEEN_AREAS = Dim.dp24; - - /** Padding around the view. */ - private static final float VIEW_PADDING = Dim.dp16; - - /** Height of the hue bar. */ - private static final float HUE_BAR_HEIGHT = Dim.dp12; - - /** Height of the opacity slider. */ - private static final float OPACITY_BAR_HEIGHT = Dim.dp12; - - /** Corner radius for the hue bar. */ - private static final float HUE_CORNER_RADIUS = Dim.dp6; - - /** Corner radius for the opacity slider. */ - private static final float OPACITY_CORNER_RADIUS = Dim.dp6; - - /** Radius of the selector handles. */ - private static final float SELECTOR_RADIUS = Dim.dp12; - - /** Stroke width for the selector handle outlines. */ - private static final float SELECTOR_STROKE_WIDTH = 8; - - /** - * Hue and opacity fill radius. Use slightly smaller radius for the selector handle fill, - * otherwise the anti-aliasing causes the fill color to bleed past the selector outline. - */ - private static final float SELECTOR_FILL_RADIUS = SELECTOR_RADIUS - SELECTOR_STROKE_WIDTH / 2; - - /** Thin dark outline stroke width for the selector rings. */ - private static final float SELECTOR_EDGE_STROKE_WIDTH = 1; - - /** Radius for the outer edge of the selector rings, including stroke width. */ - public static final float SELECTOR_EDGE_RADIUS = - SELECTOR_RADIUS + SELECTOR_STROKE_WIDTH / 2 + SELECTOR_EDGE_STROKE_WIDTH / 2; - - /** Selector outline inner color. */ - @ColorInt - private static final int SELECTOR_OUTLINE_COLOR = Color.WHITE; - - /** Dark edge color for the selector rings. */ - @ColorInt - private static final int SELECTOR_EDGE_COLOR = Color.parseColor("#CFCFCF"); - - /** Precomputed array of hue colors for the hue bar (0-360 degrees). */ - private static final int[] HUE_COLORS = new int[361]; - static { - for (int i = 0; i < 361; i++) { - HUE_COLORS[i] = Color.HSVToColor(new float[]{i, 1, 1}); - } - } - - /** Paint for the hue bar. */ - private final Paint huePaint = new Paint(Paint.ANTI_ALIAS_FLAG); - - /** Paint for the opacity slider. */ - private final Paint opacityPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - - /** Paint for the saturation-value selector. */ - private final Paint saturationValuePaint = new Paint(Paint.ANTI_ALIAS_FLAG); - - /** Paint for the draggable selector handles. */ - private final Paint selectorPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - { - selectorPaint.setStrokeWidth(SELECTOR_STROKE_WIDTH); - } - - /** Bounds of the hue bar. */ - private final RectF hueRect = new RectF(); - - /** Bounds of the opacity slider. */ - private final RectF opacityRect = new RectF(); - - /** Bounds of the saturation-value selector. */ - private final RectF saturationValueRect = new RectF(); - - /** HSV color calculations to avoid allocations during drawing. */ - private final float[] hsvArray = {1, 1, 1}; - - /** Current hue value (0-360). */ - private float hue = 0f; - - /** Current saturation value (0-1). */ - private float saturation = 1f; - - /** Current value (brightness) value (0-1). */ - private float value = 1f; - - /** Current opacity value (0-1). */ - private float opacity = 1f; - - /** The currently selected color, including alpha channel if opacity slider is enabled. */ - @ColorInt - private int selectedColor; - - /** Listener for color change events. */ - private OnColorChangedListener colorChangedListener; - - /** Tracks if the hue selector is being dragged. */ - private boolean isDraggingHue; - - /** Tracks if the saturation-value selector is being dragged. */ - private boolean isDraggingSaturation; - - /** Tracks if the opacity selector is being dragged. */ - private boolean isDraggingOpacity; - - /** Flag to enable/disable the opacity slider. */ - private boolean opacitySliderEnabled = false; - - public ColorPickerView(Context context) { - super(context); - } - - public ColorPickerView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public ColorPickerView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - /** - * Enables or disables the opacity slider. - */ - public void setOpacitySliderEnabled(boolean enabled) { - if (opacitySliderEnabled != enabled) { - opacitySliderEnabled = enabled; - if (!enabled) { - opacity = 1f; // Reset to fully opaque when disabled. - updateSelectedColor(); - } - updateOpacityShader(); - requestLayout(); // Trigger re-measure to account for opacity slider. - invalidate(); - } - } - - /** - * Measures the view, ensuring a consistent aspect ratio and minimum dimensions. - */ - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final float DESIRED_ASPECT_RATIO = 0.8f; // height = width * 0.8 - - final int minWidth = Dim.dp(250); - final int minHeight = (int) (minWidth * DESIRED_ASPECT_RATIO) + (int) (HUE_BAR_HEIGHT + MARGIN_BETWEEN_AREAS) - + (opacitySliderEnabled ? (int) (OPACITY_BAR_HEIGHT + MARGIN_BETWEEN_AREAS) : 0); - - int width = resolveSize(minWidth, widthMeasureSpec); - int height = resolveSize(minHeight, heightMeasureSpec); - - // Ensure minimum dimensions for usability. - width = Math.max(width, minWidth); - height = Math.max(height, minHeight); - - // Adjust height to maintain desired aspect ratio if possible. - final int desiredHeight = (int) (width * DESIRED_ASPECT_RATIO) + (int) (HUE_BAR_HEIGHT + MARGIN_BETWEEN_AREAS) - + (opacitySliderEnabled ? (int) (OPACITY_BAR_HEIGHT + MARGIN_BETWEEN_AREAS) : 0); - if (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY) { - height = desiredHeight; - } - - setMeasuredDimension(width, height); - } - - /** - * Updates the view's layout when its size changes, recalculating bounds and shaders. - */ - @Override - protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) { - super.onSizeChanged(width, height, oldWidth, oldHeight); - - // Calculate bounds with hue bar and optional opacity bar at the bottom. - final float effectiveWidth = width - (2 * VIEW_PADDING); - final float effectiveHeight = height - (2 * VIEW_PADDING) - HUE_BAR_HEIGHT - MARGIN_BETWEEN_AREAS - - (opacitySliderEnabled ? OPACITY_BAR_HEIGHT + MARGIN_BETWEEN_AREAS : 0); - - // Adjust rectangles to account for padding and density-independent dimensions. - saturationValueRect.set( - VIEW_PADDING, - VIEW_PADDING, - VIEW_PADDING + effectiveWidth, - VIEW_PADDING + effectiveHeight - ); - - hueRect.set( - VIEW_PADDING, - height - VIEW_PADDING - HUE_BAR_HEIGHT - (opacitySliderEnabled ? OPACITY_BAR_HEIGHT + MARGIN_BETWEEN_AREAS : 0), - VIEW_PADDING + effectiveWidth, - height - VIEW_PADDING - (opacitySliderEnabled ? OPACITY_BAR_HEIGHT + MARGIN_BETWEEN_AREAS : 0) - ); - - if (opacitySliderEnabled) { - opacityRect.set( - VIEW_PADDING, - height - VIEW_PADDING - OPACITY_BAR_HEIGHT, - VIEW_PADDING + effectiveWidth, - height - VIEW_PADDING - ); - } - - // Update the shaders. - updateHueShader(); - updateSaturationValueShader(); - updateOpacityShader(); - } - - /** - * Updates the shader for the hue bar to reflect the color gradient. - */ - private void updateHueShader() { - LinearGradient hueShader = new LinearGradient( - hueRect.left, hueRect.top, - hueRect.right, hueRect.top, - HUE_COLORS, - null, - Shader.TileMode.CLAMP - ); - - huePaint.setShader(hueShader); - } - - /** - * Updates the shader for the opacity slider to reflect the current RGB color with varying opacity. - */ - private void updateOpacityShader() { - if (!opacitySliderEnabled) { - opacityPaint.setShader(null); - return; - } - - // Create a linear gradient for opacity from transparent to opaque, using the current RGB color. - int rgbColor = Color.HSVToColor(0, new float[]{hue, saturation, value}); - LinearGradient opacityShader = new LinearGradient( - opacityRect.left, opacityRect.top, - opacityRect.right, opacityRect.top, - rgbColor & 0x00FFFFFF, // Fully transparent - rgbColor | 0xFF000000, // Fully opaque - Shader.TileMode.CLAMP - ); - - opacityPaint.setShader(opacityShader); - } - - /** - * Updates the shader for the saturation-value selector to reflect the current hue. - */ - private void updateSaturationValueShader() { - // Create a saturation-value gradient based on the current hue. - // Calculate the start color (white with the selected hue) for the saturation gradient. - final int startColor = Color.HSVToColor(new float[]{hue, 0f, 1f}); - - // Calculate the middle color (fully saturated color with the selected hue) for the saturation gradient. - final int midColor = Color.HSVToColor(new float[]{hue, 1f, 1f}); - - // Create a linear gradient for the saturation from startColor to midColor (horizontal). - LinearGradient satShader = new LinearGradient( - saturationValueRect.left, saturationValueRect.top, - saturationValueRect.right, saturationValueRect.top, - startColor, - midColor, - Shader.TileMode.CLAMP - ); - - // Create a linear gradient for the value (brightness) from white to black (vertical). - LinearGradient valShader = new LinearGradient( - saturationValueRect.left, saturationValueRect.top, - saturationValueRect.left, saturationValueRect.bottom, - Color.WHITE, - Color.BLACK, - Shader.TileMode.CLAMP - ); - - // Combine the saturation and value shaders using PorterDuff.Mode.MULTIPLY to create the final color. - ComposeShader combinedShader = new ComposeShader(satShader, valShader, PorterDuff.Mode.MULTIPLY); - - // Set the combined shader for the saturation-value paint. - saturationValuePaint.setShader(combinedShader); - } - - /** - * Draws the color picker components, including the saturation-value selector, hue bar, opacity slider, and their respective handles. - */ - @Override - protected void onDraw(Canvas canvas) { - // Draw the saturation-value selector rectangle. - canvas.drawRect(saturationValueRect, saturationValuePaint); - - // Draw the hue bar. - canvas.drawRoundRect(hueRect, HUE_CORNER_RADIUS, HUE_CORNER_RADIUS, huePaint); - - // Draw the opacity bar if enabled. - if (opacitySliderEnabled) { - canvas.drawRoundRect(opacityRect, OPACITY_CORNER_RADIUS, OPACITY_CORNER_RADIUS, opacityPaint); - } - - final float hueSelectorX = hueRect.left + (hue / 360f) * hueRect.width(); - final float hueSelectorY = hueRect.centerY(); - - final float satSelectorX = saturationValueRect.left + saturation * saturationValueRect.width(); - final float satSelectorY = saturationValueRect.top + (1 - value) * saturationValueRect.height(); - - // Draw the saturation and hue selector handles filled with their respective colors (fully opaque). - hsvArray[0] = hue; - final int hueHandleColor = Color.HSVToColor(0xFF, hsvArray); // Force opaque for hue handle. - final int satHandleColor = Color.HSVToColor(0xFF, new float[]{hue, saturation, value}); // Force opaque for sat-val handle. - selectorPaint.setStyle(Paint.Style.FILL_AND_STROKE); - - selectorPaint.setColor(hueHandleColor); - canvas.drawCircle(hueSelectorX, hueSelectorY, SELECTOR_FILL_RADIUS, selectorPaint); - - selectorPaint.setColor(satHandleColor); - canvas.drawCircle(satSelectorX, satSelectorY, SELECTOR_FILL_RADIUS, selectorPaint); - - if (opacitySliderEnabled) { - final float opacitySelectorX = opacityRect.left + opacity * opacityRect.width(); - final float opacitySelectorY = opacityRect.centerY(); - selectorPaint.setColor(selectedColor); // Use full ARGB color to show opacity. - canvas.drawCircle(opacitySelectorX, opacitySelectorY, SELECTOR_FILL_RADIUS, selectorPaint); - } - - // Draw white outlines for the handles. - selectorPaint.setColor(SELECTOR_OUTLINE_COLOR); - selectorPaint.setStyle(Paint.Style.STROKE); - selectorPaint.setStrokeWidth(SELECTOR_STROKE_WIDTH); - canvas.drawCircle(hueSelectorX, hueSelectorY, SELECTOR_RADIUS, selectorPaint); - canvas.drawCircle(satSelectorX, satSelectorY, SELECTOR_RADIUS, selectorPaint); - if (opacitySliderEnabled) { - final float opacitySelectorX = opacityRect.left + opacity * opacityRect.width(); - final float opacitySelectorY = opacityRect.centerY(); - canvas.drawCircle(opacitySelectorX, opacitySelectorY, SELECTOR_RADIUS, selectorPaint); - } - - // Draw thin dark outlines for the handles at the outer edge of the white outline. - selectorPaint.setColor(SELECTOR_EDGE_COLOR); - selectorPaint.setStrokeWidth(SELECTOR_EDGE_STROKE_WIDTH); - canvas.drawCircle(hueSelectorX, hueSelectorY, SELECTOR_EDGE_RADIUS, selectorPaint); - canvas.drawCircle(satSelectorX, satSelectorY, SELECTOR_EDGE_RADIUS, selectorPaint); - if (opacitySliderEnabled) { - final float opacitySelectorX = opacityRect.left + opacity * opacityRect.width(); - final float opacitySelectorY = opacityRect.centerY(); - canvas.drawCircle(opacitySelectorX, opacitySelectorY, SELECTOR_EDGE_RADIUS, selectorPaint); - } - } - - /** - * Handles touch events to allow dragging of the hue, saturation-value, and opacity selectors. - * - * @param event The motion event. - * @return True if the event was handled, false otherwise. - */ - @SuppressLint("ClickableViewAccessibility") - @Override - public boolean onTouchEvent(MotionEvent event) { - try { - final float x = event.getX(); - final float y = event.getY(); - final int action = event.getAction(); - Logger.printDebug(() -> "onTouchEvent action: " + action + " x: " + x + " y: " + y); - - // Define touch expansion for the hue and opacity bars. - RectF expandedHueRect = new RectF( - hueRect.left, - hueRect.top - TOUCH_EXPANSION, - hueRect.right, - hueRect.bottom + TOUCH_EXPANSION - ); - RectF expandedOpacityRect = opacitySliderEnabled ? new RectF( - opacityRect.left, - opacityRect.top - TOUCH_EXPANSION, - opacityRect.right, - opacityRect.bottom + TOUCH_EXPANSION - ) : new RectF(); - - switch (action) { - case MotionEvent.ACTION_DOWN: - // Calculate current handle positions. - final float hueSelectorX = hueRect.left + (hue / 360f) * hueRect.width(); - final float hueSelectorY = hueRect.centerY(); - - final float satSelectorX = saturationValueRect.left + saturation * saturationValueRect.width(); - final float valSelectorY = saturationValueRect.top + (1 - value) * saturationValueRect.height(); - - final float opacitySelectorX = opacitySliderEnabled ? opacityRect.left + opacity * opacityRect.width() : 0; - final float opacitySelectorY = opacitySliderEnabled ? opacityRect.centerY() : 0; - - // Create hit areas for all handles. - RectF hueHitRect = new RectF( - hueSelectorX - SELECTOR_RADIUS, - hueSelectorY - SELECTOR_RADIUS, - hueSelectorX + SELECTOR_RADIUS, - hueSelectorY + SELECTOR_RADIUS - ); - RectF satValHitRect = new RectF( - satSelectorX - SELECTOR_RADIUS, - valSelectorY - SELECTOR_RADIUS, - satSelectorX + SELECTOR_RADIUS, - valSelectorY + SELECTOR_RADIUS - ); - RectF opacityHitRect = opacitySliderEnabled ? new RectF( - opacitySelectorX - SELECTOR_RADIUS, - opacitySelectorY - SELECTOR_RADIUS, - opacitySelectorX + SELECTOR_RADIUS, - opacitySelectorY + SELECTOR_RADIUS - ) : new RectF(); - - // Check if the touch started on a handle or within the expanded bar areas. - if (hueHitRect.contains(x, y)) { - isDraggingHue = true; - updateHueFromTouch(x); - } else if (satValHitRect.contains(x, y)) { - isDraggingSaturation = true; - updateSaturationValueFromTouch(x, y); - } else if (opacitySliderEnabled && opacityHitRect.contains(x, y)) { - isDraggingOpacity = true; - updateOpacityFromTouch(x); - } else if (expandedHueRect.contains(x, y)) { - // Handle touch within the expanded hue bar area. - isDraggingHue = true; - updateHueFromTouch(x); - } else if (saturationValueRect.contains(x, y)) { - isDraggingSaturation = true; - updateSaturationValueFromTouch(x, y); - } else if (opacitySliderEnabled && expandedOpacityRect.contains(x, y)) { - isDraggingOpacity = true; - updateOpacityFromTouch(x); - } - break; - - case MotionEvent.ACTION_MOVE: - // Continue updating values even if touch moves outside the view. - if (isDraggingHue) { - updateHueFromTouch(x); - } else if (isDraggingSaturation) { - updateSaturationValueFromTouch(x, y); - } else if (isDraggingOpacity) { - updateOpacityFromTouch(x); - } - break; - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - isDraggingHue = false; - isDraggingSaturation = false; - isDraggingOpacity = false; - break; - } - } catch (Exception ex) { - Logger.printException(() -> "onTouchEvent failure", ex); - } - - return true; - } - - /** - * Updates the hue value based on a touch event. - */ - private void updateHueFromTouch(float x) { - // Clamp x to the hue rectangle bounds. - final float clampedX = Utils.clamp(x, hueRect.left, hueRect.right); - final float updatedHue = ((clampedX - hueRect.left) / hueRect.width()) * 360f; - if (hue == updatedHue) { - return; - } - - hue = updatedHue; - updateSaturationValueShader(); - updateOpacityShader(); - updateSelectedColor(); - } - - /** - * Updates the saturation and value based on a touch event. - */ - private void updateSaturationValueFromTouch(float x, float y) { - // Clamp x and y to the saturation-value rectangle bounds. - final float clampedX = Utils.clamp(x, saturationValueRect.left, saturationValueRect.right); - final float clampedY = Utils.clamp(y, saturationValueRect.top, saturationValueRect.bottom); - - final float updatedSaturation = (clampedX - saturationValueRect.left) / saturationValueRect.width(); - final float updatedValue = 1 - ((clampedY - saturationValueRect.top) / saturationValueRect.height()); - - if (saturation == updatedSaturation && value == updatedValue) { - return; - } - saturation = updatedSaturation; - value = updatedValue; - updateOpacityShader(); - updateSelectedColor(); - } - - /** - * Updates the opacity value based on a touch event. - */ - private void updateOpacityFromTouch(float x) { - if (!opacitySliderEnabled) { - return; - } - final float clampedX = Utils.clamp(x, opacityRect.left, opacityRect.right); - final float updatedOpacity = (clampedX - opacityRect.left) / opacityRect.width(); - if (opacity == updatedOpacity) { - return; - } - opacity = updatedOpacity; - updateSelectedColor(); - } - - /** - * Updates the selected color based on the current hue, saturation, value, and opacity. - */ - private void updateSelectedColor() { - final int rgbColor = Color.HSVToColor(0, new float[]{hue, saturation, value}); - final int updatedColor = opacitySliderEnabled - ? (rgbColor & 0x00FFFFFF) | (((int) (opacity * 255)) << 24) - : (rgbColor & 0x00FFFFFF) | 0xFF000000; - - if (selectedColor != updatedColor) { - selectedColor = updatedColor; - - if (colorChangedListener != null) { - colorChangedListener.onColorChanged(updatedColor); - } - } - - // Must always redraw, otherwise if saturation is pure grey or black - // then the hue slider cannot be changed. - invalidate(); - } - - /** - * Sets the selected color, updating the hue, saturation, value and opacity sliders accordingly. - */ - public void setColor(@ColorInt int color) { - if (selectedColor == color) { - return; - } - - // Update the selected color. - selectedColor = color; - Logger.printDebug(() -> "setColor: " + getColorString(selectedColor, opacitySliderEnabled)); - - // Convert the ARGB color to HSV values. - float[] hsv = new float[3]; - Color.colorToHSV(color, hsv); - - // Update the hue, saturation, and value. - hue = hsv[0]; - saturation = hsv[1]; - value = hsv[2]; - opacity = opacitySliderEnabled ? ((color >> 24) & 0xFF) / 255f : 1f; - - // Update the saturation-value shader based on the new hue. - updateSaturationValueShader(); - updateOpacityShader(); - - // Notify the listener if it's set. - if (colorChangedListener != null) { - colorChangedListener.onColorChanged(selectedColor); - } - - // Invalidate the view to trigger a redraw. - invalidate(); - } - - /** - * Gets the currently selected color. - */ - @ColorInt - public int getColor() { - return selectedColor; - } - - /** - * Sets a listener to be notified when the selected color changes. - */ - public void setOnColorChangedListener(OnColorChangedListener listener) { - colorChangedListener = listener; - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ColorPickerWithOpacitySliderPreference.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ColorPickerWithOpacitySliderPreference.java deleted file mode 100644 index 5e24f7bf36..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ColorPickerWithOpacitySliderPreference.java +++ /dev/null @@ -1,34 +0,0 @@ -package app.revanced.extension.shared.settings.preference; - -import android.content.Context; -import android.util.AttributeSet; - -/** - * Extended ColorPickerPreference that enables the opacity slider for color selection. - */ -@SuppressWarnings("unused") -public class ColorPickerWithOpacitySliderPreference extends ColorPickerPreference { - - public ColorPickerWithOpacitySliderPreference(Context context) { - super(context); - init(); - } - - public ColorPickerWithOpacitySliderPreference(Context context, AttributeSet attrs) { - super(context, attrs); - init(); - } - - public ColorPickerWithOpacitySliderPreference(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(); - } - - /** - * Initialize the preference with opacity slider enabled. - */ - private void init() { - // Enable the opacity slider for alpha channel support. - setOpacitySliderEnabled(true); - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/CustomDialogListPreference.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/CustomDialogListPreference.java deleted file mode 100644 index 48c50c1f33..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/CustomDialogListPreference.java +++ /dev/null @@ -1,267 +0,0 @@ -package app.revanced.extension.shared.settings.preference; - -import static app.revanced.extension.shared.Utils.getResourceIdentifierOrThrow; - -import android.app.Dialog; -import android.content.Context; -import android.os.Bundle; -import android.preference.ListPreference; -import android.util.AttributeSet; -import android.util.Pair; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ListView; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import app.revanced.extension.shared.ResourceType; -import app.revanced.extension.shared.Utils; -import app.revanced.extension.shared.ui.CustomDialog; - -/** - * A custom ListPreference that uses a styled custom dialog with a custom checkmark indicator, - * supports a static summary and highlighted entries for search functionality. - */ -@SuppressWarnings({"unused", "deprecation"}) -public class CustomDialogListPreference extends ListPreference { - - public static final int ID_REVANCED_CHECK_ICON = getResourceIdentifierOrThrow( - ResourceType.ID, "revanced_check_icon"); - public static final int ID_REVANCED_CHECK_ICON_PLACEHOLDER = getResourceIdentifierOrThrow( - ResourceType.ID, "revanced_check_icon_placeholder"); - public static final int ID_REVANCED_ITEM_TEXT = getResourceIdentifierOrThrow( - ResourceType.ID, "revanced_item_text"); - public static final int LAYOUT_REVANCED_CUSTOM_LIST_ITEM_CHECKED = getResourceIdentifierOrThrow( - ResourceType.LAYOUT, "revanced_custom_list_item_checked"); - public static final int DRAWABLE_CHECKMARK = getResourceIdentifierOrThrow( - ResourceType.DRAWABLE, "revanced_settings_custom_checkmark"); - public static final int DRAWABLE_CHECKMARK_BOLD = getResourceIdentifierOrThrow( - ResourceType.DRAWABLE, "revanced_settings_custom_checkmark_bold"); - - private String staticSummary = null; - private CharSequence[] highlightedEntriesForDialog = null; - - /** - * Set a static summary that will not be overwritten by value changes. - */ - public void setStaticSummary(String summary) { - this.staticSummary = summary; - } - - /** - * Returns the static summary if set, otherwise null. - */ - @Nullable - public String getStaticSummary() { - return staticSummary; - } - - /** - * Always return static summary if set. - */ - @Override - public CharSequence getSummary() { - if (staticSummary != null) { - return staticSummary; - } - return super.getSummary(); - } - - /** - * Sets highlighted entries for display in the dialog. - * These entries are used only for the current dialog and are automatically cleared. - */ - public void setHighlightedEntriesForDialog(CharSequence[] highlightedEntries) { - this.highlightedEntriesForDialog = highlightedEntries; - } - - /** - * Clears highlighted entries after the dialog is closed. - */ - public void clearHighlightedEntriesForDialog() { - this.highlightedEntriesForDialog = null; - } - - /** - * Returns entries for display in the dialog. - * If highlighted entries exist, they are used; otherwise, the original entries are returned. - */ - private CharSequence[] getEntriesForDialog() { - return highlightedEntriesForDialog != null ? highlightedEntriesForDialog : getEntries(); - } - - /** - * Custom ArrayAdapter to handle checkmark visibility. - */ - public static class ListPreferenceArrayAdapter extends ArrayAdapter { - private static class SubViewDataContainer { - ImageView checkIcon; - View placeholder; - TextView itemText; - } - - final int layoutResourceId; - final CharSequence[] entryValues; - String selectedValue; - - public ListPreferenceArrayAdapter(Context context, int resource, - CharSequence[] entries, - CharSequence[] entryValues, - String selectedValue) { - super(context, resource, entries); - this.layoutResourceId = resource; - this.entryValues = entryValues; - this.selectedValue = selectedValue; - } - - @NonNull - @Override - public View getView(int position, View convertView, @NonNull ViewGroup parent) { - View view = convertView; - SubViewDataContainer holder; - - if (view == null) { - LayoutInflater inflater = LayoutInflater.from(getContext()); - view = inflater.inflate(layoutResourceId, parent, false); - holder = new SubViewDataContainer(); - holder.placeholder = view.findViewById(ID_REVANCED_CHECK_ICON_PLACEHOLDER); - holder.itemText = view.findViewById(ID_REVANCED_ITEM_TEXT); - holder.checkIcon = view.findViewById(ID_REVANCED_CHECK_ICON); - holder.checkIcon.setImageResource(Utils.appIsUsingBoldIcons() - ? DRAWABLE_CHECKMARK_BOLD - : DRAWABLE_CHECKMARK - ); - view.setTag(holder); - } else { - holder = (SubViewDataContainer) view.getTag(); - } - - CharSequence itemText = getItem(position); - holder.itemText.setText(itemText); - holder.itemText.setTextColor(Utils.getAppForegroundColor()); - - // Show or hide checkmark and placeholder. - String currentValue = entryValues[position].toString(); - boolean isSelected = currentValue.equals(selectedValue); - holder.checkIcon.setVisibility(isSelected ? View.VISIBLE : View.GONE); - holder.checkIcon.setColorFilter(Utils.getAppForegroundColor()); - holder.placeholder.setVisibility(isSelected ? View.GONE : View.VISIBLE); - - return view; - } - - public void setSelectedValue(String value) { - this.selectedValue = value; - } - } - - public CustomDialogListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - public CustomDialogListPreference(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - public CustomDialogListPreference(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public CustomDialogListPreference(Context context) { - super(context); - } - - @Override - protected void showDialog(Bundle state) { - Context context = getContext(); - - CharSequence[] entriesToShow = getEntriesForDialog(); - CharSequence[] entryValues = getEntryValues(); - - // Create ListView. - ListView listView = new ListView(context); - listView.setId(android.R.id.list); - listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); - - // Create custom adapter for the ListView. - ListPreferenceArrayAdapter adapter = new ListPreferenceArrayAdapter( - context, - LAYOUT_REVANCED_CUSTOM_LIST_ITEM_CHECKED, - entriesToShow, - entryValues, - getValue() - ); - listView.setAdapter(adapter); - - // Set checked item. - String currentValue = getValue(); - if (currentValue != null) { - for (int i = 0, length = entryValues.length; i < length; i++) { - if (currentValue.equals(entryValues[i].toString())) { - listView.setItemChecked(i, true); - listView.setSelection(i); - break; - } - } - } - - // Create the custom dialog without OK button. - Pair dialogPair = CustomDialog.create( - context, - getTitle() != null ? getTitle().toString() : "", - null, - null, - null, - null, - this::clearHighlightedEntriesForDialog, // Cancel button action. - null, - null, - true - ); - - Dialog dialog = dialogPair.first; - // Add a listener to clear when the dialog is closed in any way. - dialog.setOnDismissListener(dialogInterface -> clearHighlightedEntriesForDialog()); - - // Add the ListView to the main layout. - LinearLayout mainLayout = dialogPair.second; - LinearLayout.LayoutParams listViewParams = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - 0, - 1.0f - ); - mainLayout.addView(listView, mainLayout.getChildCount() - 1, listViewParams); - - // Handle item click to select value and dismiss dialog. - listView.setOnItemClickListener((parent, view, position, id) -> { - String selectedValue = entryValues[position].toString(); - if (callChangeListener(selectedValue)) { - setValue(selectedValue); - - // Update summaries from the original entries (without highlighting). - if (staticSummary == null) { - CharSequence[] originalEntries = getEntries(); - if (originalEntries != null && position < originalEntries.length) { - setSummary(originalEntries[position]); - } - } - - adapter.setSelectedValue(selectedValue); - adapter.notifyDataSetChanged(); - } - - // Clear highlighted entries before closing. - clearHighlightedEntriesForDialog(); - dialog.dismiss(); - }); - - // Show the dialog. - dialog.show(); - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ExportLogToClipboardPreference.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ExportLogToClipboardPreference.java deleted file mode 100644 index 57fb128232..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ExportLogToClipboardPreference.java +++ /dev/null @@ -1,33 +0,0 @@ -package app.revanced.extension.shared.settings.preference; - -import android.content.Context; -import android.util.AttributeSet; -import android.preference.Preference; - -/** - * A custom preference that triggers exporting ReVanced debug logs to the clipboard when clicked. - * Invokes the {@link LogBufferManager#exportToClipboard} method. - */ -@SuppressWarnings({"deprecation", "unused"}) -public class ExportLogToClipboardPreference extends Preference { - - { - setOnPreferenceClickListener(pref -> { - LogBufferManager.exportToClipboard(); - return true; - }); - } - - public ExportLogToClipboardPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - public ExportLogToClipboardPreference(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - public ExportLogToClipboardPreference(Context context, AttributeSet attrs) { - super(context, attrs); - } - public ExportLogToClipboardPreference(Context context) { - super(context); - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/FeatureFlagsManagerPreference.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/FeatureFlagsManagerPreference.java deleted file mode 100644 index 4e6d2e5cdd..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/FeatureFlagsManagerPreference.java +++ /dev/null @@ -1,626 +0,0 @@ -package app.revanced.extension.shared.settings.preference; - -import static app.revanced.extension.shared.StringRef.str; -import static app.revanced.extension.shared.Utils.getResourceIdentifierOrThrow; - -import android.annotation.SuppressLint; -import android.app.Dialog; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.ShapeDrawable; -import android.graphics.drawable.shapes.RoundRectShape; -import android.preference.Preference; -import android.text.Editable; -import android.text.InputType; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.util.AttributeSet; -import android.util.Pair; -import android.util.SparseBooleanArray; -import android.view.Gravity; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.widget.ArrayAdapter; -import android.widget.EditText; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ListView; -import android.widget.Space; -import android.widget.TextView; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; - -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.ResourceType; -import app.revanced.extension.shared.Utils; -import app.revanced.extension.shared.patches.EnableDebuggingPatch; -import app.revanced.extension.shared.settings.BaseSettings; -import app.revanced.extension.shared.ui.CustomDialog; -import app.revanced.extension.shared.ui.Dim; - -/** - * A custom preference that opens a dialog for managing feature flags. - * Allows moving boolean flags between active and blocked states with advanced selection. - */ -@SuppressWarnings({"deprecation", "unused"}) -public class FeatureFlagsManagerPreference extends Preference { - - private static final int DRAWABLE_REVANCED_SETTINGS_SELECT_ALL = - getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_select_all"); - private static final int DRAWABLE_REVANCED_SETTINGS_DESELECT_ALL = - getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_deselect_all"); - private static final int DRAWABLE_REVANCED_SETTINGS_COPY_ALL = - getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_copy_all"); - private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_RIGHT_ONE = - getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_arrow_right_one"); - private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_RIGHT_DOUBLE = - getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_arrow_right_double"); - private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_LEFT_ONE = - getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_arrow_left_one"); - private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_LEFT_DOUBLE = - getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_arrow_left_double"); - - /** - * Flags to hide from the UI. - */ - private static final Set FLAGS_TO_IGNORE = Set.of( - 45386834L, // 'You' tab settings icon. - 45532100L // Cairo flag. Turning this off with all other flags causes the settings menu to be a mix of old/new. - ); - - /** - * Tracks state for range selection in ListView. - */ - private static class ListViewSelectionState { - int lastClickedPosition = -1; // Position of the last clicked item. - boolean isRangeSelecting = false; // True while a range is being selected. - } - - /** - * Helper class to pass ListView and Adapter together. - */ - private record ColumnViews(ListView listView, FlagAdapter adapter) {} - - { - setOnPreferenceClickListener(pref -> { - showFlagsManagerDialog(); - return true; - }); - } - - public FeatureFlagsManagerPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - public FeatureFlagsManagerPreference(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - public FeatureFlagsManagerPreference(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public FeatureFlagsManagerPreference(Context context) { - super(context); - } - - /** - * Shows the main dialog for managing feature flags. - */ - private void showFlagsManagerDialog() { - if (!BaseSettings.DEBUG.get()) { - Utils.showToastShort(str("revanced_debug_logs_disabled")); - return; - } - - Context context = getContext(); - - // Load all known and disabled flags. - TreeSet allKnownFlags = new TreeSet<>(EnableDebuggingPatch.getAllLoggedFlags()); - allKnownFlags.removeAll(FLAGS_TO_IGNORE); - - TreeSet disabledFlags = new TreeSet<>(EnableDebuggingPatch.parseFlags( - BaseSettings.DISABLED_FEATURE_FLAGS.get())); - disabledFlags.removeAll(FLAGS_TO_IGNORE); - - if (allKnownFlags.isEmpty() && disabledFlags.isEmpty()) { - // It's impossible to reach the settings menu without reaching at least one flag. - // So if theres no flags, then that means the user has just enabled debugging - // but has not restarted the app yet. - Utils.showToastShort(str("revanced_debug_feature_flags_manager_toast_no_flags")); - return; - } - - TreeSet availableFlags = new TreeSet<>(allKnownFlags); - availableFlags.removeAll(disabledFlags); - TreeSet blockedFlags = new TreeSet<>(disabledFlags); - - Pair dialogPair = CustomDialog.create( - context, - getTitle() != null ? getTitle().toString() : "", - null, - null, - str("revanced_settings_save"), - () -> saveFlags(blockedFlags), - () -> {}, - str("revanced_settings_reset"), - this::resetFlags, - true - ); - - LinearLayout mainLayout = dialogPair.second; - LinearLayout.LayoutParams contentParams = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, 0, 1.0f); - - // Insert content before the dialog button row. - View contentView = createContentView(context, availableFlags, blockedFlags); - mainLayout.addView(contentView, mainLayout.getChildCount() - 1, contentParams); - - Dialog dialog = dialogPair.first; - dialog.show(); - - Window window = dialog.getWindow(); - if (window != null) { - Utils.setDialogWindowParameters(window, Gravity.CENTER, 0, 100, false); - } - } - - /** - * Creates the main content view with two columns. - */ - private View createContentView(Context context, TreeSet availableFlags, TreeSet blockedFlags) { - LinearLayout contentLayout = new LinearLayout(context); - contentLayout.setOrientation(LinearLayout.VERTICAL); - - // Headers. - TextView availableHeader = createHeader(context, "revanced_debug_feature_flags_manager_active_header"); - TextView blockedHeader = createHeader(context, "revanced_debug_feature_flags_manager_blocked_header"); - - LinearLayout headersLayout = new LinearLayout(context); - headersLayout.setOrientation(LinearLayout.HORIZONTAL); - headersLayout.addView(availableHeader, new LinearLayout.LayoutParams( - 0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)); - headersLayout.addView(blockedHeader, new LinearLayout.LayoutParams( - 0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)); - - // Columns. - View leftColumn = createColumn(context, availableFlags, availableHeader); - View rightColumn = createColumn(context, blockedFlags, blockedHeader); - - ColumnViews leftViews = (ColumnViews) leftColumn.getTag(); - ColumnViews rightViews = (ColumnViews) rightColumn.getTag(); - - updateHeaderCount(availableHeader, leftViews.adapter); - updateHeaderCount(blockedHeader, rightViews.adapter); - - // Main columns layout. - LinearLayout columnsLayout = new LinearLayout(context); - columnsLayout.setOrientation(LinearLayout.HORIZONTAL); - columnsLayout.setLayoutParams(new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f)); - columnsLayout.addView(leftColumn, new LinearLayout.LayoutParams( - 0, ViewGroup.LayoutParams.MATCH_PARENT, 1f)); - - Space spaceBetweenColumns = new Space(context); - spaceBetweenColumns.setLayoutParams(new LinearLayout.LayoutParams(Dim.dp8, ViewGroup.LayoutParams.MATCH_PARENT)); - columnsLayout.addView(spaceBetweenColumns); - - columnsLayout.addView(rightColumn, new LinearLayout.LayoutParams( - 0, ViewGroup.LayoutParams.MATCH_PARENT, 1f)); - - // Move buttons below columns. - Pair moveButtons = createMoveButtons(context, - leftViews.listView, rightViews.listView, - availableFlags, blockedFlags, availableHeader, blockedHeader); - - // Layout for buttons row. - LinearLayout buttonsRow = new LinearLayout(context); - buttonsRow.setOrientation(LinearLayout.HORIZONTAL); - buttonsRow.setLayoutParams(new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - - buttonsRow.addView(moveButtons.first, new LinearLayout.LayoutParams( - 0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)); - - Space spaceBetweenButtons = new Space(context); - spaceBetweenButtons.setLayoutParams(new LinearLayout.LayoutParams(Dim.dp8, ViewGroup.LayoutParams.WRAP_CONTENT)); - buttonsRow.addView(spaceBetweenButtons); - - buttonsRow.addView(moveButtons.second, new LinearLayout.LayoutParams( - 0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)); - - contentLayout.addView(headersLayout); - contentLayout.addView(columnsLayout); - contentLayout.addView(buttonsRow); - - return contentLayout; - } - - /** - * Creates a header TextView. - */ - private TextView createHeader(Context context, String tag) { - TextView textview = new TextView(context); - textview.setTag(tag); - textview.setTextSize(16); - textview.setTextColor(Utils.getAppForegroundColor()); - textview.setGravity(Gravity.CENTER); - - return textview; - } - - /** - * Creates a single column (search + buttons + list). - */ - private View createColumn(Context context, TreeSet flags, TextView countText) { - LinearLayout wrapper = new LinearLayout(context); - wrapper.setOrientation(LinearLayout.VERTICAL); - - Pair pair = createListView(context, flags, countText); - ListView listView = pair.first; - FlagAdapter adapter = pair.second; - - EditText search = createSearchBox(context, adapter, listView, countText); - LinearLayout buttons = createActionButtons(context, listView, adapter); - - listView.setLayoutParams(new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f)); - ShapeDrawable background = new ShapeDrawable(new RoundRectShape( - Dim.roundedCorners(10), null, null)); - background.getPaint().setColor(Utils.getEditTextBackground()); - listView.setPadding(0, Dim.dp4, 0, Dim.dp4); - listView.setBackground(background); - listView.setOverScrollMode(View.OVER_SCROLL_NEVER); - - wrapper.addView(search); - wrapper.addView(buttons); - wrapper.addView(listView); - - // Save references for move buttons. - wrapper.setTag(new ColumnViews(listView, adapter)); - - return wrapper; - } - - /** - * Updates the header text with the current count. - */ - private void updateHeaderCount(TextView header, FlagAdapter adapter) { - header.setText(str((String) header.getTag(), adapter.getCount())); - } - - /** - * Creates a search box that filters the list. - */ - @SuppressLint("ClickableViewAccessibility") - private EditText createSearchBox(Context context, FlagAdapter adapter, ListView listView, TextView countText) { - EditText search = new EditText(context); - search.setInputType(InputType.TYPE_CLASS_NUMBER); - search.setTextSize(16); - search.setHint(str("revanced_debug_feature_flags_manager_search_hint")); - search.setHapticFeedbackEnabled(false); - search.setLayoutParams(new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)); - - search.addTextChangedListener(new TextWatcher() { - @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} - @Override public void onTextChanged(CharSequence s, int start, int before, int count) { - adapter.setSearchQuery(s.toString()); - listView.clearChoices(); - updateHeaderCount(countText, adapter); - Drawable clearIcon = context.getResources().getDrawable(android.R.drawable.ic_menu_close_clear_cancel); - clearIcon.setBounds(0, 0, Dim.dp20, Dim.dp20); - search.setCompoundDrawables(null, null, TextUtils.isEmpty(s) ? null : clearIcon, null); - } - @Override public void afterTextChanged(Editable s) {} - }); - - search.setOnTouchListener((v, event) -> { - if (event.getAction() == MotionEvent.ACTION_UP) { - Drawable[] compoundDrawables = search.getCompoundDrawables(); - if (compoundDrawables[2] != null && - event.getRawX() >= (search.getRight() - compoundDrawables[2].getBounds().width())) { - search.setText(""); - return true; - } - } - return false; - }); - - return search; - } - - /** - * Creates action buttons. - */ - private LinearLayout createActionButtons(Context context, ListView listView, FlagAdapter adapter) { - LinearLayout row = new LinearLayout(context); - row.setOrientation(LinearLayout.HORIZONTAL); - row.setGravity(Gravity.CENTER); - row.setLayoutParams(new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)); - - ImageButton selectAll = createButton(context, DRAWABLE_REVANCED_SETTINGS_SELECT_ALL, - () -> { - for (int i = 0, count = adapter.getCount(); i < count; i++) { - listView.setItemChecked(i, true); - } - }); - - ImageButton clearAll = createButton(context, DRAWABLE_REVANCED_SETTINGS_DESELECT_ALL, - () -> { - listView.clearChoices(); - adapter.notifyDataSetChanged(); - }); - - ImageButton copy = createButton(context, DRAWABLE_REVANCED_SETTINGS_COPY_ALL, - () -> { - List items = new ArrayList<>(); - SparseBooleanArray checked = listView.getCheckedItemPositions(); - - if (checked.size() > 0) { - for (int i = 0, count = adapter.getCount(); i < count; i++) { - if (checked.get(i)) { - items.add(adapter.getItem(i)); - } - } - } else { - for (Long flag : adapter.getFullFlags()) { - items.add(String.valueOf(flag)); - } - } - - Utils.setClipboard(TextUtils.join("\n", items)); - - Utils.showToastShort(str("revanced_debug_feature_flags_manager_toast_copied")); - }); - - row.addView(selectAll); - row.addView(clearAll); - row.addView(copy); - - return row; - } - - /** - * Creates the move buttons (left and right groups). - */ - private Pair createMoveButtons(Context context, - ListView availableListView, ListView blockedListView, - TreeSet availableFlags, TreeSet blockedFlags, - TextView availableCountText, TextView blockedCountText) { - // Left group: >> > - LinearLayout leftButtons = new LinearLayout(context); - leftButtons.setOrientation(LinearLayout.HORIZONTAL); - leftButtons.setGravity(Gravity.CENTER); - - ImageButton moveAllRight = createButton(context, DRAWABLE_REVANCED_SETTINGS_ARROW_RIGHT_DOUBLE, - () -> moveFlags(availableListView, blockedListView, availableFlags, blockedFlags, - availableCountText, blockedCountText, true)); - - ImageButton moveOneRight = createButton(context, DRAWABLE_REVANCED_SETTINGS_ARROW_RIGHT_ONE, - () -> moveFlags(availableListView, blockedListView, availableFlags, blockedFlags, - availableCountText, blockedCountText, false)); - - leftButtons.addView(moveAllRight); - leftButtons.addView(moveOneRight); - - // Right group: < << - LinearLayout rightButtons = new LinearLayout(context); - rightButtons.setOrientation(LinearLayout.HORIZONTAL); - rightButtons.setGravity(Gravity.CENTER); - - ImageButton moveOneLeft = createButton(context, DRAWABLE_REVANCED_SETTINGS_ARROW_LEFT_ONE, - () -> moveFlags(blockedListView, availableListView, blockedFlags, availableFlags, - blockedCountText, availableCountText, false)); - - ImageButton moveAllLeft = createButton(context, DRAWABLE_REVANCED_SETTINGS_ARROW_LEFT_DOUBLE, - () -> moveFlags(blockedListView, availableListView, blockedFlags, availableFlags, - blockedCountText, availableCountText, true)); - - rightButtons.addView(moveOneLeft); - rightButtons.addView(moveAllLeft); - - return new Pair<>(leftButtons, rightButtons); - } - - /** - * Creates a styled ImageButton. - */ - @SuppressLint("ResourceType") - private ImageButton createButton(Context context, int drawableResId, Runnable action) { - ImageButton button = new ImageButton(context); - - button.setImageResource(drawableResId); - button.setScaleType(ImageView.ScaleType.CENTER); - int[] attrs = {android.R.attr.selectableItemBackgroundBorderless}; - //noinspection Recycle - TypedArray ripple = context.obtainStyledAttributes(attrs); - button.setBackgroundDrawable(ripple.getDrawable(0)); - ripple.close(); - - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(Dim.dp32, Dim.dp32); - params.setMargins(Dim.dp8, Dim.dp8, Dim.dp8, Dim.dp8); - button.setLayoutParams(params); - - button.setOnClickListener(v -> action.run()); - - return button; - } - - /** - * Custom adapter with search filtering. - */ - private static class FlagAdapter extends ArrayAdapter { - private final TreeSet fullFlags; - private String searchQuery = ""; - - public FlagAdapter(Context context, TreeSet fullFlags) { - super(context, android.R.layout.simple_list_item_multiple_choice, new ArrayList<>()); - this.fullFlags = fullFlags; - updateFiltered(); - } - - public void setSearchQuery(String query) { - searchQuery = query == null ? "" : query.trim(); - updateFiltered(); - } - - private void updateFiltered() { - clear(); - for (Long flag : fullFlags) { - String flagString = String.valueOf(flag); - if (searchQuery.isEmpty() || flagString.contains(searchQuery)) { - add(flagString); - } - } - notifyDataSetChanged(); - } - - public void refresh() { - updateFiltered(); - } - - public List getFullFlags() { - return new ArrayList<>(fullFlags); - } - } - - /** - * Creates a ListView with filtering, multi-select, and range selection. - */ - @SuppressLint("ClickableViewAccessibility") - private Pair createListView(Context context, - TreeSet flags, TextView countText) { - ListView listView = new ListView(context); - listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); - listView.setDividerHeight(0); - - FlagAdapter adapter = new FlagAdapter(context, flags); - listView.setAdapter(adapter); - - final ListViewSelectionState state = new ListViewSelectionState(); - - listView.setOnItemClickListener((parent, view, position, id) -> { - if (!state.isRangeSelecting) { - state.lastClickedPosition = position; - } else { - state.isRangeSelecting = false; - } - }); - - listView.setOnItemLongClickListener((parent, view, position, id) -> { - if (state.lastClickedPosition == -1) { - listView.setItemChecked(position, true); - state.lastClickedPosition = position; - } else { - int start = Math.min(state.lastClickedPosition, position); - int end = Math.max(state.lastClickedPosition, position); - for (int i = start; i <= end; i++) { - listView.setItemChecked(i, true); - } - state.isRangeSelecting = true; - } - return true; - }); - - listView.setOnTouchListener((view, event) -> { - if (event.getAction() == MotionEvent.ACTION_UP && state.isRangeSelecting) { - state.isRangeSelecting = false; - } - return false; - }); - - return new Pair<>(listView, adapter); - } - - /** - * Moves selected or all flags from one list to another. - * - * @param fromListView Source ListView. - * @param toListView Destination ListView. - * @param fromFlags Source flag set. - * @param toFlags Destination flag set. - * @param fromCountText Header showing count of source items. - * @param toCountText Header showing count of destination items. - * @param moveAll If true, move all items; if false, move only selected. - */ - private void moveFlags(ListView fromListView, ListView toListView, - TreeSet fromFlags, TreeSet toFlags, - TextView fromCountText, TextView toCountText, - boolean moveAll) { - if (fromListView == null || toListView == null) return; - - List flagsToMove = new ArrayList<>(); - FlagAdapter fromAdapter = (FlagAdapter) fromListView.getAdapter(); - - if (moveAll) { - flagsToMove.addAll(fromFlags); - } else { - SparseBooleanArray checked = fromListView.getCheckedItemPositions(); - for (int i = 0, count = fromAdapter.getCount(); i < count; i++) { - if (checked.get(i)) { - String item = fromAdapter.getItem(i); - if (item != null) { - flagsToMove.add(Long.parseLong(item)); - } - } - } - } - - if (flagsToMove.isEmpty()) return; - - for (Long flag : flagsToMove) { - fromFlags.remove(flag); - toFlags.add(flag); - } - - // Clear selections before refreshing. - fromListView.clearChoices(); - toListView.clearChoices(); - - // Refresh both adapters. - fromAdapter.refresh(); - ((FlagAdapter) toListView.getAdapter()).refresh(); - - // Update headers. - updateHeaderCount(fromCountText, fromAdapter); - updateHeaderCount(toCountText, (FlagAdapter) toListView.getAdapter()); - } - - /** - * Saves blocked flags to settings. - */ - private void saveFlags(TreeSet blockedFlags) { - StringBuilder flagsString = new StringBuilder(); - for (Long flag : blockedFlags) { - if (flagsString.length() > 0) { - flagsString.append("\n"); - } - flagsString.append(flag); - } - - BaseSettings.DISABLED_FEATURE_FLAGS.save(flagsString.toString()); - Utils.showToastShort(str("revanced_debug_feature_flags_manager_toast_saved")); - Logger.printDebug(() -> "Feature flags saved. Blocked: " + blockedFlags.size()); - - AbstractPreferenceFragment.showRestartDialog(getContext()); - } - - /** - * Resets all blocked flags. - */ - private void resetFlags() { - BaseSettings.DISABLED_FEATURE_FLAGS.save(""); - Utils.showToastShort(str("revanced_debug_feature_flags_manager_toast_reset")); - - AbstractPreferenceFragment.showRestartDialog(getContext()); - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ForceOriginalAudioSwitchPreference.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ForceOriginalAudioSwitchPreference.java deleted file mode 100644 index fdcde3668d..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ForceOriginalAudioSwitchPreference.java +++ /dev/null @@ -1,63 +0,0 @@ -package app.revanced.extension.shared.settings.preference; - -import static app.revanced.extension.shared.StringRef.str; - -import android.content.Context; -import android.preference.SwitchPreference; -import android.util.AttributeSet; - -import app.revanced.extension.shared.settings.BaseSettings; -import app.revanced.extension.shared.spoof.ClientType; -import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch; - -@SuppressWarnings({"deprecation", "unused"}) -public class ForceOriginalAudioSwitchPreference extends SwitchPreference { - - // Spoof stream patch is not included, or is not currently spoofing to Android Studio. - private static final boolean available = !SpoofVideoStreamsPatch.isPatchIncluded() - || !(BaseSettings.SPOOF_VIDEO_STREAMS.get() - && SpoofVideoStreamsPatch.getPreferredClient() == ClientType.ANDROID_CREATOR); - - { - if (!available) { - // Show why force audio is not available. - String summary = str("revanced_force_original_audio_not_available"); - super.setSummary(summary); - super.setSummaryOn(summary); - super.setSummaryOff(summary); - super.setEnabled(false); - } - } - - public ForceOriginalAudioSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - public ForceOriginalAudioSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - public ForceOriginalAudioSwitchPreference(Context context, AttributeSet attrs) { - super(context, attrs); - } - public ForceOriginalAudioSwitchPreference(Context context) { - super(context); - } - - @Override - public void setEnabled(boolean enabled) { - if (!available) { - return; - } - - super.setEnabled(enabled); - } - - @Override - public void setSummary(CharSequence summary) { - if (!available) { - return; - } - - super.setSummary(summary); - } -} - diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ImportExportPreference.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ImportExportPreference.java index 1044ba424e..a1d051c2b8 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ImportExportPreference.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ImportExportPreference.java @@ -1,24 +1,19 @@ package app.revanced.extension.shared.settings.preference; -import static app.revanced.extension.shared.StringRef.str; - -import android.app.Dialog; +import android.app.AlertDialog; import android.content.Context; import android.os.Build; -import android.os.Bundle; import android.preference.EditTextPreference; import android.preference.Preference; import android.text.InputType; import android.util.AttributeSet; -import android.util.Pair; -import android.view.inputmethod.InputMethodManager; +import android.util.TypedValue; import android.widget.EditText; -import android.widget.LinearLayout; - +import app.revanced.extension.shared.settings.Setting; import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Utils; -import app.revanced.extension.shared.settings.Setting; -import app.revanced.extension.shared.ui.CustomDialog; + +import static app.revanced.extension.shared.StringRef.str; @SuppressWarnings({"unused", "deprecation"}) public class ImportExportPreference extends EditTextPreference implements Preference.OnPreferenceClickListener { @@ -34,7 +29,7 @@ public class ImportExportPreference extends EditTextPreference implements Prefer editText.setAutofillHints((String) null); } editText.setInputType(editText.getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); - editText.setTextSize(14); + editText.setTextSize(TypedValue.COMPLEX_UNIT_PT, 7); // Use a smaller font to reduce text wrap. setOnPreferenceClickListener(this); } @@ -59,8 +54,7 @@ public class ImportExportPreference extends EditTextPreference implements Prefer @Override public boolean onPreferenceClick(Preference preference) { try { - // Must set text before showing dialog, - // otherwise text is non-selectable if this preference is later reopened. + // Must set text before preparing dialog, otherwise text is non selectable if this preference is later reopened. existingSettings = Setting.exportToJson(getContext()); getEditText().setText(existingSettings); } catch (Exception ex) { @@ -70,46 +64,18 @@ public class ImportExportPreference extends EditTextPreference implements Prefer } @Override - protected void showDialog(Bundle state) { + protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { try { - Context context = getContext(); - EditText editText = getEditText(); + Utils.setEditTextDialogTheme(builder); - // Create a custom dialog with the EditText. - Pair dialogPair = CustomDialog.create( - context, - str("revanced_pref_import_export_title"), // Title. - null, // No message (EditText replaces it). - editText, // Pass the EditText. - str("revanced_settings_import"), // OK button text. - () -> importSettings(context, editText.getText().toString()), // OK button action. - () -> {}, // Cancel button action (dismiss only). - str("revanced_settings_import_copy"), // Neutral button (Copy) text. - () -> { - // Neutral button (Copy) action. Show the user the settings in JSON format. - Utils.setClipboard(editText.getText()); - }, - true // Dismiss dialog when onNeutralClick. - ); - - // If there are no settings yet, then show the on screen keyboard and bring focus to - // the edit text. This makes it easier to paste saved settings after a reinstall. - dialogPair.first.setOnShowListener(dialogInterface -> { - if (existingSettings.isEmpty()) { - editText.postDelayed(() -> { - editText.requestFocus(); - - InputMethodManager inputMethodManager = (InputMethodManager) - editText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - inputMethodManager.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT); - }, 100); - } - }); - - // Show the dialog. - dialogPair.first.show(); + // Show the user the settings in JSON format. + builder.setNeutralButton(str("revanced_settings_import_copy"), (dialog, which) -> { + Utils.setClipboard(getEditText().getText().toString()); + }).setPositiveButton(str("revanced_settings_import"), (dialog, which) -> { + importSettings(builder.getContext(), getEditText().getText().toString()); + }); } catch (Exception ex) { - Logger.printException(() -> "showDialog failure", ex); + Logger.printException(() -> "onPrepareDialogBuilder failure", ex); } } @@ -122,7 +88,7 @@ public class ImportExportPreference extends EditTextPreference implements Prefer final boolean rebootNeeded = Setting.importFromJSON(context, replacementSettings); if (rebootNeeded) { - AbstractPreferenceFragment.showRestartDialog(context); + AbstractPreferenceFragment.showRestartDialog(getContext()); } } catch (Exception ex) { Logger.printException(() -> "importSettings failure", ex); @@ -130,4 +96,5 @@ public class ImportExportPreference extends EditTextPreference implements Prefer AbstractPreferenceFragment.settingImportInProgress = false; } } -} + +} \ No newline at end of file diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/LogBufferManager.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/LogBufferManager.java deleted file mode 100644 index 4bd54c65be..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/LogBufferManager.java +++ /dev/null @@ -1,113 +0,0 @@ -package app.revanced.extension.shared.settings.preference; - -import static app.revanced.extension.shared.StringRef.str; - -import java.util.Deque; -import java.util.Objects; -import java.util.concurrent.ConcurrentLinkedDeque; -import java.util.concurrent.atomic.AtomicInteger; - -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.Utils; -import app.revanced.extension.shared.settings.BaseSettings; - -/** - * Manages a buffer for storing debug logs from {@link Logger}. - * Stores just under 1MB of the most recent log data. - * - * All methods are thread-safe. - */ -public final class LogBufferManager { - /** Maximum byte size of all buffer entries. Must be less than Android's 1 MB Binder transaction limit. */ - private static final int BUFFER_MAX_BYTES = 900_000; - /** Limit number of log lines. */ - private static final int BUFFER_MAX_SIZE = 10_000; - - private static final Deque logBuffer = new ConcurrentLinkedDeque<>(); - private static final AtomicInteger logBufferByteSize = new AtomicInteger(); - - /** - * Appends a log message to the internal buffer if debugging is enabled. - * The buffer is limited to approximately {@link #BUFFER_MAX_BYTES} or {@link #BUFFER_MAX_SIZE} - * to prevent excessive memory usage. - * - * @param message The log message to append. - */ - public static void appendToLogBuffer(String message) { - Objects.requireNonNull(message); - - // It's very important that no Settings are used in this method, - // as this code is used when a context is not set and thus referencing - // a setting will crash the app. - logBuffer.addLast(message); - int newSize = logBufferByteSize.addAndGet(message.length()); - - // Remove oldest entries if over the log size limits. - while (newSize > BUFFER_MAX_BYTES || logBuffer.size() > BUFFER_MAX_SIZE) { - String removed = logBuffer.pollFirst(); - if (removed == null) { - // Thread race of two different calls to this method, and the other thread won. - return; - } - - newSize = logBufferByteSize.addAndGet(-removed.length()); - } - } - - /** - * Exports all logs from the internal buffer to the clipboard. - * Displays a toast with the result. - */ - public static void exportToClipboard() { - try { - if (!BaseSettings.DEBUG.get()) { - Utils.showToastShort(str("revanced_debug_logs_disabled")); - return; - } - - if (logBuffer.isEmpty()) { - Utils.showToastShort(str("revanced_debug_logs_none_found")); - clearLogBufferData(); // Clear toast log entry that was just created. - return; - } - - // Most (but not all) Android 13+ devices always show a "copied to clipboard" toast - // and there is no way to programmatically detect if a toast will show or not. - // Show a toast even if using Android 13+, but show ReVanced toast first (before copying to clipboard). - Utils.showToastShort(str("revanced_debug_logs_copied_to_clipboard")); - - Utils.setClipboard(String.join("\n", logBuffer)); - } catch (Exception ex) { - // Handle security exception if clipboard access is denied. - String errorMessage = String.format(str("revanced_debug_logs_failed_to_export"), ex.getMessage()); - Utils.showToastLong(errorMessage); - Logger.printDebug(() -> errorMessage, ex); - } - } - - private static void clearLogBufferData() { - // Cannot simply clear the log buffer because there is no - // write lock for both the deque and the atomic int. - // Instead pop off log entries and decrement the size one by one. - while (!logBuffer.isEmpty()) { - String removed = logBuffer.pollFirst(); - if (removed != null) { - logBufferByteSize.addAndGet(-removed.length()); - } - } - } - - /** - * Clears the internal log buffer and displays a toast with the result. - */ - public static void clearLogBuffer() { - if (!BaseSettings.DEBUG.get()) { - Utils.showToastShort(str("revanced_debug_logs_disabled")); - return; - } - - // Show toast before clearing, otherwise toast log will still remain. - Utils.showToastShort(str("revanced_debug_logs_clear_toast")); - clearLogBufferData(); - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/NoTitlePreferenceCategory.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/NoTitlePreferenceCategory.java deleted file mode 100644 index d6b895f22a..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/NoTitlePreferenceCategory.java +++ /dev/null @@ -1,58 +0,0 @@ -package app.revanced.extension.shared.settings.preference; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.preference.PreferenceCategory; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewGroup; - -/** - * Empty preference category with no title, used to organize and group related preferences together. - */ -@SuppressWarnings({"unused", "deprecation"}) -public class NoTitlePreferenceCategory extends PreferenceCategory { - - public NoTitlePreferenceCategory(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public NoTitlePreferenceCategory(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - public NoTitlePreferenceCategory(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - public NoTitlePreferenceCategory(Context context) { - super(context); - } - - @Override - @SuppressLint("MissingSuperCall") - protected View onCreateView(ViewGroup parent) { - // Return an zero-height view to eliminate empty title space. - return new View(getContext()); - } - - @Override - public CharSequence getTitle() { - // Title can be used for sorting. Return the first sub preference title. - if (getPreferenceCount() > 0) { - return getPreference(0).getTitle(); - } - - return super.getTitle(); - } - - @Override - public int getTitleRes() { - if (getPreferenceCount() > 0) { - return getPreference(0).getTitleRes(); - } - - return super.getTitleRes(); - } -} - diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ReVancedAboutPreference.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ReVancedAboutPreference.java index 0d4003b913..cd62235fc7 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ReVancedAboutPreference.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ReVancedAboutPreference.java @@ -4,25 +4,21 @@ import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.requests.Route.Method.GET; import android.annotation.SuppressLint; -import android.app.Activity; import android.app.Dialog; import android.app.ProgressDialog; import android.content.Context; import android.content.Intent; -import android.graphics.drawable.ShapeDrawable; -import android.graphics.drawable.shapes.RoundRectShape; +import android.content.res.Configuration; +import android.graphics.Color; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.preference.Preference; import android.util.AttributeSet; -import android.view.Gravity; -import android.view.View; import android.view.Window; import android.webkit.WebView; import android.webkit.WebViewClient; -import android.widget.LinearLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -40,7 +36,6 @@ import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.requests.Requester; import app.revanced.extension.shared.requests.Route; -import app.revanced.extension.shared.ui.Dim; /** * Opens a dialog showing official links. @@ -54,6 +49,30 @@ public class ReVancedAboutPreference extends Preference { return text.replace("-", "‑"); // #8209 = non breaking hyphen. } + private static String getColorHexString(int color) { + return String.format("#%06X", (0x00FFFFFF & color)); + } + + protected boolean isDarkModeEnabled() { + Configuration config = getContext().getResources().getConfiguration(); + final int currentNightMode = config.uiMode & Configuration.UI_MODE_NIGHT_MASK; + return currentNightMode == Configuration.UI_MODE_NIGHT_YES; + } + + /** + * Subclasses can override this and provide a themed color. + */ + protected int getLightColor() { + return Color.WHITE; + } + + /** + * Subclasses can override this and provide a themed color. + */ + protected int getDarkColor() { + return Color.BLACK; + } + /** * Apps that do not support bundling resources must override this. * @@ -70,8 +89,9 @@ public class ReVancedAboutPreference extends Preference { builder.append(""); builder.append(""); - String foregroundColorHex = Utils.getColorHexString(Utils.getAppForegroundColor()); - String backgroundColorHex = Utils.getColorHexString(Utils.getDialogBackgroundColor()); + final boolean isDarkMode = isDarkModeEnabled(); + String backgroundColorHex = getColorHexString(isDarkMode ? getDarkColor() : getLightColor()); + String foregroundColorHex = getColorHexString(isDarkMode ? getLightColor() : getDarkColor()); // Apply light/dark mode colors. builder.append(String.format( "", @@ -126,8 +146,6 @@ public class ReVancedAboutPreference extends Preference { { setOnPreferenceClickListener(pref -> { - Context context = pref.getContext(); - // Show a progress spinner if the social links are not fetched yet. if (!AboutLinksRoutes.hasFetchedLinks() && Utils.isNetworkConnected()) { // Show a progress spinner, but only if the api fetch takes more than a half a second. @@ -140,18 +158,17 @@ public class ReVancedAboutPreference extends Preference { handler.postDelayed(showDialogRunnable, delayToShowProgressSpinner); Utils.runOnBackgroundThread(() -> - fetchLinksAndShowDialog(context, handler, showDialogRunnable, progress)); + fetchLinksAndShowDialog(handler, showDialogRunnable, progress)); } else { // No network call required and can run now. - fetchLinksAndShowDialog(context, null, null, null); + fetchLinksAndShowDialog(null, null, null); } return false; }); } - private void fetchLinksAndShowDialog(Context context, - @Nullable Handler handler, + private void fetchLinksAndShowDialog(@Nullable Handler handler, Runnable showDialogRunnable, @Nullable ProgressDialog progress) { WebLink[] links = AboutLinksRoutes.fetchAboutLinks(); @@ -168,17 +185,7 @@ public class ReVancedAboutPreference extends Preference { if (handler != null) { handler.removeCallbacks(showDialogRunnable); } - - // Don't continue if the activity is done. To test this tap the - // about dialog and immediately press back before the dialog can show. - if (context instanceof Activity activity) { - if (activity.isFinishing() || activity.isDestroyed()) { - Logger.printDebug(() -> "Not showing about dialog, activity is closed"); - return; - } - } - - if (progress != null && progress.isShowing()) { + if (progress != null) { progress.dismiss(); } new WebViewDialog(getContext(), htmlDialog).show(); @@ -216,37 +223,14 @@ class WebViewDialog extends Dialog { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - requestWindowFeature(Window.FEATURE_NO_TITLE); // Remove default title bar. + requestWindowFeature(Window.FEATURE_NO_TITLE); - // Create main layout. - LinearLayout mainLayout = new LinearLayout(getContext()); - mainLayout.setOrientation(LinearLayout.VERTICAL); - - mainLayout.setPadding(Dim.dp10, Dim.dp10, Dim.dp10, Dim.dp10); - // Set rounded rectangle background. - ShapeDrawable mainBackground = new ShapeDrawable(new RoundRectShape( - Dim.roundedCorners(28), null, null)); - mainBackground.getPaint().setColor(Utils.getDialogBackgroundColor()); - mainLayout.setBackground(mainBackground); - - // Create WebView. WebView webView = new WebView(getContext()); - webView.setVerticalScrollBarEnabled(false); // Disable the vertical scrollbar. - webView.setOverScrollMode(View.OVER_SCROLL_NEVER); webView.getSettings().setJavaScriptEnabled(true); webView.setWebViewClient(new OpenLinksExternallyWebClient()); webView.loadDataWithBaseURL(null, htmlContent, "text/html", "utf-8", null); - // Add WebView to layout. - mainLayout.addView(webView); - - setContentView(mainLayout); - - // Set dialog window attributes. - Window window = getWindow(); - if (window != null) { - Utils.setDialogWindowParameters(window, Gravity.CENTER, 0, 90, false); - } + setContentView(webView); } private class OpenLinksExternallyWebClient extends WebViewClient { @@ -334,7 +318,7 @@ class AboutLinksRoutes { // Do not show an exception toast if the server is down final int responseCode = connection.getResponseCode(); if (responseCode != 200) { - Logger.printDebug(() -> "Failed to get social links. Response code: " + responseCode); + Logger.printDebug(() -> "Failed to get social links. Response code: " + responseCode); return NO_CONNECTION_STATIC_LINKS; } diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ResettableEditTextPreference.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ResettableEditTextPreference.java index c6f323ceb4..3e9a969611 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ResettableEditTextPreference.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ResettableEditTextPreference.java @@ -1,33 +1,24 @@ package app.revanced.extension.shared.settings.preference; -import static app.revanced.extension.shared.StringRef.str; - -import android.app.Dialog; +import android.app.AlertDialog; import android.content.Context; import android.os.Bundle; import android.preference.EditTextPreference; import android.util.AttributeSet; -import android.util.Pair; +import android.widget.Button; import android.widget.EditText; -import android.widget.LinearLayout; -import androidx.annotation.Nullable; +import app.revanced.extension.shared.Utils; +import app.revanced.extension.shared.settings.Setting; +import app.revanced.extension.shared.Logger; import java.util.Objects; -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.settings.Setting; -import app.revanced.extension.shared.ui.CustomDialog; +import static app.revanced.extension.shared.StringRef.str; @SuppressWarnings({"unused", "deprecation"}) public class ResettableEditTextPreference extends EditTextPreference { - /** - * Setting to reset. - */ - @Nullable - private Setting setting; - public ResettableEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @@ -41,65 +32,36 @@ public class ResettableEditTextPreference extends EditTextPreference { super(context); } - public void setSetting(@Nullable Setting setting) { - this.setting = setting; + @Override + protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { + super.onPrepareDialogBuilder(builder); + Utils.setEditTextDialogTheme(builder); + + Setting setting = Setting.getSettingFromPath(getKey()); + if (setting != null) { + builder.setNeutralButton(str("revanced_settings_reset"), null); + } } @Override protected void showDialog(Bundle state) { - try { - Context context = getContext(); - EditText editText = getEditText(); + super.showDialog(state); - // Resolve setting if not already set. - if (setting == null) { - String key = getKey(); - if (key != null) { - setting = Setting.getSettingFromPath(key); - } - } - - // Set initial EditText value to the current persisted value or empty string. - String initialValue = getText() != null ? getText() : ""; - editText.setText(initialValue); - editText.setSelection(initialValue.length()); // Move cursor to end. - - // Create custom dialog. - String neutralButtonText = (setting != null) ? str("revanced_settings_reset") : null; - Pair dialogPair = CustomDialog.create( - context, - getTitle() != null ? getTitle().toString() : "", // Title. - null, // Message is replaced by EditText. - editText, // Pass the EditText. - null, // OK button text. - () -> { - // OK button action. Persist the EditText value when OK is clicked. - String newValue = editText.getText().toString(); - if (callChangeListener(newValue)) { - setText(newValue); - } - }, - () -> {}, // Cancel button action (dismiss only). - neutralButtonText, // Neutral button text (Reset). - () -> { - // Neutral button action. - if (setting != null) { - try { - String defaultStringValue = Objects.requireNonNull(setting).defaultValue.toString(); - editText.setText(defaultStringValue); - editText.setSelection(defaultStringValue.length()); // Move cursor to end of text. - } catch (Exception ex) { - Logger.printException(() -> "reset failure", ex); - } - } - }, - false // Do not dismiss dialog when onNeutralClick. - ); - - // Show the dialog. - dialogPair.first.show(); - } catch (Exception ex) { - Logger.printException(() -> "showDialog failure", ex); + // Override the button click listener to prevent dismissing the dialog. + Button button = ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_NEUTRAL); + if (button == null) { + return; } + button.setOnClickListener(v -> { + try { + Setting setting = Objects.requireNonNull(Setting.getSettingFromPath(getKey())); + String defaultStringValue = setting.defaultValue.toString(); + EditText editText = getEditText(); + editText.setText(defaultStringValue); + editText.setSelection(defaultStringValue.length()); // move cursor to end of text + } catch (Exception ex) { + Logger.printException(() -> "reset failure", ex); + } + }); } } diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/SharedPrefCategory.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/SharedPrefCategory.java index ed5db6b235..4e9c1f2e0b 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/SharedPrefCategory.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/SharedPrefCategory.java @@ -36,18 +36,18 @@ public class SharedPrefCategory { } private void saveObjectAsString(@NonNull String key, @Nullable Object value) { - preferences.edit().putString(key, (value == null ? null : value.toString())).commit(); + preferences.edit().putString(key, (value == null ? null : value.toString())).apply(); } /** * Removes any preference data type that has the specified key. */ public void removeKey(@NonNull String key) { - preferences.edit().remove(Objects.requireNonNull(key)).commit(); + preferences.edit().remove(Objects.requireNonNull(key)).apply(); } public void saveBoolean(@NonNull String key, boolean value) { - preferences.edit().putBoolean(key, value).commit(); + preferences.edit().putBoolean(key, value).apply(); } /** diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/SortedListPreference.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/SortedListPreference.java deleted file mode 100644 index fb32e7bc07..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/SortedListPreference.java +++ /dev/null @@ -1,124 +0,0 @@ -package app.revanced.extension.shared.settings.preference; - -import android.content.Context; -import android.util.AttributeSet; -import android.util.Pair; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import app.revanced.extension.shared.Utils; - -/** - * PreferenceList that sorts itself. - * By default the first entry is preserved in its original position, - * and all other entries are sorted alphabetically. - * - * Ideally the 'keep first entries to preserve' is an xml parameter, - * but currently that's not so simple since Extensions code cannot use - * generated code from the Patches repo (which is required for custom xml parameters). - * - * If any class wants to use a different getFirstEntriesToPreserve value, - * it needs to subclass this preference and override {@link #getFirstEntriesToPreserve}. - */ -@SuppressWarnings({"unused", "deprecation"}) -public class SortedListPreference extends CustomDialogListPreference { - - /** - * Sorts the current list entries. - * - * @param firstEntriesToPreserve The number of entries to preserve in their original position, - * or a negative value to not sort and leave entries - * as they current are. - */ - public void sortEntryAndValues(int firstEntriesToPreserve) { - CharSequence[] entries = getEntries(); - CharSequence[] entryValues = getEntryValues(); - if (entries == null || entryValues == null) { - return; - } - - final int entrySize = entries.length; - if (entrySize != entryValues.length) { - // Xml array declaration has a missing/extra entry. - throw new IllegalStateException(); - } - - if (firstEntriesToPreserve < 0) { - return; // Nothing to do. - } - - List> firstEntries = new ArrayList<>(firstEntriesToPreserve); - - // Android does not have a triple class like Kotlin, So instead use a nested pair. - // Cannot easily use a SortedMap, because if two entries incorrectly have - // identical names then the duplicates entries are not preserved. - List>> lastEntries = new ArrayList<>(); - - for (int i = 0; i < entrySize; i++) { - Pair pair = new Pair<>(entries[i], entryValues[i]); - if (i < firstEntriesToPreserve) { - firstEntries.add(pair); - } else { - lastEntries.add(new Pair<>(Utils.removePunctuationToLowercase(pair.first), pair)); - } - } - - //noinspection ComparatorCombinators - Collections.sort(lastEntries, (pair1, pair2) - -> pair1.first.compareTo(pair2.first)); - - CharSequence[] sortedEntries = new CharSequence[entrySize]; - CharSequence[] sortedEntryValues = new CharSequence[entrySize]; - - int i = 0; - for (Pair pair : firstEntries) { - sortedEntries[i] = pair.first; - sortedEntryValues[i] = pair.second; - i++; - } - - for (Pair> outer : lastEntries) { - Pair inner = outer.second; - sortedEntries[i] = inner.first; - sortedEntryValues[i] = inner.second; - i++; - } - - super.setEntries(sortedEntries); - super.setEntryValues(sortedEntryValues); - } - - public SortedListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - - sortEntryAndValues(getFirstEntriesToPreserve()); - } - - public SortedListPreference(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - - sortEntryAndValues(getFirstEntriesToPreserve()); - } - - public SortedListPreference(Context context, AttributeSet attrs) { - super(context, attrs); - - sortEntryAndValues(getFirstEntriesToPreserve()); - } - - public SortedListPreference(Context context) { - super(context); - - sortEntryAndValues(getFirstEntriesToPreserve()); - } - - /** - * @return The number of first entries to leave exactly where they are, and do not sort them. - * A negative value indicates do not sort any entries. - */ - protected int getFirstEntriesToPreserve() { - return 1; - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ToolbarPreferenceFragment.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ToolbarPreferenceFragment.java deleted file mode 100644 index 8b1d8b882d..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ToolbarPreferenceFragment.java +++ /dev/null @@ -1,173 +0,0 @@ -package app.revanced.extension.shared.settings.preference; - -import android.annotation.SuppressLint; -import android.app.Dialog; -import android.graphics.Insets; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.preference.Preference; -import android.preference.PreferenceGroup; -import android.preference.PreferenceScreen; -import android.view.ViewGroup; -import android.view.Window; -import android.view.WindowInsets; -import android.widget.TextView; -import android.widget.Toolbar; - -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; - -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.ResourceType; -import app.revanced.extension.shared.Utils; -import app.revanced.extension.shared.settings.BaseActivityHook; -import app.revanced.extension.shared.ui.Dim; - -@SuppressWarnings("deprecation") -@RequiresApi(api = Build.VERSION_CODES.O) -public class ToolbarPreferenceFragment extends AbstractPreferenceFragment { - - /** - * Removes the list of preferences from this fragment, if they exist. - * @param keys Preference keys. - */ - protected void removePreferences(String ... keys) { - for (String key : keys) { - Preference pref = findPreference(key); - if (pref != null) { - PreferenceGroup parent = pref.getParent(); - if (parent != null) { - Logger.printDebug(() -> "Removing preference: " + key); - parent.removePreference(pref); - } - } - } - } - - /** - * Sets toolbar for all nested preference screens. - */ - protected void setPreferenceScreenToolbar(PreferenceScreen parentScreen) { - for (int i = 0, count = parentScreen.getPreferenceCount(); i < count; i++) { - Preference childPreference = parentScreen.getPreference(i); - if (childPreference instanceof PreferenceScreen) { - // Recursively set sub preferences. - setPreferenceScreenToolbar((PreferenceScreen) childPreference); - - childPreference.setOnPreferenceClickListener( - childScreen -> { - Dialog preferenceScreenDialog = ((PreferenceScreen) childScreen).getDialog(); - ViewGroup rootView = (ViewGroup) preferenceScreenDialog - .findViewById(android.R.id.content) - .getParent(); - - // Allow package-specific background customization. - customizeDialogBackground(rootView); - - // Fix the system navigation bar color for submenus. - setNavigationBarColor(preferenceScreenDialog.getWindow()); - - // Fix edge-to-edge screen with Android 15 and YT 19.45+ - // https://developer.android.com/develop/ui/views/layout/edge-to-edge#system-bars-insets - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - rootView.setOnApplyWindowInsetsListener((v, insets) -> { - Insets statusInsets = insets.getInsets(WindowInsets.Type.statusBars()); - Insets navInsets = insets.getInsets(WindowInsets.Type.navigationBars()); - Insets cutoutInsets = insets.getInsets(WindowInsets.Type.displayCutout()); - - // Apply padding for display cutout in landscape. - int leftPadding = cutoutInsets.left; - int rightPadding = cutoutInsets.right; - int topPadding = statusInsets.top; - int bottomPadding = navInsets.bottom; - - v.setPadding(leftPadding, topPadding, rightPadding, bottomPadding); - return insets; - }); - } - - Toolbar toolbar = new Toolbar(childScreen.getContext()); - toolbar.setTitle(childScreen.getTitle()); - toolbar.setNavigationIcon(getBackButtonDrawable()); - toolbar.setNavigationOnClickListener(view -> preferenceScreenDialog.dismiss()); - - toolbar.setTitleMargin(Dim.dp16, 0, Dim.dp16, 0); - - TextView toolbarTextView = Utils.getChildView(toolbar, - true, TextView.class::isInstance); - if (toolbarTextView != null) { - toolbarTextView.setTextColor(Utils.getAppForegroundColor()); - toolbarTextView.setTextSize(20); - } - - // Allow package-specific toolbar customization. - customizeToolbar(toolbar); - - // Allow package-specific post-toolbar setup. - onPostToolbarSetup(toolbar, preferenceScreenDialog); - - rootView.addView(toolbar, 0); - return false; - } - ); - } - } - } - - /** - * Sets the system navigation bar color for the activity. - * Applies the background color obtained from {@link Utils#getAppBackgroundColor()} to the navigation bar. - * For Android 10 (API 29) and above, enforces navigation bar contrast to ensure visibility. - */ - public static void setNavigationBarColor(@Nullable Window window) { - if (window == null) { - Logger.printDebug(() -> "Cannot set navigation bar color, window is null"); - return; - } - - window.setNavigationBarColor(Utils.getAppBackgroundColor()); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - window.setNavigationBarContrastEnforced(true); - } - } - - /** - * Returns the drawable for the back button. - */ - @SuppressLint("UseCompatLoadingForDrawables") - public static Drawable getBackButtonDrawable() { - final int backButtonResource = Utils.getResourceIdentifierOrThrow(ResourceType.DRAWABLE, - Utils.appIsUsingBoldIcons() - ? "revanced_settings_toolbar_arrow_left_bold" - : "revanced_settings_toolbar_arrow_left"); - Drawable drawable = Utils.getContext().getResources().getDrawable(backButtonResource); - customizeBackButtonDrawable(drawable); - return drawable; - } - - /** - * Customizes the back button drawable. - */ - protected static void customizeBackButtonDrawable(Drawable drawable) { - drawable.setTint(Utils.getAppForegroundColor()); - } - - /** - * Allows subclasses to customize the dialog's root view background. - */ - protected void customizeDialogBackground(ViewGroup rootView) { - rootView.setBackgroundColor(Utils.getAppBackgroundColor()); - } - - /** - * Allows subclasses to customize the toolbar. - */ - protected void customizeToolbar(Toolbar toolbar) { - BaseActivityHook.setToolbarLayoutParams(toolbar); - } - - /** - * Allows subclasses to perform actions after toolbar setup. - */ - protected void onPostToolbarSetup(Toolbar toolbar, Dialog preferenceScreenDialog) {} -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/URLLinkPreference.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/URLLinkPreference.java deleted file mode 100644 index 59f3077ceb..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/URLLinkPreference.java +++ /dev/null @@ -1,44 +0,0 @@ -package app.revanced.extension.shared.settings.preference; - -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.preference.Preference; -import android.util.AttributeSet; - -import app.revanced.extension.shared.Logger; - -/** - * Simple preference that opens a URL when clicked. - */ -@SuppressWarnings("deprecation") -public class URLLinkPreference extends Preference { - - protected String externalURL; - - { - setOnPreferenceClickListener(pref -> { - if (externalURL == null) { - Logger.printException(() -> "URL not set " + getClass().getSimpleName()); - return false; - } - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse(externalURL)); - pref.getContext().startActivity(i); - return true; - }); - } - - public URLLinkPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - public URLLinkPreference(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - public URLLinkPreference(Context context, AttributeSet attrs) { - super(context, attrs); - } - public URLLinkPreference(Context context) { - super(context); - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/search/BaseSearchResultItem.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/search/BaseSearchResultItem.java deleted file mode 100644 index 95731418d2..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/search/BaseSearchResultItem.java +++ /dev/null @@ -1,373 +0,0 @@ -package app.revanced.extension.shared.settings.search; - -import android.graphics.Color; -import android.preference.ListPreference; -import android.preference.Preference; -import android.preference.SwitchPreference; -import android.text.SpannableStringBuilder; -import android.text.TextUtils; -import android.text.style.BackgroundColorSpan; - -import androidx.annotation.ColorInt; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import app.revanced.extension.shared.ResourceType; -import app.revanced.extension.shared.Utils; -import app.revanced.extension.shared.settings.preference.ColorPickerPreference; -import app.revanced.extension.shared.settings.preference.CustomDialogListPreference; -import app.revanced.extension.shared.settings.preference.URLLinkPreference; - -/** - * Abstract base class for search result items, defining common fields and behavior. - */ -public abstract class BaseSearchResultItem { - // Enum to represent view types. - public enum ViewType { - REGULAR, - SWITCH, - LIST, - COLOR_PICKER, - GROUP_HEADER, - NO_RESULTS, - URL_LINK; - - // Get the corresponding layout resource ID. - public int getLayoutResourceId() { - return switch (this) { - case REGULAR, URL_LINK -> getResourceIdentifier("revanced_preference_search_result_regular"); - case SWITCH -> getResourceIdentifier("revanced_preference_search_result_switch"); - case LIST -> getResourceIdentifier("revanced_preference_search_result_list"); - case COLOR_PICKER -> getResourceIdentifier("revanced_preference_search_result_color"); - case GROUP_HEADER -> getResourceIdentifier("revanced_preference_search_result_group_header"); - case NO_RESULTS -> getResourceIdentifier("revanced_preference_search_no_result"); - }; - } - - private static int getResourceIdentifier(String name) { - // Placeholder for actual resource identifier retrieval. - return Utils.getResourceIdentifierOrThrow(ResourceType.LAYOUT, name); - } - } - - final String navigationPath; - final List navigationKeys; - final ViewType preferenceType; - CharSequence highlightedTitle; - CharSequence highlightedSummary; - boolean highlightingApplied; - - BaseSearchResultItem(String navPath, List navKeys, ViewType type) { - this.navigationPath = navPath; - this.navigationKeys = new ArrayList<>(navKeys != null ? navKeys : Collections.emptyList()); - this.preferenceType = type; - this.highlightedTitle = ""; - this.highlightedSummary = ""; - this.highlightingApplied = false; - } - - abstract boolean matchesQuery(String query); - abstract void applyHighlighting(Pattern queryPattern); - abstract void clearHighlighting(); - - // Shared method for highlighting text with search query. - protected static CharSequence highlightSearchQuery(CharSequence text, Pattern queryPattern) { - if (TextUtils.isEmpty(text) || queryPattern == null) return text; - - final int adjustedColor = Utils.adjustColorBrightness( - Utils.getAppBackgroundColor(), 0.95f, 1.20f); - BackgroundColorSpan highlightSpan = new BackgroundColorSpan(adjustedColor); - SpannableStringBuilder spannable = new SpannableStringBuilder(text); - - Matcher matcher = queryPattern.matcher(text); - while (matcher.find()) { - int start = matcher.start(); - int end = matcher.end(); - if (start == end) continue; // Skip zero matches. - spannable.setSpan(highlightSpan, start, end, - SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - } - - return spannable; - } - - /** - * Search result item for group headers (navigation path only). - */ - public static class GroupHeaderItem extends BaseSearchResultItem { - GroupHeaderItem(String navPath, List navKeys) { - super(navPath, navKeys, ViewType.GROUP_HEADER); - this.highlightedTitle = navPath; - } - - @Override - boolean matchesQuery(String query) { - return false; // Headers are not directly searchable. - } - - @Override - void applyHighlighting(Pattern queryPattern) {} - - @Override - void clearHighlighting() {} - } - - /** - * Search result item for preferences, handling type-specific data and search text. - */ - @SuppressWarnings("deprecation") - public static class PreferenceSearchItem extends BaseSearchResultItem { - public final Preference preference; - final String searchableText; - final CharSequence originalTitle; - final CharSequence originalSummary; - final CharSequence originalSummaryOn; - final CharSequence originalSummaryOff; - final CharSequence[] originalEntries; - private CharSequence[] highlightedEntries; - private boolean entriesHighlightingApplied; - - @ColorInt - private int color; - - // Store last applied highlighting pattern to reapply when needed. - Pattern lastQueryPattern; - - PreferenceSearchItem(Preference pref, String navPath, List navKeys) { - super(navPath, navKeys, determineType(pref)); - this.preference = pref; - this.originalTitle = pref.getTitle() != null ? pref.getTitle() : ""; - this.originalSummary = pref.getSummary(); - this.highlightedTitle = this.originalTitle; - this.highlightedSummary = this.originalSummary != null ? this.originalSummary : ""; - this.color = 0; - this.lastQueryPattern = null; - - // Initialize type-specific fields. - FieldInitializationResult result = initTypeSpecificFields(pref); - this.originalSummaryOn = result.summaryOn; - this.originalSummaryOff = result.summaryOff; - this.originalEntries = result.entries; - - // Build searchable text. - this.searchableText = buildSearchableText(pref); - } - - private static class FieldInitializationResult { - CharSequence summaryOn = null; - CharSequence summaryOff = null; - CharSequence[] entries = null; - } - - private static ViewType determineType(Preference pref) { - if (pref instanceof SwitchPreference) return ViewType.SWITCH; - if (pref instanceof ListPreference) return ViewType.LIST; - if (pref instanceof ColorPickerPreference) return ViewType.COLOR_PICKER; - if (pref instanceof URLLinkPreference) return ViewType.URL_LINK; - if ("no_results_placeholder".equals(pref.getKey())) return ViewType.NO_RESULTS; - return ViewType.REGULAR; - } - - private FieldInitializationResult initTypeSpecificFields(Preference pref) { - FieldInitializationResult result = new FieldInitializationResult(); - - if (pref instanceof SwitchPreference switchPref) { - result.summaryOn = switchPref.getSummaryOn(); - result.summaryOff = switchPref.getSummaryOff(); - } else if (pref instanceof ColorPickerPreference colorPref) { - String colorString = colorPref.getText(); - this.color = TextUtils.isEmpty(colorString) ? 0 : Color.parseColor(colorString); - } else if (pref instanceof ListPreference listPref) { - result.entries = listPref.getEntries(); - if (result.entries != null) { - this.highlightedEntries = new CharSequence[result.entries.length]; - System.arraycopy(result.entries, 0, this.highlightedEntries, 0, result.entries.length); - } - } - - this.entriesHighlightingApplied = false; - return result; - } - - private String buildSearchableText(Preference pref) { - StringBuilder searchBuilder = new StringBuilder(); - String key = pref.getKey(); - String normalizedKey = ""; - if (key != null) { - // Normalize preference key by removing the common "revanced_" prefix - // so that users can search by the meaningful part only. - normalizedKey = key.startsWith("revanced_") - ? key.substring("revanced_".length()) - : key; - } - appendText(searchBuilder, normalizedKey); - appendText(searchBuilder, originalTitle); - appendText(searchBuilder, originalSummary); - - // Add type-specific searchable content. - if (pref instanceof ListPreference) { - if (originalEntries != null) { - for (CharSequence entry : originalEntries) { - appendText(searchBuilder, entry); - } - } - } else if (pref instanceof SwitchPreference) { - appendText(searchBuilder, originalSummaryOn); - appendText(searchBuilder, originalSummaryOff); - } else if (pref instanceof ColorPickerPreference) { - appendText(searchBuilder, ColorPickerPreference.getColorString(color, false)); - } - - // Include navigation path in searchable text. - appendText(searchBuilder, navigationPath); - - return searchBuilder.toString(); - } - - /** - * Appends normalized searchable text to the builder. - * Uses full Unicode normalization for accurate search across all languages. - */ - private void appendText(StringBuilder builder, CharSequence text) { - if (!TextUtils.isEmpty(text)) { - if (builder.length() > 0) builder.append(" "); - builder.append(Utils.normalizeTextToLowercase(text)); - } - } - - /** - * Gets the current effective summary for this preference, considering state-dependent summaries. - */ - public CharSequence getCurrentEffectiveSummary() { - if (preference instanceof CustomDialogListPreference customPref) { - String staticSum = customPref.getStaticSummary(); - if (staticSum != null) { - return staticSum; - } - } - if (preference instanceof SwitchPreference switchPref) { - boolean currentState = switchPref.isChecked(); - return currentState - ? (originalSummaryOn != null ? originalSummaryOn : - originalSummary != null ? originalSummary : "") - : (originalSummaryOff != null ? originalSummaryOff : - originalSummary != null ? originalSummary : ""); - } else if (preference instanceof ListPreference listPref) { - String value = listPref.getValue(); - CharSequence[] entries = listPref.getEntries(); - CharSequence[] entryValues = listPref.getEntryValues(); - if (value != null && entries != null && entryValues != null) { - for (int i = 0, length = entries.length; i < length; i++) { - if (value.equals(entryValues[i].toString())) { - return originalEntries != null && i < originalEntries.length && originalEntries[i] != null - ? originalEntries[i] - : originalSummary != null ? originalSummary : ""; - } - } - } - return originalSummary != null ? originalSummary : ""; - } - return originalSummary != null ? originalSummary : ""; - } - - /** - * Checks if this search result item matches the provided query. - * Uses case-insensitive matching against the searchable text. - */ - @Override - boolean matchesQuery(String query) { - return searchableText.contains(Utils.normalizeTextToLowercase(query)); - } - - /** - * Get highlighted entries to show in dialog. - */ - public CharSequence[] getHighlightedEntries() { - return highlightedEntries; - } - - /** - * Whether highlighting is applied to entries. - */ - public boolean isEntriesHighlightingApplied() { - return entriesHighlightingApplied; - } - - /** - * Highlights the search query in the title and summary. - */ - @Override - void applyHighlighting(Pattern queryPattern) { - this.lastQueryPattern = queryPattern; - // Highlight the title. - highlightedTitle = highlightSearchQuery(originalTitle, queryPattern); - - // Get the current effective summary and highlight it. - CharSequence currentSummary = getCurrentEffectiveSummary(); - highlightedSummary = highlightSearchQuery(currentSummary, queryPattern); - - // Highlight the entries. - if (preference instanceof ListPreference && originalEntries != null) { - highlightedEntries = new CharSequence[originalEntries.length]; - for (int i = 0, length = originalEntries.length; i < length; i++) { - if (originalEntries[i] != null) { - highlightedEntries[i] = highlightSearchQuery(originalEntries[i], queryPattern); - } else { - highlightedEntries[i] = null; - } - } - entriesHighlightingApplied = true; - } - - highlightingApplied = true; - } - - /** - * Clears all search query highlighting and restores original state completely. - */ - @Override - void clearHighlighting() { - if (!highlightingApplied) return; - - // Restore original title. - highlightedTitle = originalTitle; - - // Restore current effective summary without highlighting. - highlightedSummary = getCurrentEffectiveSummary(); - - // Restore original entries. - if (originalEntries != null && highlightedEntries != null) { - System.arraycopy(originalEntries, 0, highlightedEntries, 0, - Math.min(originalEntries.length, highlightedEntries.length)); - } - - entriesHighlightingApplied = false; - highlightingApplied = false; - lastQueryPattern = null; - } - - /** - * Refreshes highlighting for dynamic summaries (like switch preferences). - * Should be called when the preference state changes. - */ - public void refreshHighlighting() { - if (highlightingApplied && lastQueryPattern != null) { - CharSequence currentSummary = getCurrentEffectiveSummary(); - highlightedSummary = highlightSearchQuery(currentSummary, lastQueryPattern); - } - } - - public void setColor(int newColor) { - this.color = newColor; - } - - @ColorInt - public int getColor() { - return color; - } - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/search/BaseSearchResultsAdapter.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/search/BaseSearchResultsAdapter.java deleted file mode 100644 index 04d69c6b6b..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/search/BaseSearchResultsAdapter.java +++ /dev/null @@ -1,621 +0,0 @@ -package app.revanced.extension.shared.settings.search; - -import static app.revanced.extension.shared.Utils.getResourceIdentifierOrThrow; - -import android.animation.AnimatorSet; -import android.animation.ArgbEvaluator; -import android.animation.ObjectAnimator; -import android.content.Context; -import android.os.Handler; -import android.os.Looper; -import android.preference.ListPreference; -import android.preference.Preference; -import android.preference.PreferenceGroup; -import android.preference.PreferenceScreen; -import android.preference.SwitchPreference; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AbsListView; -import android.widget.ArrayAdapter; -import android.widget.ImageView; -import android.widget.ListAdapter; -import android.widget.ListView; -import android.widget.Switch; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.lang.reflect.Method; -import java.util.List; - -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.ResourceType; -import app.revanced.extension.shared.Utils; -import app.revanced.extension.shared.settings.preference.ColorPickerPreference; -import app.revanced.extension.shared.settings.preference.CustomDialogListPreference; -import app.revanced.extension.shared.settings.preference.URLLinkPreference; -import app.revanced.extension.shared.ui.ColorDot; - -/** - * Abstract adapter for displaying search results in overlay ListView with ViewHolder pattern. - */ -@SuppressWarnings("deprecation") -public abstract class BaseSearchResultsAdapter extends ArrayAdapter { - protected final LayoutInflater inflater; - protected final BaseSearchViewController.BasePreferenceFragment fragment; - protected final BaseSearchViewController searchViewController; - protected AnimatorSet currentAnimator; - protected abstract PreferenceScreen getMainPreferenceScreen(); - - protected static final int BLINK_DURATION = 400; - protected static final int PAUSE_BETWEEN_BLINKS = 100; - - protected static final int ID_PREFERENCE_TITLE = getResourceIdentifierOrThrow( - ResourceType.ID, "preference_title"); - protected static final int ID_PREFERENCE_SUMMARY = getResourceIdentifierOrThrow( - ResourceType.ID, "preference_summary"); - protected static final int ID_PREFERENCE_PATH = getResourceIdentifierOrThrow( - ResourceType.ID, "preference_path"); - protected static final int ID_PREFERENCE_SWITCH = getResourceIdentifierOrThrow( - ResourceType.ID, "preference_switch"); - protected static final int ID_PREFERENCE_COLOR_DOT = getResourceIdentifierOrThrow( - ResourceType.ID, "preference_color_dot"); - - protected static class RegularViewHolder { - TextView titleView; - TextView summaryView; - } - - protected static class SwitchViewHolder { - TextView titleView; - TextView summaryView; - Switch switchWidget; - } - - protected static class ColorViewHolder { - TextView titleView; - TextView summaryView; - View colorDot; - } - - protected static class GroupHeaderViewHolder { - TextView pathView; - } - - protected static class NoResultsViewHolder { - TextView titleView; - TextView summaryView; - ImageView iconView; - } - - public BaseSearchResultsAdapter(Context context, List items, - BaseSearchViewController.BasePreferenceFragment fragment, - BaseSearchViewController searchViewController) { - super(context, 0, items); - this.inflater = LayoutInflater.from(context); - this.fragment = fragment; - this.searchViewController = searchViewController; - } - - @Override - public int getItemViewType(int position) { - BaseSearchResultItem item = getItem(position); - return item == null ? 0 : item.preferenceType.ordinal(); - } - - @Override - public int getViewTypeCount() { - return BaseSearchResultItem.ViewType.values().length; - } - - @NonNull - @Override - public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { - BaseSearchResultItem item = getItem(position); - if (item == null) return new View(getContext()); - // Use the ViewType enum. - BaseSearchResultItem.ViewType viewType = item.preferenceType; - // Create or reuse preference view based on type. - return createPreferenceView(item, convertView, viewType, parent); - } - - @Override - public boolean isEnabled(int position) { - BaseSearchResultItem item = getItem(position); - // Disable for NO_RESULTS items to prevent ripple/selection. - return item != null && item.preferenceType != BaseSearchResultItem.ViewType.NO_RESULTS; - } - - /** - * Creates or reuses a view for the given SearchResultItem. - *

- * Thanks to {@link #getItemViewType(int)} and {@link #getViewTypeCount()}, ListView knows - * how many different row types exist and keeps a separate "recycling pool" for each. - * That means convertView passed here is ALWAYS of the correct type for this position. - * So only need to check if (view == null), and if so – inflate a new layout and create the proper ViewHolder. - */ - protected View createPreferenceView(BaseSearchResultItem item, View convertView, - BaseSearchResultItem.ViewType viewType, ViewGroup parent) { - View view = convertView; - if (view == null) { - view = inflateViewForType(viewType, parent); - createViewHolderForType(view, viewType); - } - - // Retrieve the cached ViewHolder. - Object holder = view.getTag(); - bindDataToViewHolder(item, holder, viewType, view); - return view; - } - - protected View inflateViewForType(BaseSearchResultItem.ViewType viewType, ViewGroup parent) { - return inflater.inflate(viewType.getLayoutResourceId(), parent, false); - } - - protected void createViewHolderForType(View view, BaseSearchResultItem.ViewType viewType) { - switch (viewType) { - case REGULAR, LIST, URL_LINK -> { - RegularViewHolder regularHolder = new RegularViewHolder(); - regularHolder.titleView = view.findViewById(ID_PREFERENCE_TITLE); - regularHolder.summaryView = view.findViewById(ID_PREFERENCE_SUMMARY); - view.setTag(regularHolder); - } - case SWITCH -> { - SwitchViewHolder switchHolder = new SwitchViewHolder(); - switchHolder.titleView = view.findViewById(ID_PREFERENCE_TITLE); - switchHolder.summaryView = view.findViewById(ID_PREFERENCE_SUMMARY); - switchHolder.switchWidget = view.findViewById(ID_PREFERENCE_SWITCH); - view.setTag(switchHolder); - } - case COLOR_PICKER -> { - ColorViewHolder colorHolder = new ColorViewHolder(); - colorHolder.titleView = view.findViewById(ID_PREFERENCE_TITLE); - colorHolder.summaryView = view.findViewById(ID_PREFERENCE_SUMMARY); - colorHolder.colorDot = view.findViewById(ID_PREFERENCE_COLOR_DOT); - view.setTag(colorHolder); - } - case GROUP_HEADER -> { - GroupHeaderViewHolder groupHolder = new GroupHeaderViewHolder(); - groupHolder.pathView = view.findViewById(ID_PREFERENCE_PATH); - view.setTag(groupHolder); - } - case NO_RESULTS -> { - NoResultsViewHolder noResultsHolder = new NoResultsViewHolder(); - noResultsHolder.titleView = view.findViewById(ID_PREFERENCE_TITLE); - noResultsHolder.summaryView = view.findViewById(ID_PREFERENCE_SUMMARY); - noResultsHolder.iconView = view.findViewById(android.R.id.icon); - view.setTag(noResultsHolder); - } - default -> throw new IllegalStateException("Unknown viewType: " + viewType); - } - } - - protected void bindDataToViewHolder(BaseSearchResultItem item, Object holder, - BaseSearchResultItem.ViewType viewType, View view) { - switch (viewType) { - case REGULAR, URL_LINK, LIST -> bindRegularViewHolder(item, (RegularViewHolder) holder, view); - case SWITCH -> bindSwitchViewHolder(item, (SwitchViewHolder) holder, view); - case COLOR_PICKER -> bindColorViewHolder(item, (ColorViewHolder) holder, view); - case GROUP_HEADER -> bindGroupHeaderViewHolder(item, (GroupHeaderViewHolder) holder, view); - case NO_RESULTS -> bindNoResultsViewHolder(item, (NoResultsViewHolder) holder); - default -> throw new IllegalStateException("Unknown viewType: " + viewType); - } - } - - protected void bindRegularViewHolder(BaseSearchResultItem item, RegularViewHolder holder, View view) { - BaseSearchResultItem.PreferenceSearchItem prefItem = (BaseSearchResultItem.PreferenceSearchItem) item; - prefItem.refreshHighlighting(); - holder.titleView.setText(item.highlightedTitle); - holder.summaryView.setText(item.highlightedSummary); - holder.summaryView.setVisibility(TextUtils.isEmpty(item.highlightedSummary) ? View.GONE : View.VISIBLE); - setupPreferenceView(view, holder.titleView, holder.summaryView, prefItem.preference, - () -> { - handlePreferenceClick(prefItem.preference); - if (prefItem.preference instanceof ListPreference) { - prefItem.refreshHighlighting(); - holder.summaryView.setText(prefItem.getCurrentEffectiveSummary()); - holder.summaryView.setVisibility(TextUtils.isEmpty(prefItem.highlightedSummary) ? View.GONE : View.VISIBLE); - notifyDataSetChanged(); - } - }, - () -> navigateAndScrollToPreference(item)); - } - - protected void bindSwitchViewHolder(BaseSearchResultItem item, SwitchViewHolder holder, View view) { - BaseSearchResultItem.PreferenceSearchItem prefItem = (BaseSearchResultItem.PreferenceSearchItem) item; - SwitchPreference switchPref = (SwitchPreference) prefItem.preference; - holder.titleView.setText(item.highlightedTitle); - holder.switchWidget.setBackground(null); // Remove ripple/highlight. - // Sync switch state with preference without animation. - boolean currentState = switchPref.isChecked(); - if (holder.switchWidget.isChecked() != currentState) { - holder.switchWidget.setChecked(currentState); - holder.switchWidget.jumpDrawablesToCurrentState(); - } - prefItem.refreshHighlighting(); - holder.summaryView.setText(prefItem.highlightedSummary); - holder.summaryView.setVisibility(TextUtils.isEmpty(prefItem.highlightedSummary) ? View.GONE : View.VISIBLE); - setupPreferenceView(view, holder.titleView, holder.summaryView, switchPref, - () -> { - boolean newState = !switchPref.isChecked(); - switchPref.setChecked(newState); - holder.switchWidget.setChecked(newState); - prefItem.refreshHighlighting(); - holder.summaryView.setText(prefItem.getCurrentEffectiveSummary()); - holder.summaryView.setVisibility(TextUtils.isEmpty(prefItem.highlightedSummary) ? View.GONE : View.VISIBLE); - if (switchPref.getOnPreferenceChangeListener() != null) { - switchPref.getOnPreferenceChangeListener().onPreferenceChange(switchPref, newState); - } - notifyDataSetChanged(); - }, - () -> navigateAndScrollToPreference(item)); - holder.switchWidget.setEnabled(switchPref.isEnabled()); - } - - protected void bindColorViewHolder(BaseSearchResultItem item, ColorViewHolder holder, View view) { - BaseSearchResultItem.PreferenceSearchItem prefItem = (BaseSearchResultItem.PreferenceSearchItem) item; - holder.titleView.setText(item.highlightedTitle); - holder.summaryView.setText(item.highlightedSummary); - holder.summaryView.setVisibility(TextUtils.isEmpty(item.highlightedSummary) ? View.GONE : View.VISIBLE); - ColorDot.applyColorDot(holder.colorDot, prefItem.getColor(), prefItem.preference.isEnabled()); - setupPreferenceView(view, holder.titleView, holder.summaryView, prefItem.preference, - () -> handlePreferenceClick(prefItem.preference), - () -> navigateAndScrollToPreference(item)); - } - - protected void bindGroupHeaderViewHolder(BaseSearchResultItem item, GroupHeaderViewHolder holder, View view) { - holder.pathView.setText(item.highlightedTitle); - view.setOnClickListener(v -> navigateToTargetScreen(item)); - } - - protected void bindNoResultsViewHolder(BaseSearchResultItem item, NoResultsViewHolder holder) { - holder.titleView.setText(item.highlightedTitle); - holder.summaryView.setText(item.highlightedSummary); - holder.summaryView.setVisibility(TextUtils.isEmpty(item.highlightedSummary) ? View.GONE : View.VISIBLE); - holder.iconView.setImageResource(BaseSearchViewController.getSearchIcon()); - } - - /** - * Sets up a preference view with click listeners and proper enabled state handling. - */ - protected void setupPreferenceView(View view, TextView titleView, TextView summaryView, Preference preference, - Runnable onClickAction, Runnable onLongClickAction) { - boolean enabled = preference.isEnabled(); - - // To enable long-click navigation for disabled settings, manually control the enabled state of the title - // and summary and disable the ripple effect instead of using 'view.setEnabled(enabled)'. - - titleView.setEnabled(enabled); - summaryView.setEnabled(enabled); - - if (!enabled) view.setBackground(null); // Disable ripple effect. - - // In light mode, alpha 0.5 is applied to a disabled title automatically, - // but in dark mode it needs to be applied manually. - if (Utils.isDarkModeEnabled()) { - titleView.setAlpha(enabled ? 1.0f : ColorPickerPreference.DISABLED_ALPHA); - } - // Set up click and long-click listeners. - view.setOnClickListener(enabled ? v -> onClickAction.run() : null); - view.setOnLongClickListener(v -> { - onLongClickAction.run(); - return true; - }); - } - - /** - * Navigates to the settings screen containing the given search result item and triggers scrolling. - */ - protected void navigateAndScrollToPreference(BaseSearchResultItem item) { - // No navigation for URL_LINK items. - if (item.preferenceType == BaseSearchResultItem.ViewType.URL_LINK) return; - - PreferenceScreen targetScreen = navigateToTargetScreen(item); - if (targetScreen == null) return; - if (!(item instanceof BaseSearchResultItem.PreferenceSearchItem prefItem)) return; - - Preference targetPreference = prefItem.preference; - - fragment.getView().post(() -> { - ListView listView = targetScreen == getMainPreferenceScreen() - ? getPreferenceListView() - : targetScreen.getDialog().findViewById(android.R.id.list); - - if (listView == null) return; - - int targetPosition = findPreferencePosition(targetPreference, listView); - if (targetPosition == -1) return; - - int firstVisible = listView.getFirstVisiblePosition(); - int lastVisible = listView.getLastVisiblePosition(); - - if (targetPosition >= firstVisible && targetPosition <= lastVisible) { - // The preference is already visible, but still scroll it to the bottom of the list for consistency. - View child = listView.getChildAt(targetPosition - firstVisible); - if (child != null) { - // Calculate how much to scroll so the item is aligned at the bottom. - int scrollAmount = child.getBottom() - listView.getHeight(); - if (scrollAmount > 0) { - // Perform smooth scroll animation for better user experience. - listView.smoothScrollBy(scrollAmount, 300); - } - } - // Highlight the preference once it is positioned. - highlightPreferenceAtPosition(listView, targetPosition); - } else { - // The preference is outside of the current visible range, scroll to it from the top. - listView.smoothScrollToPositionFromTop(targetPosition, 0); - - Handler handler = new Handler(Looper.getMainLooper()); - // Fallback runnable in case the OnScrollListener does not trigger. - Runnable fallback = () -> { - listView.setOnScrollListener(null); - highlightPreferenceAtPosition(listView, targetPosition); - }; - // Post fallback with a small delay. - handler.postDelayed(fallback, 350); - - listView.setOnScrollListener(new AbsListView.OnScrollListener() { - private boolean isScrolling = false; - - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - if (scrollState == SCROLL_STATE_TOUCH_SCROLL || scrollState == SCROLL_STATE_FLING) { - // Mark that scrolling has started. - isScrolling = true; - } - if (scrollState == SCROLL_STATE_IDLE && isScrolling) { - // Scrolling is finished, cleanup listener and cancel fallback. - isScrolling = false; - listView.setOnScrollListener(null); - handler.removeCallbacks(fallback); - // Highlight the target preference when scrolling is done. - highlightPreferenceAtPosition(listView, targetPosition); - } - } - - @Override - public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {} - }); - } - }); - } - - /** - * Navigates to the final PreferenceScreen using preference keys or titles as fallback. - */ - protected PreferenceScreen navigateToTargetScreen(BaseSearchResultItem item) { - PreferenceScreen currentScreen = getMainPreferenceScreen(); - Preference targetPref = null; - - // Try key-based navigation first. - if (item.navigationKeys != null && !item.navigationKeys.isEmpty()) { - String finalKey = item.navigationKeys.get(item.navigationKeys.size() - 1); - targetPref = findPreferenceByKey(currentScreen, finalKey); - } - - // Fallback to title-based navigation. - if (targetPref == null && !TextUtils.isEmpty(item.navigationPath)) { - String[] pathSegments = item.navigationPath.split(" > "); - String finalSegment = pathSegments[pathSegments.length - 1].trim(); - if (!TextUtils.isEmpty(finalSegment)) { - targetPref = findPreferenceByTitle(currentScreen, finalSegment); - } - } - - if (targetPref instanceof PreferenceScreen targetScreen) { - handlePreferenceClick(targetScreen); - return targetScreen; - } - - return currentScreen; - } - - /** - * Recursively searches for a preference by title in a preference group. - */ - protected Preference findPreferenceByTitle(PreferenceGroup group, String title) { - for (int i = 0; i < group.getPreferenceCount(); i++) { - Preference pref = group.getPreference(i); - CharSequence prefTitle = pref.getTitle(); - if (prefTitle != null && (prefTitle.toString().trim().equalsIgnoreCase(title) - || normalizeString(prefTitle.toString()).equals(normalizeString(title)))) { - return pref; - } - if (pref instanceof PreferenceGroup) { - Preference found = findPreferenceByTitle((PreferenceGroup) pref, title); - if (found != null) { - return found; - } - } - } - return null; - } - - /** - * Normalizes string for comparison (removes extra characters, spaces etc.). - */ - protected String normalizeString(String input) { - if (TextUtils.isEmpty(input)) return ""; - return input.trim().toLowerCase().replaceAll("\\s+", " ").replaceAll("[^\\w\\s]", ""); - } - - /** - * Gets the ListView from the PreferenceFragment. - */ - protected ListView getPreferenceListView() { - View fragmentView = fragment.getView(); - if (fragmentView != null) { - ListView listView = findListViewInViewGroup(fragmentView); - if (listView != null) { - return listView; - } - } - return fragment.getActivity().findViewById(android.R.id.list); - } - - /** - * Recursively searches for a ListView in a ViewGroup. - */ - protected ListView findListViewInViewGroup(View view) { - if (view instanceof ListView) { - return (ListView) view; - } - if (view instanceof ViewGroup group) { - for (int i = 0; i < group.getChildCount(); i++) { - ListView result = findListViewInViewGroup(group.getChildAt(i)); - if (result != null) { - return result; - } - } - } - return null; - } - - /** - * Finds the position of a preference in the ListView adapter. - */ - protected int findPreferencePosition(Preference targetPreference, ListView listView) { - ListAdapter adapter = listView.getAdapter(); - if (adapter == null) { - return -1; - } - - for (int i = 0, count = adapter.getCount(); i < count; i++) { - Object item = adapter.getItem(i); - if (item == targetPreference) { - return i; - } - if (item instanceof Preference pref && targetPreference.getKey() != null) { - if (targetPreference.getKey().equals(pref.getKey())) { - return i; - } - } - } - return -1; - } - - /** - * Highlights a preference at the specified position with a blink effect. - */ - protected void highlightPreferenceAtPosition(ListView listView, int position) { - int firstVisible = listView.getFirstVisiblePosition(); - if (position < firstVisible || position > listView.getLastVisiblePosition()) { - return; - } - - View itemView = listView.getChildAt(position - firstVisible); - if (itemView != null) { - blinkView(itemView); - } - } - - /** - * Creates a smooth double-blink effect on a view's background without affecting the text. - * @param view The View to apply the animation to. - */ - protected void blinkView(View view) { - // If a previous animation is still running, cancel it to prevent conflicts. - if (currentAnimator != null && currentAnimator.isRunning()) { - currentAnimator.cancel(); - } - final int startColor = Utils.getAppBackgroundColor(); - final int highlightColor = Utils.adjustColorBrightness( - startColor, - Utils.isDarkModeEnabled() ? 1.25f : 0.8f - ); - // Animator for transitioning from the start color to the highlight color. - ObjectAnimator fadeIn = ObjectAnimator.ofObject( - view, - "backgroundColor", - new ArgbEvaluator(), - startColor, - highlightColor - ); - fadeIn.setDuration(BLINK_DURATION); - // Animator to return to the start color. - ObjectAnimator fadeOut = ObjectAnimator.ofObject( - view, - "backgroundColor", - new ArgbEvaluator(), - highlightColor, - startColor - ); - fadeOut.setDuration(BLINK_DURATION); - - currentAnimator = new AnimatorSet(); - // Create the sequence: fadeIn -> fadeOut -> (pause) -> fadeIn -> fadeOut. - AnimatorSet firstBlink = new AnimatorSet(); - firstBlink.playSequentially(fadeIn, fadeOut); - AnimatorSet secondBlink = new AnimatorSet(); - secondBlink.playSequentially(fadeIn.clone(), fadeOut.clone()); // Use clones for the second blink. - - currentAnimator.play(secondBlink).after(firstBlink).after(PAUSE_BETWEEN_BLINKS); - currentAnimator.start(); - } - - /** - * Recursively finds a preference by key in a preference group. - */ - protected Preference findPreferenceByKey(PreferenceGroup group, String key) { - if (group == null || TextUtils.isEmpty(key)) { - return null; - } - - // First search on current level. - for (int i = 0, count = group.getPreferenceCount(); i < count; i++) { - Preference pref = group.getPreference(i); - if (key.equals(pref.getKey())) { - return pref; - } - if (pref instanceof PreferenceGroup) { - Preference found = findPreferenceByKey((PreferenceGroup) pref, key); - if (found != null) { - return found; - } - } - } - return null; - } - - /** - * Handles preference click actions by invoking the preference's performClick method via reflection. - */ - @SuppressWarnings("all") - private void handlePreferenceClick(Preference preference) { - try { - if (preference instanceof CustomDialogListPreference listPref) { - BaseSearchResultItem.PreferenceSearchItem searchItem = - searchViewController.findSearchItemByPreference(preference); - if (searchItem != null && searchItem.isEntriesHighlightingApplied()) { - listPref.setHighlightedEntriesForDialog(searchItem.getHighlightedEntries()); - } - } - - Method m = Preference.class.getDeclaredMethod("performClick", PreferenceScreen.class); - m.setAccessible(true); - m.invoke(preference, fragment.getPreferenceScreenForSearch()); - } catch (Exception e) { - Logger.printException(() -> "Failed to invoke performClick()", e); - } - } - - /** - * Checks if a preference has navigation capability (can open a new screen). - */ - boolean hasNavigationCapability(Preference preference) { - // PreferenceScreen always allows navigation. - if (preference instanceof PreferenceScreen) return true; - // URLLinkPreference does not navigate to a new screen, it opens an external URL. - if (preference instanceof URLLinkPreference) return false; - // Other group types that might have their own screens. - if (preference instanceof PreferenceGroup) { - // Check if it has its own fragment or intent. - return preference.getIntent() != null || preference.getFragment() != null; - } - return false; - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/search/BaseSearchViewController.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/search/BaseSearchViewController.java deleted file mode 100644 index 3be942f6f6..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/search/BaseSearchViewController.java +++ /dev/null @@ -1,704 +0,0 @@ -package app.revanced.extension.shared.settings.search; - -import static app.revanced.extension.shared.StringRef.str; -import static app.revanced.extension.shared.Utils.getResourceIdentifierOrThrow; - -import android.app.Activity; -import android.content.Context; -import android.graphics.Color; -import android.graphics.drawable.GradientDrawable; -import android.os.Build; -import android.preference.Preference; -import android.preference.PreferenceCategory; -import android.preference.PreferenceGroup; -import android.preference.PreferenceScreen; -import android.text.TextUtils; -import android.view.Gravity; -import android.view.MenuItem; -import android.view.View; -import android.view.WindowManager; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; -import android.widget.EditText; -import android.widget.FrameLayout; -import android.widget.ListView; -import android.widget.SearchView; -import android.widget.Toolbar; - -import androidx.annotation.ColorInt; -import androidx.annotation.RequiresApi; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.regex.Pattern; - -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.ResourceType; -import app.revanced.extension.shared.Utils; -import app.revanced.extension.shared.settings.AppLanguage; -import app.revanced.extension.shared.settings.BaseSettings; -import app.revanced.extension.shared.settings.Setting; -import app.revanced.extension.shared.settings.preference.ColorPickerPreference; -import app.revanced.extension.shared.settings.preference.CustomDialogListPreference; -import app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory; -import app.revanced.extension.shared.ui.Dim; - -/** - * Abstract controller for managing the overlay search view in ReVanced settings. - * Subclasses must implement app-specific preference handling. - */ -@SuppressWarnings("deprecation") -public abstract class BaseSearchViewController { - protected SearchView searchView; - protected FrameLayout searchContainer; - protected FrameLayout overlayContainer; - protected final Toolbar toolbar; - protected final Activity activity; - protected final BasePreferenceFragment fragment; - protected final CharSequence originalTitle; - protected BaseSearchResultsAdapter searchResultsAdapter; - protected final List allSearchItems; - protected final List filteredSearchItems; - protected final Map keyToSearchItem; - protected final InputMethodManager inputMethodManager; - protected SearchHistoryManager searchHistoryManager; - protected boolean isSearchActive; - protected boolean isShowingSearchHistory; - - protected static final int MAX_SEARCH_RESULTS = 50; // Maximum number of search results displayed. - - protected static final int ID_REVANCED_SEARCH_VIEW = getResourceIdentifierOrThrow( - ResourceType.ID, "revanced_search_view"); - protected static final int ID_REVANCED_SEARCH_VIEW_CONTAINER = getResourceIdentifierOrThrow( - ResourceType.ID, "revanced_search_view_container"); - protected static final int ID_ACTION_SEARCH = getResourceIdentifierOrThrow( - ResourceType.ID, "action_search"); - protected static final int ID_REVANCED_SETTINGS_FRAGMENTS = getResourceIdentifierOrThrow( - ResourceType.ID, "revanced_settings_fragments"); - private static final int DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON = getResourceIdentifierOrThrow( - ResourceType.DRAWABLE, "revanced_settings_search_icon"); - private static final int DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON_BOLD = getResourceIdentifierOrThrow( - ResourceType.DRAWABLE, "revanced_settings_search_icon_bold"); - protected static final int MENU_REVANCED_SEARCH_MENU = getResourceIdentifierOrThrow( - ResourceType.MENU, "revanced_search_menu"); - - /** - * @return The search icon, either bold or not bold, depending on the ReVanced UI setting. - */ - public static int getSearchIcon() { - return Utils.appIsUsingBoldIcons() - ? DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON_BOLD - : DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON; - } - - /** - * Constructs a new BaseSearchViewController instance. - * - * @param activity The activity hosting the search view. - * @param toolbar The toolbar containing the search action. - * @param fragment The preference fragment to manage search preferences. - */ - protected BaseSearchViewController(Activity activity, Toolbar toolbar, BasePreferenceFragment fragment) { - this.activity = activity; - this.toolbar = toolbar; - this.fragment = fragment; - this.originalTitle = toolbar.getTitle(); - this.allSearchItems = new ArrayList<>(); - this.filteredSearchItems = new ArrayList<>(); - this.keyToSearchItem = new HashMap<>(); - this.inputMethodManager = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); - this.isShowingSearchHistory = false; - - // Initialize components - initializeSearchView(); - initializeOverlayContainer(); - initializeSearchHistoryManager(); - setupToolbarMenu(); - setupListeners(); - } - - /** - * Initializes the search view with proper configurations, such as background, query hint, and RTL support. - */ - private void initializeSearchView() { - // Retrieve SearchView and container from XML. - searchView = activity.findViewById(ID_REVANCED_SEARCH_VIEW); - EditText searchEditText = searchView.findViewById(Utils.getResourceIdentifierOrThrow( - null, "android:id/search_src_text")); - // Disable fullscreen keyboard mode. - searchEditText.setImeOptions(searchEditText.getImeOptions() | EditorInfo.IME_FLAG_NO_EXTRACT_UI); - - searchContainer = activity.findViewById(ID_REVANCED_SEARCH_VIEW_CONTAINER); - - // Set background and query hint. - searchView.setBackground(createBackgroundDrawable()); - searchView.setQueryHint(str("revanced_settings_search_hint")); - - // Set text size. - searchEditText.setTextSize(16); - - // Set cursor color. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - setCursorColor(searchEditText); - } - - // Configure RTL support based on app language. - AppLanguage appLanguage = BaseSettings.REVANCED_LANGUAGE.get(); - if (Utils.isRightToLeftLocale(appLanguage.getLocale())) { - searchView.setTextDirection(View.TEXT_DIRECTION_RTL); - searchView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_END); - } - } - - /** - * Sets the cursor color (for Android 10+ devices). - */ - @RequiresApi(api = Build.VERSION_CODES.Q) - private void setCursorColor(EditText editText) { - // Get the cursor color based on the current theme. - final int cursorColor = Utils.isDarkModeEnabled() ? Color.WHITE : Color.BLACK; - - // Create cursor drawable. - GradientDrawable cursorDrawable = new GradientDrawable(); - cursorDrawable.setShape(GradientDrawable.RECTANGLE); - cursorDrawable.setSize(Dim.dp2, -1); // Width: 2dp, Height: match text height. - cursorDrawable.setColor(cursorColor); - - // Set cursor drawable. - editText.setTextCursorDrawable(cursorDrawable); - } - - /** - * Initializes the overlay container for displaying search results and history. - */ - private void initializeOverlayContainer() { - // Create overlay container for search results and history. - overlayContainer = new FrameLayout(activity); - overlayContainer.setVisibility(View.GONE); - overlayContainer.setBackgroundColor(Utils.getAppBackgroundColor()); - overlayContainer.setElevation(Dim.dp8); - - // Container for search results. - FrameLayout searchResultsContainer = new FrameLayout(activity); - searchResultsContainer.setVisibility(View.VISIBLE); - - // Create a ListView for the results. - ListView searchResultsListView = new ListView(activity); - searchResultsListView.setDivider(null); - searchResultsListView.setDividerHeight(0); - searchResultsAdapter = createSearchResultsAdapter(); - searchResultsListView.setAdapter(searchResultsAdapter); - - // Add results list into container. - searchResultsContainer.addView(searchResultsListView, new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.MATCH_PARENT)); - - // Add results container into overlay. - overlayContainer.addView(searchResultsContainer, new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.MATCH_PARENT)); - - // Add overlay to the main content container. - FrameLayout mainContainer = activity.findViewById(ID_REVANCED_SETTINGS_FRAGMENTS); - if (mainContainer != null) { - FrameLayout.LayoutParams overlayParams = new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.MATCH_PARENT); - overlayParams.gravity = Gravity.TOP; - mainContainer.addView(overlayContainer, overlayParams); - } - } - - /** - * Initializes the search history manager with the specified overlay container and listener. - */ - private void initializeSearchHistoryManager() { - searchHistoryManager = new SearchHistoryManager(activity, overlayContainer, query -> { - searchView.setQuery(query, true); - hideSearchHistory(); - }); - } - - // Abstract methods that subclasses must implement. - protected abstract BaseSearchResultsAdapter createSearchResultsAdapter(); - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - protected abstract boolean isSpecialPreferenceGroup(Preference preference); - protected abstract void setupSpecialPreferenceListeners(BaseSearchResultItem item); - - // Abstract interface for preference fragments. - public interface BasePreferenceFragment { - PreferenceScreen getPreferenceScreenForSearch(); - android.view.View getView(); - Activity getActivity(); - } - - /** - * Determines whether a preference should be included in the search index. - * - * @param preference The preference to evaluate. - * @param currentDepth The current depth in the preference hierarchy. - * @param includeDepth The maximum depth to include in the search index. - * @return True if the preference should be included, false otherwise. - */ - protected boolean shouldIncludePreference(Preference preference, int currentDepth, int includeDepth) { - return includeDepth <= currentDepth - && !(preference instanceof PreferenceCategory) - && !isSpecialPreferenceGroup(preference) - && !(preference instanceof PreferenceScreen); - } - - /** - * Sets up the toolbar menu for the search action. - */ - protected void setupToolbarMenu() { - toolbar.inflateMenu(MENU_REVANCED_SEARCH_MENU); - toolbar.setOnMenuItemClickListener(item -> { - if (item.getItemId() == ID_ACTION_SEARCH && !isSearchActive) { - openSearch(); - return true; - } - return false; - }); - - // Set bold icon if needed. - MenuItem search = toolbar.getMenu().findItem(ID_ACTION_SEARCH); - search.setIcon(getSearchIcon()); - } - - /** - * Configures listeners for the search view and toolbar navigation. - */ - protected void setupListeners() { - searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { - @Override - public boolean onQueryTextSubmit(String query) { - try { - String queryTrimmed = query.trim(); - if (!queryTrimmed.isEmpty()) { - searchHistoryManager.saveSearchQuery(queryTrimmed); - } - } catch (Exception ex) { - Logger.printException(() -> "onQueryTextSubmit failure", ex); - } - return false; - } - - @Override - public boolean onQueryTextChange(String newText) { - try { - Logger.printDebug(() -> "Search query: " + newText); - - String trimmedText = newText.trim(); - if (!isSearchActive) { - Logger.printDebug(() -> "Search is not active, skipping query processing"); - return true; - } - - if (trimmedText.isEmpty()) { - // If empty query: show history. - hideSearchResults(); - showSearchHistory(); - } else { - // If has search text: hide history and show search results. - hideSearchHistory(); - filterAndShowResults(newText); - } - } catch (Exception ex) { - Logger.printException(() -> "onQueryTextChange failure", ex); - } - return true; - } - }); - // Set navigation click listener. - toolbar.setNavigationOnClickListener(view -> { - if (isSearchActive) { - closeSearch(); - } else { - activity.finish(); - } - }); - } - - /** - * Initializes search data by collecting all searchable preferences from the fragment. - * This method should be called after the preference fragment is fully loaded. - * Runs on the UI thread to ensure proper access to preference components. - */ - public void initializeSearchData() { - allSearchItems.clear(); - keyToSearchItem.clear(); - // Wait until fragment is properly initialized. - activity.runOnUiThread(() -> { - try { - PreferenceScreen screen = fragment.getPreferenceScreenForSearch(); - if (screen != null) { - collectSearchablePreferences(screen); - for (BaseSearchResultItem item : allSearchItems) { - if (item instanceof BaseSearchResultItem.PreferenceSearchItem prefItem) { - String key = prefItem.preference.getKey(); - if (key != null) { - keyToSearchItem.put(key, item); - } - } - } - setupPreferenceListeners(); - Logger.printDebug(() -> "Collected " + allSearchItems.size() + " searchable preferences"); - } - } catch (Exception ex) { - Logger.printException(() -> "Failed to initialize search data", ex); - } - }); - } - - /** - * Sets up listeners for preferences to keep search results in sync when preference values change. - */ - protected void setupPreferenceListeners() { - for (BaseSearchResultItem item : allSearchItems) { - // Skip non-preference items. - if (!(item instanceof BaseSearchResultItem.PreferenceSearchItem prefItem)) continue; - Preference pref = prefItem.preference; - - if (pref instanceof ColorPickerPreference colorPref) { - colorPref.setOnColorChangeListener((prefKey, newColor) -> { - BaseSearchResultItem.PreferenceSearchItem searchItem = - (BaseSearchResultItem.PreferenceSearchItem) keyToSearchItem.get(prefKey); - if (searchItem != null) { - searchItem.setColor(newColor); - refreshSearchResults(); - } - }); - } else if (pref instanceof CustomDialogListPreference listPref) { - listPref.setOnPreferenceChangeListener((preference, newValue) -> { - BaseSearchResultItem.PreferenceSearchItem searchItem = - (BaseSearchResultItem.PreferenceSearchItem) keyToSearchItem.get(preference.getKey()); - if (searchItem == null) return true; - - int index = listPref.findIndexOfValue(newValue.toString()); - if (index >= 0) { - // Check if a static summary is set. - boolean isStaticSummary = listPref.getStaticSummary() != null; - if (!isStaticSummary) { - // Only update summary if it is not static. - CharSequence newSummary = listPref.getEntries()[index]; - listPref.setSummary(newSummary); - } - } - - listPref.clearHighlightedEntriesForDialog(); - searchItem.refreshHighlighting(); - refreshSearchResults(); - return true; - }); - } - - // Let subclasses handle special preferences. - setupSpecialPreferenceListeners(item); - } - } - - /** - * Collects searchable preferences from a preference group. - */ - protected void collectSearchablePreferences(PreferenceGroup group) { - collectSearchablePreferencesWithKeys(group, "", new ArrayList<>(), 1, 0); - } - - /** - * Collects searchable preferences with their navigation paths and keys. - * - * @param group The preference group to collect from. - * @param parentPath The navigation path of the parent group. - * @param parentKeys The keys of parent preferences. - * @param includeDepth The maximum depth to include in the search index. - * @param currentDepth The current depth in the preference hierarchy. - */ - protected void collectSearchablePreferencesWithKeys(PreferenceGroup group, String parentPath, - List parentKeys, int includeDepth, int currentDepth) { - if (group == null) return; - - for (int i = 0, count = group.getPreferenceCount(); i < count; i++) { - Preference preference = group.getPreference(i); - - // Add to search results only if it is not a category, special group, or PreferenceScreen. - if (shouldIncludePreference(preference, currentDepth, includeDepth)) { - allSearchItems.add(new BaseSearchResultItem.PreferenceSearchItem( - preference, parentPath, parentKeys)); - } - - // If the preference is a group, recurse into it. - if (preference instanceof PreferenceGroup subGroup) { - String newPath = parentPath; - List newKeys = new ArrayList<>(parentKeys); - - // Append the group title to the path and save key for navigation. - if (!isSpecialPreferenceGroup(preference) - && !(preference instanceof NoTitlePreferenceCategory)) { - CharSequence title = preference.getTitle(); - if (!TextUtils.isEmpty(title)) { - newPath = TextUtils.isEmpty(parentPath) - ? title.toString() - : parentPath + " > " + title; - } - - // Add key for navigation if this is a PreferenceScreen or group with navigation capability. - String key = preference.getKey(); - if (!TextUtils.isEmpty(key) && (preference instanceof PreferenceScreen - || searchResultsAdapter.hasNavigationCapability(preference))) { - newKeys.add(key); - } - } - - collectSearchablePreferencesWithKeys(subGroup, newPath, newKeys, includeDepth, currentDepth + 1); - } - } - } - - /** - * Filters all search items based on the provided query and displays results in the overlay. - * Applies highlighting to matching text and shows a "no results" message if nothing matches. - */ - protected void filterAndShowResults(String query) { - hideSearchHistory(); - // Keep track of the previously displayed items to clear their highlights. - List previouslyDisplayedItems = new ArrayList<>(filteredSearchItems); - - filteredSearchItems.clear(); - - String queryLower = Utils.normalizeTextToLowercase(query); - Pattern queryPattern = Pattern.compile(Pattern.quote(queryLower), Pattern.CASE_INSENSITIVE); - - // Clear highlighting only for items that were previously visible. - // This avoids iterating through all items on every keystroke during filtering. - for (BaseSearchResultItem item : previouslyDisplayedItems) { - item.clearHighlighting(); - } - - // Collect matched items first. - List matched = new ArrayList<>(); - int matchCount = 0; - for (BaseSearchResultItem item : allSearchItems) { - if (matchCount >= MAX_SEARCH_RESULTS) break; // Stop after collecting max results. - if (item.matchesQuery(queryLower)) { - item.applyHighlighting(queryPattern); - matched.add(item); - matchCount++; - } - } - - // Build filteredSearchItems, inserting parent enablers for disabled dependents. - Set addedParentKeys = new HashSet<>(2 * matched.size()); - for (BaseSearchResultItem item : matched) { - if (item instanceof BaseSearchResultItem.PreferenceSearchItem prefItem) { - String key = prefItem.preference.getKey(); - Setting setting = (key != null) ? Setting.getSettingFromPath(key) : null; - if (setting != null && !setting.isAvailable()) { - List> parentSettings = setting.getParentSettings(); - for (Setting parentSetting : parentSettings) { - BaseSearchResultItem parentItem = keyToSearchItem.get(parentSetting.key); - if (parentItem != null && !addedParentKeys.contains(parentSetting.key)) { - if (!parentItem.matchesQuery(queryLower)) { - // Apply highlighting to parent items even if they don't match the query. - // This ensures they get their current effective summary calculated. - parentItem.applyHighlighting(queryPattern); - filteredSearchItems.add(parentItem); - } - addedParentKeys.add(parentSetting.key); - } - } - } - filteredSearchItems.add(item); - if (key != null) { - addedParentKeys.add(key); - } - } - } - - if (!filteredSearchItems.isEmpty()) { - //noinspection ComparatorCombinators - Collections.sort(filteredSearchItems, (o1, o2) -> - o1.navigationPath.compareTo(o2.navigationPath) - ); - List displayItems = new ArrayList<>(); - String currentPath = null; - for (BaseSearchResultItem item : filteredSearchItems) { - if (!item.navigationPath.equals(currentPath)) { - BaseSearchResultItem header = new BaseSearchResultItem.GroupHeaderItem(item.navigationPath, item.navigationKeys); - displayItems.add(header); - currentPath = item.navigationPath; - } - displayItems.add(item); - } - filteredSearchItems.clear(); - filteredSearchItems.addAll(displayItems); - } - // Show "No results found" if search results are empty. - if (filteredSearchItems.isEmpty()) { - Preference noResultsPreference = new Preference(activity); - noResultsPreference.setKey("no_results_placeholder"); - noResultsPreference.setTitle(str("revanced_settings_search_no_results_title", query)); - noResultsPreference.setSummary(str("revanced_settings_search_no_results_summary")); - noResultsPreference.setSelectable(false); - noResultsPreference.setIcon(getSearchIcon()); - filteredSearchItems.add(new BaseSearchResultItem.PreferenceSearchItem(noResultsPreference, "", Collections.emptyList())); - } - - searchResultsAdapter.notifyDataSetChanged(); - overlayContainer.setVisibility(View.VISIBLE); - } - - /** - * Opens the search interface by showing the search view and hiding the menu item. - * Configures the UI for search mode, shows the keyboard, and displays search suggestions. - */ - protected void openSearch() { - isSearchActive = true; - toolbar.getMenu().findItem(ID_ACTION_SEARCH).setVisible(false); - toolbar.setTitle(""); - searchContainer.setVisibility(View.VISIBLE); - searchView.requestFocus(); - // Configure soft input mode to adjust layout and show keyboard. - activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN - | WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); - inputMethodManager.showSoftInput(searchView, InputMethodManager.SHOW_IMPLICIT); - // Always show search history when opening search. - showSearchHistory(); - } - - /** - * Closes the search interface and restores the normal UI state. - * Hides the overlay, clears search results, dismisses the keyboard, and removes highlighting. - */ - public void closeSearch() { - isSearchActive = false; - isShowingSearchHistory = false; - - searchHistoryManager.hideSearchHistoryContainer(); - overlayContainer.setVisibility(View.GONE); - - filteredSearchItems.clear(); - - searchContainer.setVisibility(View.GONE); - toolbar.getMenu().findItem(ID_ACTION_SEARCH).setVisible(true); - toolbar.setTitle(originalTitle); - searchView.setQuery("", false); - // Hide keyboard and reset soft input mode. - inputMethodManager.hideSoftInputFromWindow(searchView.getWindowToken(), 0); - activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN); - // Clear highlighting for all search items. - for (BaseSearchResultItem item : allSearchItems) { - item.clearHighlighting(); - } - - searchResultsAdapter.notifyDataSetChanged(); - } - - /** - * Shows the search history if enabled. - */ - protected void showSearchHistory() { - if (searchHistoryManager.isSearchHistoryEnabled()) { - overlayContainer.setVisibility(View.VISIBLE); - searchHistoryManager.showSearchHistory(); - isShowingSearchHistory = true; - } else { - hideAllOverlays(); - } - } - - /** - * Hides the search history container. - */ - protected void hideSearchHistory() { - searchHistoryManager.hideSearchHistoryContainer(); - isShowingSearchHistory = false; - } - - /** - * Hides all overlay containers, including search results and history. - */ - protected void hideAllOverlays() { - hideSearchHistory(); - hideSearchResults(); - } - - /** - * Hides the search results overlay and clears the filtered results. - */ - protected void hideSearchResults() { - overlayContainer.setVisibility(View.GONE); - filteredSearchItems.clear(); - searchResultsAdapter.notifyDataSetChanged(); - for (BaseSearchResultItem item : allSearchItems) { - item.clearHighlighting(); - } - } - - /** - * Refreshes the search results display if the search is active and history is not shown. - */ - protected void refreshSearchResults() { - if (isSearchActive && !isShowingSearchHistory) { - searchResultsAdapter.notifyDataSetChanged(); - } - } - - /** - * Finds a search item corresponding to the given preference. - * - * @param preference The preference to find a search item for. - * @return The corresponding PreferenceSearchItem, or null if not found. - */ - public BaseSearchResultItem.PreferenceSearchItem findSearchItemByPreference(Preference preference) { - // First, search in filtered results. - for (BaseSearchResultItem item : filteredSearchItems) { - if (item instanceof BaseSearchResultItem.PreferenceSearchItem prefItem) { - if (prefItem.preference == preference) { - return prefItem; - } - } - } - // If not found, search in all items. - for (BaseSearchResultItem item : allSearchItems) { - if (item instanceof BaseSearchResultItem.PreferenceSearchItem prefItem) { - if (prefItem.preference == preference) { - return prefItem; - } - } - } - - return null; - } - - /** - * Gets the background color for search view components based on current theme. - */ - @ColorInt - public static int getSearchViewBackground() { - return Utils.adjustColorBrightness(Utils.getDialogBackgroundColor(), Utils.isDarkModeEnabled() ? 1.11f : 0.95f); - } - - /** - * Creates a rounded background drawable for the main search view. - */ - protected static GradientDrawable createBackgroundDrawable() { - GradientDrawable background = new GradientDrawable(); - background.setShape(GradientDrawable.RECTANGLE); - background.setCornerRadius(Dim.dp28); - background.setColor(getSearchViewBackground()); - return background; - } - - /** - * Return if a search is currently active. - */ - public boolean isSearchActive() { - return isSearchActive; - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/search/SearchHistoryManager.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/search/SearchHistoryManager.java deleted file mode 100644 index 773c8a9241..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/search/SearchHistoryManager.java +++ /dev/null @@ -1,402 +0,0 @@ -package app.revanced.extension.shared.settings.search; - -import static app.revanced.extension.shared.StringRef.str; -import static app.revanced.extension.shared.Utils.getResourceIdentifierOrThrow; -import static app.revanced.extension.shared.settings.BaseSettings.SETTINGS_SEARCH_ENTRIES; -import static app.revanced.extension.shared.settings.BaseSettings.SETTINGS_SEARCH_HISTORY; - -import android.app.Activity; -import android.app.Dialog; -import android.content.Context; -import android.util.Pair; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Deque; -import java.util.LinkedList; - -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.ResourceType; -import app.revanced.extension.shared.Utils; -import app.revanced.extension.shared.settings.preference.BulletPointPreference; -import app.revanced.extension.shared.ui.CustomDialog; - -/** - * Manager for search history functionality. - */ -public class SearchHistoryManager { - /** - * Interface for handling history item selection. - */ - private static final int MAX_HISTORY_SIZE = 5; // Maximum history items stored. - - private static final int ID_CLEAR_HISTORY_BUTTON = getResourceIdentifierOrThrow( - ResourceType.ID, "clear_history_button"); - private static final int ID_HISTORY_TEXT = getResourceIdentifierOrThrow( - ResourceType.ID, "history_text"); - private static final int ID_HISTORY_ICON = getResourceIdentifierOrThrow( - ResourceType.ID, "history_icon"); - private static final int ID_DELETE_ICON = getResourceIdentifierOrThrow( - ResourceType.ID, "delete_icon"); - private static final int ID_EMPTY_HISTORY_TITLE = getResourceIdentifierOrThrow( - ResourceType.ID, "empty_history_title"); - private static final int ID_EMPTY_HISTORY_SUMMARY = getResourceIdentifierOrThrow( - ResourceType.ID, "empty_history_summary"); - private static final int ID_SEARCH_HISTORY_HEADER = getResourceIdentifierOrThrow( - ResourceType.ID, "search_history_header"); - private static final int ID_SEARCH_TIPS_SUMMARY = getResourceIdentifierOrThrow( - ResourceType.ID, "revanced_settings_search_tips_summary"); - private static final int LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_SCREEN = getResourceIdentifierOrThrow( - ResourceType.LAYOUT, "revanced_preference_search_history_screen"); - private static final int LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_ITEM = getResourceIdentifierOrThrow( - ResourceType.LAYOUT, "revanced_preference_search_history_item"); - private static final int ID_SEARCH_HISTORY_LIST = getResourceIdentifierOrThrow( - ResourceType.ID, "search_history_list"); - private static final int ID_SEARCH_REMOVE_ICON = getResourceIdentifierOrThrow( - ResourceType.DRAWABLE, "revanced_settings_search_remove"); - private static final int ID_SEARCH_REMOVE_ICON_BOLD = getResourceIdentifierOrThrow( - ResourceType.DRAWABLE, "revanced_settings_search_remove_bold"); - private static final int ID_SEARCH_ARROW_TIME_ICON = getResourceIdentifierOrThrow( - ResourceType.DRAWABLE, "revanced_settings_arrow_time"); - private static final int ID_SEARCH_ARROW_TIME_ICON_BOLD = getResourceIdentifierOrThrow( - ResourceType.DRAWABLE, "revanced_settings_arrow_time_bold"); - - private final Deque searchHistory; - private final Activity activity; - private final SearchHistoryAdapter searchHistoryAdapter; - private final boolean showSettingsSearchHistory; - private final FrameLayout searchHistoryContainer; - - public interface OnSelectHistoryItemListener { - void onSelectHistoryItem(String query); - } - - /** - * Constructor for SearchHistoryManager. - * - * @param activity The parent activity. - * @param overlayContainer The overlay container to hold the search history container. - * @param onSelectHistoryItemAction Callback for when a history item is selected. - */ - SearchHistoryManager(Activity activity, FrameLayout overlayContainer, - OnSelectHistoryItemListener onSelectHistoryItemAction) { - this.activity = activity; - this.showSettingsSearchHistory = SETTINGS_SEARCH_HISTORY.get(); - this.searchHistory = new LinkedList<>(); - - // Initialize search history from settings. - if (showSettingsSearchHistory) { - String entries = SETTINGS_SEARCH_ENTRIES.get(); - if (!entries.isBlank()) { - searchHistory.addAll(Arrays.asList(entries.split("\n"))); - } - } else { - // Clear old saved history if the feature is disabled. - SETTINGS_SEARCH_ENTRIES.resetToDefault(); - } - - // Create search history container. - this.searchHistoryContainer = new FrameLayout(activity); - searchHistoryContainer.setVisibility(View.GONE); - - // Inflate search history layout. - LayoutInflater inflater = LayoutInflater.from(activity); - View historyView = inflater.inflate(LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_SCREEN, - searchHistoryContainer, false); - searchHistoryContainer.addView(historyView, new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.MATCH_PARENT)); - - // Add history container to overlay. - FrameLayout.LayoutParams overlayParams = new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.MATCH_PARENT); - overlayParams.gravity = Gravity.TOP; - overlayContainer.addView(searchHistoryContainer, overlayParams); - - // Find the LinearLayout for the history list within the container. - LinearLayout searchHistoryListView = searchHistoryContainer.findViewById(ID_SEARCH_HISTORY_LIST); - if (searchHistoryListView == null) { - throw new IllegalStateException("Search history list view not found in container"); - } - - // Set up history adapter. Use a copy of the search history. - this.searchHistoryAdapter = new SearchHistoryAdapter(activity, searchHistoryListView, - new ArrayList<>(searchHistory), onSelectHistoryItemAction); - - // Set up clear history button. - TextView clearHistoryButton = searchHistoryContainer.findViewById(ID_CLEAR_HISTORY_BUTTON); - clearHistoryButton.setOnClickListener(v -> createAndShowDialog( - str("revanced_settings_search_clear_history"), - str("revanced_settings_search_clear_history_message"), - this::clearAllSearchHistory - )); - - // Set up search tips summary. - CharSequence text = BulletPointPreference.formatIntoBulletPoints( - str("revanced_settings_search_tips_summary")); - TextView tipsSummary = historyView.findViewById(ID_SEARCH_TIPS_SUMMARY); - tipsSummary.setText(text); - } - - /** - * Shows search history screen - either with history items or empty history message. - */ - public void showSearchHistory() { - if (!showSettingsSearchHistory) { - return; - } - - // Find all view elements. - TextView emptyHistoryTitle = searchHistoryContainer.findViewById(ID_EMPTY_HISTORY_TITLE); - TextView emptyHistorySummary = searchHistoryContainer.findViewById(ID_EMPTY_HISTORY_SUMMARY); - TextView historyHeader = searchHistoryContainer.findViewById(ID_SEARCH_HISTORY_HEADER); - LinearLayout historyList = searchHistoryContainer.findViewById(ID_SEARCH_HISTORY_LIST); - TextView clearHistoryButton = searchHistoryContainer.findViewById(ID_CLEAR_HISTORY_BUTTON); - - if (searchHistory.isEmpty()) { - // Show empty history state. - showEmptyHistoryViews(emptyHistoryTitle, emptyHistorySummary); - hideHistoryViews(historyHeader, historyList, clearHistoryButton); - } else { - // Show history list state. - hideEmptyHistoryViews(emptyHistoryTitle, emptyHistorySummary); - showHistoryViews(historyHeader, historyList, clearHistoryButton); - - // Update adapter with current history. - searchHistoryAdapter.clear(); - searchHistoryAdapter.addAll(searchHistory); - searchHistoryAdapter.notifyDataSetChanged(); - } - - // Show the search history container. - showSearchHistoryContainer(); - } - - /** - * Saves a search query to the history, maintaining the size limit. - */ - public void saveSearchQuery(String query) { - if (!showSettingsSearchHistory) return; - - searchHistory.remove(query); // Remove if already exists to update position. - searchHistory.addFirst(query); // Add to the most recent. - - // Remove extra old entries. - while (searchHistory.size() > MAX_HISTORY_SIZE) { - String last = searchHistory.removeLast(); - Logger.printDebug(() -> "Removing search history query: " + last); - } - - saveSearchHistory(); - } - - /** - * Saves the search history to shared preferences. - */ - protected void saveSearchHistory() { - Logger.printDebug(() -> "Saving search history: " + searchHistory); - SETTINGS_SEARCH_ENTRIES.save(String.join("\n", searchHistory)); - } - - /** - * Removes a search query from the history. - */ - public void removeSearchQuery(String query) { - searchHistory.remove(query); - saveSearchHistory(); - } - - /** - * Clears all search history. - */ - public void clearAllSearchHistory() { - searchHistory.clear(); - saveSearchHistory(); - searchHistoryAdapter.clear(); - searchHistoryAdapter.notifyDataSetChanged(); - showSearchHistory(); - } - - /** - * Checks if search history feature is enabled. - */ - public boolean isSearchHistoryEnabled() { - return showSettingsSearchHistory; - } - - /** - * Shows the search history container and overlay. - */ - public void showSearchHistoryContainer() { - searchHistoryContainer.setVisibility(View.VISIBLE); - } - - /** - * Hides the search history container. - */ - public void hideSearchHistoryContainer() { - searchHistoryContainer.setVisibility(View.GONE); - } - - /** - * Helper method to show empty history views. - */ - protected void showEmptyHistoryViews(TextView emptyTitle, TextView emptySummary) { - emptyTitle.setVisibility(View.VISIBLE); - emptyTitle.setText(str("revanced_settings_search_empty_history_title")); - emptySummary.setVisibility(View.VISIBLE); - emptySummary.setText(str("revanced_settings_search_empty_history_summary")); - } - - /** - * Helper method to hide empty history views. - */ - protected void hideEmptyHistoryViews(TextView emptyTitle, TextView emptySummary) { - emptyTitle.setVisibility(View.GONE); - emptySummary.setVisibility(View.GONE); - } - - /** - * Helper method to show history list views. - */ - protected void showHistoryViews(TextView header, LinearLayout list, TextView clearButton) { - header.setVisibility(View.VISIBLE); - list.setVisibility(View.VISIBLE); - clearButton.setVisibility(View.VISIBLE); - } - - /** - * Helper method to hide history list views. - */ - protected void hideHistoryViews(TextView header, LinearLayout list, TextView clearButton) { - header.setVisibility(View.GONE); - list.setVisibility(View.GONE); - clearButton.setVisibility(View.GONE); - } - - /** - * Creates and shows a dialog with the specified title, message, and confirmation action. - * - * @param title The title of the dialog. - * @param message The message to display in the dialog. - * @param confirmAction The action to perform when the dialog is confirmed. - */ - protected void createAndShowDialog(String title, String message, Runnable confirmAction) { - Pair dialogPair = CustomDialog.create( - activity, - title, - message, - null, - null, - confirmAction, - () -> {}, - null, - null, - false - ); - - Dialog dialog = dialogPair.first; - dialog.setCancelable(true); - dialog.show(); - } - - - /** - * Custom adapter for search history items. - */ - protected class SearchHistoryAdapter { - protected final Collection history; - protected final LayoutInflater inflater; - protected final LinearLayout container; - protected final OnSelectHistoryItemListener onSelectHistoryItemListener; - - public SearchHistoryAdapter(Context context, LinearLayout container, Collection history, - OnSelectHistoryItemListener listener) { - this.history = history; - this.inflater = LayoutInflater.from(context); - this.container = container; - this.onSelectHistoryItemListener = listener; - } - - /** - * Updates the container with current history items. - */ - public void notifyDataSetChanged() { - container.removeAllViews(); - for (String query : history) { - View view = inflater.inflate(LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_ITEM, - container, false); - // Set click listener for main item (select query). - view.setOnClickListener(v -> onSelectHistoryItemListener.onSelectHistoryItem(query)); - - // Set history icon. - ImageView historyIcon = view.findViewById(ID_HISTORY_ICON); - historyIcon.setImageResource(Utils.appIsUsingBoldIcons() - ? ID_SEARCH_ARROW_TIME_ICON_BOLD - : ID_SEARCH_ARROW_TIME_ICON - ); - - TextView historyText = view.findViewById(ID_HISTORY_TEXT); - historyText.setText(query); - - // Set click listener for delete icon. - ImageView deleteIcon = view.findViewById(ID_DELETE_ICON); - - deleteIcon.setImageResource(Utils.appIsUsingBoldIcons() - ? ID_SEARCH_REMOVE_ICON_BOLD - : ID_SEARCH_REMOVE_ICON - ); - - deleteIcon.setOnClickListener(v -> createAndShowDialog( - query, - str("revanced_settings_search_remove_message"), - () -> { - removeSearchQuery(query); - remove(query); - notifyDataSetChanged(); - } - )); - - container.addView(view); - } - } - - /** - * Clears all views from the container and history list. - */ - public void clear() { - history.clear(); - container.removeAllViews(); - } - - /** - * Adds all provided history items to the container. - */ - public void addAll(Collection items) { - history.addAll(items); - notifyDataSetChanged(); - } - - /** - * Removes a query from the history and updates the container. - */ - public void remove(String query) { - history.remove(query); - if (history.isEmpty()) { - // If history is now empty, show the empty history state. - showSearchHistory(); - } else { - notifyDataSetChanged(); - } - } - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/ClientType.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/ClientType.java deleted file mode 100644 index 9abd430719..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/ClientType.java +++ /dev/null @@ -1,270 +0,0 @@ -package app.revanced.extension.shared.spoof; - -import android.os.Build; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.util.Locale; -import java.util.Objects; - -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.Utils; - -@SuppressWarnings("ConstantLocale") -public enum ClientType { - /** - * Video not playable: Paid, Movie, Private, Age-restricted. - * Uses non-adaptive bitrate. - * AV1 codec available. - */ - ANDROID_REEL( - 3, - "ANDROID", - "com.google.android.youtube", - Build.MANUFACTURER, - Build.MODEL, - "Android", - Build.VERSION.RELEASE, - String.valueOf(Build.VERSION.SDK_INT), - Build.ID, - "20.44.38", - // This client has been used by most open-source YouTube stream extraction tools since 2024, including NewPipe Extractor, SmartTube, and Grayjay. - // This client can log in, but if an access token is used in the request, GVS can more easily identify the request as coming from ReVanced. - // This means that the GVS server can strengthen its validation of the ANDROID_REEL client. - true, - true, - false, - "Android Reel" - ), - /** - * Video not playable: Kids / Paid / Movie / Private / Age-restricted. - * This client can only be used when logged out. - */ - // https://dumps.tadiphone.dev/dumps/oculus/eureka - ANDROID_VR_1_61_48( - 28, - "ANDROID_VR", - "com.google.android.apps.youtube.vr.oculus", - "Oculus", - "Quest 3", - "Android", - "12", - // Android 12.1 - "32", - "SQ3A.220605.009.A1", - "1.61.48", - false, - false, - true, - "Android VR 1.61" - ), - /** - * Uses non adaptive bitrate, which fixes audio stuttering with YT Music. - * Does not use AV1. - */ - ANDROID_VR_1_43_32( - ANDROID_VR_1_61_48.id, - ANDROID_VR_1_61_48.clientName, - Objects.requireNonNull(ANDROID_VR_1_61_48.packageName), - ANDROID_VR_1_61_48.deviceMake, - ANDROID_VR_1_61_48.deviceModel, - ANDROID_VR_1_61_48.osName, - ANDROID_VR_1_61_48.osVersion, - Objects.requireNonNull(ANDROID_VR_1_61_48.androidSdkVersion), - Objects.requireNonNull(ANDROID_VR_1_61_48.buildId), - "1.43.32", - ANDROID_VR_1_61_48.useAuth, - ANDROID_VR_1_61_48.supportsMultiAudioTracks, - ANDROID_VR_1_61_48.usePlayerEndpoint, - "Android VR 1.43" - ), - /** - * Cannot play livestreams and lacks HDR, but can play videos with music and labeled "for children". - * Google Pixel 9 Pro Fold - */ - ANDROID_CREATOR( - 14, - "ANDROID_CREATOR", - "com.google.android.apps.youtube.creator", - "Google", - "Pixel 9 Pro Fold", - "Android", - "15", - "35", - "AP3A.241005.015.A2", - "23.47.101", - true, - false, - true, - "Android Studio" - ), - /** - * Internal YT client for an unreleased YT client. May stop working at any time. - */ - VISIONOS(101, - "VISIONOS", - "Apple", - "RealityDevice14,1", - "visionOS", - "1.3.21O771", - "0.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15", - false, - false, - true, - "visionOS" - ); - - /** - * YouTube - * client type - */ - public final int id; - - public final String clientName; - - /** - * App package name. - */ - @Nullable - private final String packageName; - - /** - * Player user-agent. - */ - public final String userAgent; - - /** - * Device model, equivalent to {@link Build#MANUFACTURER} (System property: ro.product.vendor.manufacturer) - */ - public final String deviceMake; - - /** - * Device model, equivalent to {@link Build#MODEL} (System property: ro.product.vendor.model) - */ - public final String deviceModel; - - /** - * Device OS name. - */ - public final String osName; - - /** - * Device OS version. - */ - public final String osVersion; - - /** - * Android SDK version, equivalent to {@link Build.VERSION#SDK} (System property: ro.build.version.sdk) - * Field is null if not applicable. - */ - @Nullable - public final String androidSdkVersion; - - /** - * Android build id, equivalent to {@link Build#ID}. - * Field is null if not applicable. - */ - @Nullable - private final String buildId; - - /** - * App version. - */ - public final String clientVersion; - - /** - * If the client should use authentication if available. - */ - public final boolean useAuth; - - /** - * If the client supports multiple audio tracks. - */ - public final boolean supportsMultiAudioTracks; - - /** - * If the client should use the player endpoint for stream extraction. - */ - public final boolean usePlayerEndpoint; - - /** - * Friendly name displayed in stats for nerds. - */ - public final String friendlyName; - - /** - * Android constructor. - */ - ClientType(int id, - String clientName, - @NonNull String packageName, - String deviceMake, - String deviceModel, - String osName, - String osVersion, - @NonNull String androidSdkVersion, - @NonNull String buildId, - String clientVersion, - boolean useAuth, - boolean supportsMultiAudioTracks, - boolean usePlayerEndpoint, - String friendlyName) { - this.id = id; - this.clientName = clientName; - this.packageName = packageName; - this.deviceMake = deviceMake; - this.deviceModel = deviceModel; - this.osName = osName; - this.osVersion = osVersion; - this.androidSdkVersion = androidSdkVersion; - this.buildId = buildId; - this.clientVersion = clientVersion; - this.useAuth = useAuth; - this.supportsMultiAudioTracks = supportsMultiAudioTracks; - this.usePlayerEndpoint = usePlayerEndpoint; - this.friendlyName = friendlyName; - - Locale defaultLocale = Locale.getDefault(); - this.userAgent = String.format("%s/%s (Linux; U; Android %s; %s; %s; Build/%s)", - packageName, - clientVersion, - osVersion, - defaultLocale, - deviceModel, - buildId - ); - Logger.printDebug(() -> "userAgent: " + this.userAgent); - } - - @SuppressWarnings("ConstantLocale") - ClientType(int id, - String clientName, - String deviceMake, - String deviceModel, - String osName, - String osVersion, - String clientVersion, - String userAgent, - boolean useAuth, - boolean supportsMultiAudioTracks, - boolean usePlayerEndpoint, - String friendlyName) { - this.id = id; - this.clientName = clientName; - this.deviceMake = deviceMake; - this.deviceModel = deviceModel; - this.osName = osName; - this.osVersion = osVersion; - this.clientVersion = clientVersion; - this.userAgent = userAgent; - this.useAuth = useAuth; - this.supportsMultiAudioTracks = supportsMultiAudioTracks; - this.usePlayerEndpoint = usePlayerEndpoint; - this.friendlyName = friendlyName; - this.packageName = null; - this.androidSdkVersion = null; - this.buildId = null; - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/SpoofVideoStreamsPatch.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/SpoofVideoStreamsPatch.java deleted file mode 100644 index 0c861510fe..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/SpoofVideoStreamsPatch.java +++ /dev/null @@ -1,322 +0,0 @@ -package app.revanced.extension.shared.spoof; - -import android.net.Uri; -import android.text.TextUtils; - -import androidx.annotation.Nullable; - -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.Utils; -import app.revanced.extension.shared.settings.AppLanguage; -import app.revanced.extension.shared.settings.BaseSettings; -import app.revanced.extension.shared.spoof.requests.StreamingDataRequest; - -@SuppressWarnings("unused") -public class SpoofVideoStreamsPatch { - - /** - * Domain used for internet connectivity verification. - * It has an empty response body and is only used to check for a 204 response code. - *

- * If an unreachable IP address (127.0.0.1) is used, no response code is provided. - *

- * YouTube handles unreachable IP addresses without issue. - * YouTube Music has an issue with waiting for the Cronet connect timeout of 30s on mobile networks. - *

- * Using a VPN or DNS can temporarily resolve this issue, - * But the ideal workaround is to avoid using an unreachable IP address. - */ - private static final String INTERNET_CONNECTION_CHECK_URI_STRING = "https://www.google.com/gen_204"; - private static final Uri INTERNET_CONNECTION_CHECK_URI = Uri.parse(INTERNET_CONNECTION_CHECK_URI_STRING); - - private static final boolean SPOOF_STREAMING_DATA = BaseSettings.SPOOF_VIDEO_STREAMS.get(); - - @Nullable - private static volatile AppLanguage languageOverride; - - private static volatile ClientType preferredClient = ClientType.ANDROID_REEL; - - /** - * @return If this patch was included during patching. - */ - public static boolean isPatchIncluded() { - return false; // Modified during patching. - } - - @Nullable - public static AppLanguage getLanguageOverride() { - return languageOverride; - } - - /** - * @param language Language override for non-authenticated requests. - */ - public static void setLanguageOverride(@Nullable AppLanguage language) { - languageOverride = language; - } - - public static void setClientsToUse(List availableClients, ClientType client) { - preferredClient = Objects.requireNonNull(client); - StreamingDataRequest.setClientOrderToUse(availableClients, client); - } - - public static ClientType getPreferredClient() { - return preferredClient; - } - - public static boolean spoofingToClientWithNoMultiAudioStreams() { - return isPatchIncluded() - && SPOOF_STREAMING_DATA - && !preferredClient.supportsMultiAudioTracks; - } - - /** - * Injection point. - * Blocks /get_watch requests by returning an unreachable URI. - * - * @param playerRequestUri The URI of the player request. - * @return An unreachable URI if the request is a /get_watch request, otherwise the original URI. - */ - public static Uri blockGetWatchRequest(Uri playerRequestUri) { - if (SPOOF_STREAMING_DATA) { - try { - String path = playerRequestUri.getPath(); - - if (path != null && path.contains("get_watch")) { - Logger.printDebug(() -> "Blocking 'get_watch' by returning internet connection check uri"); - - return INTERNET_CONNECTION_CHECK_URI; - } - } catch (Exception ex) { - Logger.printException(() -> "blockGetWatchRequest failure", ex); - } - } - - return playerRequestUri; - } - - /** - * Injection point. - * - * Blocks /get_watch requests by returning an unreachable URI. - * /att/get requests are used to obtain a PoToken challenge. - * See: botGuardScript.js#L15 - *

- * Since the Spoof streaming data patch was implemented because a valid PoToken cannot be obtained, - * Blocking /att/get requests are not a problem. - */ - public static String blockGetAttRequest(String originalUrlString) { - if (SPOOF_STREAMING_DATA) { - try { - var originalUri = Uri.parse(originalUrlString); - String path = originalUri.getPath(); - - if (path != null && path.contains("att/get")) { - Logger.printDebug(() -> "Blocking 'att/get' by returning internet connection check uri"); - - return INTERNET_CONNECTION_CHECK_URI_STRING; - } - } catch (Exception ex) { - Logger.printException(() -> "blockGetAttRequest failure", ex); - } - } - - return originalUrlString; - } - - /** - * Injection point. - *

- * Blocks /initplayback requests. - */ - public static String blockInitPlaybackRequest(String originalUrlString) { - if (SPOOF_STREAMING_DATA) { - try { - var originalUri = Uri.parse(originalUrlString); - String path = originalUri.getPath(); - - if (path != null && path.contains("initplayback")) { - Logger.printDebug(() -> "Blocking 'initplayback' by returning internet connection check uri"); - - return INTERNET_CONNECTION_CHECK_URI_STRING; - } - } catch (Exception ex) { - Logger.printException(() -> "blockInitPlaybackRequest failure", ex); - } - } - - return originalUrlString; - } - - /** - * Injection point. - */ - public static boolean isSpoofingEnabled() { - return SPOOF_STREAMING_DATA; - } - - /** - * Injection point. - * Only invoked when playing a livestream on an Apple client. - */ - public static boolean fixHLSCurrentTime(boolean original) { - if (!SPOOF_STREAMING_DATA) { - return original; - } - return false; - } - - /* - * Injection point. - * Fix audio stuttering in YouTube Music. - */ - public static boolean disableSABR() { - return SPOOF_STREAMING_DATA; - } - - /** - * Injection point. - * Turns off a feature flag that interferes with spoofing. - */ - public static boolean useMediaFetchHotConfigReplacement(boolean original) { - if (original) { - Logger.printDebug(() -> "useMediaFetchHotConfigReplacement is set on"); - } - - if (!SPOOF_STREAMING_DATA) { - return original; - } - return false; - } - - /** - * Injection point. - * Turns off a feature flag that interferes with video playback. - */ - public static boolean usePlaybackStartFeatureFlag(boolean original) { - if (original) { - Logger.printDebug(() -> "usePlaybackStartFeatureFlag is set on"); - } - - if (!SPOOF_STREAMING_DATA) { - return original; - } - return false; - } - - /** - * Injection point. - */ - public static void fetchStreams(String url, Map requestHeaders) { - if (SPOOF_STREAMING_DATA) { - try { - Uri uri = Uri.parse(url); - String path = uri.getPath(); - if (path == null || !path.contains("player")) { - return; - } - - // 'get_drm_license' has no video id and appears to happen when waiting for a paid video to start. - // 'heartbeat' has no video id and appears to be only after playback has started. - // 'refresh' has no video id and appears to happen when waiting for a livestream to start. - // 'ad_break' has no video id. - if (path.contains("get_drm_license") || path.contains("heartbeat") - || path.contains("refresh") || path.contains("ad_break")) { - Logger.printDebug(() -> "Ignoring path: " + path); - return; - } - - String id = uri.getQueryParameter("id"); - if (id == null) { - Logger.printException(() -> "Ignoring request with no id: " + url); - return; - } - - StreamingDataRequest.fetchRequest(id, requestHeaders); - } catch (Exception ex) { - Logger.printException(() -> "buildRequest failure", ex); - } - } - } - - /** - * Injection point. - * Fix playback by replace the streaming data. - * Called after {@link #fetchStreams(String, Map)}. - */ - @Nullable - public static byte[] getStreamingData(String videoId) { - if (SPOOF_STREAMING_DATA) { - try { - StreamingDataRequest request = StreamingDataRequest.getRequestForVideoId(videoId); - if (request != null) { - // This hook is always called off the main thread, - // but this can later be called for the same video id from the main thread. - // This is not a concern, since the fetch will always be finished - // and never block the main thread. - // But if debugging, then still verify this is the situation. - if (BaseSettings.DEBUG.get() && !request.fetchCompleted() && Utils.isCurrentlyOnMainThread()) { - Logger.printException(() -> "Error: Blocking main thread"); - } - - var stream = request.getStream(); - if (stream != null) { - Logger.printDebug(() -> "Overriding video stream: " + videoId); - return stream; - } - } - - Logger.printDebug(() -> "Not overriding streaming data (video stream is null): " + videoId); - } catch (Exception ex) { - Logger.printException(() -> "getStreamingData failure", ex); - } - } - - return null; - } - - /** - * Injection point. - * Called after {@link #getStreamingData(String)}. - */ - @Nullable - public static byte[] removeVideoPlaybackPostBody(Uri uri, int method, byte[] postData) { - if (SPOOF_STREAMING_DATA) { - try { - final int methodPost = 2; - if (method == methodPost) { - String path = uri.getPath(); - if (path != null && path.contains("videoplayback")) { - return null; - } - } - } catch (Exception ex) { - Logger.printException(() -> "removeVideoPlaybackPostBody failure", ex); - } - } - - return postData; - } - - /** - * Injection point. - */ - public static String appendSpoofedClient(String videoFormat) { - try { - if (SPOOF_STREAMING_DATA && BaseSettings.SPOOF_STREAMING_DATA_STATS_FOR_NERDS.get() - && !TextUtils.isEmpty(videoFormat)) { - // Force LTR layout, to match the same LTR video time/length layout YouTube uses for all languages. - return "\u202D" + videoFormat + "\u2009(" // u202D = left to right override - + StreamingDataRequest.getLastSpoofedClientName() + ")"; - } - } catch (Exception ex) { - Logger.printException(() -> "appendSpoofedClient failure", ex); - } - - return videoFormat; - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/requests/PlayerRoutes.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/requests/PlayerRoutes.java deleted file mode 100644 index 959048d1e2..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/requests/PlayerRoutes.java +++ /dev/null @@ -1,109 +0,0 @@ -package app.revanced.extension.shared.spoof.requests; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.IOException; -import java.net.HttpURLConnection; -import java.util.Locale; - -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.requests.Requester; -import app.revanced.extension.shared.requests.Route; -import app.revanced.extension.shared.settings.AppLanguage; -import app.revanced.extension.shared.spoof.ClientType; -import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch; - -final class PlayerRoutes { - static final Route.CompiledRoute GET_PLAYER_STREAMING_DATA = new Route( - Route.Method.POST, - "player" + - "?fields=streamingData" + - "&alt=proto" - ).compile(); - - static final Route.CompiledRoute GET_REEL_STREAMING_DATA = new Route( - Route.Method.POST, - "reel/reel_item_watch" + - "?fields=playerResponse.playabilityStatus,playerResponse.streamingData" + - "&alt=proto" - ).compile(); - - private static final String YT_API_URL = "https://youtubei.googleapis.com/youtubei/v1/"; - - /** - * TCP connection and HTTP read timeout - */ - private static final int CONNECTION_TIMEOUT_MILLISECONDS = 10 * 1000; // 10 Seconds. - - private PlayerRoutes() { - } - - static String createInnertubeBody(ClientType clientType, String videoId) { - JSONObject innerTubeBody = new JSONObject(); - - try { - JSONObject context = new JSONObject(); - - AppLanguage language = SpoofVideoStreamsPatch.getLanguageOverride(); - if (language == null) { - // Force original audio has not overrode the language. - language = AppLanguage.DEFAULT; - } - //noinspection ExtractMethodRecommender - Locale streamLocale = language.getLocale(); - - JSONObject client = new JSONObject(); - - client.put("deviceMake", clientType.deviceMake); - client.put("deviceModel", clientType.deviceModel); - client.put("clientName", clientType.clientName); - client.put("clientVersion", clientType.clientVersion); - client.put("osName", clientType.osName); - client.put("osVersion", clientType.osVersion); - if (clientType.androidSdkVersion != null) { - client.put("androidSdkVersion", clientType.androidSdkVersion); - } - client.put("hl", streamLocale.getLanguage()); - client.put("gl", streamLocale.getCountry()); - context.put("client", client); - - innerTubeBody.put("context", context); - - if (clientType.usePlayerEndpoint) { - innerTubeBody.put("contentCheckOk", true); - innerTubeBody.put("racyCheckOk", true); - innerTubeBody.put("videoId", videoId); - } else { - JSONObject playerRequest = new JSONObject(); - playerRequest.put("contentCheckOk", true); - playerRequest.put("racyCheckOk", true); - playerRequest.put("videoId", videoId); - innerTubeBody.put("playerRequest", playerRequest); - innerTubeBody.put("disablePlayerResponse", false); - } - } catch (JSONException e) { - Logger.printException(() -> "Failed to create innerTubeBody", e); - } - - return innerTubeBody.toString(); - } - - @SuppressWarnings("SameParameterValue") - static HttpURLConnection getPlayerResponseConnectionFromRoute(Route.CompiledRoute route, ClientType clientType) throws IOException { - var connection = Requester.getConnectionFromCompiledRoute(YT_API_URL, route); - - connection.setRequestProperty("Content-Type", "application/json"); - connection.setRequestProperty("User-Agent", clientType.userAgent); - // Not a typo. "Client-Name" uses the client type id. - connection.setRequestProperty("X-YouTube-Client-Name", String.valueOf(clientType.id)); - connection.setRequestProperty("X-YouTube-Client-Version", clientType.clientVersion); - - connection.setUseCaches(false); - connection.setDoOutput(true); - - connection.setConnectTimeout(CONNECTION_TIMEOUT_MILLISECONDS); - connection.setReadTimeout(CONNECTION_TIMEOUT_MILLISECONDS); - return connection; - } -} \ No newline at end of file diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/requests/StreamingDataRequest.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/requests/StreamingDataRequest.java deleted file mode 100644 index fb8a8e79e8..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/requests/StreamingDataRequest.java +++ /dev/null @@ -1,318 +0,0 @@ -package app.revanced.extension.shared.spoof.requests; - -import static app.revanced.extension.shared.ByteTrieSearch.convertStringsToBytes; -import static app.revanced.extension.shared.Utils.isNotEmpty; -import static app.revanced.extension.shared.spoof.requests.PlayerRoutes.GET_PLAYER_STREAMING_DATA; -import static app.revanced.extension.shared.spoof.requests.PlayerRoutes.GET_REEL_STREAMING_DATA; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.SocketTimeoutException; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import app.revanced.extension.shared.ByteTrieSearch; -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.Utils; -import app.revanced.extension.shared.innertube.PlayerResponseOuterClass; -import app.revanced.extension.shared.innertube.PlayerResponseOuterClass.PlayerResponse; -import app.revanced.extension.shared.innertube.PlayerResponseOuterClass.StreamingData; -import app.revanced.extension.shared.innertube.ReelItemWatchResponseOuterClass.ReelItemWatchResponse; -import app.revanced.extension.shared.requests.Route; -import app.revanced.extension.shared.settings.BaseSettings; -import app.revanced.extension.shared.spoof.ClientType; - -/** - * Video streaming data. Fetching is tied to the behavior YT uses, - * where this class fetches the streams only when YT fetches. - *

- * Effectively the cache expiration of these fetches is the same as the stock app, - * since the stock app would not use expired streams and therefor - * the extension replace stream hook is called only if YT - * did use its own client streams. - */ -public class StreamingDataRequest { - - private static volatile ClientType[] clientOrderToUse = ClientType.values(); - - public static void setClientOrderToUse(List availableClients, ClientType preferredClient) { - Objects.requireNonNull(preferredClient); - - int availableClientSize = availableClients.size(); - if (!availableClients.contains(preferredClient)) { - availableClientSize++; - } - - clientOrderToUse = new ClientType[availableClientSize]; - clientOrderToUse[0] = preferredClient; - - int i = 1; - for (ClientType c : availableClients) { - if (c != preferredClient) { - clientOrderToUse[i++] = c; - } - } - - Logger.printDebug(() -> "Available spoof clients: " + Arrays.toString(clientOrderToUse)); - } - - private static final String AUTHORIZATION_HEADER = "Authorization"; - - private static final String[] REQUEST_HEADER_KEYS = { - AUTHORIZATION_HEADER, // Available only to logged-in users. - "X-GOOG-API-FORMAT-VERSION", - "X-Goog-Visitor-Id" - }; - - /** - * TCP connection and HTTP read timeout. - */ - private static final int HTTP_TIMEOUT_MILLISECONDS = 10 * 1000; - - /** - * Any arbitrarily large value, but must be at least twice {@link #HTTP_TIMEOUT_MILLISECONDS} - */ - private static final int MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000; - - /** - * Cache limit must be greater than the maximum number of videos open at once, - * which theoretically is more than 4 (3 Shorts + one regular minimized video). - * But instead use a much larger value, to handle if a video viewed a while ago - * is somehow still referenced. Each stream is a small array of Strings - * so memory usage is not a concern. - */ - private static final Map cache = Collections.synchronizedMap( - Utils.createSizeRestrictedMap(50)); - - /** - * Strings found in the response if the video is a livestream. - */ - private static final ByteTrieSearch liveStreamBufferSearch = new ByteTrieSearch( - convertStringsToBytes( - "yt_live_broadcast", - "yt_premiere_broadcast" - ) - ); - - private static volatile ClientType lastSpoofedClientType; - - public static String getLastSpoofedClientName() { - ClientType client = lastSpoofedClientType; - return client == null ? "Unknown" : client.friendlyName; - } - - private final String videoId; - - private final Future future; - - private StreamingDataRequest(String videoId, Map playerHeaders) { - Objects.requireNonNull(playerHeaders); - this.videoId = videoId; - this.future = Utils.submitOnBackgroundThread(() -> fetch(videoId, playerHeaders)); - } - - public static void fetchRequest(String videoId, Map fetchHeaders) { - // Always fetch, even if there is an existing request for the same video. - cache.put(videoId, new StreamingDataRequest(videoId, fetchHeaders)); - } - - @Nullable - public static StreamingDataRequest getRequestForVideoId(String videoId) { - return cache.get(videoId); - } - - private static void handleConnectionError(String toastMessage, @Nullable Exception ex, boolean showToast) { - if (showToast) Utils.showToastShort(toastMessage); - Logger.printInfo(() -> toastMessage, ex); - } - - private static void handleDebugToast(String toastMessage, ClientType clientType) { - if (BaseSettings.DEBUG.get() && BaseSettings.DEBUG_TOAST_ON_ERROR.get()) { - Utils.showToastShort(String.format(toastMessage, clientType)); - } - } - - @Nullable - private static HttpURLConnection send(ClientType clientType, - String videoId, - Map playerHeaders, - boolean showErrorToasts) { - Objects.requireNonNull(clientType); - Objects.requireNonNull(videoId); - Objects.requireNonNull(playerHeaders); - - final long startTime = System.currentTimeMillis(); - - try { - Route.CompiledRoute route = clientType.usePlayerEndpoint ? - GET_PLAYER_STREAMING_DATA : GET_REEL_STREAMING_DATA; - - HttpURLConnection connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(route, clientType); - connection.setConnectTimeout(HTTP_TIMEOUT_MILLISECONDS); - connection.setReadTimeout(HTTP_TIMEOUT_MILLISECONDS); - - boolean authHeadersIncludes = false; - - for (String key : REQUEST_HEADER_KEYS) { - String value = playerHeaders.get(key); - - if (value != null) { - if (key.equals(AUTHORIZATION_HEADER)) { - if (!clientType.useAuth) { - Logger.printDebug(() -> "Not including request header: " + key); - continue; - } - authHeadersIncludes = true; - } - - Logger.printDebug(() -> "Including request header: " + key); - connection.setRequestProperty(key, value); - } - } - - if (!authHeadersIncludes && clientType.useAuth) { - Logger.printDebug(() -> "Skipping client since user is not logged in: " + clientType - + " videoId: " + videoId); - return null; - } - - Logger.printDebug(() -> "Fetching video streams for: " + videoId + " using client: " + clientType); - - String innerTubeBody = PlayerRoutes.createInnertubeBody(clientType, videoId); - byte[] requestBody = innerTubeBody.getBytes(StandardCharsets.UTF_8); - connection.setFixedLengthStreamingMode(requestBody.length); - connection.getOutputStream().write(requestBody); - - final int responseCode = connection.getResponseCode(); - if (responseCode == 200) return connection; - - // This situation likely means the patches are outdated. - // Use a toast message that suggests updating. - handleConnectionError("Playback error (App is outdated?) " + clientType + ": " - + responseCode + " response: " + connection.getResponseMessage(), - null, showErrorToasts); - } catch (SocketTimeoutException ex) { - handleConnectionError("Connection timeout", ex, showErrorToasts); - } catch (IOException ex) { - handleConnectionError("Network error", ex, showErrorToasts); - } catch (Exception ex) { - Logger.printException(() -> "send failed", ex); - } finally { - Logger.printDebug(() -> "video: " + videoId + " took: " + (System.currentTimeMillis() - startTime) + "ms"); - } - - return null; - } - - private static byte[] fetch(String videoId, Map playerHeaders) { - final boolean debugEnabled = BaseSettings.DEBUG.get(); - - // Retry with different client if empty response body is received. - int i = 0; - for (ClientType clientType : clientOrderToUse) { - // Show an error if the last client type fails, or if debug is enabled then show for all attempts. - final boolean showErrorToast = (++i == clientOrderToUse.length) || debugEnabled; - - HttpURLConnection connection = send(clientType, videoId, playerHeaders, showErrorToast); - if (connection != null) { - byte[] playerResponseBuffer = buildPlayerResponseBuffer(clientType, connection); - if (playerResponseBuffer != null) { - lastSpoofedClientType = clientType; - - return playerResponseBuffer; - } - } - } - - lastSpoofedClientType = null; - handleConnectionError("Could not fetch any client streams", null, true); - return null; - } - - @Nullable - private static byte[] buildPlayerResponseBuffer(ClientType clientType, - HttpURLConnection connection) { - // gzip encoding doesn't response with content length (-1), - // but empty response body does. - if (connection.getContentLength() == 0) { - handleDebugToast("Debug: Ignoring empty spoof stream client (%s)", clientType); - return null; - } - - try (InputStream inputStream = connection.getInputStream()) { - PlayerResponse playerResponse = clientType.usePlayerEndpoint - ? PlayerResponse.parseFrom(inputStream) - : ReelItemWatchResponse.parseFrom(inputStream).getPlayerResponse(); - - var playabilityStatus = playerResponse.getPlayabilityStatus(); - if (playabilityStatus.getStatus() != PlayerResponseOuterClass.Status.OK) { - handleDebugToast("Debug: Ignoring unplayable video (%s)", clientType); - String reason = playabilityStatus.getReason(); - if (isNotEmpty(reason)) { - Logger.printDebug(() -> String.format("Debug: Ignoring unplayable video (%s), reason: %s", clientType, reason)); - } - - return null; - } - - PlayerResponse.Builder responseBuilder = playerResponse.toBuilder(); - if (!playerResponse.hasStreamingData()) { - handleDebugToast("Debug: Ignoring empty streaming data (%s)", clientType); - return null; - } - - // Android Studio only supports the HLS protocol for live streams. - // HLS protocol can theoretically be played with ExoPlayer, - // but the related code has not yet been implemented. - // If DASH protocol is not available, the client will be skipped. - StreamingData streamingData = playerResponse.getStreamingData(); - if (streamingData.getAdaptiveFormatsCount() == 0) { - handleDebugToast("Debug: Ignoring empty adaptiveFormat (%s)", clientType); - return null; - } - - return responseBuilder.build().toByteArray(); - } catch (IOException ex) { - Logger.printException(() -> "Failed to write player response to buffer array", ex); - return null; - } - } - - public boolean fetchCompleted() { - return future.isDone(); - } - - @Nullable - public byte[] getStream() { - try { - return future.get(MAX_MILLISECONDS_TO_WAIT_FOR_FETCH, TimeUnit.MILLISECONDS); - } catch (TimeoutException ex) { - Logger.printInfo(() -> "getStream timed out", ex); - } catch (InterruptedException ex) { - Logger.printException(() -> "getStream interrupted", ex); - Thread.currentThread().interrupt(); // Restore interrupt status flag. - } catch (ExecutionException ex) { - Logger.printException(() -> "getStream failure", ex); - } - - return null; - } - - @NonNull - @Override - public String toString() { - return "StreamingDataRequest{" + "videoId='" + videoId + '\'' + '}'; - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/theme/BaseThemePatch.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/theme/BaseThemePatch.java deleted file mode 100644 index 2d12b0c1f3..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/theme/BaseThemePatch.java +++ /dev/null @@ -1,48 +0,0 @@ -package app.revanced.extension.shared.theme; - -import androidx.annotation.Nullable; - -import app.revanced.extension.shared.Utils; - -@SuppressWarnings("unused") -public abstract class BaseThemePatch { - // Background colors. - protected static final int BLACK_COLOR = Utils.getResourceColor("yt_black1"); - protected static final int WHITE_COLOR = Utils.getResourceColor("yt_white1"); - - /** - * Check if a value matches any of the provided values. - * - * @param value The value to check. - * @param of The array of values to compare against. - * @return True if the value matches any of the provided values. - */ - protected static boolean anyEquals(int value, int... of) { - for (int v : of) { - if (value == v) { - return true; - } - } - return false; - } - - /** - * Helper method to process color values for Litho components. - * - * @param originalValue The original color value. - * @param darkValues Array of dark mode color values to match. - * @param lightValues Array of light mode color values to match. - * @return The new or original color value. - */ - protected static int processColorValue(int originalValue, int[] darkValues, @Nullable int[] lightValues) { - if (Utils.isDarkModeEnabled()) { - if (anyEquals(originalValue, darkValues)) { - return BLACK_COLOR; - } - } else if (lightValues != null && anyEquals(originalValue, lightValues)) { - return WHITE_COLOR; - } - - return originalValue; - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/ui/ColorDot.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/ui/ColorDot.java deleted file mode 100644 index 07e9b41135..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/ui/ColorDot.java +++ /dev/null @@ -1,60 +0,0 @@ -package app.revanced.extension.shared.ui; - -import static app.revanced.extension.shared.Utils.adjustColorBrightness; -import static app.revanced.extension.shared.Utils.getAppBackgroundColor; -import static app.revanced.extension.shared.Utils.isDarkModeEnabled; -import static app.revanced.extension.shared.settings.preference.ColorPickerPreference.DISABLED_ALPHA; - -import android.graphics.Color; -import android.graphics.drawable.GradientDrawable; -import android.view.View; - -import androidx.annotation.ColorInt; - -public class ColorDot { - private static final int STROKE_WIDTH = Dim.dp(1.5f); - - /** - * Creates a circular drawable with a main fill and a stroke. - * Stroke adapts to dark/light theme and transparency, applied only when color is transparent or matches app background. - */ - public static GradientDrawable createColorDotDrawable(@ColorInt int color) { - final boolean isDarkTheme = isDarkModeEnabled(); - final boolean isTransparent = Color.alpha(color) == 0; - final int opaqueColor = color | 0xFF000000; - final int appBackground = getAppBackgroundColor(); - final int strokeColor; - final int strokeWidth; - - // Determine stroke color. - if (isTransparent || (opaqueColor == appBackground)) { - final int baseColor = isTransparent ? appBackground : opaqueColor; - strokeColor = adjustColorBrightness(baseColor, isDarkTheme ? 1.2f : 0.8f); - strokeWidth = STROKE_WIDTH; - } else { - strokeColor = 0; - strokeWidth = 0; - } - - // Create circular drawable with conditional stroke. - GradientDrawable circle = new GradientDrawable(); - circle.setShape(GradientDrawable.OVAL); - circle.setColor(color); - circle.setStroke(strokeWidth, strokeColor); - - return circle; - } - - /** - * Applies the color dot drawable to the target view. - */ - public static void applyColorDot(View targetView, @ColorInt int color, boolean enabled) { - if (targetView == null) return; - targetView.setBackground(createColorDotDrawable(color)); - targetView.setAlpha(enabled ? 1.0f : DISABLED_ALPHA); - if (!isDarkModeEnabled()) { - targetView.setClipToOutline(true); - targetView.setElevation(Dim.dp2); - } - } -} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/ui/CustomDialog.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/ui/CustomDialog.java deleted file mode 100644 index 15d80c916e..0000000000 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/ui/CustomDialog.java +++ /dev/null @@ -1,461 +0,0 @@ -package app.revanced.extension.shared.ui; - -import android.app.Dialog; -import android.content.Context; -import android.graphics.Color; -import android.graphics.Typeface; -import android.graphics.drawable.ShapeDrawable; -import android.graphics.drawable.shapes.RoundRectShape; -import android.text.Spanned; -import android.text.TextUtils; -import android.text.method.LinkMovementMethod; -import android.util.Pair; -import android.view.Gravity; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.widget.Button; -import android.widget.EditText; -import android.widget.LinearLayout; -import android.widget.ScrollView; -import android.widget.TextView; -import androidx.annotation.Nullable; -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.Utils; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; - -/** - * A utility class for creating a customizable dialog with a title, message or EditText, and up to three buttons (OK, Cancel, Neutral). - * The dialog supports themed colors, rounded corners, and dynamic button layout based on screen width. It is dismissible by default. - */ -public class CustomDialog { - private final Context context; - private final Dialog dialog; - private final LinearLayout mainLayout; - - /** - * Creates a custom dialog with a styled layout, including a title, message, buttons, and an optional EditText. - * The dialog's appearance adapts to the app's dark mode setting, with rounded corners and customizable button actions. - * Buttons adjust dynamically to their text content and are arranged in a single row if they fit within 80% of the - * screen width, with the Neutral button aligned to the left and OK/Cancel buttons centered on the right. - * If buttons do not fit, each is placed on a separate row, all aligned to the right. - * - * @param context Context used to create the dialog. - * @param title Title text of the dialog. - * @param message Message text of the dialog (supports Spanned for HTML), or null if replaced by EditText. - * @param editText EditText to include in the dialog, or null if no EditText is needed. - * @param okButtonText OK button text, or null to use the default "OK" string. - * @param onOkClick Action to perform when the OK button is clicked. - * @param onCancelClick Action to perform when the Cancel button is clicked, or null if no Cancel button is needed. - * @param neutralButtonText Neutral button text, or null if no Neutral button is needed. - * @param onNeutralClick Action to perform when the Neutral button is clicked, or null if no Neutral button is needed. - * @param dismissDialogOnNeutralClick If the dialog should be dismissed when the Neutral button is clicked. - * @return The Dialog and its main LinearLayout container. - */ - public static Pair create(Context context, CharSequence title, CharSequence message, - @Nullable EditText editText, CharSequence okButtonText, - Runnable onOkClick, Runnable onCancelClick, - @Nullable CharSequence neutralButtonText, - @Nullable Runnable onNeutralClick, - boolean dismissDialogOnNeutralClick) { - Logger.printDebug(() -> "Creating custom dialog with title: " + title); - CustomDialog customDialog = new CustomDialog(context, title, message, editText, - okButtonText, onOkClick, onCancelClick, - neutralButtonText, onNeutralClick, dismissDialogOnNeutralClick); - return new Pair<>(customDialog.dialog, customDialog.mainLayout); - } - - /** - * Initializes a custom dialog with the specified parameters. - * - * @param context Context used to create the dialog. - * @param title Title text of the dialog. - * @param message Message text of the dialog, or null if replaced by EditText. - * @param editText EditText to include in the dialog, or null if no EditText is needed. - * @param okButtonText OK button text, or null to use the default "OK" string. - * @param onOkClick Action to perform when the OK button is clicked. - * @param onCancelClick Action to perform when the Cancel button is clicked, or null if no Cancel button is needed. - * @param neutralButtonText Neutral button text, or null if no Neutral button is needed. - * @param onNeutralClick Action to perform when the Neutral button is clicked, or null if no Neutral button is needed. - * @param dismissDialogOnNeutralClick If the dialog should be dismissed when the Neutral button is clicked. - */ - private CustomDialog(Context context, CharSequence title, CharSequence message, @Nullable EditText editText, - CharSequence okButtonText, Runnable onOkClick, Runnable onCancelClick, - @Nullable CharSequence neutralButtonText, @Nullable Runnable onNeutralClick, - boolean dismissDialogOnNeutralClick) { - this.context = context; - this.dialog = new Dialog(context); - this.dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); // Remove default title bar. - - // Create main layout. - mainLayout = createMainLayout(); - addTitle(title); - addContent(message, editText); - addButtons(okButtonText, onOkClick, onCancelClick, neutralButtonText, onNeutralClick, dismissDialogOnNeutralClick); - - // Set dialog content and window attributes. - dialog.setContentView(mainLayout); - Window window = dialog.getWindow(); - if (window != null) { - Utils.setDialogWindowParameters(window, Gravity.CENTER, 0, 90, false); - } - } - - /** - * Creates the main layout for the dialog with vertical orientation and rounded corners. - * - * @return The configured LinearLayout for the dialog. - */ - private LinearLayout createMainLayout() { - LinearLayout layout = new LinearLayout(context); - layout.setOrientation(LinearLayout.VERTICAL); - layout.setPadding(Dim.dp24, Dim.dp16, Dim.dp24, Dim.dp24); - - // Set rounded rectangle background. - ShapeDrawable background = new ShapeDrawable(new RoundRectShape( - Dim.roundedCorners(28), null, null)); - // Dialog background. - background.getPaint().setColor(Utils.getDialogBackgroundColor()); - layout.setBackground(background); - - return layout; - } - - /** - * Adds a title to the dialog if provided. - * - * @param title The title text to display. - */ - private void addTitle(CharSequence title) { - if (TextUtils.isEmpty(title)) return; - - TextView titleView = new TextView(context); - titleView.setText(title); - titleView.setTypeface(Typeface.DEFAULT_BOLD); - titleView.setTextSize(18); - titleView.setTextColor(Utils.getAppForegroundColor()); - titleView.setGravity(Gravity.CENTER); - - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT); - params.setMargins(0, 0, 0, Dim.dp16); - titleView.setLayoutParams(params); - - mainLayout.addView(titleView); - } - - /** - * Adds a message or EditText to the dialog within a ScrollView. - * - * @param message The message text to display (supports Spanned for HTML), or null if replaced by EditText. - * @param editText The EditText to include, or null if no EditText is needed. - */ - private void addContent(CharSequence message, @Nullable EditText editText) { - // Create content container (message/EditText) inside a ScrollView only if message or editText is provided. - if (message == null && editText == null) return; - - ScrollView scrollView = new ScrollView(context); - // Disable the vertical scrollbar. - scrollView.setVerticalScrollBarEnabled(false); - scrollView.setOverScrollMode(View.OVER_SCROLL_NEVER); - - LinearLayout contentContainer = new LinearLayout(context); - contentContainer.setOrientation(LinearLayout.VERTICAL); - scrollView.addView(contentContainer); - - // EditText (if provided). - if (editText != null) { - ShapeDrawable background = new ShapeDrawable(new RoundRectShape( - Dim.roundedCorners(10), null, null)); - background.getPaint().setColor(Utils.getEditTextBackground()); - scrollView.setPadding(Dim.dp8, Dim.dp8, Dim.dp8, Dim.dp8); - scrollView.setBackground(background); - scrollView.setClipToOutline(true); - - // Remove EditText from its current parent, if any. - ViewGroup parent = (ViewGroup) editText.getParent(); - if (parent != null) parent.removeView(editText); - // Style the EditText to match the dialog theme. - editText.setTextColor(Utils.getAppForegroundColor()); - editText.setBackgroundColor(Color.TRANSPARENT); - editText.setPadding(0, 0, 0, 0); - contentContainer.addView(editText, new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT)); - // Message (if not replaced by EditText). - } else { - TextView messageView = new TextView(context); - // Supports Spanned (HTML). - messageView.setText(message); - messageView.setTextSize(16); - messageView.setTextColor(Utils.getAppForegroundColor()); - // Enable HTML link clicking if the message contains links. - if (message instanceof Spanned) { - messageView.setMovementMethod(LinkMovementMethod.getInstance()); - } - contentContainer.addView(messageView, new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT)); - } - - // Weight to take available space. - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - 0, - 1.0f); - scrollView.setLayoutParams(params); - // Add ScrollView to main layout only if content exist. - mainLayout.addView(scrollView); - } - - /** - * Adds buttons to the dialog, arranging them dynamically based on their widths. - * - * @param okButtonText OK button text, or null to use the default "OK" string. - * @param onOkClick Action for the OK button click. - * @param onCancelClick Action for the Cancel button click, or null if no Cancel button. - * @param neutralButtonText Neutral button text, or null if no Neutral button. - * @param onNeutralClick Action for the Neutral button click, or null if no Neutral button. - * @param dismissDialogOnNeutralClick If the dialog should dismiss on Neutral button click. - */ - private void addButtons(CharSequence okButtonText, Runnable onOkClick, Runnable onCancelClick, - @Nullable CharSequence neutralButtonText, @Nullable Runnable onNeutralClick, - boolean dismissDialogOnNeutralClick) { - // Button container. - LinearLayout buttonContainer = new LinearLayout(context); - buttonContainer.setOrientation(LinearLayout.VERTICAL); - LinearLayout.LayoutParams buttonContainerParams = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT); - buttonContainerParams.setMargins(0, Dim.dp16, 0, 0); - buttonContainer.setLayoutParams(buttonContainerParams); - - List