Compare commits

...

64 Commits

Author SHA1 Message Date
semantic-release-bot
e279491724 chore: Release v5.25.0 [skip ci]
# [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 ([798596f](798596fd83))
* **Hide ADB status:** Resolve app crash on startup ([#5029](https://github.com/ReVanced/revanced-patches/issues/5029)) ([2984d73](2984d7362d))
* **Messenger:** Remove outdated `Disable switching emoji to sticker` patch ([#5044](https://github.com/ReVanced/revanced-patches/issues/5044)) ([517368e](517368eda7))
* **Spotify Lite:** Remove obsolete `Enable on demand` patch ([#5046](https://github.com/ReVanced/revanced-patches/issues/5046)) ([b712f38](b712f38017))
* **YouTube - GmsCore support:** Restore patch functionality from prior merge ([b6047fa](b6047fa6b3))
* **YouTube - Hide ads:** Hide new type of general ad ([#5004](https://github.com/ReVanced/revanced-patches/issues/5004)) ([bc0c3c4](bc0c3c452d))
* **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)) ([4ab1f0c](4ab1f0cfa9))
* **YouTube:** Better handle incorrect duplicate translations ([8d61ba9](8d61ba90c3))
* **Yuka - Unlock premium:** Remove broken patch that is no longer supported ([#5018](https://github.com/ReVanced/revanced-patches/issues/5018)) ([e286dab](e286dab74e))

### Features

* Add `Disable pairip license check` patch ([#4927](https://github.com/ReVanced/revanced-patches/issues/4927)) ([dea7108](dea7108c45))
* **Messenger:** Add `Remove Meta AI` patch ([#4945](https://github.com/ReVanced/revanced-patches/issues/4945)) ([52b9dc5](52b9dc5c9f))
* **Prime Video:** Add `Rename shared permissions` patch ([#5049](https://github.com/ReVanced/revanced-patches/issues/5049)) ([02373b0](02373b0bd2))
* **Spotify:** Add `Fix Facebook login` patch ([#5023](https://github.com/ReVanced/revanced-patches/issues/5023)) ([795016a](795016abce))
* **Threads:** Hide Ads ([#5064](https://github.com/ReVanced/revanced-patches/issues/5064)) ([bf1f26d](bf1f26d8bb))
* **YouTube - Enable debugging:** Add settings menu to share debug logs ([#5021](https://github.com/ReVanced/revanced-patches/issues/5021)) ([83c148a](83c148addc))
* **YouTube - Settings:** Add a color picker ([#4981](https://github.com/ReVanced/revanced-patches/issues/4981)) ([584b00f](584b00fd87))
* **YouTube - Swipe controls:** Add separate color settings for the brightness and volume bars ([#5043](https://github.com/ReVanced/revanced-patches/issues/5043)) ([443b54b](443b54bf09))
* **YouTube:** Add `Disable haptic feedback` patch ([#5033](https://github.com/ReVanced/revanced-patches/issues/5033)) ([5c8ed05](5c8ed05727))
2025-05-29 07:26:20 +00:00
LisoUseInAIKyrios
495260fe2b chore: Merge branch dev to main (#5010) 2025-05-29 09:22:53 +02:00
github-actions[bot]
40f069fff7 chore: Sync translations (#5066) 2025-05-29 09:20:59 +02:00
semantic-release-bot
de263c1061 chore: Release v5.25.0-dev.14 [skip ci]
# [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)) ([bf1f26d](bf1f26d8bb))
2025-05-29 07:17:42 +00:00
scruz
bf1f26d8bb feat(Threads): Hide Ads (#5064) 2025-05-29 09:14:24 +02:00
semantic-release-bot
0ee2ed72d4 chore: Release v5.25.0-dev.13 [skip ci]
# [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)) ([02373b0](02373b0bd2))
2025-05-28 20:41:45 +00:00
hoodles
02373b0bd2 feat(Prime Video): Add Rename shared permissions patch (#5049) 2025-05-28 22:38:09 +02:00
github-actions[bot]
97c8e2489d chore: Sync translations (#5061) 2025-05-28 22:37:18 +02:00
semantic-release-bot
08b2b2e104 chore: Release v5.25.0-dev.12 [skip ci]
# [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)) ([443b54b](443b54bf09))
2025-05-28 10:02:34 +00:00
github-actions[bot]
6b386b67d2 chore: Sync translations (#5055) 2025-05-28 11:58:56 +02:00
Nuckyz
f8343ae9f6 refactor(Spotify): Improve protobuf array list mutability patch (#5053)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-05-28 11:55:25 +02:00
LisoUseInAIKyrios
3ba791ac7d refactor(YouTube - Enabling debugging): Adjust logger formatting to preserve backwards compatibility (#5054) 2025-05-28 11:55:02 +02:00
MarcaD
443b54bf09 feat(YouTube - Swipe controls): Add separate color settings for the brightness and volume bars (#5043) 2025-05-28 11:54:28 +02:00
semantic-release-bot
53587f190d chore: Release v5.25.0-dev.11 [skip ci]
# [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)) ([83c148a](83c148addc))
* **YouTube:** Add `Disable haptic feedback` patch ([#5033](https://github.com/ReVanced/revanced-patches/issues/5033)) ([5c8ed05](5c8ed05727))
2025-05-27 07:55:39 +00:00
MarcaD
83c148addc feat(YouTube - Enable debugging): Add settings menu to share debug logs (#5021)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-05-27 09:52:24 +02:00
MarcaD
5c8ed05727 feat(YouTube): Add Disable haptic feedback patch (#5033) 2025-05-27 09:52:01 +02:00
semantic-release-bot
33833d7a1e chore: Release v5.25.0-dev.10 [skip ci]
# [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)) ([517368e](517368eda7))
* **Spotify Lite:** Remove obsolete `Enable on demand` patch ([#5046](https://github.com/ReVanced/revanced-patches/issues/5046)) ([b712f38](b712f38017))
2025-05-27 06:51:05 +00:00
LisoUseInAIKyrios
b712f38017 fix(Spotify Lite): Remove obsolete Enable on demand patch (#5046) 2025-05-27 08:47:56 +02:00
LisoUseInAIKyrios
517368eda7 fix(Messenger): Remove outdated Disable switching emoji to sticker patch (#5044) 2025-05-27 08:47:09 +02:00
LisoUseInAIKyrios
2093c0c175 chore: fix api dump 2025-05-26 12:36:16 +02:00
semantic-release-bot
a7cfd80bfe chore: Release v5.25.0-dev.9 [skip ci]
# [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)) ([795016a](795016abce))
* **YouTube - Settings:** Add a color picker ([#4981](https://github.com/ReVanced/revanced-patches/issues/4981)) ([584b00f](584b00fd87))
2025-05-26 09:48:51 +00:00
github-actions[bot]
2990dc6d4e chore: Sync translations (#5038) 2025-05-26 10:39:02 +02:00
semantic-release-bot
c0e52bb6b3 chore: Release v5.25.0-dev.9 [skip ci]
# [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)) ([795016a](795016abce))
* **YouTube - Settings:** Add a color picker ([#4981](https://github.com/ReVanced/revanced-patches/issues/4981)) ([584b00f](584b00fd87))
2025-05-26 07:53:26 +00:00
github-actions[bot]
93fdd6f538 chore: Sync translations (#5037) 2025-05-26 09:49:50 +02:00
semantic-release-bot
decd249f20 chore: Release v5.25.0-dev.9 [skip ci]
# [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)) ([795016a](795016abce))
* **YouTube - Settings:** Add a color picker ([#4981](https://github.com/ReVanced/revanced-patches/issues/4981)) ([584b00f](584b00fd87))
2025-05-26 07:29:23 +00:00
github-actions[bot]
d79cb3eea8 chore: Sync translations (#5036) 2025-05-26 09:25:54 +02:00
MarcaD
584b00fd87 feat(YouTube - Settings): Add a color picker (#4981)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-05-26 09:08:45 +02:00
Nuckyz
795016abce feat(Spotify): Add Fix Facebook login patch (#5023) 2025-05-26 09:08:15 +02:00
semantic-release-bot
dc1dbd50a8 chore: Release v5.25.0-dev.8 [skip ci]
# [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)) ([2984d73](2984d7362d))
2025-05-25 17:41:52 +00:00
1fexd
2984d7362d fix(Hide ADB status): Resolve app crash on startup (#5029) 2025-05-25 19:38:19 +02:00
semantic-release-bot
627aed4010 chore: Release v5.25.0-dev.7 [skip ci]
# [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)) ([4ab1f0c](4ab1f0cfa9))
2025-05-24 11:16:03 +00:00
LisoUseInAIKyrios
4ab1f0cfa9 fix(YouTube - Open Shorts in regular player): Do not exit app when pressing back button in regular player (#5020) 2025-05-24 13:12:39 +02:00
semantic-release-bot
86e8e61ab2 chore: Release v5.25.0-dev.6 [skip ci]
# [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)) ([e286dab](e286dab74e))
2025-05-23 19:58:47 +00:00
LisoUseInAIKyrios
e286dab74e fix(Yuka - Unlock premium): Remove broken patch that is no longer supported (#5018) 2025-05-23 21:55:04 +02:00
github-actions[bot]
712a82439f chore: Sync translations (#5019) 2025-05-23 21:54:47 +02:00
semantic-release-bot
4449546c85 chore: Release v5.25.0-dev.5 [skip ci]
# [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 ([8d61ba9](8d61ba90c3))
2025-05-22 19:23:25 +00:00
LisoUseInAIKyrios
8d61ba90c3 fix(YouTube): Better handle incorrect duplicate translations 2025-05-22 21:20:01 +02:00
semantic-release-bot
689be79f71 chore: Release v5.25.0-dev.4 [skip ci]
# [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 ([b6047fa](b6047fa6b3))
2025-05-22 17:28:53 +00:00
LisoUseInAIKyrios
b6047fa6b3 fix(YouTube - GmsCore support): Restore patch functionality from prior merge 2025-05-22 19:25:15 +02:00
semantic-release-bot
82bbd603ac chore: Release v5.25.0-dev.3 [skip ci]
# [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)) ([bc0c3c4](bc0c3c452d))
2025-05-22 16:38:22 +00:00
ILoveOpenSourceApplications
bc0c3c452d fix(YouTube - Hide ads): Hide new type of general ad (#5004) 2025-05-22 18:35:11 +02:00
Pun Butrach
fe864d8331 ci: Attest release artifacts (#4816)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-05-22 14:56:33 +02:00
semantic-release-bot
4f686935c3 chore: Release v5.25.0-dev.2 [skip ci]
# [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 ([798596f](798596fd83))
2025-05-22 10:18:30 +00:00
LisoUseInAIKyrios
798596fd83 fix(Disable Pairip license check): Change patch to default off 2025-05-22 12:14:33 +02:00
semantic-release-bot
38b37f182a chore: Release v5.25.0-dev.1 [skip ci]
# [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)) ([dea7108](dea7108c45))
* **Messenger:** Add `Remove Meta AI` patch ([#4945](https://github.com/ReVanced/revanced-patches/issues/4945)) ([52b9dc5](52b9dc5c9f))
2025-05-22 08:39:21 +00:00
Dawid Krajcarz
52b9dc5c9f feat(Messenger): Add Remove Meta AI patch (#4945) 2025-05-22 10:35:31 +02:00
hoodles
dea7108c45 feat: Add Disable pairip license check patch (#4927)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-05-22 10:35:16 +02:00
github-actions[bot]
24b4579cb9 chore: Sync translations (#5009) 2025-05-22 10:33:36 +02:00
semantic-release-bot
0b52f3d192 chore: Release v5.24.0 [skip ci]
# [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 ([e68cd70](e68cd70f66))
* **YouTube - Hide layout components:** Fix `Hide video recommendation labels` ([#4956](https://github.com/ReVanced/revanced-patches/issues/4956)) ([9aec199](9aec1999bb))
* **YouTube - Settings:** Correctly show summary text if search box is closed before searching ([e59c9e9](e59c9e9b3c))
* **YouTube - SponsorBlock:** Fix segment category summary not showing category description ([b2b09a2](b2b09a2025))

### Features

* **GmsCore support:** Open vendor specific DontKillMyApp if available ([#4952](https://github.com/ReVanced/revanced-patches/issues/4952)) ([d2b440d](d2b440d800))
* **NU.nl:** Support version `11.3.0` ([#4925](https://github.com/ReVanced/revanced-patches/issues/4925)) ([887c9f0](887c9f0d75))
* **Spotify:** Add `Fix third party launchers widgets` patch ([#4893](https://github.com/ReVanced/revanced-patches/issues/4893)) ([db68c41](db68c41d5e))
* **YouTube - Hide description components:** Add `Hide Ask` ([#4972](https://github.com/ReVanced/revanced-patches/issues/4972)) ([e582908](e58290839f))
* **YouTube - Hide layout components:** Add `Hide ticket shelf` ([#4969](https://github.com/ReVanced/revanced-patches/issues/4969)) ([4cd0ae9](4cd0ae9b92))
* **YouTube - Hide player components:** Hide related video overlay in fullscreen ([#4938](https://github.com/ReVanced/revanced-patches/issues/4938)) ([f454183](f454183646))
* **YouTube - Settings:** Add ability to search in settings ([#4881](https://github.com/ReVanced/revanced-patches/issues/4881)) ([14a8f4f](14a8f4fb96))
2025-05-19 10:32:36 +00:00
LisoUseInAIKyrios
18c374a81e chore: Merge branch dev to main (#4947) 2025-05-19 14:28:47 +04:00
github-actions[bot]
092303e431 chore: Sync translations (#4995) 2025-05-19 14:25:25 +04:00
semantic-release-bot
6bf5bf9d45 chore: Release v5.24.0-dev.9 [skip ci]
# [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 ([b2b09a2](b2b09a2025))
2025-05-18 08:29:10 +00:00
LisoUseInAIKyrios
b2b09a2025 fix(YouTube - SponsorBlock): Fix segment category summary not showing category description 2025-05-18 12:25:43 +04:00
semantic-release-bot
4a3a7f1674 chore: Release v5.24.0-dev.8 [skip ci]
# [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 ([e59c9e9](e59c9e9b3c))
2025-05-17 18:00:22 +00:00
LisoUseInAIKyrios
e59c9e9b3c fix(YouTube - Settings): Correctly show summary text if search box is closed before searching 2025-05-17 21:57:03 +04:00
github-actions[bot]
dfb552b01a chore: Sync translations (#4978) 2025-05-17 21:56:48 +04:00
github-actions[bot]
94999c56b1 chore: Sync translations (#4975) 2025-05-17 15:19:14 +04:00
semantic-release-bot
c4fd1f0146 chore: Release v5.24.0-dev.7 [skip ci]
# [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)) ([4cd0ae9](4cd0ae9b92))
2025-05-17 10:51:31 +00:00
ILoveOpenSourceApplications
4cd0ae9b92 feat(YouTube - Hide layout components): Add Hide ticket shelf (#4969) 2025-05-17 14:48:05 +04:00
github-actions[bot]
9548d581c1 chore: Sync translations (#4974) 2025-05-17 14:46:10 +04:00
LisoUseInAIKyrios
a2fe3af6be refactor(YouTube - Litho filter): Simplify debug logging code 2025-05-17 12:26:37 +04:00
semantic-release-bot
6ef6504d41 chore: Release v5.24.0-dev.6 [skip ci]
# [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)) ([e582908](e58290839f))
2025-05-17 07:56:03 +00:00
ILoveOpenSourceApplications
e58290839f feat(YouTube - Hide description components): Add Hide Ask (#4972) 2025-05-17 11:52:33 +04:00
github-actions[bot]
e18260bd65 chore: Sync translations (#4973) 2025-05-17 11:46:53 +04:00
174 changed files with 5515 additions and 2089 deletions

View File

@@ -19,11 +19,11 @@ jobs:
- name: Setup Java - name: Setup Java
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
distribution: "temurin" distribution: 'temurin'
java-version: "17" java-version: '17'
- name: Cache Gradle - name: Cache Gradle
uses: burrunan/gradle-cache-action@v1 uses: burrunan/gradle-cache-action@v3
- name: Build - name: Build
env: env:

View File

@@ -13,24 +13,23 @@ jobs:
permissions: permissions:
contents: write contents: write
packages: write packages: write
id-token: write
attestations: write
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
with: 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 fetch-depth: 0
- name: Setup Java - name: Setup Java
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
distribution: "temurin" distribution: 'temurin'
java-version: "17" java-version: '17'
- name: Cache Gradle - name: Cache Gradle
uses: burrunan/gradle-cache-action@v1 uses: burrunan/gradle-cache-action@v3
- name: Build - name: Build
env: env:
@@ -40,7 +39,7 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: "lts/*" node-version: 'lts/*'
cache: 'npm' cache: 'npm'
- name: Install dependencies - name: Install dependencies
@@ -54,6 +53,14 @@ jobs:
fingerprint: ${{ vars.GPG_FINGERPRINT }} fingerprint: ${{ vars.GPG_FINGERPRINT }}
- name: Release - name: Release
uses: cycjimmy/semantic-release-action@v4
id: release
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: npm exec semantic-release
- name: Attest
if: steps.release.outputs.new_release_published == 'true'
uses: actions/attest-build-provenance@v2
with:
subject-name: 'ReVanced Patches ${{ steps.release.outputs.new_release_git_tag }}'
subject-path: patches/build/libs/patches-*.rvp

View File

@@ -22,7 +22,7 @@
{ {
"assets": [ "assets": [
"CHANGELOG.md", "CHANGELOG.md",
"gradle.properties", "gradle.properties"
], ],
"message": "chore: Release v${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" "message": "chore: Release v${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
} }
@@ -33,16 +33,16 @@
"assets": [ "assets": [
{ {
"path": "patches/build/libs/patches-!(*sources*|*javadoc*).rvp?(.asc)" "path": "patches/build/libs/patches-!(*sources*|*javadoc*).rvp?(.asc)"
}, }
], ],
successComment: false "successComment": false
} }
], ],
[ [
"@saithodev/semantic-release-backmerge", "@saithodev/semantic-release-backmerge",
{ {
backmergeBranches: [{"from": "main", "to": "dev"}], "backmergeBranches": [{"from": "main", "to": "dev"}],
clearWorkspace: true "clearWorkspace": true
} }
] ]
] ]

View File

@@ -1,3 +1,198 @@
# [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) # [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)

View File

@@ -0,0 +1,3 @@
dependencies {
compileOnly(project(":extensions:shared:library"))
}

View File

@@ -0,0 +1 @@
<manifest/>

View File

@@ -0,0 +1,15 @@
package app.revanced.extension.messenger.metaai;
@SuppressWarnings("unused")
public class RemoveMetaAIPatch {
public static boolean overrideConfigBool(long id, boolean value) {
// It seems like all configs starting with 363219 are related to Meta AI.
// A list of specific ones that need disabling would probably be better,
// but these config numbers seem to change slightly with each update.
// These first 6 digits don't though.
if (Long.toString(id).startsWith("363219"))
return false;
return value;
}
}

View File

@@ -1,15 +1,26 @@
package app.revanced.extension.shared; 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 android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import app.revanced.extension.shared.settings.BaseSettings;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import static app.revanced.extension.shared.settings.BaseSettings.*; import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.preference.LogBufferManager;
/**
* ReVanced specific logger. Logging is done to standard device log (accessible thru ADB),
* and additionally accessible thru {@link LogBufferManager}.
*
* All methods are thread safe.
*/
public class Logger { public class Logger {
/** /**
@@ -17,99 +28,159 @@ public class Logger {
*/ */
@FunctionalInterface @FunctionalInterface
public interface LogMessage { public interface LogMessage {
/**
* @return Logger string message. This method is only called if logging is enabled.
*/
@NonNull @NonNull
String buildMessageString(); String buildMessageString();
}
/** private enum LogLevel {
* @return For outer classes, this returns {@link Class#getSimpleName()}. DEBUG,
* For static, inner, or anonymous classes, this returns the simple name of the enclosing class. INFO,
* <br> ERROR
* For example, each of these classes return 'SomethingView': }
* <code>
* com.company.SomethingView
* com.company.SomethingView$StaticClass
* com.company.SomethingView$1
* </code>
*/
private String findOuterClassSimpleName() {
var selfClass = this.getClass();
String fullClassName = selfClass.getName(); /**
final int dollarSignIndex = fullClassName.indexOf('$'); * Log tag prefix. Only used for system logging.
if (dollarSignIndex < 0) { */
return selfClass.getSimpleName(); // Already an outer class. 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.
* <br>
* For example, each of these classes returns 'SomethingView':
* <code>
* com.company.SomethingView
* com.company.SomethingView$StaticClass
* com.company.SomethingView$1
* </code>
*/
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;
} }
}
// Class is inner, static, or anonymous. if (includeStackTrace) {
// Parse the simple name full name. var sw = new StringWriter();
// A class with no package returns index of -1, but incrementing gives index zero which is correct. new Throwable().printStackTrace(new PrintWriter(sw));
final int simpleClassNameStartIndex = fullClassName.lastIndexOf('.') + 1; String stackTrace = sw.toString();
return fullClassName.substring(simpleClassNameStartIndex, dollarSignIndex); // 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);
} }
} }
private static final String REVANCED_LOG_PREFIX = "revanced: ";
/** /**
* Logs debug messages under the outer class name of the code calling this method. * 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()} * <p>
* 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(@NonNull LogMessage message) { public static void printDebug(LogMessage message) {
printDebug(message, null); printDebug(message, null);
} }
/** /**
* Logs debug messages under the outer class name of the code calling this method. * 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()} * <p>
* 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(@NonNull LogMessage message, @Nullable Exception ex) { public static void printDebug(LogMessage message, @Nullable Exception ex) {
if (DEBUG.get()) { if (DEBUG.get()) {
String logMessage = message.buildMessageString(); logInternal(LogLevel.DEBUG, message, ex, DEBUG_STACKTRACE.get(), false);
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. * Logs information messages using the outer class name of the code calling this method.
*/ */
public static void printInfo(@NonNull LogMessage message) { public static void printInfo(LogMessage message) {
printInfo(message, null); printInfo(message, null);
} }
/** /**
* Logs information messages using the outer class name of the code calling this method. * Logs information messages using the outer class name of the code calling this method.
*/ */
public static void printInfo(@NonNull LogMessage message, @Nullable Exception ex) { public static void printInfo(LogMessage message, @Nullable Exception ex) {
String logTag = REVANCED_LOG_PREFIX + message.findOuterClassSimpleName(); logInternal(LogLevel.INFO, message, ex, DEBUG_STACKTRACE.get(), false);
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. * 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(@NonNull LogMessage message) { public static void printException(LogMessage message) {
printException(message, null); printException(message, null);
} }
@@ -122,35 +193,23 @@ public class Logger {
* @param message log message * @param message log message
* @param ex exception (optional) * @param ex exception (optional)
*/ */
public static void printException(@NonNull LogMessage message, @Nullable Throwable ex) { public static void printException(LogMessage message, @Nullable Throwable ex) {
String messageString = message.buildMessageString(); logInternal(LogLevel.ERROR, message, ex, DEBUG_STACKTRACE.get(), DEBUG_TOAST_ON_ERROR.get());
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. * Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#getContext()} may not be initialized.
* Normally this method should not be used. * Normally this method should not be used.
*/ */
public static void initializationInfo(@NonNull Class<?> callingClass, @NonNull String message) { public static void initializationInfo(LogMessage message) {
Log.i(REVANCED_LOG_PREFIX + callingClass.getSimpleName(), message); logInternal(LogLevel.INFO, message, null, false, false);
} }
/** /**
* Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#getContext()} may not be initialized. * Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#getContext()} may not be initialized.
* Normally this method should not be used. * Normally this method should not be used.
*/ */
public static void initializationException(@NonNull Class<?> callingClass, @NonNull String message, public static void initializationException(LogMessage message, @Nullable Exception ex) {
@Nullable Exception ex) { logInternal(LogLevel.ERROR, message, ex, false, false);
Log.e(REVANCED_LOG_PREFIX + callingClass.getSimpleName(), message, ex);
} }
}
}

View File

@@ -1,7 +1,11 @@
package app.revanced.extension.shared; package app.revanced.extension.shared;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.*; import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.Fragment;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
@@ -18,6 +22,8 @@ import android.os.Looper;
import android.preference.Preference; import android.preference.Preference;
import android.preference.PreferenceGroup; import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen; import android.preference.PreferenceScreen;
import android.util.Pair;
import android.util.TypedValue;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewParent; import android.view.ViewParent;
@@ -357,15 +363,17 @@ public class Utils {
public static Context getContext() { public static Context getContext() {
if (context == null) { if (context == null) {
Logger.initializationException(Utils.class, "Context is not set by extension hook, returning null", null); Logger.initializationException(() -> "Context is not set by extension hook, returning null", null);
} }
return context; return context;
} }
public static void setContext(Context appContext) { public static void setContext(Context appContext) {
// Intentionally use logger before context is set,
// to expose any bugs in the 'no context available' logger method.
Logger.initializationInfo(() -> "Set context: " + appContext);
// Must initially set context to check the app language. // Must initially set context to check the app language.
context = appContext; context = appContext;
Logger.initializationInfo(Utils.class, "Set context: " + appContext);
AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get(); AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get();
if (language != AppLanguage.DEFAULT) { if (language != AppLanguage.DEFAULT) {
@@ -377,8 +385,9 @@ public class Utils {
} }
} }
public static void setClipboard(@NonNull String text) { public static void setClipboard(CharSequence text) {
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context
.getSystemService(Context.CLIPBOARD_SERVICE);
android.content.ClipData clip = android.content.ClipData.newPlainText("ReVanced", text); android.content.ClipData clip = android.content.ClipData.newPlainText("ReVanced", text);
clipboard.setPrimaryClip(clip); clipboard.setPrimaryClip(clip);
} }
@@ -542,24 +551,25 @@ public class Utils {
private static void showToast(@NonNull String messageToToast, int toastDuration) { private static void showToast(@NonNull String messageToToast, int toastDuration) {
Objects.requireNonNull(messageToToast); Objects.requireNonNull(messageToToast);
runOnMainThreadNowOrLater(() -> { runOnMainThreadNowOrLater(() -> {
if (context == null) { Context currentContext = context;
Logger.initializationException(Utils.class, "Cannot show toast (context is null): " + messageToToast, null);
} else { if (currentContext == null) {
Logger.printDebug(() -> "Showing toast: " + messageToToast); Logger.initializationException(() -> "Cannot show toast (context is null): " + messageToToast, null);
Toast.makeText(context, messageToToast, toastDuration).show(); } else {
} Logger.printDebug(() -> "Showing toast: " + messageToToast);
} Toast.makeText(currentContext, messageToToast, toastDuration).show();
); }
});
} }
public static boolean isDarkModeEnabled(Context context) { public static boolean isDarkModeEnabled() {
Configuration config = context.getResources().getConfiguration(); Configuration config = Resources.getSystem().getConfiguration();
final int currentNightMode = config.uiMode & Configuration.UI_MODE_NIGHT_MASK; final int currentNightMode = config.uiMode & Configuration.UI_MODE_NIGHT_MASK;
return currentNightMode == Configuration.UI_MODE_NIGHT_YES; return currentNightMode == Configuration.UI_MODE_NIGHT_YES;
} }
public static boolean isLandscapeOrientation() { public static boolean isLandscapeOrientation() {
final int orientation = context.getResources().getConfiguration().orientation; final int orientation = Resources.getSystem().getConfiguration().orientation;
return orientation == Configuration.ORIENTATION_LANDSCAPE; return orientation == Configuration.ORIENTATION_LANDSCAPE;
} }
@@ -573,7 +583,7 @@ public class Utils {
} }
/** /**
* Automatically logs any exceptions the runnable throws * Automatically logs any exceptions the runnable throws.
*/ */
public static void runOnMainThreadDelayed(@NonNull Runnable runnable, long delayMillis) { public static void runOnMainThreadDelayed(@NonNull Runnable runnable, long delayMillis) {
Runnable loggingRunnable = () -> { Runnable loggingRunnable = () -> {
@@ -599,14 +609,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() { public static boolean isCurrentlyOnMainThread() {
return Looper.getMainLooper().isCurrentThread(); 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 { public static void verifyOnMainThread() throws IllegalStateException {
if (!isCurrentlyOnMainThread()) { if (!isCurrentlyOnMainThread()) {
@@ -615,7 +625,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 { public static void verifyOffMainThread() throws IllegalStateException {
if (isCurrentlyOnMainThread()) { if (isCurrentlyOnMainThread()) {
@@ -629,13 +639,23 @@ public class Utils {
OTHER, OTHER,
} }
/**
* Calling extension code must ensure the un-patched app has the permission
* <code>android.permission.ACCESS_NETWORK_STATE</code>, otherwise the app will crash
* if this method is used.
*/
public static boolean isNetworkConnected() { public static boolean isNetworkConnected() {
NetworkType networkType = getNetworkType(); NetworkType networkType = getNetworkType();
return networkType == NetworkType.MOBILE return networkType == NetworkType.MOBILE
|| networkType == NetworkType.OTHER; || networkType == NetworkType.OTHER;
} }
@SuppressLint({"MissingPermission", "deprecation"}) // Permission already included in YouTube. /**
* Calling extension code must ensure the un-patched app has the permission
* <code>android.permission.ACCESS_NETWORK_STATE</code>, otherwise the app will crash
* if this method is used.
*/
@SuppressLint({"MissingPermission", "deprecation"})
public static NetworkType getNetworkType() { public static NetworkType getNetworkType() {
Context networkContext = getContext(); Context networkContext = getContext();
if (networkContext == null) { if (networkContext == null) {
@@ -738,9 +758,9 @@ public class Utils {
* then the preferences are left unsorted. * then the preferences are left unsorted.
*/ */
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public static void sortPreferenceGroups(@NonNull PreferenceGroup group) { public static void sortPreferenceGroups(PreferenceGroup group) {
Sort groupSort = Sort.fromKey(group.getKey(), Sort.UNSORTED); Sort groupSort = Sort.fromKey(group.getKey(), Sort.UNSORTED);
SortedMap<String, Preference> preferences = new TreeMap<>(); List<Pair<String, Preference>> preferences = new ArrayList<>();
for (int i = 0, prefCount = group.getPreferenceCount(); i < prefCount; i++) { for (int i = 0, prefCount = group.getPreferenceCount(); i < prefCount; i++) {
Preference preference = group.getPreference(i); Preference preference = group.getPreference(i);
@@ -769,17 +789,22 @@ public class Utils {
throw new IllegalStateException(); throw new IllegalStateException();
} }
preferences.put(sortValue, preference); preferences.add(new Pair<>(sortValue, preference));
} }
//noinspection ComparatorCombinators
Collections.sort(preferences, (pair1, pair2)
-> pair1.first.compareTo(pair2.first));
int index = 0; int index = 0;
for (Preference pref : preferences.values()) { for (Pair<String, Preference> pair : preferences) {
int order = index++; int order = index++;
Preference pref = pair.second;
// Move any screens, intents, and the one off About preference to the top. // Move any screens, intents, and the one off About preference to the top.
if (pref instanceof PreferenceScreen || pref instanceof ReVancedAboutPreference if (pref instanceof PreferenceScreen || pref instanceof ReVancedAboutPreference
|| pref.getIntent() != null) { || pref.getIntent() != null) {
// Arbitrary high number. // Any arbitrary large number.
order -= 1000; order -= 1000;
} }
@@ -843,6 +868,20 @@ public class Utils {
return getResourceColor(colorString); return getResourceColor(colorString);
} }
/**
* Converts dip value to actual device pixels.
*
* @param dip The density-independent pixels value
* @return The device pixel value
*/
public static int dipToPixels(float dip) {
return (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dip,
Resources.getSystem().getDisplayMetrics()
);
}
public static int clamp(int value, int lower, int upper) { public static int clamp(int value, int lower, int upper) {
return Math.max(lower, Math.min(value, upper)); return Math.max(lower, Math.min(value, upper));
} }

View File

@@ -0,0 +1,442 @@
package app.revanced.extension.shared.settings.preference;
import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
import android.app.AlertDialog;
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.SpannableString;
import android.text.Spanned;
import android.text.TextWatcher;
import android.text.style.ForegroundColorSpan;
import android.text.style.RelativeSizeSpan;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import java.util.Locale;
import java.util.regex.Pattern;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.settings.StringSetting;
/**
* 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 {
/**
* Character to show the color appearance.
*/
public static final String COLOR_DOT_STRING = "";
/**
* Length of a valid color string of format #RRGGBB.
*/
public static final int COLOR_STRING_LENGTH = 7;
/**
* 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.
*/
private static final float DISABLED_ALPHA = 0.5f; // 50%
/**
* View displaying a colored dot in the widget area.
*/
private View widgetColorDot;
/**
* Current color in RGB format (without alpha).
*/
@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 TextView displaying a colored dot for the selected color preview in the dialog.
*/
private TextView dialogColorPreview;
/**
* Dialog color picker view.
*/
private ColorPickerView dialogColorPickerView;
/**
* Removes non valid hex characters, converts to all uppercase,
* and adds # character to the start if not present.
*/
public static String cleanupColorCodeString(String colorString) {
// Remove non-hex chars, convert to uppercase, and ensure correct length
String result = "#" + PATTERN_NOT_HEX.matcher(colorString)
.replaceAll("").toUpperCase(Locale.ROOT);
if (result.length() < COLOR_STRING_LENGTH) {
return result;
}
return result.substring(0, COLOR_STRING_LENGTH);
}
/**
* @param color RGB color, without an alpha channel.
* @return #RRGGBB hex color string
*/
public static String getColorString(@ColorInt int color) {
String colorString = String.format("#%06X", color);
if ((color & 0xFF000000) != 0) {
// Likely a bug somewhere.
Logger.printException(() -> "getColorString: color has alpha channel: " + colorString);
}
return colorString;
}
/**
* Creates a Spanned object for a colored dot using SpannableString.
*
* @param color The RGB color (without alpha).
* @return A Spanned object with the colored dot.
*/
public static Spanned getColorDot(@ColorInt int color) {
SpannableString spannable = new SpannableString(COLOR_DOT_STRING);
spannable.setSpan(new ForegroundColorSpan(color | 0xFF000000), 0, COLOR_DOT_STRING.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spannable.setSpan(new RelativeSizeSpan(1.5f), 0, 1,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
return spannable;
}
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() {
colorSetting = (StringSetting) Setting.getSettingFromPath(getKey());
if (colorSetting == null) {
Logger.printException(() -> "Could not find color setting for: " + getKey());
}
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(getResourceIdentifier("revanced_color_dot_widget", "layout"));
}
/**
* Sets the selected color and updates the UI and settings.
*
* @param colorString The color in hexadecimal format (e.g., "#RRGGBB").
* @throws IllegalArgumentException If the color string is invalid.
*/
@Override
public final void setText(String colorString) {
try {
Logger.printDebug(() -> "setText: " + colorString);
super.setText(colorString);
currentColor = Color.parseColor(colorString) & 0x00FFFFFF;
if (colorSetting != null) {
colorSetting.save(getColorString(currentColor));
}
updateColorPreview();
updateWidgetColorDot();
} 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);
}
}
@Override
protected void onBindView(View view) {
super.onBindView(view);
widgetColorDot = view.findViewById(getResourceIdentifier(
"revanced_color_dot_widget", "id"));
widgetColorDot.setBackgroundResource(getResourceIdentifier(
"revanced_settings_circle_background", "drawable"));
widgetColorDot.getBackground().setTint(currentColor | 0xFF000000);
widgetColorDot.setAlpha(isEnabled() ? 1.0f : DISABLED_ALPHA);
}
/**
* Creates a layout with a color preview and EditText for hex color input.
*
* @param context The context for creating the layout.
* @return A LinearLayout containing the color preview and EditText.
*/
private LinearLayout createDialogLayout(Context context) {
LinearLayout layout = new LinearLayout(context);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(70, 0, 70, 0);
// Inflate color picker.
View colorPicker = LayoutInflater.from(context).inflate(
getResourceIdentifier("revanced_color_picker", "layout"), null);
dialogColorPickerView = colorPicker.findViewById(
getResourceIdentifier("color_picker_view", "id"));
dialogColorPickerView.setColor(currentColor);
layout.addView(colorPicker);
// Horizontal layout for preview and EditText.
LinearLayout inputLayout = new LinearLayout(context);
inputLayout.setOrientation(LinearLayout.HORIZONTAL);
inputLayout.setPadding(0, 20, 0, 0);
dialogColorPreview = new TextView(context);
inputLayout.addView(dialogColorPreview);
updateColorPreview();
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);
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);
layout.addView(inputLayout);
// 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);
Logger.printDebug(() -> "onColorChanged: " + updatedColorString);
currentColor = color;
editText.setText(updatedColorString);
editText.setSelection(updatedColorString.length());
updateColorPreview();
updateWidgetColorDot();
});
return layout;
}
/**
* Updates the color preview TextView with a colored dot.
*/
private void updateColorPreview() {
if (dialogColorPreview != null) {
dialogColorPreview.setText(getColorDot(currentColor));
}
}
private void updateWidgetColorDot() {
if (widgetColorDot != null) {
widgetColorDot.getBackground().setTint(currentColor | 0xFF000000);
widgetColorDot.setAlpha(isEnabled() ? 1.0f : DISABLED_ALPHA);
}
}
/**
* Creates a TextWatcher to monitor changes in the EditText for color input.
*
* @return A TextWatcher that updates the color preview on valid 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);
if (!sanitizedColorString.equals(colorString)) {
edit.replace(0, colorString.length(), sanitizedColorString);
return;
}
if (sanitizedColorString.length() != COLOR_STRING_LENGTH) {
// User is still typing out the color.
return;
}
final int newColor = Color.parseColor(colorString);
if (currentColor != newColor) {
Logger.printDebug(() -> "afterTextChanged: " + sanitizedColorString);
currentColor = newColor;
updateColorPreview();
updateWidgetColorDot();
colorPickerView.setColor(newColor);
}
} catch (Exception ex) {
// Should never be reached since input is validated before using.
Logger.printException(() -> "afterTextChanged failure", ex);
}
}
};
}
/**
* Prepares the dialog builder with a custom view and reset button.
*
* @param builder The AlertDialog.Builder to configure.
*/
@Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
Utils.setEditTextDialogTheme(builder);
LinearLayout dialogLayout = createDialogLayout(builder.getContext());
builder.setView(dialogLayout);
final int originalColor = currentColor;
builder.setNeutralButton(str("revanced_settings_reset_color"), null);
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
try {
String colorString = getEditText().getText().toString();
if (colorString.length() != COLOR_STRING_LENGTH) {
Utils.showToastShort(str("revanced_settings_color_invalid"));
setText(getColorString(originalColor));
return;
}
setText(colorString);
} 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(() -> "setPositiveButton failure", ex);
}
});
builder.setNegativeButton(android.R.string.cancel, (dialog, which) -> {
try {
// Restore the original color.
setText(getColorString(originalColor));
} catch (Exception ex) {
Logger.printException(() -> "setNegativeButton failure", ex);
}
});
}
@Override
protected void showDialog(Bundle state) {
super.showDialog(state);
AlertDialog dialog = (AlertDialog) getDialog();
dialog.setCanceledOnTouchOutside(false);
// Do not close dialog when reset is pressed.
Button button = dialog.getButton(AlertDialog.BUTTON_NEUTRAL);
button.setOnClickListener(view -> {
try {
final int defaultColor = Color.parseColor(colorSetting.defaultValue) & 0x00FFFFFF;
// Setting view color causes listener callback into this class.
dialogColorPickerView.setColor(defaultColor);
} catch (Exception ex) {
Logger.printException(() -> "setOnClickListener failure", ex);
}
});
}
@Override
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
if (colorTextWatcher != null) {
getEditText().removeTextChangedListener(colorTextWatcher);
colorTextWatcher = null;
}
dialogColorPreview = null;
dialogColorPickerView = null;
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
updateWidgetColorDot();
}
}

View File

@@ -0,0 +1,500 @@
package app.revanced.extension.shared.settings.preference;
import static app.revanced.extension.shared.Utils.dipToPixels;
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;
/**
* A custom color picker view that allows the user to select a color using a hue slider and a saturation-value selector.
* This implementation is density-independent and responsive across different screen sizes and DPIs.
*
* <p>
* This view displays two main components for color selection:
* <ul>
* <li><b>Hue Bar:</b> A vertical bar on the right that allows the user to select the hue component of the color.
* <li><b>Saturation-Value Selector:</b> A rectangular area that allows the user to select the saturation and value (brightness)
* components of the color based on the selected hue.
* </ul>
*
* <p>
* The view uses {@link LinearGradient} and {@link ComposeShader} to create the color gradients for the hue bar and the
* saturation-value selector. It also uses {@link Paint} to draw the selectors (draggable handles).
*
* <p>
* 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.
*
* Important: Callback color uses RGB format with zero alpha channel.
*
* @param color The new selected color.
*/
void onColorChanged(@ColorInt int color);
}
/** Expanded touch area for the hue bar to increase the touch-sensitive area. */
public static final float TOUCH_EXPANSION = dipToPixels(20f);
private static final float MARGIN_BETWEEN_AREAS = dipToPixels(24);
private static final float VIEW_PADDING = dipToPixels(16);
private static final float HUE_BAR_WIDTH = dipToPixels(12);
private static final float HUE_CORNER_RADIUS = dipToPixels(6);
private static final float SELECTOR_RADIUS = dipToPixels(12);
private static final float SELECTOR_STROKE_WIDTH = 8;
/**
* Hue 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;
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");
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});
}
}
/** Hue bar. */
private final Paint huePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
/** Saturation-value selector. */
private final Paint saturationValuePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
/** Draggable selector. */
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 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;
/** The currently selected color in RGB format with no alpha channel. */
@ColorInt
private int selectedColor;
private OnColorChangedListener colorChangedListener;
/** Track if we're currently dragging the hue or saturation handle. */
private boolean isDraggingHue;
private boolean isDraggingSaturation;
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);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final float DESIRED_ASPECT_RATIO = 0.8f; // height = width * 0.8
final int minWidth = Utils.dipToPixels(250);
final int minHeight = (int) (minWidth * DESIRED_ASPECT_RATIO);
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);
if (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY) {
height = desiredHeight;
}
setMeasuredDimension(width, height);
}
/**
* Called when the size of the view changes.
* This method calculates and sets the bounds of the hue bar and saturation-value selector.
* It also creates the necessary shaders for the gradients.
*/
@Override
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
super.onSizeChanged(width, height, oldWidth, oldHeight);
// Calculate bounds with hue bar on the right
final float effectiveWidth = width - (2 * VIEW_PADDING);
final float selectorWidth = effectiveWidth - HUE_BAR_WIDTH - MARGIN_BETWEEN_AREAS;
// Adjust rectangles to account for padding and density-independent dimensions
saturationValueRect.set(
VIEW_PADDING,
VIEW_PADDING,
VIEW_PADDING + selectorWidth,
height - VIEW_PADDING
);
hueRect.set(
width - VIEW_PADDING - HUE_BAR_WIDTH,
VIEW_PADDING,
width - VIEW_PADDING,
height - VIEW_PADDING
);
// Update the shaders.
updateHueShader();
updateSaturationValueShader();
}
/**
* Updates the hue full spectrum (0-360 degrees).
*/
private void updateHueShader() {
LinearGradient hueShader = new LinearGradient(
hueRect.left, hueRect.top,
hueRect.left, hueRect.bottom,
HUE_COLORS,
null,
Shader.TileMode.CLAMP
);
huePaint.setShader(hueShader);
}
/**
* Updates the shader for the saturation-value selector based on the currently selected hue.
* This method creates a combined shader that blends a saturation gradient with a value gradient.
*/
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).
//noinspection ExtractMethodRecommender
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 view on the canvas.
* This method draws the saturation-value selector, the hue bar with rounded corners,
* and the draggable handles.
*
* @param canvas The canvas on which to draw.
*/
@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);
final float hueSelectorX = hueRect.centerX();
final float hueSelectorY = hueRect.top + (hue / 360f) * hueRect.height();
final float satSelectorX = saturationValueRect.left + saturation * saturationValueRect.width();
final float satSelectorY = saturationValueRect.top + (1 - value) * saturationValueRect.height();
// Draw the saturation and hue selector handle filled with the selected color.
hsvArray[0] = hue;
final int hueHandleColor = Color.HSVToColor(0xFF, hsvArray);
selectorPaint.setStyle(Paint.Style.FILL_AND_STROKE);
selectorPaint.setColor(hueHandleColor);
canvas.drawCircle(hueSelectorX, hueSelectorY, SELECTOR_FILL_RADIUS, selectorPaint);
selectorPaint.setColor(selectedColor | 0xFF000000);
canvas.drawCircle(satSelectorX, satSelectorY, 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);
// 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);
}
/**
* Handles touch events on the view.
* This method determines whether the touch event occurred within the hue bar or the saturation-value selector,
* updates the corresponding values (hue, saturation, value), and invalidates the view to trigger a redraw.
* <p>
* In addition to testing if the touch is within the strict rectangles, an expanded hit area (by selectorRadius)
* is used so that the draggable handles remain active even when half of the handle is outside the drawn bounds.
*
* @param event The motion event.
* @return True if the event was handled, false otherwise.
*/
@SuppressLint("ClickableViewAccessibility") // performClick is not overridden, but not needed in this case.
@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 bar.
RectF expandedHueRect = new RectF(
hueRect.left - TOUCH_EXPANSION,
hueRect.top,
hueRect.right + TOUCH_EXPANSION,
hueRect.bottom
);
switch (action) {
case MotionEvent.ACTION_DOWN:
// Calculate current handle positions.
final float hueSelectorX = hueRect.centerX();
final float hueSelectorY = hueRect.top + (hue / 360f) * hueRect.height();
final float satSelectorX = saturationValueRect.left + saturation * saturationValueRect.width();
final float valSelectorY = saturationValueRect.top + (1 - value) * saturationValueRect.height();
// Create hit areas for both 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
);
// Check if the touch started on a handle or within the expanded hue bar area.
if (hueHitRect.contains(x, y)) {
isDraggingHue = true;
updateHueFromTouch(y);
} else if (satValHitRect.contains(x, y)) {
isDraggingSaturation = true;
updateSaturationValueFromTouch(x, y);
} else if (expandedHueRect.contains(x, y)) {
// Handle touch within the expanded hue bar area.
isDraggingHue = true;
updateHueFromTouch(y);
} else if (saturationValueRect.contains(x, y)) {
isDraggingSaturation = true;
updateSaturationValueFromTouch(x, y);
}
break;
case MotionEvent.ACTION_MOVE:
// Continue updating values even if touch moves outside the view.
if (isDraggingHue) {
updateHueFromTouch(y);
} else if (isDraggingSaturation) {
updateSaturationValueFromTouch(x, y);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
isDraggingHue = false;
isDraggingSaturation = false;
break;
}
} catch (Exception ex) {
Logger.printException(() -> "onTouchEvent failure", ex);
}
return true;
}
/**
* Updates the hue value based on touch position, clamping to valid range.
*
* @param y The y-coordinate of the touch position.
*/
private void updateHueFromTouch(float y) {
// Clamp y to the hue rectangle bounds.
final float clampedY = Utils.clamp(y, hueRect.top, hueRect.bottom);
final float updatedHue = ((clampedY - hueRect.top) / hueRect.height()) * 360f;
if (hue == updatedHue) {
return;
}
hue = updatedHue;
updateSaturationValueShader();
updateSelectedColor();
}
/**
* Updates saturation and value based on touch position, clamping to valid range.
*
* @param x The x-coordinate of the touch position.
* @param y The y-coordinate of the touch position.
*/
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;
updateSelectedColor();
}
/**
* Updates the selected color and notifies listeners.
*/
private void updateSelectedColor() {
final int updatedColor = Color.HSVToColor(0, new float[]{hue, saturation, value});
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 currently selected color.
*
* @param color The color to set in either ARGB or RGB format.
*/
public void setColor(@ColorInt int color) {
color &= 0x00FFFFFF;
if (selectedColor == color) {
return;
}
// Update the selected color.
selectedColor = color;
Logger.printDebug(() -> "setColor: " + getColorString(selectedColor));
// 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];
// Update the saturation-value shader based on the new hue.
updateSaturationValueShader();
// 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.
*
* @return The selected color in RGB format with no alpha channel.
*/
@ColorInt
public int getColor() {
return selectedColor;
}
/**
* Sets the listener to be notified when the selected color changes.
*
* @param listener The listener to set.
*/
public void setOnColorChangedListener(OnColorChangedListener listener) {
colorChangedListener = listener;
}
}

View File

@@ -70,7 +70,7 @@ public class ImportExportPreference extends EditTextPreference implements Prefer
// Show the user the settings in JSON format. // Show the user the settings in JSON format.
builder.setNeutralButton(str("revanced_settings_import_copy"), (dialog, which) -> { builder.setNeutralButton(str("revanced_settings_import_copy"), (dialog, which) -> {
Utils.setClipboard(getEditText().getText().toString()); Utils.setClipboard(getEditText().getText());
}).setPositiveButton(str("revanced_settings_import"), (dialog, which) -> { }).setPositiveButton(str("revanced_settings_import"), (dialog, which) -> {
importSettings(builder.getContext(), getEditText().getText().toString()); importSettings(builder.getContext(), getEditText().getText().toString());
}); });

View File

@@ -0,0 +1,113 @@
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<String> 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();
}
}

View File

@@ -8,7 +8,6 @@ import android.app.Dialog;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Color; import android.graphics.Color;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
@@ -54,7 +53,7 @@ public class ReVancedAboutPreference extends Preference {
} }
protected boolean isDarkModeEnabled() { protected boolean isDarkModeEnabled() {
return Utils.isDarkModeEnabled(getContext()); return Utils.isDarkModeEnabled();
} }
/** /**

View File

@@ -6,9 +6,8 @@ import android.util.AttributeSet;
import android.util.Pair; import android.util.Pair;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
@@ -46,17 +45,25 @@ public class SortedListPreference extends ListPreference {
} }
List<Pair<CharSequence, CharSequence>> firstEntries = new ArrayList<>(firstEntriesToPreserve); List<Pair<CharSequence, CharSequence>> firstEntries = new ArrayList<>(firstEntriesToPreserve);
SortedMap<String, Pair<CharSequence, CharSequence>> lastEntries = new TreeMap<>();
// 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<Pair<String, Pair<CharSequence, CharSequence>>> lastEntries = new ArrayList<>();
for (int i = 0; i < entrySize; i++) { for (int i = 0; i < entrySize; i++) {
Pair<CharSequence, CharSequence> pair = new Pair<>(entries[i], entryValues[i]); Pair<CharSequence, CharSequence> pair = new Pair<>(entries[i], entryValues[i]);
if (i < firstEntriesToPreserve) { if (i < firstEntriesToPreserve) {
firstEntries.add(pair); firstEntries.add(pair);
} else { } else {
lastEntries.put(Utils.removePunctuationToLowercase(pair.first), pair); 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[] sortedEntries = new CharSequence[entrySize];
CharSequence[] sortedEntryValues = new CharSequence[entrySize]; CharSequence[] sortedEntryValues = new CharSequence[entrySize];
@@ -67,9 +74,10 @@ public class SortedListPreference extends ListPreference {
i++; i++;
} }
for (Pair<CharSequence, CharSequence> pair : lastEntries.values()) { for (Pair<String, Pair<CharSequence, CharSequence>> outer : lastEntries) {
sortedEntries[i] = pair.first; Pair<CharSequence, CharSequence> inner = outer.second;
sortedEntryValues[i] = pair.second; sortedEntries[i] = inner.first;
sortedEntryValues[i] = inner.second;
i++; i++;
} }

View File

@@ -2,7 +2,6 @@ package app.revanced.extension.tiktok;
import static app.revanced.extension.shared.Utils.isDarkModeEnabled; import static app.revanced.extension.shared.Utils.isDarkModeEnabled;
import android.content.Context;
import android.graphics.Color; import android.graphics.Color;
import android.view.View; import android.view.View;
import android.widget.TextView; import android.widget.TextView;
@@ -43,8 +42,8 @@ public class Utils {
private static final @ColorInt int TEXT_LIGHT_MODE_SUMMARY private static final @ColorInt int TEXT_LIGHT_MODE_SUMMARY
= Color.argb(255, 80, 80, 80); = Color.argb(255, 80, 80, 80);
public static void setTitleAndSummaryColor(Context context, View view) { public static void setTitleAndSummaryColor(View view) {
final boolean darkModeEnabled = isDarkModeEnabled(context); final boolean darkModeEnabled = isDarkModeEnabled();
TextView title = view.findViewById(android.R.id.title); TextView title = view.findViewById(android.R.id.title);
title.setTextColor(darkModeEnabled title.setTextColor(darkModeEnabled

View File

@@ -101,7 +101,7 @@ public class DownloadPathPreference extends DialogPreference {
protected void onBindView(View view) { protected void onBindView(View view) {
super.onBindView(view); super.onBindView(view);
Utils.setTitleAndSummaryColor(getContext(), view); Utils.setTitleAndSummaryColor(view);
} }
@Override @Override

View File

@@ -22,6 +22,6 @@ public class InputTextPreference extends EditTextPreference {
protected void onBindView(View view) { protected void onBindView(View view) {
super.onBindView(view); super.onBindView(view);
Utils.setTitleAndSummaryColor(getContext(), view); Utils.setTitleAndSummaryColor(view);
} }
} }

View File

@@ -127,7 +127,7 @@ public class RangeValuePreference extends DialogPreference {
protected void onBindView(View view) { protected void onBindView(View view) {
super.onBindView(view); super.onBindView(view);
Utils.setTitleAndSummaryColor(getContext(), view); Utils.setTitleAndSummaryColor(view);
} }
@Override @Override

View File

@@ -48,6 +48,6 @@ public class ReVancedTikTokAboutPreference extends ReVancedAboutPreference {
protected void onBindView(View view) { protected void onBindView(View view) {
super.onBindView(view); super.onBindView(view);
Utils.setTitleAndSummaryColor(getContext(), view); Utils.setTitleAndSummaryColor(view);
} }
} }

View File

@@ -22,6 +22,6 @@ public class TogglePreference extends SwitchPreference {
protected void onBindView(View view) { protected void onBindView(View view) {
super.onBindView(view); super.onBindView(view);
Utils.setTitleAndSummaryColor(getContext(), view); Utils.setTitleAndSummaryColor(view);
} }
} }

View File

@@ -16,9 +16,7 @@ public class SpoofSimPatch {
return false; return false;
} }
Logger.initializationException(SpoofSimPatch.class, Logger.initializationException(() -> "Context is not yet set, cannot spoof: " + fieldSpoofed, null);
"Context is not yet set, cannot spoof: " + fieldSpoofed, null);
return true; return true;
} }

View File

@@ -0,0 +1,35 @@
package app.revanced.extension.youtube.patches;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public class DisableHapticFeedbackPatch {
/**
* Injection point.
*/
public static boolean disableChapterVibrate() {
return Settings.DISABLE_HAPTIC_FEEDBACK_CHAPTERS.get();
}
/**
* Injection point.
*/
public static boolean disableSeekUndoVibrate() {
return Settings.DISABLE_HAPTIC_FEEDBACK_SEEK_UNDO.get();
}
/**
* Injection point.
*/
public static boolean disablePreciseSeekingVibrate() {
return Settings.DISABLE_HAPTIC_FEEDBACK_PRECISE_SEEKING.get();
}
/**
* Injection point.
*/
public static boolean disableZoomVibrate() {
return Settings.DISABLE_HAPTIC_FEEDBACK_ZOOM.get();
}
}

View File

@@ -95,7 +95,7 @@ public final class NavigationButtonsPatch {
return false; return false;
} }
return Utils.isDarkModeEnabled(Utils.getContext()) return Utils.isDarkModeEnabled()
? !DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK ? !DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK
: !DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT; : !DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT;
} }

View File

@@ -31,6 +31,8 @@ public class OpenShortsInRegularPlayerPatch {
private static WeakReference<Activity> mainActivityRef = new WeakReference<>(null); private static WeakReference<Activity> mainActivityRef = new WeakReference<>(null);
private static volatile boolean overrideBackPressToExit;
/** /**
* Injection point. * Injection point.
*/ */
@@ -38,6 +40,18 @@ public class OpenShortsInRegularPlayerPatch {
mainActivityRef = new WeakReference<>(activity); mainActivityRef = new WeakReference<>(activity);
} }
/**
* Injection point.
*/
public static boolean overrideBackPressToExit(boolean original) {
if (overrideBackPressToExit) {
Logger.printDebug(() -> "Overriding back press to exit activity");
return false;
}
return original;
}
/** /**
* Injection point. * Injection point.
*/ */
@@ -45,6 +59,7 @@ public class OpenShortsInRegularPlayerPatch {
try { try {
ShortsPlayerType type = Settings.SHORTS_PLAYER_TYPE.get(); ShortsPlayerType type = Settings.SHORTS_PLAYER_TYPE.get();
if (type == ShortsPlayerType.SHORTS_PLAYER) { if (type == ShortsPlayerType.SHORTS_PLAYER) {
overrideBackPressToExit = false;
return false; // Default unpatched behavior. return false; // Default unpatched behavior.
} }
@@ -61,13 +76,17 @@ public class OpenShortsInRegularPlayerPatch {
// set to open in the regular player, so it's ignored as // set to open in the regular player, so it's ignored as
// checking the map makes the patch more complicated. // checking the map makes the patch more complicated.
Logger.printDebug(() -> "Ignoring Short with no videoId"); Logger.printDebug(() -> "Ignoring Short with no videoId");
overrideBackPressToExit = false;
return false; return false;
} }
if (NavigationButton.getSelectedNavigationButton() == NavigationButton.SHORTS) { if (NavigationButton.getSelectedNavigationButton() == NavigationButton.SHORTS) {
overrideBackPressToExit = false;
return false; // Always use Shorts player for the Shorts nav button. return false; // Always use Shorts player for the Shorts nav button.
} }
overrideBackPressToExit = true;
final boolean forceFullScreen = (type == ShortsPlayerType.REGULAR_PLAYER_FULLSCREEN); final boolean forceFullScreen = (type == ShortsPlayerType.REGULAR_PLAYER_FULLSCREEN);
OpenVideosFullscreenHookPatch.setOpenNextVideoFullscreen(forceFullScreen); OpenVideosFullscreenHookPatch.setOpenNextVideoFullscreen(forceFullScreen);

View File

@@ -33,7 +33,7 @@ public class OpenVideosFullscreenHookPatch {
} }
if (!isFullScreenPatchIncluded()) { if (!isFullScreenPatchIncluded()) {
return false; return original;
} }
return Settings.OPEN_VIDEOS_FULLSCREEN_PORTRAIT.get(); return Settings.OPEN_VIDEOS_FULLSCREEN_PORTRAIT.get();

View File

@@ -1,7 +1,5 @@
package app.revanced.extension.youtube.patches; package app.revanced.extension.youtube.patches;
import android.content.res.Resources;
import android.util.TypedValue;
import android.view.View; import android.view.View;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
@@ -33,8 +31,7 @@ public final class WideSearchbarPatch {
final int paddingRight = searchBarView.getPaddingRight(); final int paddingRight = searchBarView.getPaddingRight();
final int paddingTop = searchBarView.getPaddingTop(); final int paddingTop = searchBarView.getPaddingTop();
final int paddingBottom = searchBarView.getPaddingBottom(); final int paddingBottom = searchBarView.getPaddingBottom();
final int paddingStart = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, final int paddingStart = Utils.dipToPixels(8);
8, Resources.getSystem().getDisplayMetrics());
if (Utils.isRightToLeftLocale()) { if (Utils.isRightToLeftLocale()) {
searchBarView.setPadding(paddingLeft, paddingTop, paddingStart, paddingBottom); searchBarView.setPadding(paddingLeft, paddingTop, paddingStart, paddingBottom);

View File

@@ -1,10 +0,0 @@
package app.revanced.extension.youtube.patches;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public class ZoomHapticsPatch {
public static boolean shouldVibrate() {
return !Settings.DISABLE_ZOOM_HAPTICS.get();
}
}

View File

@@ -64,48 +64,45 @@ public final class AdsFilter extends Filter {
"_interstitial" "_interstitial"
); );
final var buttonedAd = new StringFilterGroup(
Settings.HIDE_BUTTONED_ADS,
"_ad_with",
"_buttoned_layout",
// text_image_button_group_layout, landscape_image_button_group_layout, full_width_square_image_button_group_layout
"image_button_group_layout",
"full_width_square_image_layout",
"video_display_button_group_layout",
"landscape_image_wide_button_layout",
"video_display_carousel_button_group_layout",
"video_display_full_buttoned_short_dr_layout",
"compact_landscape_image_layout", // Tablet layout search results.
"text_image_no_button_layout" // Tablet layout search results.
);
final var generalAds = new StringFilterGroup( final var generalAds = new StringFilterGroup(
Settings.HIDE_GENERAL_ADS, Settings.HIDE_GENERAL_ADS,
"_ad_with",
"_buttoned_layout",
"ads_video_with_context", "ads_video_with_context",
"banner_text_icon", "banner_text_icon",
"square_image_layout", "brand_video_shelf",
"watch_metadata_app_promo", "brand_video_singleton",
"video_display_full_layout",
"hero_promo_image",
"statement_banner",
"carousel_footered_layout", "carousel_footered_layout",
"text_image_button_layout", "carousel_headered_layout",
"compact_landscape_image_layout", // Tablet layout search results.
"composite_concurrent_carousel_layout",
"full_width_portrait_image_layout",
"full_width_square_image_carousel_layout",
"full_width_square_image_layout",
"hero_promo_image",
// text_image_button_group_layout, landscape_image_button_group_layout, full_width_square_image_button_group_layout
"image_button_group_layout",
"landscape_image_wide_button_layout",
"primetime_promo", "primetime_promo",
"product_details", "product_details",
"composite_concurrent_carousel_layout", "square_image_layout",
"carousel_headered_layout", "statement_banner",
"full_width_portrait_image_layout", "text_image_button_layout",
"brand_video_shelf", "text_image_no_button_layout", // Tablet layout search results.
"brand_video_singleton" "video_display_button_group_layout",
"video_display_carousel_button_group_layout",
"video_display_full_buttoned_short_dr_layout",
"video_display_full_layout",
"watch_metadata_app_promo"
); );
final var movieAds = new StringFilterGroup( final var movieAds = new StringFilterGroup(
Settings.HIDE_MOVIES_SECTION, Settings.HIDE_MOVIES_SECTION,
"browsy_bar", "browsy_bar",
"compact_movie", "compact_movie",
"compact_tvfilm_item",
"horizontal_movie_shelf", "horizontal_movie_shelf",
"movie_and_show_upsell_card", "movie_and_show_upsell_card",
"compact_tvfilm_item",
"offer_module_root" "offer_module_root"
); );
@@ -160,7 +157,6 @@ public final class AdsFilter extends Filter {
addPathCallbacks( addPathCallbacks(
generalAds, generalAds,
buttonedAd,
merchandise, merchandise,
viewProducts, viewProducts,
selfSponsor, selfSponsor,
@@ -177,34 +173,30 @@ public final class AdsFilter extends Filter {
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (matchedGroup == playerShoppingShelf) { if (matchedGroup == playerShoppingShelf) {
if (contentIndex == 0 && playerShoppingShelfBuffer.check(protobufBufferArray).isFiltered()) { return contentIndex == 0 && playerShoppingShelfBuffer.check(protobufBufferArray).isFiltered();
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
} }
// Check for the index because of likelihood of false positives. // Check for the index because of likelihood of false positives.
if (matchedGroup == shoppingLinks && contentIndex != 0) { if (contentIndex != 0 && matchedGroup == shoppingLinks) {
return false; return false;
} }
if (exceptions.matches(path)) if (exceptions.matches(path)) {
return false; return false;
}
if (matchedGroup == fullscreenAd) { if (matchedGroup == fullscreenAd) {
if (path.contains("|ImageType|")) closeFullscreenAd(); if (path.contains("|ImageType|")) closeFullscreenAd();
return false; // Do not actually filter the fullscreen ad otherwise it will leave a dimmed screen. // Do not actually filter the fullscreen ad otherwise it will leave a dimmed screen.
}
if (matchedGroup == channelProfile) {
if (visitStoreButton.check(protobufBufferArray).isFiltered()) {
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false; return false;
} }
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex); if (matchedGroup == channelProfile) {
return visitStoreButton.check(protobufBufferArray).isFiltered();
}
return true;
} }
/** /**

View File

@@ -99,29 +99,23 @@ final class ButtonsFilter extends Filter {
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (matchedGroup == likeSubscribeGlow) { if (matchedGroup == likeSubscribeGlow) {
if ((path.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX) || path.startsWith(COMPACT_CHANNEL_BAR_PATH_PREFIX)) return (path.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX) || path.startsWith(COMPACT_CHANNEL_BAR_PATH_PREFIX))
&& path.contains(ANIMATED_VECTOR_TYPE_PATH)) { && path.contains(ANIMATED_VECTOR_TYPE_PATH);
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
} }
// If the current matched group is the action bar group, // If the current matched group is the action bar group,
// in case every filter group is enabled, hide the action bar. // in case every filter group is enabled, hide the action bar.
if (matchedGroup == actionBarGroup) { if (matchedGroup == actionBarGroup) {
if (!isEveryFilterGroupEnabled()) { return isEveryFilterGroupEnabled();
return false;
}
} else if (matchedGroup == bufferFilterPathGroup) {
// Make sure the current path is the right one
// to avoid false positives.
if (!path.startsWith(VIDEO_ACTION_BAR_PATH)) return false;
// In case the group list has no match, return false.
if (!bufferButtonsGroupList.check(protobufBufferArray).isFiltered()) return false;
} }
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex); if (matchedGroup == bufferFilterPathGroup) {
// Make sure the current path is the right one
// to avoid false positives.
return path.startsWith(VIDEO_ACTION_BAR_PATH)
&& bufferButtonsGroupList.check(protobufBufferArray).isFiltered();
}
return true;
} }
} }

View File

@@ -88,22 +88,15 @@ final class CommentsFilter extends Filter {
if (matchedGroup == commentComposer) { if (matchedGroup == commentComposer) {
// To completely hide the emoji buttons (and leave no empty space), the timestamp button is // To completely hide the emoji buttons (and leave no empty space), the timestamp button is
// also hidden because the buffer is exactly the same and there's no way selectively hide. // also hidden because the buffer is exactly the same and there's no way selectively hide.
if (contentIndex == 0 return contentIndex == 0
&& path.endsWith(TIMESTAMP_OR_EMOJI_BUTTONS_ENDS_WITH_PATH) && path.endsWith(TIMESTAMP_OR_EMOJI_BUTTONS_ENDS_WITH_PATH)
&& emojiPickerBufferGroup.check(protobufBufferArray).isFiltered()) { && emojiPickerBufferGroup.check(protobufBufferArray).isFiltered();
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
} }
if (matchedGroup == filterChipBar) { if (matchedGroup == filterChipBar) {
if (aiCommentsSummary.check(protobufBufferArray).isFiltered()) { return aiCommentsSummary.check(protobufBufferArray).isFiltered();
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
} }
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex); return true;
} }
} }

View File

@@ -153,9 +153,11 @@ final class CustomFilter extends Filter {
if (custom.startsWith && contentIndex != 0) { if (custom.startsWith && contentIndex != 0) {
return false; return false;
} }
if (custom.bufferSearch != null && !custom.bufferSearch.matches(protobufBufferArray)) {
return false; if (custom.bufferSearch == null) {
return true; // No buffer filter, only path filtering.
} }
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
return custom.bufferSearch.matches(protobufBufferArray);
} }
} }

View File

@@ -28,6 +28,11 @@ final class DescriptionComponentsFilter extends Filter {
"cell_expandable_metadata.eml" "cell_expandable_metadata.eml"
); );
final StringFilterGroup askSection = new StringFilterGroup(
Settings.HIDE_ASK_SECTION,
"youchat_entrypoint.eml"
);
final StringFilterGroup attributesSection = new StringFilterGroup( final StringFilterGroup attributesSection = new StringFilterGroup(
Settings.HIDE_ATTRIBUTES_SECTION, Settings.HIDE_ATTRIBUTES_SECTION,
"gaming_section", "gaming_section",
@@ -73,6 +78,7 @@ final class DescriptionComponentsFilter extends Filter {
addPathCallbacks( addPathCallbacks(
aiGeneratedVideoSummarySection, aiGeneratedVideoSummarySection,
askSection,
attributesSection, attributesSection,
infoCardsSection, infoCardsSection,
howThisWasMadeSection, howThisWasMadeSection,
@@ -88,13 +94,9 @@ final class DescriptionComponentsFilter extends Filter {
if (exceptions.matches(path)) return false; if (exceptions.matches(path)) return false;
if (matchedGroup == macroMarkersCarousel) { if (matchedGroup == macroMarkersCarousel) {
if (contentIndex == 0 && macroMarkersCarouselGroupList.check(protobufBufferArray).isFiltered()) { return contentIndex == 0 && macroMarkersCarouselGroupList.check(protobufBufferArray).isFiltered();
return super.isFiltered(path, identifier, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
} }
return super.isFiltered(path, identifier, protobufBufferArray, matchedGroup, contentType, contentIndex); return true;
} }
} }

View File

@@ -6,9 +6,6 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.settings.BaseSettings;
/** /**
* Filters litho based components. * Filters litho based components.
* *
@@ -62,10 +59,7 @@ abstract class Filter {
* Called after an enabled filter has been matched. * Called after an enabled filter has been matched.
* Default implementation is to always filter the matched component and log the action. * Default implementation is to always filter the matched component and log the action.
* Subclasses can perform additional or different checks if needed. * Subclasses can perform additional or different checks if needed.
* <p> *
* If the content is to be filtered, subclasses should always
* call this method (and never return a plain 'true').
* That way the logs will always show when a component was filtered and which filter hide it.
* <p> * <p>
* Method is called off the main thread. * Method is called off the main thread.
* *
@@ -76,14 +70,6 @@ abstract class Filter {
*/ */
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (BaseSettings.DEBUG.get()) {
String filterSimpleName = getClass().getSimpleName();
if (contentType == FilterContentType.IDENTIFIER) {
Logger.printDebug(() -> filterSimpleName + " Filtered identifier: " + identifier);
} else {
Logger.printDebug(() -> filterSimpleName + " Filtered path: " + path);
}
}
return true; return true;
} }
} }

View File

@@ -576,7 +576,7 @@ final class KeywordContentFilter extends Filter {
MutableReference<String> matchRef = new MutableReference<>(); MutableReference<String> matchRef = new MutableReference<>();
if (bufferSearch.matches(protobufBufferArray, matchRef)) { if (bufferSearch.matches(protobufBufferArray, matchRef)) {
updateStats(true, matchRef.value); updateStats(true, matchRef.value);
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex); return true;
} }
updateStats(false, null); updateStats(false, null);

View File

@@ -38,6 +38,7 @@ public final class LayoutComponentsFilter extends Filter {
private final StringFilterGroup compactChannelBarInnerButton; private final StringFilterGroup compactChannelBarInnerButton;
private final ByteArrayFilterGroup joinMembershipButton; private final ByteArrayFilterGroup joinMembershipButton;
private final StringFilterGroup horizontalShelves; private final StringFilterGroup horizontalShelves;
private final ByteArrayFilterGroup ticketShelf;
public LayoutComponentsFilter() { public LayoutComponentsFilter() {
exceptions.addPatterns( exceptions.addPatterns(
@@ -244,6 +245,11 @@ public final class LayoutComponentsFilter extends Filter {
"horizontal_tile_shelf.eml" "horizontal_tile_shelf.eml"
); );
ticketShelf = new ByteArrayFilterGroup(
Settings.HIDE_TICKET_SHELF,
"ticket"
);
addPathCallbacks( addPathCallbacks(
expandableMetadata, expandableMetadata,
inFeedSurvey, inFeedSurvey,
@@ -286,43 +292,29 @@ public final class LayoutComponentsFilter extends Filter {
// From 2025, the medical information panel is no longer shown in the search results. // From 2025, the medical information panel is no longer shown in the search results.
// Therefore, this identifier does not filter when the search bar is activated. // Therefore, this identifier does not filter when the search bar is activated.
if (matchedGroup == singleItemInformationPanel) { if (matchedGroup == singleItemInformationPanel) {
if (PlayerType.getCurrent().isMaximizedOrFullscreen() || !NavigationBar.isSearchBarActive()) { return PlayerType.getCurrent().isMaximizedOrFullscreen() || !NavigationBar.isSearchBarActive();
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
} }
// The groups are excluded from the filter due to the exceptions list below. // The groups are excluded from the filter due to the exceptions list below.
// Filter them separately here. // Filter them separately here.
if (matchedGroup == notifyMe || matchedGroup == inFeedSurvey || matchedGroup == expandableMetadata) if (matchedGroup == notifyMe || matchedGroup == inFeedSurvey || matchedGroup == expandableMetadata) {
{ return true;
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
} }
if (exceptions.matches(path)) return false; // Exceptions are not filtered. if (exceptions.matches(path)) return false; // Exceptions are not filtered.
if (matchedGroup == compactChannelBarInner) { if (matchedGroup == compactChannelBarInner) {
if (compactChannelBarInnerButton.check(path).isFiltered()) { return compactChannelBarInnerButton.check(path).isFiltered()
// The filter may be broad, but in the context of a compactChannelBarInnerButton, // The filter may be broad, but in the context of a compactChannelBarInnerButton,
// it's safe to assume that the button is the only thing that should be hidden. // it's safe to assume that the button is the only thing that should be hidden.
if (joinMembershipButton.check(protobufBufferArray).isFiltered()) { && joinMembershipButton.check(protobufBufferArray).isFiltered();
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
}
return false;
} }
if (matchedGroup == horizontalShelves) { if (matchedGroup == horizontalShelves) {
if (contentIndex == 0 && hideShelves()) { return contentIndex == 0 && (hideShelves() || ticketShelf.check(protobufBufferArray).isFiltered());
return super.isFiltered(path, identifier, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
} }
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex); return true;
} }
/** /**

View File

@@ -7,6 +7,7 @@ import java.nio.ByteBuffer;
import java.util.List; import java.util.List;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.youtube.StringTrieSearch; import app.revanced.extension.youtube.StringTrieSearch;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
@@ -114,12 +115,29 @@ public final class LithoFilterPatch {
if (!group.includeInSearch()) { if (!group.includeInSearch()) {
continue; continue;
} }
for (String pattern : group.filters) { for (String pattern : group.filters) {
pathSearchTree.addPattern(pattern, (textSearched, matchedStartIndex, matchedLength, callbackParameter) -> { String filterSimpleName = filter.getClass().getSimpleName();
pathSearchTree.addPattern(pattern, (textSearched, matchedStartIndex,
matchedLength, callbackParameter) -> {
if (!group.isEnabled()) return false; if (!group.isEnabled()) return false;
LithoFilterParameters parameters = (LithoFilterParameters) callbackParameter; LithoFilterParameters parameters = (LithoFilterParameters) callbackParameter;
return filter.isFiltered(parameters.identifier, parameters.path, parameters.protoBuffer, final boolean isFiltered = filter.isFiltered(parameters.identifier,
group, type, matchedStartIndex); parameters.path, parameters.protoBuffer, group, type, matchedStartIndex);
if (isFiltered && BaseSettings.DEBUG.get()) {
if (type == Filter.FilterContentType.IDENTIFIER) {
Logger.printDebug(() -> "Filtered " + filterSimpleName
+ " identifier: " + parameters.identifier);
} else {
Logger.printDebug(() -> "Filtered " + filterSimpleName
+ " path: " + parameters.path);
}
}
return isFiltered;
} }
); );
} }

View File

@@ -99,7 +99,7 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (matchedGroup == videoQualityMenuFooter) { if (matchedGroup == videoQualityMenuFooter) {
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex); return true;
} }
if (contentIndex != 0) { if (contentIndex != 0) {
@@ -111,11 +111,6 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
return false; return false;
} }
if (flyoutFilterGroupList.check(protobufBufferArray).isFiltered()) { return flyoutFilterGroupList.check(protobufBufferArray).isFiltered();
// Super class handles logging.
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
} }
} }

View File

@@ -278,27 +278,18 @@ public final class ShortsFilter extends Filter {
if (contentType == FilterContentType.PATH) { if (contentType == FilterContentType.PATH) {
if (matchedGroup == subscribeButton || matchedGroup == joinButton || matchedGroup == paidPromotionButton) { if (matchedGroup == subscribeButton || matchedGroup == joinButton || matchedGroup == paidPromotionButton) {
// Selectively filter to avoid false positive filtering of other subscribe/join buttons. // Selectively filter to avoid false positive filtering of other subscribe/join buttons.
if (path.startsWith(REEL_CHANNEL_BAR_PATH) || path.startsWith(REEL_METAPANEL_PATH)) { return path.startsWith(REEL_CHANNEL_BAR_PATH) || path.startsWith(REEL_METAPANEL_PATH);
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
} }
if (matchedGroup == shortsCompactFeedVideoPath) { if (matchedGroup == shortsCompactFeedVideoPath) {
if (shouldHideShortsFeedItems() && shortsCompactFeedVideoBuffer.check(protobufBufferArray).isFiltered()) { return shouldHideShortsFeedItems() && shortsCompactFeedVideoBuffer.check(protobufBufferArray).isFiltered();
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
} }
// Video action buttons (comment, share, remix) have the same path. // Video action buttons (comment, share, remix) have the same path.
// Like and dislike are separate path filters and don't require buffer searching. // Like and dislike are separate path filters and don't require buffer searching.
if (matchedGroup == shortsActionBar) { if (matchedGroup == shortsActionBar) {
if (actionButton.check(path).isFiltered() return actionButton.check(path).isFiltered()
&& videoActionButtonGroupList.check(protobufBufferArray).isFiltered()) { && videoActionButtonGroupList.check(protobufBufferArray).isFiltered();
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
} }
if (matchedGroup == suggestedAction) { if (matchedGroup == suggestedAction) {
@@ -306,28 +297,23 @@ public final class ShortsFilter extends Filter {
// This has a secondary effect of hiding all new un-identified actions // This has a secondary effect of hiding all new un-identified actions
// under the assumption that the user wants all suggestions hidden. // under the assumption that the user wants all suggestions hidden.
if (isEverySuggestedActionFilterEnabled()) { if (isEverySuggestedActionFilterEnabled()) {
return super.isFiltered(path, identifier, protobufBufferArray, matchedGroup, contentType, contentIndex); return true;
} }
if (suggestedActionsGroupList.check(protobufBufferArray).isFiltered()) { return suggestedActionsGroupList.check(protobufBufferArray).isFiltered();
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
} }
} else { return true;
// Feed/search identifier components.
if (matchedGroup == shelfHeader) {
// Because the header is used in watch history and possibly other places, check for the index,
// which is 0 when the shelf header is used for Shorts.
if (contentIndex != 0) return false;
}
if (!shouldHideShortsFeedItems()) return false;
} }
// Super class handles logging. // Feed/search identifier components.
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex); if (matchedGroup == shelfHeader) {
// Because the header is used in watch history and possibly other places, check for the index,
// which is 0 when the shelf header is used for Shorts.
if (contentIndex != 0) return false;
}
return shouldHideShortsFeedItems();
} }
private static boolean shouldHideShortsFeedItems() { private static boolean shouldHideShortsFeedItems() {

View File

@@ -20,13 +20,16 @@ import app.revanced.extension.youtube.settings.Settings;
public class ProgressBarDrawable extends Drawable { public class ProgressBarDrawable extends Drawable {
private final Paint paint = new Paint(); private final Paint paint = new Paint();
{
paint.setColor(SeekbarColorPatch.getSeekbarColor());
}
@Override @Override
public void draw(@NonNull Canvas canvas) { public void draw(@NonNull Canvas canvas) {
if (Settings.HIDE_SEEKBAR_THUMBNAIL.get()) { if (Settings.HIDE_SEEKBAR_THUMBNAIL.get()) {
return; return;
} }
paint.setColor(SeekbarColorPatch.getSeekbarColor());
canvas.drawRect(getBounds(), paint); canvas.drawRect(getBounds(), paint);
} }

View File

@@ -179,7 +179,7 @@ public final class SeekbarColorPatch {
//noinspection ConstantConditions //noinspection ConstantConditions
if (false) { // Set true to force slow animation for development. if (false) { // Set true to force slow animation for development.
final int longAnimation = Utils.getResourceIdentifier( final int longAnimation = Utils.getResourceIdentifier(
Utils.isDarkModeEnabled(Utils.getContext()) Utils.isDarkModeEnabled()
? "startup_animation_5s_30fps_dark" ? "startup_animation_5s_30fps_dark"
: "startup_animation_5s_30fps_light", : "startup_animation_5s_30fps_light",
"raw"); "raw");

View File

@@ -21,8 +21,6 @@ import android.text.Spanned;
import android.text.style.ForegroundColorSpan; import android.text.style.ForegroundColorSpan;
import android.text.style.ImageSpan; import android.text.style.ImageSpan;
import android.text.style.ReplacementSpan; import android.text.style.ReplacementSpan;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import androidx.annotation.GuardedBy; import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@@ -120,16 +118,13 @@ public class ReturnYouTubeDislike {
private static final ShapeDrawable leftSeparatorShape; private static final ShapeDrawable leftSeparatorShape;
static { static {
DisplayMetrics dp = Objects.requireNonNull(Utils.getContext()).getResources().getDisplayMetrics();
leftSeparatorBounds = new Rect(0, 0, leftSeparatorBounds = new Rect(0, 0,
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1.2f, dp), Utils.dipToPixels(1.2f),
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 14, dp)); Utils.dipToPixels(14f));
final int middleSeparatorSize = final int middleSeparatorSize = Utils.dipToPixels(3.7f);
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3.7f, dp);
middleSeparatorBounds = new Rect(0, 0, middleSeparatorSize, middleSeparatorSize); middleSeparatorBounds = new Rect(0, 0, middleSeparatorSize, middleSeparatorSize);
leftSeparatorShapePaddingPixels = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8.4f, dp); leftSeparatorShapePaddingPixels = Utils.dipToPixels(8.4f);
leftSeparatorShape = new ShapeDrawable(new RectShape()); leftSeparatorShape = new ShapeDrawable(new RectShape());
leftSeparatorShape.setBounds(leftSeparatorBounds); leftSeparatorShape.setBounds(leftSeparatorBounds);

View File

@@ -6,7 +6,6 @@ import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.preference.PreferenceFragment; import android.preference.PreferenceFragment;
import android.util.TypedValue;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toolbar; import android.widget.Toolbar;
@@ -119,8 +118,7 @@ public class LicenseActivityHook {
toolbar.setNavigationIcon(ReVancedPreferenceFragment.getBackButtonDrawable()); toolbar.setNavigationIcon(ReVancedPreferenceFragment.getBackButtonDrawable());
toolbar.setTitle(getResourceIdentifier("revanced_settings_title", "string")); toolbar.setTitle(getResourceIdentifier("revanced_settings_title", "string"));
final int margin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, final int margin = Utils.dipToPixels(16);
Utils.getContext().getResources().getDisplayMetrics());
toolbar.setTitleMarginStart(margin); toolbar.setTitleMarginStart(margin);
toolbar.setTitleMarginEnd(margin); toolbar.setTitleMarginEnd(margin);
TextView toolbarTextView = Utils.getChildView(toolbar, false, TextView toolbarTextView = Utils.getChildView(toolbar, false,

View File

@@ -68,7 +68,6 @@ public class Settings extends BaseSettings {
public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", FALSE, new ForceOriginalAudioAvailability()); public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", FALSE, new ForceOriginalAudioAvailability());
// Ads // Ads
public static final BooleanSetting HIDE_BUTTONED_ADS = new BooleanSetting("revanced_hide_buttoned_ads", TRUE);
public static final BooleanSetting HIDE_END_SCREEN_STORE_BANNER = new BooleanSetting("revanced_hide_end_screen_store_banner", TRUE, true); public static final BooleanSetting HIDE_END_SCREEN_STORE_BANNER = new BooleanSetting("revanced_hide_end_screen_store_banner", TRUE, true);
public static final BooleanSetting HIDE_FULLSCREEN_ADS = new BooleanSetting("revanced_hide_fullscreen_ads", TRUE); public static final BooleanSetting HIDE_FULLSCREEN_ADS = new BooleanSetting("revanced_hide_fullscreen_ads", TRUE);
public static final BooleanSetting HIDE_GENERAL_ADS = new BooleanSetting("revanced_hide_general_ads", TRUE); public static final BooleanSetting HIDE_GENERAL_ADS = new BooleanSetting("revanced_hide_general_ads", TRUE);
@@ -107,6 +106,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_PLAYABLES = new BooleanSetting("revanced_hide_playables", TRUE); public static final BooleanSetting HIDE_PLAYABLES = new BooleanSetting("revanced_hide_playables", TRUE);
public static final BooleanSetting HIDE_SEARCH_RESULT_RECOMMENDATION_LABELS = new BooleanSetting("revanced_hide_search_result_recommendation_labels", TRUE); public static final BooleanSetting HIDE_SEARCH_RESULT_RECOMMENDATION_LABELS = new BooleanSetting("revanced_hide_search_result_recommendation_labels", TRUE);
public static final BooleanSetting HIDE_SHOW_MORE_BUTTON = new BooleanSetting("revanced_hide_show_more_button", TRUE, true); public static final BooleanSetting HIDE_SHOW_MORE_BUTTON = new BooleanSetting("revanced_hide_show_more_button", TRUE, true);
public static final BooleanSetting HIDE_TICKET_SHELF = new BooleanSetting("revanced_hide_ticket_shelf", FALSE);
// Alternative thumbnails // Alternative thumbnails
public static final EnumSetting<ThumbnailOption> ALT_THUMBNAIL_HOME = new EnumSetting<>("revanced_alt_thumbnail_home", ThumbnailOption.ORIGINAL); public static final EnumSetting<ThumbnailOption> ALT_THUMBNAIL_HOME = new EnumSetting<>("revanced_alt_thumbnail_home", ThumbnailOption.ORIGINAL);
public static final EnumSetting<ThumbnailOption> ALT_THUMBNAIL_SUBSCRIPTIONS = new EnumSetting<>("revanced_alt_thumbnail_subscription", ThumbnailOption.ORIGINAL); public static final EnumSetting<ThumbnailOption> ALT_THUMBNAIL_SUBSCRIPTIONS = new EnumSetting<>("revanced_alt_thumbnail_subscription", ThumbnailOption.ORIGINAL);
@@ -185,6 +185,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_COMMENTS_THANKS_BUTTON = new BooleanSetting("revanced_hide_comments_thanks_button", TRUE); public static final BooleanSetting HIDE_COMMENTS_THANKS_BUTTON = new BooleanSetting("revanced_hide_comments_thanks_button", TRUE);
// Description // Description
public static final BooleanSetting HIDE_AI_GENERATED_VIDEO_SUMMARY_SECTION = new BooleanSetting("revanced_hide_ai_generated_video_summary_section", FALSE); public static final BooleanSetting HIDE_AI_GENERATED_VIDEO_SUMMARY_SECTION = new BooleanSetting("revanced_hide_ai_generated_video_summary_section", FALSE);
public static final BooleanSetting HIDE_ASK_SECTION = new BooleanSetting("revanced_hide_ask_section", FALSE);
public static final BooleanSetting HIDE_ATTRIBUTES_SECTION = new BooleanSetting("revanced_hide_attributes_section", FALSE); public static final BooleanSetting HIDE_ATTRIBUTES_SECTION = new BooleanSetting("revanced_hide_attributes_section", FALSE);
public static final BooleanSetting HIDE_CHAPTERS_SECTION = new BooleanSetting("revanced_hide_chapters_section", TRUE); public static final BooleanSetting HIDE_CHAPTERS_SECTION = new BooleanSetting("revanced_hide_chapters_section", TRUE);
public static final BooleanSetting HIDE_HOW_THIS_WAS_MADE_SECTION = new BooleanSetting("revanced_hide_how_this_was_made_section", FALSE); public static final BooleanSetting HIDE_HOW_THIS_WAS_MADE_SECTION = new BooleanSetting("revanced_hide_how_this_was_made_section", FALSE);
@@ -308,16 +309,16 @@ public class Settings extends BaseSettings {
public static final BooleanSetting AUTO_REPEAT = new BooleanSetting("revanced_auto_repeat", FALSE); public static final BooleanSetting AUTO_REPEAT = new BooleanSetting("revanced_auto_repeat", FALSE);
public static final BooleanSetting BYPASS_URL_REDIRECTS = new BooleanSetting("revanced_bypass_url_redirects", TRUE); public static final BooleanSetting BYPASS_URL_REDIRECTS = new BooleanSetting("revanced_bypass_url_redirects", TRUE);
public static final BooleanSetting CHECK_WATCH_HISTORY_DOMAIN_NAME = new BooleanSetting("revanced_check_watch_history_domain_name", TRUE, false, false); public static final BooleanSetting CHECK_WATCH_HISTORY_DOMAIN_NAME = new BooleanSetting("revanced_check_watch_history_domain_name", TRUE, false, false);
public static final BooleanSetting DISABLE_ZOOM_HAPTICS = new BooleanSetting("revanced_disable_zoom_haptics", TRUE); public static final BooleanSetting DISABLE_HAPTIC_FEEDBACK_CHAPTERS = new BooleanSetting("revanced_disable_haptic_feedback_chapters", FALSE);
public static final BooleanSetting DISABLE_HAPTIC_FEEDBACK_PRECISE_SEEKING = new BooleanSetting("revanced_disable_haptic_feedback_precise_seeking", FALSE);
public static final BooleanSetting DISABLE_HAPTIC_FEEDBACK_SEEK_UNDO = new BooleanSetting("revanced_disable_haptic_feedback_seek_undo", FALSE);
public static final BooleanSetting DISABLE_HAPTIC_FEEDBACK_ZOOM = new BooleanSetting("revanced_disable_haptic_feedback_zoom", FALSE);
public static final BooleanSetting EXTERNAL_BROWSER = new BooleanSetting("revanced_external_browser", TRUE, true); public static final BooleanSetting EXTERNAL_BROWSER = new BooleanSetting("revanced_external_browser", TRUE, true);
public static final BooleanSetting REMOVE_TRACKING_QUERY_PARAMETER = new BooleanSetting("revanced_remove_tracking_query_parameter", TRUE); public static final BooleanSetting REMOVE_TRACKING_QUERY_PARAMETER = new BooleanSetting("revanced_remove_tracking_query_parameter", TRUE);
public static final BooleanSetting SPOOF_DEVICE_DIMENSIONS = new BooleanSetting("revanced_spoof_device_dimensions", FALSE, true, public static final BooleanSetting SPOOF_DEVICE_DIMENSIONS = new BooleanSetting("revanced_spoof_device_dimensions", FALSE, true,
"revanced_spoof_device_dimensions_user_dialog_message"); "revanced_spoof_device_dimensions_user_dialog_message");
/** public static final BooleanSetting DEBUG_PROTOBUFFER = new BooleanSetting("revanced_debug_protobuffer", FALSE, false,
* When enabled, share the debug logs with care. "revanced_debug_protobuffer_user_dialog_message", parent(BaseSettings.DEBUG));
* The buffer contains select user data, including the client ip address and information that could identify the end user.
*/
public static final BooleanSetting DEBUG_PROTOBUFFER = new BooleanSetting("revanced_debug_protobuffer", FALSE, parent(BaseSettings.DEBUG));
// Swipe controls // Swipe controls
public static final BooleanSetting SWIPE_CHANGE_VIDEO = new BooleanSetting("revanced_swipe_change_video", FALSE, true); public static final BooleanSetting SWIPE_CHANGE_VIDEO = new BooleanSetting("revanced_swipe_change_video", FALSE, true);
@@ -336,13 +337,17 @@ public class Settings extends BaseSettings {
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME)); parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
public static final IntegerSetting SWIPE_OVERLAY_OPACITY = new IntegerSetting("revanced_swipe_overlay_background_opacity", 60, true, public static final IntegerSetting SWIPE_OVERLAY_OPACITY = new IntegerSetting("revanced_swipe_overlay_background_opacity", 60, true,
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME)); parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
public static final StringSetting SWIPE_OVERLAY_PROGRESS_COLOR = new StringSetting("revanced_swipe_overlay_progress_color", "#FFFFFF", true, public static final StringSetting SWIPE_OVERLAY_BRIGHTNESS_COLOR = new StringSetting("revanced_swipe_overlay_progress_brightness_color", "#FFFFFF", true,
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME)); parent(SWIPE_BRIGHTNESS));
public static final StringSetting SWIPE_OVERLAY_VOLUME_COLOR = new StringSetting("revanced_swipe_overlay_progress_volume_color", "#FFFFFF", true,
parent(SWIPE_VOLUME));
public static final LongSetting SWIPE_OVERLAY_TIMEOUT = new LongSetting("revanced_swipe_overlay_timeout", 500L, true, public static final LongSetting SWIPE_OVERLAY_TIMEOUT = new LongSetting("revanced_swipe_overlay_timeout", 500L, true,
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME)); parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
public static final BooleanSetting SWIPE_SAVE_AND_RESTORE_BRIGHTNESS = new BooleanSetting("revanced_swipe_save_and_restore_brightness", TRUE, true, parent(SWIPE_BRIGHTNESS)); public static final BooleanSetting SWIPE_SAVE_AND_RESTORE_BRIGHTNESS = new BooleanSetting("revanced_swipe_save_and_restore_brightness", TRUE, true,
parent(SWIPE_BRIGHTNESS));
public static final FloatSetting SWIPE_BRIGHTNESS_VALUE = new FloatSetting("revanced_swipe_brightness_value", -1f); public static final FloatSetting SWIPE_BRIGHTNESS_VALUE = new FloatSetting("revanced_swipe_brightness_value", -1f);
public static final BooleanSetting SWIPE_LOWEST_VALUE_ENABLE_AUTO_BRIGHTNESS = new BooleanSetting("revanced_swipe_lowest_value_enable_auto_brightness", FALSE, true, parent(SWIPE_BRIGHTNESS)); public static final BooleanSetting SWIPE_LOWEST_VALUE_ENABLE_AUTO_BRIGHTNESS = new BooleanSetting("revanced_swipe_lowest_value_enable_auto_brightness", FALSE, true,
parent(SWIPE_BRIGHTNESS));
// ReturnYoutubeDislike // ReturnYoutubeDislike
public static final BooleanSetting RYD_ENABLED = new BooleanSetting("revanced_ryd_enabled", TRUE); public static final BooleanSetting RYD_ENABLED = new BooleanSetting("revanced_ryd_enabled", TRUE);

View File

@@ -0,0 +1,34 @@
package app.revanced.extension.youtube.settings.preference;
import android.content.Context;
import android.util.AttributeSet;
import android.preference.Preference;
import app.revanced.extension.shared.settings.preference.LogBufferManager;
/**
* 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);
}
}

View File

@@ -0,0 +1,34 @@
package app.revanced.extension.youtube.settings.preference;
import android.content.Context;
import android.util.AttributeSet;
import android.preference.Preference;
import app.revanced.extension.shared.settings.preference.LogBufferManager;
/**
* 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);
}
}

View File

@@ -17,7 +17,6 @@ import android.preference.SwitchPreference;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.style.BackgroundColorSpan; import android.text.style.BackgroundColorSpan;
import android.util.TypedValue;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.WindowInsets; import android.view.WindowInsets;
import android.widget.TextView; import android.widget.TextView;
@@ -245,9 +244,7 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
toolbar.setNavigationIcon(getBackButtonDrawable()); toolbar.setNavigationIcon(getBackButtonDrawable());
toolbar.setNavigationOnClickListener(view -> preferenceScreenDialog.dismiss()); toolbar.setNavigationOnClickListener(view -> preferenceScreenDialog.dismiss());
final int margin = (int) TypedValue.applyDimension( final int margin = Utils.dipToPixels(16);
TypedValue.COMPLEX_UNIT_DIP, 16, getResources().getDisplayMetrics()
);
toolbar.setTitleMargin(margin, 0, margin, 0); toolbar.setTitleMargin(margin, 0, margin, 0);
TextView toolbarTextView = Utils.getChildView(toolbar, TextView toolbarTextView = Utils.getChildView(toolbar,
@@ -420,9 +417,11 @@ class PreferenceSearchData extends AbstractPreferenceSearchData<Preference> {
@CallSuper @CallSuper
void clearHighlighting() { void clearHighlighting() {
super.clearHighlighting(); if (highlightingApplied) {
preference.setSummary(originalSummary);
}
preference.setSummary(originalSummary); super.clearHighlighting();
} }
} }
@@ -472,10 +471,12 @@ class SwitchPreferenceSearchData extends AbstractPreferenceSearchData<SwitchPref
@CallSuper @CallSuper
void clearHighlighting() { void clearHighlighting() {
super.clearHighlighting(); if (highlightingApplied) {
preference.setSummaryOn(originalSummaryOn);
preference.setSummaryOff(originalSummaryOff);
}
preference.setSummaryOn(originalSummaryOn); super.clearHighlighting();
preference.setSummaryOff(originalSummaryOff);
} }
} }
@@ -529,8 +530,10 @@ class ListPreferenceSearchData extends AbstractPreferenceSearchData<ListPreferen
@CallSuper @CallSuper
void clearHighlighting() { void clearHighlighting() {
super.clearHighlighting(); if (highlightingApplied) {
preference.setEntries(originalEntries);
}
preference.setEntries(originalEntries); super.clearHighlighting();
} }
} }

View File

@@ -5,7 +5,6 @@ import static app.revanced.extension.shared.StringRef.str;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Rect; import android.graphics.Rect;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.TypedValue;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -727,15 +726,11 @@ public class SegmentPlaybackController {
} }
} }
private static int highlightSegmentTimeBarScreenWidth = -1; // actual pixel width to use /**
private static int getHighlightSegmentTimeBarScreenWidth() { * Actual screen pixel width to use for the highlight segment time bar.
if (highlightSegmentTimeBarScreenWidth == -1) { */
highlightSegmentTimeBarScreenWidth = (int) TypedValue.applyDimension( private static final int highlightSegmentTimeBarScreenWidth
TypedValue.COMPLEX_UNIT_DIP, HIGHLIGHT_SEGMENT_DRAW_BAR_WIDTH, = Utils.dipToPixels(HIGHLIGHT_SEGMENT_DRAW_BAR_WIDTH);
Objects.requireNonNull(Utils.getContext()).getResources().getDisplayMetrics());
}
return highlightSegmentTimeBarScreenWidth;
}
/** /**
* Injection point. * Injection point.
@@ -757,7 +752,7 @@ public class SegmentPlaybackController {
final float left = leftPadding + segment.start * videoMillisecondsToPixels; final float left = leftPadding + segment.start * videoMillisecondsToPixels;
final float right; final float right;
if (segment.category == SegmentCategory.HIGHLIGHT) { if (segment.category == SegmentCategory.HIGHLIGHT) {
right = left + getHighlightSegmentTimeBarScreenWidth(); right = left + highlightSegmentTimeBarScreenWidth;
} else { } else {
right = leftPadding + segment.end * videoMillisecondsToPixels; right = leftPadding + segment.end * videoMillisecondsToPixels;
} }

View File

@@ -1,6 +1,7 @@
package app.revanced.extension.youtube.sponsorblock.objects; package app.revanced.extension.youtube.sponsorblock.objects;
import static app.revanced.extension.shared.StringRef.sf; import static app.revanced.extension.shared.StringRef.sf;
import static app.revanced.extension.shared.settings.preference.ColorPickerPreference.COLOR_DOT_STRING;
import static app.revanced.extension.youtube.settings.Settings.*; import static app.revanced.extension.youtube.settings.Settings.*;
import android.graphics.Color; import android.graphics.Color;
@@ -9,7 +10,9 @@ import android.text.Spannable;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.style.ForegroundColorSpan; import android.text.style.ForegroundColorSpan;
import android.text.style.RelativeSizeSpan;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -134,7 +137,8 @@ public enum SegmentCategory {
updateEnabledCategories(); updateEnabledCategories();
} }
public static int applyOpacityToColor(int color, float opacity) { @ColorInt
public static int applyOpacityToColor(@ColorInt int color, float opacity) {
if (opacity < 0 || opacity > 1.0f) { if (opacity < 0 || opacity > 1.0f) {
throw new IllegalArgumentException("Invalid opacity: " + opacity); throw new IllegalArgumentException("Invalid opacity: " + opacity);
} }
@@ -165,29 +169,28 @@ public enum SegmentCategory {
/** /**
* Skipped segment toast, if the skip occurred in the first quarter of the video * Skipped segment toast, if the skip occurred in the first quarter of the video
*/ */
@NonNull
public final StringRef skippedToastBeginning; public final StringRef skippedToastBeginning;
/** /**
* Skipped segment toast, if the skip occurred in the middle half of the video * Skipped segment toast, if the skip occurred in the middle half of the video
*/ */
@NonNull
public final StringRef skippedToastMiddle; public final StringRef skippedToastMiddle;
/** /**
* Skipped segment toast, if the skip occurred in the last quarter of the video * Skipped segment toast, if the skip occurred in the last quarter of the video
*/ */
@NonNull
public final StringRef skippedToastEnd; public final StringRef skippedToastEnd;
@NonNull
public final Paint paint; public final Paint paint;
/**
* Category color with opacity applied.
*/
@ColorInt
private int color; private int color;
/** /**
* Value must be changed using {@link #setBehaviour(CategoryBehaviour)}. * Value must be changed using {@link #setBehaviour(CategoryBehaviour)}.
* Caller must also {@link #updateEnabledCategories()}. * Caller must also {@link #updateEnabledCategories()}.
*/ */
@NonNull
public CategoryBehaviour behaviour = CategoryBehaviour.IGNORE; public CategoryBehaviour behaviour = CategoryBehaviour.IGNORE;
SegmentCategory(String keyValue, StringRef title, StringRef description, SegmentCategory(String keyValue, StringRef title, StringRef description,
@@ -247,7 +250,7 @@ public enum SegmentCategory {
} }
} }
public void setBehaviour(@NonNull CategoryBehaviour behaviour) { public void setBehaviour(CategoryBehaviour behaviour) {
this.behaviour = Objects.requireNonNull(behaviour); this.behaviour = Objects.requireNonNull(behaviour);
this.behaviorSetting.save(behaviour.reVancedKeyValue); this.behaviorSetting.save(behaviour.reVancedKeyValue);
} }
@@ -273,6 +276,10 @@ public enum SegmentCategory {
return opacitySetting.get(); return opacitySetting.get();
} }
public float getOpacityDefault() {
return opacitySetting.defaultValue;
}
public void resetColorAndOpacity() { public void resetColorAndOpacity() {
setColor(colorSetting.defaultValue); setColor(colorSetting.defaultValue);
setOpacity(opacitySetting.defaultValue); setOpacity(opacitySetting.defaultValue);
@@ -291,10 +298,19 @@ public enum SegmentCategory {
/** /**
* @return Integer color of #RRGGBB format. * @return Integer color of #RRGGBB format.
*/ */
@ColorInt
public int getColorNoOpacity() { public int getColorNoOpacity() {
return color & 0x00FFFFFF; return color & 0x00FFFFFF;
} }
/**
* @return Integer color of #RRGGBB format.
*/
@ColorInt
public int getColorNoOpacityDefault() {
return Color.parseColor(colorSetting.defaultValue) & 0x00FFFFFF;
}
/** /**
* @return Hex color string of #RRGGBB format with no opacity level. * @return Hex color string of #RRGGBB format with no opacity level.
*/ */
@@ -302,22 +318,27 @@ public enum SegmentCategory {
return String.format(Locale.US, "#%06X", getColorNoOpacity()); return String.format(Locale.US, "#%06X", getColorNoOpacity());
} }
private static SpannableString getCategoryColorDotSpan(String text, int color) { private static SpannableString getCategoryColorDotSpan(String text, @ColorInt int color) {
SpannableString dotSpan = new SpannableString('⬤' + text); SpannableString dotSpan = new SpannableString(COLOR_DOT_STRING + text);
dotSpan.setSpan(new ForegroundColorSpan(color), 0, 1, dotSpan.setSpan(new ForegroundColorSpan(color), 0, 1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return dotSpan; return dotSpan;
} }
public static SpannableString getCategoryColorDot(int color) { public static SpannableString getCategoryColorDot(@ColorInt int color) {
return getCategoryColorDotSpan("", color); SpannableString dotSpan = new SpannableString(COLOR_DOT_STRING);
dotSpan.setSpan(new ForegroundColorSpan(color), 0, 1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
dotSpan.setSpan(new RelativeSizeSpan(1.5f), 0, 1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return dotSpan;
} }
public SpannableString getCategoryColorDot() { public SpannableString getCategoryColorDot() {
return getCategoryColorDot(color); return getCategoryColorDot(color);
} }
public SpannableString getTitleWithColorDot(int categoryColor) { public SpannableString getTitleWithColorDot(@ColorInt int categoryColor) {
return getCategoryColorDotSpan(" " + title, categoryColor); return getCategoryColorDotSpan(" " + title, categoryColor);
} }

View File

@@ -1,35 +1,46 @@
package app.revanced.extension.youtube.sponsorblock.objects; package app.revanced.extension.youtube.sponsorblock.objects;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
import static app.revanced.extension.shared.settings.preference.ColorPickerPreference.getColorString;
import static app.revanced.extension.youtube.sponsorblock.objects.SegmentCategory.applyOpacityToColor; import static app.revanced.extension.youtube.sponsorblock.objects.SegmentCategory.applyOpacityToColor;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Bundle;
import android.preference.ListPreference; import android.preference.ListPreference;
import android.text.Editable; import android.text.Editable;
import android.text.InputType; import android.text.InputType;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.GridLayout; import android.widget.GridLayout;
import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.ColorInt;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.preference.ColorPickerPreference;
import app.revanced.extension.shared.settings.preference.ColorPickerView;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public class SegmentCategoryListPreference extends ListPreference { public class SegmentCategoryListPreference extends ListPreference {
private final SegmentCategory category; private final SegmentCategory category;
private TextView colorDotView;
private EditText colorEditText;
private EditText opacityEditText;
/** /**
* #RRGGBB * RGB format (no alpha).
*/ */
@ColorInt
private int categoryColor; private int categoryColor;
/** /**
* [0, 1] * [0, 1]
@@ -37,6 +48,11 @@ public class SegmentCategoryListPreference extends ListPreference {
private float categoryOpacity; private float categoryOpacity;
private int selectedDialogEntryIndex; private int selectedDialogEntryIndex;
private TextView dialogColorDotView;
private EditText dialogColorEditText;
private EditText dialogOpacityEditText;
private ColorPickerView dialogColorPickerView;
public SegmentCategoryListPreference(Context context, SegmentCategory category) { public SegmentCategoryListPreference(Context context, SegmentCategory category) {
super(context); super(context);
this.category = Objects.requireNonNull(category); this.category = Objects.requireNonNull(category);
@@ -53,7 +69,7 @@ public class SegmentCategoryListPreference extends ListPreference {
setEntryValues(isHighlightCategory setEntryValues(isHighlightCategory
? CategoryBehaviour.getBehaviorKeyValuesWithoutSkipOnce() ? CategoryBehaviour.getBehaviorKeyValuesWithoutSkipOnce()
: CategoryBehaviour.getBehaviorKeyValues()); : CategoryBehaviour.getBehaviorKeyValues());
setSummary(category.description.toString()); super.setSummary(category.description.toString());
updateUI(); updateUI();
} }
@@ -67,8 +83,20 @@ public class SegmentCategoryListPreference extends ListPreference {
categoryOpacity = category.getOpacity(); categoryOpacity = category.getOpacity();
Context context = builder.getContext(); Context context = builder.getContext();
LinearLayout mainLayout = new LinearLayout(context);
mainLayout.setOrientation(LinearLayout.VERTICAL);
mainLayout.setPadding(70, 0, 70, 0);
// Inflate the color picker view.
View colorPickerContainer = LayoutInflater.from(context)
.inflate(getResourceIdentifier("revanced_color_picker", "layout"), null);
dialogColorPickerView = colorPickerContainer.findViewById(
getResourceIdentifier("color_picker_view", "id"));
dialogColorPickerView.setColor(categoryColor);
mainLayout.addView(colorPickerContainer);
// Grid layout for color and opacity inputs.
GridLayout gridLayout = new GridLayout(context); GridLayout gridLayout = new GridLayout(context);
gridLayout.setPadding(70, 0, 150, 0); // Padding for the entire layout.
gridLayout.setColumnCount(3); gridLayout.setColumnCount(3);
gridLayout.setRowCount(2); gridLayout.setRowCount(2);
@@ -84,19 +112,22 @@ public class SegmentCategoryListPreference extends ListPreference {
gridParams.rowSpec = GridLayout.spec(0); // First row. gridParams.rowSpec = GridLayout.spec(0); // First row.
gridParams.columnSpec = GridLayout.spec(1); // Second column. gridParams.columnSpec = GridLayout.spec(1); // Second column.
gridParams.setMargins(0, 0, 10, 0); gridParams.setMargins(0, 0, 10, 0);
colorDotView = new TextView(context); dialogColorDotView = new TextView(context);
colorDotView.setLayoutParams(gridParams); dialogColorDotView.setLayoutParams(gridParams);
gridLayout.addView(colorDotView); gridLayout.addView(dialogColorDotView);
updateCategoryColorDot(); updateCategoryColorDot();
gridParams = new GridLayout.LayoutParams(); gridParams = new GridLayout.LayoutParams();
gridParams.rowSpec = GridLayout.spec(0); // First row. gridParams.rowSpec = GridLayout.spec(0); // First row.
gridParams.columnSpec = GridLayout.spec(2); // Third column. gridParams.columnSpec = GridLayout.spec(2); // Third column.
colorEditText = new EditText(context); dialogColorEditText = new EditText(context);
colorEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS); dialogColorEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS
colorEditText.setTextLocale(Locale.US); | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
colorEditText.setText(category.getColorString()); dialogColorEditText.setAutofillHints((String) null);
colorEditText.addTextChangedListener(new TextWatcher() { dialogColorEditText.setTypeface(Typeface.MONOSPACE);
dialogColorEditText.setTextLocale(Locale.US);
dialogColorEditText.setText(getColorString(categoryColor));
dialogColorEditText.addTextChangedListener(new TextWatcher() {
@Override @Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) { public void beforeTextChanged(CharSequence s, int start, int count, int after) {
} }
@@ -109,28 +140,30 @@ public class SegmentCategoryListPreference extends ListPreference {
public void afterTextChanged(Editable edit) { public void afterTextChanged(Editable edit) {
try { try {
String colorString = edit.toString(); String colorString = edit.toString();
final int colorStringLength = colorString.length(); String normalizedColorString = ColorPickerPreference.cleanupColorCodeString(colorString);
if (!colorString.startsWith("#")) { if (!normalizedColorString.equals(colorString)) {
edit.insert(0, "#"); // Recursively calls back into this method. edit.replace(0, colorString.length(), normalizedColorString);
return; return;
} }
final int maxColorStringLength = 7; // #RRGGBB if (normalizedColorString.length() != ColorPickerPreference.COLOR_STRING_LENGTH) {
if (colorStringLength > maxColorStringLength) { // User is still typing out the color.
edit.delete(maxColorStringLength, colorStringLength);
return; return;
} }
categoryColor = Color.parseColor(colorString); // Remove the alpha channel.
updateCategoryColorDot(); final int newColor = Color.parseColor(colorString) & 0x00FFFFFF;
} catch (IllegalArgumentException ex) { // Changing view color causes callback into this class.
// Ignore. dialogColorPickerView.setColor(newColor);
} catch (Exception ex) {
// Should never be reached since input is validated before using.
Logger.printException(() -> "colorEditText afterTextChanged failure", ex);
} }
} }
}); });
colorEditText.setLayoutParams(gridParams); dialogColorEditText.setLayoutParams(gridParams);
gridLayout.addView(colorEditText); gridLayout.addView(dialogColorEditText);
gridParams = new GridLayout.LayoutParams(); gridParams = new GridLayout.LayoutParams();
gridParams.rowSpec = GridLayout.spec(1); // Second row. gridParams.rowSpec = GridLayout.spec(1); // Second row.
@@ -143,9 +176,13 @@ public class SegmentCategoryListPreference extends ListPreference {
gridParams = new GridLayout.LayoutParams(); gridParams = new GridLayout.LayoutParams();
gridParams.rowSpec = GridLayout.spec(1); // Second row. gridParams.rowSpec = GridLayout.spec(1); // Second row.
gridParams.columnSpec = GridLayout.spec(2); // Third column. gridParams.columnSpec = GridLayout.spec(2); // Third column.
opacityEditText = new EditText(context); dialogOpacityEditText = new EditText(context);
opacityEditText.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL); dialogOpacityEditText.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL
opacityEditText.addTextChangedListener(new TextWatcher() { | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
dialogOpacityEditText.setAutofillHints((String) null);
dialogOpacityEditText.setTypeface(Typeface.MONOSPACE);
dialogOpacityEditText.setTextLocale(Locale.US);
dialogOpacityEditText.addTextChangedListener(new TextWatcher() {
@Override @Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) { public void beforeTextChanged(CharSequence s, int start, int count, int after) {
} }
@@ -183,31 +220,40 @@ public class SegmentCategoryListPreference extends ListPreference {
} }
updateCategoryColorDot(); updateCategoryColorDot();
} catch (NumberFormatException ex) { } catch (Exception ex) {
// Should never happen. // Should never happen.
Logger.printException(() -> "Could not parse opacity string", ex); Logger.printException(() -> "opacityEditText afterTextChanged failure", ex);
} }
} }
}); });
opacityEditText.setLayoutParams(gridParams); dialogOpacityEditText.setLayoutParams(gridParams);
gridLayout.addView(opacityEditText); gridLayout.addView(dialogOpacityEditText);
updateOpacityText(); updateOpacityText();
builder.setView(gridLayout); mainLayout.addView(gridLayout);
// Set up color picker listener.
// Do last to prevent listener callbacks while setting up view.
dialogColorPickerView.setOnColorChangedListener(color -> {
if (categoryColor == color) {
return;
}
categoryColor = color;
String hexColor = getColorString(color);
Logger.printDebug(() -> "onColorChanged: " + hexColor);
updateCategoryColorDot();
dialogColorEditText.setText(hexColor);
dialogColorEditText.setSelection(hexColor.length());
});
builder.setView(mainLayout);
builder.setTitle(category.title.toString()); builder.setTitle(category.title.toString());
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> { builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
onClick(dialog, DialogInterface.BUTTON_POSITIVE); onClick(dialog, DialogInterface.BUTTON_POSITIVE);
}); });
builder.setNeutralButton(str("revanced_sb_reset_color"), (dialog, which) -> { builder.setNeutralButton(str("revanced_settings_reset_color"), null);
try {
category.resetColorAndOpacity();
updateUI();
Utils.showToastShort(str("revanced_sb_color_reset"));
} catch (Exception ex) {
Logger.printException(() -> "setNeutralButton failure", ex);
}
});
builder.setNegativeButton(android.R.string.cancel, null); builder.setNegativeButton(android.R.string.cancel, null);
selectedDialogEntryIndex = findIndexOfValue(getValue()); selectedDialogEntryIndex = findIndexOfValue(getValue());
@@ -218,6 +264,25 @@ public class SegmentCategoryListPreference extends ListPreference {
} }
} }
@Override
protected void showDialog(Bundle state) {
super.showDialog(state);
// Do not close dialog when reset is pressed.
Button button = ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_NEUTRAL);
button.setOnClickListener(view -> {
try {
// Setting view color causes callback to update the UI.
dialogColorPickerView.setColor(category.getColorNoOpacityDefault());
categoryOpacity = category.getOpacityDefault();
updateOpacityText();
} catch (Exception ex) {
Logger.printException(() -> "setOnClickListener failure", ex);
}
});
}
@Override @Override
protected void onDialogClosed(boolean positiveResult) { protected void onDialogClosed(boolean positiveResult) {
try { try {
@@ -230,42 +295,50 @@ public class SegmentCategoryListPreference extends ListPreference {
} }
try { try {
String colorString = colorEditText.getText().toString(); category.setColor(dialogColorEditText.getText().toString());
if (!colorString.equals(category.getColorString()) || categoryOpacity != category.getOpacity()) { category.setOpacity(categoryOpacity);
category.setColor(colorString);
category.setOpacity(categoryOpacity);
Utils.showToastShort(str("revanced_sb_color_changed"));
}
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
Utils.showToastShort(str("revanced_sb_color_invalid")); Utils.showToastShort(str("revanced_settings_color_invalid"));
} }
updateUI(); updateUI();
} }
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "onDialogClosed failure", ex); Logger.printException(() -> "onDialogClosed failure", ex);
} finally {
dialogColorDotView = null;
dialogColorEditText = null;
dialogOpacityEditText = null;
dialogColorPickerView = null;
} }
} }
private void applyOpacityToCategoryColor() { @ColorInt
categoryColor = applyOpacityToColor(categoryColor, categoryOpacity); private int applyOpacityToCategoryColor() {
return applyOpacityToColor(categoryColor, categoryOpacity);
} }
public void updateUI() { public void updateUI() {
categoryColor = category.getColorNoOpacity(); categoryColor = category.getColorNoOpacity();
categoryOpacity = category.getOpacity(); categoryOpacity = category.getOpacity();
applyOpacityToCategoryColor();
setTitle(category.getTitleWithColorDot(categoryColor)); setTitle(category.getTitleWithColorDot(applyOpacityToCategoryColor()));
} }
private void updateCategoryColorDot() { private void updateCategoryColorDot() {
applyOpacityToCategoryColor(); dialogColorDotView.setText(SegmentCategory.getCategoryColorDot(applyOpacityToCategoryColor()));
colorDotView.setText(SegmentCategory.getCategoryColorDot(categoryColor));
} }
private void updateOpacityText() { private void updateOpacityText() {
opacityEditText.setText(String.format(Locale.US, "%.2f", categoryOpacity)); dialogOpacityEditText.setText(String.format(Locale.US, "%.2f", categoryOpacity));
} }
}
@Override
public void setSummary(CharSequence summary) {
// Ignore calls to set the summary.
// Summary is always the description of the category.
//
// This is required otherwise the ReVanced preference fragment
// sets all ListPreference summaries to show the current selection.
}
}

View File

@@ -376,7 +376,11 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
Utils.setEditTextDialogTheme(builder); Utils.setEditTextDialogTheme(builder);
builder.setNeutralButton(str("revanced_sb_settings_copy"), (dialog, which) -> { builder.setNeutralButton(str("revanced_sb_settings_copy"), (dialog, which) -> {
Utils.setClipboard(getEditText().getText().toString()); try {
Utils.setClipboard(getEditText().getText());
} catch (Exception ex) {
Logger.printException(() -> "Copy settings failure", ex);
}
}); });
} }
}; };
@@ -421,7 +425,7 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
.setTitle(apiUrl.getTitle()) .setTitle(apiUrl.getTitle())
.setView(editText) .setView(editText)
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
.setNeutralButton(str("revanced_sb_reset"), urlChangeListener) .setNeutralButton(str("revanced_settings_reset"), urlChangeListener)
.setPositiveButton(android.R.string.ok, urlChangeListener) .setPositiveButton(android.R.string.ok, urlChangeListener)
.show(); .show();
return true; return true;
@@ -433,7 +437,11 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
Utils.setEditTextDialogTheme(builder); Utils.setEditTextDialogTheme(builder);
builder.setNeutralButton(str("revanced_sb_settings_copy"), (dialog, which) -> { builder.setNeutralButton(str("revanced_sb_settings_copy"), (dialog, which) -> {
Utils.setClipboard(getEditText().getText().toString()); try {
Utils.setClipboard(getEditText().getText());
} catch (Exception ex) {
Logger.printException(() -> "Copy settings failure", ex);
}
}); });
} }
}; };

View File

@@ -1,10 +1,10 @@
package app.revanced.extension.youtube.swipecontrols package app.revanced.extension.youtube.swipecontrols
import android.annotation.SuppressLint
import android.graphics.Color import android.graphics.Color
import app.revanced.extension.shared.Logger import app.revanced.extension.shared.Logger
import app.revanced.extension.shared.StringRef.str import app.revanced.extension.shared.StringRef.str
import app.revanced.extension.shared.Utils import app.revanced.extension.shared.Utils
import app.revanced.extension.shared.settings.StringSetting
import app.revanced.extension.youtube.settings.Settings import app.revanced.extension.youtube.settings.Settings
import app.revanced.extension.youtube.shared.PlayerType import app.revanced.extension.youtube.shared.PlayerType
@@ -51,105 +51,112 @@ class SwipeControlsConfigurationProvider {
/** /**
* Indicates whether press-to-swipe mode is enabled, requiring a press before swiping to activate controls. * Indicates whether press-to-swipe mode is enabled, requiring a press before swiping to activate controls.
*/ */
val shouldEnablePressToSwipe: Boolean val shouldEnablePressToSwipe = Settings.SWIPE_PRESS_TO_ENGAGE.get()
get() = Settings.SWIPE_PRESS_TO_ENGAGE.get()
/** /**
* The threshold for detecting swipe gestures, in pixels. * The threshold for detecting swipe gestures, in pixels.
* Loaded once to ensure consistent behavior during rapid scroll events. * Loaded once to ensure consistent behavior during rapid scroll events.
*/ */
val swipeMagnitudeThreshold: Int val swipeMagnitudeThreshold = Settings.SWIPE_MAGNITUDE_THRESHOLD.get()
get() = Settings.SWIPE_MAGNITUDE_THRESHOLD.get()
/** /**
* The sensitivity of volume swipe gestures, determining how much volume changes per swipe. * The sensitivity of volume swipe gestures, determining how much volume changes per swipe.
* Resets to default if set to 0, as it would disable swiping. * Resets to default if set to 0, as it would disable swiping.
*/ */
val volumeSwipeSensitivity: Int val volumeSwipeSensitivity: Int by lazy {
get() { val sensitivity = Settings.SWIPE_VOLUME_SENSITIVITY.get()
val sensitivity = Settings.SWIPE_VOLUME_SENSITIVITY.get()
if (sensitivity < 1) { if (sensitivity < 1) {
return Settings.SWIPE_VOLUME_SENSITIVITY.resetToDefault() return@lazy Settings.SWIPE_VOLUME_SENSITIVITY.resetToDefault()
}
return sensitivity
} }
sensitivity
}
//endregion //endregion
//region overlay adjustments //region overlay adjustments
/** /**
* Indicates whether haptic feedback should be enabled for swipe control interactions. * Indicates whether haptic feedback should be enabled for swipe control interactions.
*/ */
val shouldEnableHapticFeedback: Boolean val shouldEnableHapticFeedback = Settings.SWIPE_HAPTIC_FEEDBACK.get()
get() = Settings.SWIPE_HAPTIC_FEEDBACK.get()
/** /**
* The duration in milliseconds that the overlay should remain visible after a change. * The duration in milliseconds that the overlay should remain visible after a change.
*/ */
val overlayShowTimeoutMillis: Long val overlayShowTimeoutMillis = Settings.SWIPE_OVERLAY_TIMEOUT.get()
get() = Settings.SWIPE_OVERLAY_TIMEOUT.get()
/** /**
* The background opacity of the overlay, converted from a percentage (0-100) to an alpha value (0-255). * The background opacity of the overlay, converted from a percentage (0-100) to an alpha value (0-255).
* Resets to default and shows a toast if the value is out of range. * Resets to default and shows a toast if the value is out of range.
*/ */
val overlayBackgroundOpacity: Int val overlayBackgroundOpacity: Int by lazy {
get() { var opacity = Settings.SWIPE_OVERLAY_OPACITY.get()
var opacity = Settings.SWIPE_OVERLAY_OPACITY.get()
if (opacity < 0 || opacity > 100) { if (opacity < 0 || opacity > 100) {
Utils.showToastLong(str("revanced_swipe_overlay_background_opacity_invalid_toast")) Utils.showToastLong(str("revanced_swipe_overlay_background_opacity_invalid_toast"))
opacity = Settings.SWIPE_OVERLAY_OPACITY.resetToDefault() opacity = Settings.SWIPE_OVERLAY_OPACITY.resetToDefault()
}
opacity = opacity * 255 / 100
return Color.argb(opacity, 0, 0, 0)
} }
opacity = opacity * 255 / 100
Color.argb(opacity, 0, 0, 0)
}
/** /**
* The color of the progress bar in the overlay. * The color of the progress bar in the overlay for brightness.
* Resets to default and shows a toast if the color string is invalid or empty. * Resets to default and shows a toast if the color string is invalid or empty.
*/ */
val overlayProgressColor: Int val overlayBrightnessProgressColor: Int by lazy {
get() { // Use lazy to avoid repeat parsing. Changing color requires app restart.
try { getSettingColor(Settings.SWIPE_OVERLAY_BRIGHTNESS_COLOR)
@SuppressLint("UseKtx") }
val color = Color.parseColor(Settings.SWIPE_OVERLAY_PROGRESS_COLOR.get())
return (0xBF000000.toInt() or (color and 0xFFFFFF)) /**
} catch (ex: IllegalArgumentException) { * The color of the progress bar in the overlay for volume.
Logger.printDebug({ "Could not parse color" }, ex) * Resets to default and shows a toast if the color string is invalid or empty.
Utils.showToastLong(str("revanced_swipe_overlay_progress_color_invalid_toast")) */
Settings.SWIPE_OVERLAY_PROGRESS_COLOR.resetToDefault() val overlayVolumeProgressColor: Int by lazy {
return overlayProgressColor // Recursively return. getSettingColor(Settings.SWIPE_OVERLAY_VOLUME_COLOR)
} }
private fun getSettingColor(setting: StringSetting): Int {
try {
//noinspection UseKtx
val color = Color.parseColor(setting.get())
return (0xBF000000.toInt() or (color and 0x00FFFFFF))
} catch (ex: IllegalArgumentException) {
// This code should never be reached.
// Color picker rejects and will not save bad colors to a setting.
// If a user imports bad data, the color picker preference resets the
// bad color before this method can be called.
Logger.printDebug({ "Could not parse color: $setting" }, ex)
Utils.showToastLong(str("revanced_settings_color_invalid"))
setting.resetToDefault()
return getSettingColor(setting) // Recursively return.
} }
}
/** /**
* The background color used for the filled portion of the progress bar in the overlay. * The background color used for the filled portion of the progress bar in the overlay.
*/ */
val overlayFillBackgroundPaint: Int val overlayFillBackgroundPaint = 0x80D3D3D3.toInt()
get() = 0x80D3D3D3.toInt()
/** /**
* The color used for text and icons in the overlay. * The color used for text and icons in the overlay.
*/ */
val overlayTextColor: Int val overlayTextColor = Color.WHITE
get() = Color.WHITE
/** /**
* The text size in the overlay, in density-independent pixels (dp). * The text size in the overlay, in density-independent pixels (dp).
* Must be between 1 and 30 dp; resets to default and shows a toast if invalid. * Must be between 1 and 30 dp; resets to default and shows a toast if invalid.
*/ */
val overlayTextSize: Int val overlayTextSize: Int by lazy {
get() { val size = Settings.SWIPE_OVERLAY_TEXT_SIZE.get()
val size = Settings.SWIPE_OVERLAY_TEXT_SIZE.get() if (size < 1 || size > 30) {
if (size < 1 || size > 30) { Utils.showToastLong(str("revanced_swipe_text_overlay_size_invalid_toast"))
Utils.showToastLong(str("revanced_swipe_text_overlay_size_invalid_toast")) return@lazy Settings.SWIPE_OVERLAY_TEXT_SIZE.resetToDefault()
return Settings.SWIPE_OVERLAY_TEXT_SIZE.resetToDefault()
}
return size
} }
size
}
/** /**
* Defines the style of the swipe controls overlay, determining its layout and appearance. * Defines the style of the swipe controls overlay, determining its layout and appearance.
@@ -199,28 +206,25 @@ class SwipeControlsConfigurationProvider {
/** /**
* A minimal vertical progress bar. * A minimal vertical progress bar.
*/ */
VERTICAL_MINIMAL(isMinimal = true, isVertical = true) VERTICAL_MINIMAL(isMinimal = true, isVertical = true)
} }
/** /**
* The current style of the overlay, determining its layout and appearance. * The current style of the overlay, determining its layout and appearance.
*/ */
val overlayStyle: SwipeOverlayStyle val overlayStyle = Settings.SWIPE_OVERLAY_STYLE.get()
get() = Settings.SWIPE_OVERLAY_STYLE.get()
//endregion //endregion
//region behaviour //region behaviour
/** /**
* Indicates whether the brightness level should be saved and restored when entering or exiting fullscreen mode. * Indicates whether the brightness level should be saved and restored when entering or exiting fullscreen mode.
*/ */
val shouldSaveAndRestoreBrightness: Boolean val shouldSaveAndRestoreBrightness = Settings.SWIPE_SAVE_AND_RESTORE_BRIGHTNESS.get()
get() = Settings.SWIPE_SAVE_AND_RESTORE_BRIGHTNESS.get()
/** /**
* Indicates whether auto-brightness should be enabled when the brightness gesture reaches its lowest value. * Indicates whether auto-brightness should be enabled when the brightness gesture reaches its lowest value.
*/ */
val shouldLowestValueEnableAutoBrightness: Boolean val shouldLowestValueEnableAutoBrightness = Settings.SWIPE_LOWEST_VALUE_ENABLE_AUTO_BRIGHTNESS.get()
get() = Settings.SWIPE_LOWEST_VALUE_ENABLE_AUTO_BRIGHTNESS.get()
/** /**
* The saved brightness value for the swipe gesture, used to restore brightness in fullscreen mode. * The saved brightness value for the swipe gesture, used to restore brightness in fullscreen mode.
@@ -229,4 +233,4 @@ class SwipeControlsConfigurationProvider {
get() = Settings.SWIPE_BRIGHTNESS_VALUE.get() get() = Settings.SWIPE_BRIGHTNESS_VALUE.get()
set(value) = Settings.SWIPE_BRIGHTNESS_VALUE.save(value) set(value) = Settings.SWIPE_BRIGHTNESS_VALUE.save(value)
//endregion //endregion
} }

View File

@@ -39,7 +39,7 @@ class SwipeControlsOverlayLayout(
constructor(context: Context) : this(context, SwipeControlsConfigurationProvider()) constructor(context: Context) : this(context, SwipeControlsConfigurationProvider())
// Drawable icons for brightness and volume // Drawable icons for brightness and volume.
private val autoBrightnessIcon: Drawable = getDrawable("revanced_ic_sc_brightness_auto") private val autoBrightnessIcon: Drawable = getDrawable("revanced_ic_sc_brightness_auto")
private val lowBrightnessIcon: Drawable = getDrawable("revanced_ic_sc_brightness_low") private val lowBrightnessIcon: Drawable = getDrawable("revanced_ic_sc_brightness_low")
private val mediumBrightnessIcon: Drawable = getDrawable("revanced_ic_sc_brightness_medium") private val mediumBrightnessIcon: Drawable = getDrawable("revanced_ic_sc_brightness_medium")
@@ -50,7 +50,7 @@ class SwipeControlsOverlayLayout(
private val normalVolumeIcon: Drawable = getDrawable("revanced_ic_sc_volume_normal") private val normalVolumeIcon: Drawable = getDrawable("revanced_ic_sc_volume_normal")
private val fullVolumeIcon: Drawable = getDrawable("revanced_ic_sc_volume_high") private val fullVolumeIcon: Drawable = getDrawable("revanced_ic_sc_volume_high")
// Function to retrieve drawable resources by name // Function to retrieve drawable resources by name.
private fun getDrawable(name: String): Drawable { private fun getDrawable(name: String): Drawable {
val drawable = resources.getDrawable( val drawable = resources.getDrawable(
Utils.getResourceIdentifier(context, name, "drawable"), Utils.getResourceIdentifier(context, name, "drawable"),
@@ -60,19 +60,19 @@ class SwipeControlsOverlayLayout(
return drawable return drawable
} }
// Initialize progress bars // Initialize progress bars.
private val circularProgressView: CircularProgressView private val circularProgressView: CircularProgressView
private val horizontalProgressView: HorizontalProgressView private val horizontalProgressView: HorizontalProgressView
private val verticalBrightnessProgressView: VerticalProgressView private val verticalBrightnessProgressView: VerticalProgressView
private val verticalVolumeProgressView: VerticalProgressView private val verticalVolumeProgressView: VerticalProgressView
init { init {
// Initialize circular progress bar // Initialize circular progress bar.
circularProgressView = CircularProgressView( circularProgressView = CircularProgressView(
context, context,
config.overlayBackgroundOpacity, config.overlayBackgroundOpacity,
config.overlayStyle.isMinimal, config.overlayStyle.isMinimal,
config.overlayProgressColor, config.overlayBrightnessProgressColor, // Placeholder, updated in showFeedbackView.
config.overlayFillBackgroundPaint, config.overlayFillBackgroundPaint,
config.overlayTextColor, config.overlayTextColor,
config.overlayTextSize config.overlayTextSize
@@ -80,18 +80,18 @@ class SwipeControlsOverlayLayout(
layoutParams = LayoutParams(100f.toDisplayPixels().toInt(), 100f.toDisplayPixels().toInt()).apply { layoutParams = LayoutParams(100f.toDisplayPixels().toInt(), 100f.toDisplayPixels().toInt()).apply {
addRule(CENTER_IN_PARENT, TRUE) addRule(CENTER_IN_PARENT, TRUE)
} }
visibility = GONE // Initially hidden visibility = GONE // Initially hidden.
} }
addView(circularProgressView) addView(circularProgressView)
// Initialize horizontal progress bar // Initialize horizontal progress bar.
val screenWidth = resources.displayMetrics.widthPixels val screenWidth = resources.displayMetrics.widthPixels
val layoutWidth = (screenWidth * 4 / 5).toInt() // Cap at ~360dp val layoutWidth = (screenWidth * 4 / 5).toInt() // Cap at ~360dp.
horizontalProgressView = HorizontalProgressView( horizontalProgressView = HorizontalProgressView(
context, context,
config.overlayBackgroundOpacity, config.overlayBackgroundOpacity,
config.overlayStyle.isMinimal, config.overlayStyle.isMinimal,
config.overlayProgressColor, config.overlayBrightnessProgressColor, // Placeholder, updated in showFeedbackView.
config.overlayFillBackgroundPaint, config.overlayFillBackgroundPaint,
config.overlayTextColor, config.overlayTextColor,
config.overlayTextSize config.overlayTextSize
@@ -104,16 +104,16 @@ class SwipeControlsOverlayLayout(
topMargin = 20f.toDisplayPixels().toInt() topMargin = 20f.toDisplayPixels().toInt()
} }
} }
visibility = GONE // Initially hidden visibility = GONE // Initially hidden.
} }
addView(horizontalProgressView) addView(horizontalProgressView)
// Initialize vertical progress bar for brightness (right side) // Initialize vertical progress bar for brightness (right side).
verticalBrightnessProgressView = VerticalProgressView( verticalBrightnessProgressView = VerticalProgressView(
context, context,
config.overlayBackgroundOpacity, config.overlayBackgroundOpacity,
config.overlayStyle.isMinimal, config.overlayStyle.isMinimal,
config.overlayProgressColor, config.overlayBrightnessProgressColor,
config.overlayFillBackgroundPaint, config.overlayFillBackgroundPaint,
config.overlayTextColor, config.overlayTextColor,
config.overlayTextSize config.overlayTextSize
@@ -123,16 +123,16 @@ class SwipeControlsOverlayLayout(
rightMargin = 40f.toDisplayPixels().toInt() rightMargin = 40f.toDisplayPixels().toInt()
addRule(CENTER_VERTICAL) addRule(CENTER_VERTICAL)
} }
visibility = GONE // Initially hidden visibility = GONE // Initially hidden.
} }
addView(verticalBrightnessProgressView) addView(verticalBrightnessProgressView)
// Initialize vertical progress bar for volume (left side) // Initialize vertical progress bar for volume (left side).
verticalVolumeProgressView = VerticalProgressView( verticalVolumeProgressView = VerticalProgressView(
context, context,
config.overlayBackgroundOpacity, config.overlayBackgroundOpacity,
config.overlayStyle.isMinimal, config.overlayStyle.isMinimal,
config.overlayProgressColor, config.overlayVolumeProgressColor,
config.overlayFillBackgroundPaint, config.overlayFillBackgroundPaint,
config.overlayTextColor, config.overlayTextColor,
config.overlayTextSize config.overlayTextSize
@@ -142,12 +142,12 @@ class SwipeControlsOverlayLayout(
leftMargin = 40f.toDisplayPixels().toInt() leftMargin = 40f.toDisplayPixels().toInt()
addRule(CENTER_VERTICAL) addRule(CENTER_VERTICAL)
} }
visibility = GONE // Initially hidden visibility = GONE // Initially hidden.
} }
addView(verticalVolumeProgressView) addView(verticalVolumeProgressView)
} }
// Handler and callback for hiding progress bars // Handler and callback for hiding progress bars.
private val feedbackHideHandler = Handler(Looper.getMainLooper()) private val feedbackHideHandler = Handler(Looper.getMainLooper())
private val feedbackHideCallback = Runnable { private val feedbackHideCallback = Runnable {
circularProgressView.visibility = GONE circularProgressView.visibility = GONE
@@ -165,29 +165,42 @@ class SwipeControlsOverlayLayout(
val viewToShow = when { val viewToShow = when {
config.overlayStyle.isCircular -> circularProgressView config.overlayStyle.isCircular -> circularProgressView
config.overlayStyle.isVertical -> if (isBrightness) verticalBrightnessProgressView else verticalVolumeProgressView config.overlayStyle.isVertical ->
if (isBrightness)
verticalBrightnessProgressView
else
verticalVolumeProgressView
else -> horizontalProgressView else -> horizontalProgressView
} }
viewToShow.apply { viewToShow.apply {
// Set the appropriate progress color.
if (this is CircularProgressView || this is HorizontalProgressView) {
setProgressColor(
if (isBrightness)
config.overlayBrightnessProgressColor
else
config.overlayVolumeProgressColor
)
}
setProgress(progress, max, value, isBrightness) setProgress(progress, max, value, isBrightness)
this.icon = icon this.icon = icon
visibility = VISIBLE visibility = VISIBLE
} }
} }
// Handle volume change // Handle volume change.
override fun onVolumeChanged(newVolume: Int, maximumVolume: Int) { override fun onVolumeChanged(newVolume: Int, maximumVolume: Int) {
val volumePercentage = (newVolume.toFloat() / maximumVolume) * 100 val volumePercentage = (newVolume.toFloat() / maximumVolume) * 100
val icon = when { val icon = when {
newVolume == 0 -> mutedVolumeIcon newVolume == 0 -> mutedVolumeIcon
volumePercentage < 33 -> lowVolumeIcon volumePercentage < 25 -> lowVolumeIcon
volumePercentage < 66 -> normalVolumeIcon volumePercentage < 50 -> normalVolumeIcon
else -> fullVolumeIcon else -> fullVolumeIcon
} }
showFeedbackView("$newVolume", newVolume, maximumVolume, icon, isBrightness = false) showFeedbackView("$newVolume", newVolume, maximumVolume, icon, isBrightness = false)
} }
// Handle brightness change // Handle brightness change.
override fun onBrightnessChanged(brightness: Double) { override fun onBrightnessChanged(brightness: Double) {
if (config.shouldLowestValueEnableAutoBrightness && brightness <= 0) { if (config.shouldLowestValueEnableAutoBrightness && brightness <= 0) {
val displayText = if (config.overlayStyle.isVertical) "А" val displayText = if (config.overlayStyle.isVertical) "А"
@@ -195,18 +208,19 @@ class SwipeControlsOverlayLayout(
showFeedbackView(displayText, 0, 100, autoBrightnessIcon, isBrightness = true) showFeedbackView(displayText, 0, 100, autoBrightnessIcon, isBrightness = true)
} else { } else {
val brightnessValue = round(brightness).toInt() val brightnessValue = round(brightness).toInt()
val clampedProgress = max(0, brightnessValue)
val icon = when { val icon = when {
brightnessValue < 25 -> lowBrightnessIcon clampedProgress < 25 -> lowBrightnessIcon
brightnessValue < 50 -> mediumBrightnessIcon clampedProgress < 50 -> mediumBrightnessIcon
brightnessValue < 75 -> highBrightnessIcon clampedProgress < 75 -> highBrightnessIcon
else -> fullBrightnessIcon else -> fullBrightnessIcon
} }
val displayText = if (config.overlayStyle.isVertical) "$brightnessValue" else "$brightnessValue%" val displayText = if (config.overlayStyle.isVertical) "$clampedProgress" else "$clampedProgress%"
showFeedbackView(displayText, brightnessValue, 100, icon, isBrightness = true) showFeedbackView(displayText, clampedProgress, 100, icon, isBrightness = true)
} }
} }
// Begin swipe session // Begin swipe session.
override fun onEnterSwipeSession() { override fun onEnterSwipeSession() {
if (config.shouldEnableHapticFeedback) { if (config.shouldEnableHapticFeedback) {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@@ -233,25 +247,41 @@ abstract class AbstractProgressView(
defStyleAttr: Int = 0 defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) { ) : View(context, attrs, defStyleAttr) {
// Combined paint creation function for both fill and stroke styles // Combined paint creation function for both fill and stroke styles.
private fun createPaint(color: Int, style: Paint.Style = Paint.Style.FILL, strokeCap: Paint.Cap = Paint.Cap.BUTT, strokeWidth: Float = 0f) = Paint(Paint.ANTI_ALIAS_FLAG).apply { private fun createPaint(
color: Int,
style: Paint.Style = Paint.Style.FILL,
strokeCap: Paint.Cap = Paint.Cap.BUTT,
strokeWidth: Float = 0f
) = Paint(Paint.ANTI_ALIAS_FLAG).apply {
this.style = style this.style = style
this.color = color this.color = color
this.strokeCap = strokeCap this.strokeCap = strokeCap
this.strokeWidth = strokeWidth this.strokeWidth = strokeWidth
} }
// Initialize paints // Initialize paints.
val backgroundPaint = createPaint(overlayBackgroundOpacity, style = Paint.Style.FILL) val backgroundPaint = createPaint(
val progressPaint = createPaint(overlayProgressColor, style = Paint.Style.STROKE, strokeCap = Paint.Cap.ROUND, strokeWidth = 6f.toDisplayPixels()) overlayBackgroundOpacity,
val fillBackgroundPaint = createPaint(overlayFillBackgroundPaint, style = Paint.Style.FILL) style = Paint.Style.FILL
val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { )
color = overlayTextColor val progressPaint = createPaint(
overlayProgressColor,
style = Paint.Style.STROKE,
strokeCap = Paint.Cap.ROUND,
strokeWidth = 6f.toDisplayPixels()
)
val fillBackgroundPaint = createPaint(
overlayFillBackgroundPaint,
style = Paint.Style.FILL
)
val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = overlayTextColor
textAlign = Paint.Align.CENTER textAlign = Paint.Align.CENTER
textSize = overlayTextSize.toFloat().toDisplayPixels() textSize = overlayTextSize.toFloat().toDisplayPixels()
} }
// Rect for text measurement // Rect for text measurement.
protected val textBounds = Rect() protected val textBounds = Rect()
protected var progress = 0 protected var progress = 0
@@ -268,13 +298,18 @@ abstract class AbstractProgressView(
invalidate() invalidate()
} }
fun setProgressColor(color: Int) {
progressPaint.color = color
invalidate()
}
protected fun measureTextWidth(text: String, paint: Paint): Int { protected fun measureTextWidth(text: String, paint: Paint): Int {
paint.getTextBounds(text, 0, text.length, textBounds) paint.getTextBounds(text, 0, text.length, textBounds)
return textBounds.width() return textBounds.width()
} }
override fun onDraw(canvas: Canvas) { override fun onDraw(canvas: Canvas) {
// Base class implementation can be empty // Base class implementation can be empty.
} }
} }
@@ -393,8 +428,8 @@ class HorizontalProgressView(
} }
/** /**
* Calculate required width based on content * Calculate required width based on content.
* @return Required width to display all elements * @return Required width to display all elements.
*/ */
private fun calculateRequiredWidth(): Float { private fun calculateRequiredWidth(): Float {
textWidth = measureTextWidth(displayText, textPaint).toFloat() textWidth = measureTextWidth(displayText, textPaint).toFloat()
@@ -537,8 +572,8 @@ class VerticalProgressView(
} }
/** /**
* Calculate required height based on content * Calculate required height based on content.
* @return Required height to display all elements * @return Required height to display all elements.
*/ */
private fun calculateRequiredHeight(): Float { private fun calculateRequiredHeight(): Float {
return if (!isMinimalStyle) { return if (!isMinimalStyle) {
@@ -633,4 +668,4 @@ class VerticalProgressView(
super.setProgress(value, max, text, isBrightnessMode) super.setProgress(value, max, text, isBrightnessMode)
requestLayout() requestLayout()
} }
} }

View File

@@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M
org.gradle.parallel = true org.gradle.parallel = true
android.useAndroidX = true android.useAndroidX = true
kotlin.code.style = official kotlin.code.style = official
version = 5.24.0-dev.5 version = 5.25.0

View File

@@ -284,10 +284,22 @@ public final class app/revanced/patches/messenger/inputfield/DisableTypingIndica
public static final fun getDisableTypingIndicatorPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getDisableTypingIndicatorPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/messenger/metaai/RemoveMetaAIPatchKt {
public static final fun getRemoveMetaAIPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/messenger/misc/extension/ExtensionPatchKt {
public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/messenger/navbar/RemoveMetaAITabPatchKt { public final class app/revanced/patches/messenger/navbar/RemoveMetaAITabPatchKt {
public static final fun getRemoveMetaAITabPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getRemoveMetaAITabPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/meta/ads/HideAdsPatchKt {
public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/mifitness/misc/locale/ForceEnglishLocalePatchKt { public final class app/revanced/patches/mifitness/misc/locale/ForceEnglishLocalePatchKt {
public static final fun getForceEnglishLocalePatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getForceEnglishLocalePatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
@@ -432,6 +444,10 @@ public final class app/revanced/patches/primevideo/misc/extension/ExtensionPatch
public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/primevideo/misc/permissions/RenamePermissionsPatchKt {
public static final fun getRenamePermissionsPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
}
public final class app/revanced/patches/protonmail/signature/RemoveSentFromSignaturePatchKt { public final class app/revanced/patches/protonmail/signature/RemoveSentFromSignaturePatchKt {
public static final fun getRemoveSentFromSignaturePatch ()Lapp/revanced/patcher/patch/ResourcePatch; public static final fun getRemoveSentFromSignaturePatch ()Lapp/revanced/patcher/patch/ResourcePatch;
} }
@@ -652,6 +668,10 @@ public final class app/revanced/patches/shared/misc/mapping/ResourceMappingPatch
public static final fun getResourceMappings ()Ljava/util/List; public static final fun getResourceMappings ()Ljava/util/List;
} }
public final class app/revanced/patches/shared/misc/pairip/license/DisableLicenseCheckPatchKt {
public static final fun getDisableLicenseCheckPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/shared/misc/settings/SettingsPatchKt { public final class app/revanced/patches/shared/misc/settings/SettingsPatchKt {
public static final fun settingsPatch (Ljava/util/List;Ljava/util/Set;)Lapp/revanced/patcher/patch/ResourcePatch; public static final fun settingsPatch (Ljava/util/List;Ljava/util/Set;)Lapp/revanced/patcher/patch/ResourcePatch;
public static final fun settingsPatch (Lkotlin/Pair;Ljava/util/Set;)Lapp/revanced/patcher/patch/ResourcePatch; public static final fun settingsPatch (Lkotlin/Pair;Ljava/util/Set;)Lapp/revanced/patcher/patch/ResourcePatch;
@@ -872,6 +892,10 @@ public final class app/revanced/patches/spotify/misc/fix/SpoofSignaturePatchKt {
public static final fun getSpoofSignaturePatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getSpoofSignaturePatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/spotify/misc/fix/login/FixFacebookLoginPatchKt {
public static final fun getFixFacebookLoginPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/spotify/misc/privacy/SanitizeSharingLinksPatchKt { public final class app/revanced/patches/spotify/misc/privacy/SanitizeSharingLinksPatchKt {
public static final fun getSanitizeSharingLinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getSanitizeSharingLinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
@@ -1383,6 +1407,10 @@ public final class app/revanced/patches/youtube/misc/gms/GmsCoreSupportPatchKt {
public static final fun getGmsCoreSupportPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getGmsCoreSupportPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/youtube/misc/hapticfeedback/DisableHapticFeedbackPatchKt {
public static final fun getDisableHapticFeedbackPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/youtube/misc/imageurlhook/CronetImageUrlHookKt { public final class app/revanced/patches/youtube/misc/imageurlhook/CronetImageUrlHookKt {
public static final fun addImageUrlErrorCallbackHook (Ljava/lang/String;)V public static final fun addImageUrlErrorCallbackHook (Ljava/lang/String;)V
public static final fun addImageUrlHook (Ljava/lang/String;Z)V public static final fun addImageUrlHook (Ljava/lang/String;Z)V
@@ -1615,8 +1643,23 @@ public final class app/revanced/util/BytecodeUtilsKt {
public static final fun indexOfFirstResourceIdOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;)I public static final fun indexOfFirstResourceIdOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;)I
public static final fun injectHideViewCall (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;IILjava/lang/String;Ljava/lang/String;)V public static final fun injectHideViewCall (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;IILjava/lang/String;Ljava/lang/String;)V
public static final fun literal (Lapp/revanced/patcher/FingerprintBuilder;Lkotlin/jvm/functions/Function0;)V public static final fun literal (Lapp/revanced/patcher/FingerprintBuilder;Lkotlin/jvm/functions/Function0;)V
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;B)V
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;C)V
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;D)V
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;F)V
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;I)V
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;J)V
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;S)V
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Z)V public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Z)V
public static synthetic fun returnEarly$default (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ZILjava/lang/Object;)V public static synthetic fun returnEarly$default (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ZILjava/lang/Object;)V
public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;B)V
public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;C)V
public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;D)V
public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;F)V
public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;I)V
public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;J)V
public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;S)V
public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Z)V
public static final fun transformMethods (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;Lkotlin/jvm/functions/Function1;)V public static final fun transformMethods (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;Lkotlin/jvm/functions/Function1;)V
public static final fun traverseClassHierarchy (Lapp/revanced/patcher/patch/BytecodePatchContext;Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;Lkotlin/jvm/functions/Function1;)V public static final fun traverseClassHierarchy (Lapp/revanced/patcher/patch/BytecodePatchContext;Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;Lkotlin/jvm/functions/Function1;)V
} }

View File

@@ -47,7 +47,7 @@ val hideAdbStatusPatch = bytecodePatch(
.takeIf { it.opcode == Opcode.INVOKE_STATIC } .takeIf { it.opcode == Opcode.INVOKE_STATIC }
?.getReference<MethodReference>() ?.getReference<MethodReference>()
?.takeIf { ?.takeIf {
it.anyMethodSignatureMatches(it, it.anyMethodSignatureMatches(
SETTINGS_GLOBAL_GET_INT_OR_THROW_METHOD_REFERENCE, SETTINGS_GLOBAL_GET_INT_OR_THROW_METHOD_REFERENCE,
SETTINGS_GLOBAL_GET_INT_OR_DEFAULT_METHOD_REFERENCE SETTINGS_GLOBAL_GET_INT_OR_DEFAULT_METHOD_REFERENCE
) )

View File

@@ -17,7 +17,8 @@ val removeShareTargetsPatch = resourcePatch(
try { try {
document("res/xml/shortcuts.xml") document("res/xml/shortcuts.xml")
} catch (_: FileNotFoundException) { } catch (_: FileNotFoundException) {
return@execute Logger.getLogger(this::class.java.name).warning("The app has no shortcuts") return@execute Logger.getLogger(this::class.java.name).warning(
"The app has no shortcuts. No changes applied.")
}.use { document -> }.use { document ->
val rootNode = document.getNode("shortcuts") as? Element ?: return@use val rootNode = document.getNode("shortcuts") as? Element ?: return@use

View File

@@ -1,14 +1,17 @@
package app.revanced.patches.angulus.ads package app.revanced.patches.angulus.ads
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.shared.misc.pairip.license.disableLicenseCheckPatch
import app.revanced.util.returnEarly import app.revanced.util.returnEarly
@Suppress("unused") @Suppress("unused")
val angulusPatch = bytecodePatch(name = "Hide ads") { val angulusPatch = bytecodePatch(name = "Hide ads") {
compatibleWith("com.drinkplusplus.angulus") compatibleWith("com.drinkplusplus.angulus")
dependsOn(disableLicenseCheckPatch)
execute { execute {
// Always return 0 as the daily measurement count. // Always return 0 as the daily measurement count.
getDailyMeasurementCountFingerprint.method.returnEarly() getDailyMeasurementCountFingerprint.method.returnEarly(0)
} }
} }

View File

@@ -1,23 +1,9 @@
package app.revanced.patches.instagram.ads package app.revanced.patches.instagram.ads
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
@Deprecated("Patch was moved to different package: app.revanced.patches.meta.ads.hideAdsPatch")
@Suppress("unused") @Suppress("unused")
val hideAdsPatch = bytecodePatch( val hideAdsPatch = bytecodePatch {
name = "Hide ads", dependsOn(app.revanced.patches.meta.ads.hideAdsPatch)
description = "Hides ads in stories, discover, profile, etc. " +
"An ad can still appear once when refreshing the home feed.",
) {
compatibleWith("com.instagram.android")
execute {
adInjectorFingerprint.method.addInstructions(
0,
"""
const/4 v0, 0x0
return v0
""",
)
}
} }

View File

@@ -5,9 +5,14 @@ import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
/**
* This patch will be deleted soon.
*
* Pull requests to update this patch to the latest app target are invited.
*/
@Deprecated("This patch only works with an outdated app target that is no longer fully supported by Facebook.")
@Suppress("unused") @Suppress("unused")
val disableSwitchingEmojiToStickerPatch = bytecodePatch( val disableSwitchingEmojiToStickerPatch = bytecodePatch(
name = "Disable switching emoji to sticker",
description = "Disables switching from emoji to sticker search mode in message input field.", description = "Disables switching from emoji to sticker search mode in message input field.",
) { ) {
compatibleWith("com.facebook.orca"("439.0.0.29.119")) compatibleWith("com.facebook.orca"("439.0.0.29.119"))

View File

@@ -0,0 +1,14 @@
package app.revanced.patches.messenger.metaai
import com.android.tools.smali.dexlib2.Opcode
import app.revanced.patcher.fingerprint
internal val getMobileConfigBoolFingerprint = fingerprint {
parameters("J")
returns("Z")
opcodes(Opcode.RETURN)
custom { method, classDef ->
method.implementation ?: return@custom false // unsure if this is necessary
classDef.interfaces.contains("Lcom/facebook/mobileconfig/factory/MobileConfigUnsafeContext;")
}
}

View File

@@ -0,0 +1,34 @@
package app.revanced.patches.messenger.metaai
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.messenger.misc.extension.sharedExtensionPatch
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/messenger/metaai/RemoveMetaAIPatch;"
@Suppress("unused")
val removeMetaAIPatch = bytecodePatch(
name = "Remove Meta AI",
description = "Removes UI elements related to Meta AI."
) {
compatibleWith("com.facebook.orca")
dependsOn(sharedExtensionPatch)
execute {
getMobileConfigBoolFingerprint.method.apply {
val returnIndex = getMobileConfigBoolFingerprint.patternMatch!!.startIndex
val returnRegister = getInstruction<OneRegisterInstruction>(returnIndex).registerA
addInstructions(
returnIndex,
"""
invoke-static { p1, p2, v$returnRegister }, $EXTENSION_CLASS_DESCRIPTOR->overrideConfigBool(JZ)Z
move-result v$returnRegister
"""
)
}
}
}

View File

@@ -0,0 +1,5 @@
package app.revanced.patches.messenger.misc.extension
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
val sharedExtensionPatch = sharedExtensionPatch("messenger", mainActivityOnCreateHook)

View File

@@ -0,0 +1,7 @@
package app.revanced.patches.messenger.misc.extension
import app.revanced.patches.shared.misc.extension.extensionHook
internal val mainActivityOnCreateHook = extensionHook {
strings("MainActivity_onCreate_begin")
}

View File

@@ -1,16 +0,0 @@
package app.revanced.patches.messenger.navbar
import app.revanced.patcher.fingerprint
import com.android.tools.smali.dexlib2.Opcode
internal val createTabConfigurationFingerprint = fingerprint {
strings("MessengerTabConfigurationCreator.createTabConfiguration")
opcodes(
Opcode.INVOKE_DIRECT,
Opcode.MOVE_RESULT,
Opcode.IF_EQZ,
Opcode.INVOKE_DIRECT,
Opcode.MOVE_RESULT,
Opcode.IF_EQZ,
)
}

View File

@@ -1,25 +1,12 @@
package app.revanced.patches.messenger.navbar package app.revanced.patches.messenger.navbar
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patches.messenger.metaai.removeMetaAIPatch
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@Deprecated("Superseded by removeMetaAIPatch", ReplaceWith("removeMetaAIPatch"))
@Suppress("unused") @Suppress("unused")
val removeMetaAITabPatch = bytecodePatch( val removeMetaAITabPatch = bytecodePatch(
name = "Remove Meta AI tab",
description = "Removes the 'Meta AI' tab from the navbar.", description = "Removes the 'Meta AI' tab from the navbar.",
) { ) {
compatibleWith("com.facebook.orca") dependsOn(removeMetaAIPatch)
execute {
createTabConfigurationFingerprint.let {
val moveResultIndex = it.patternMatch!!.startIndex + 1
val enabledRegister = it.method.getInstruction<OneRegisterInstruction>(moveResultIndex).registerA
it.method.replaceInstruction(
moveResultIndex,
"const/4 v$enabledRegister, 0x0"
)
}
}
} }

View File

@@ -1,13 +1,13 @@
package app.revanced.patches.instagram.ads package app.revanced.patches.meta.ads
import app.revanced.patcher.fingerprint import app.revanced.patcher.fingerprint
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
internal val adInjectorFingerprint = fingerprint { internal val adInjectorFingerprint = fingerprint {
accessFlags(AccessFlags.PRIVATE) accessFlags(AccessFlags.PRIVATE)
returns("Z") returns("Z")
parameters("L", "L") parameters("L", "L")
strings( strings(
"SponsoredContentController.insertItem", "SponsoredContentController.insertItem",
) )
} }

View File

@@ -0,0 +1,21 @@
package app.revanced.patches.meta.ads
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.returnEarly
@Suppress("unused")
val hideAdsPatch = bytecodePatch(
name = "Hide ads",
) {
/**
* Patch is identical for both Instagram and Threads app.
*/
compatibleWith(
"com.instagram.android",
"com.instagram.barcelona",
)
execute {
adInjectorFingerprint.method.returnEarly(false)
}
}

View File

@@ -0,0 +1,44 @@
package app.revanced.patches.primevideo.misc.permissions
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.resourcePatch
import app.revanced.util.asSequence
import app.revanced.util.getNode
import org.w3c.dom.Element
@Suppress("unused")
val renamePermissionsPatch = resourcePatch(
name = "Rename shared permissions",
description = "Rename certain permissions shared across Amazon apps. " +
"Applying this patch can fix installation errors, but can also break features in certain apps.",
use = false
) {
compatibleWith("com.amazon.avod.thirdpartyclient")
val permissionNames = setOf(
"com.amazon.identity.permission.CAN_CALL_MAP_INFORMATION_PROVIDER",
"com.amazon.identity.auth.device.perm.AUTH_SDK",
"com.amazon.dcp.sso.permission.account.changed",
"com.amazon.dcp.sso.permission.AmazonAccountPropertyService.property.changed",
"com.amazon.identity.permission.CALL_AMAZON_DEVICE_INFORMATION_PROVIDER",
"com.amazon.appmanager.preload.permission.READ_PRELOAD_DEVICE_INFO_PROVIDER"
)
execute {
document("AndroidManifest.xml").use { document ->
val manifest = document.getNode("manifest") as Element
val permissions = manifest
.getElementsByTagName("permission")
.asSequence()
.map { Pair(it as Element, it.getAttribute("android:name")) }
.filter { (_, name) -> name in permissionNames }
if (permissions.none()) throw PatchException("Could not find any permissions to rename")
permissions.forEach { (element, name) ->
element.setAttribute("android:name", "revanced.$name")
}
}
}
}

View File

@@ -198,7 +198,7 @@ fun gmsCoreSupportPatch(
// Google Play Utility is not present in all apps, so we need to check if it's present. // Google Play Utility is not present in all apps, so we need to check if it's present.
if (googlePlayUtilityFingerprint.methodOrNull != null) { if (googlePlayUtilityFingerprint.methodOrNull != null) {
googlePlayUtilityFingerprint.method.returnEarly() googlePlayUtilityFingerprint.method.returnEarly(0)
} }
// Verify GmsCore is installed and whitelisted for power optimizations and background usage. // Verify GmsCore is installed and whitelisted for power optimizations and background usage.

View File

@@ -0,0 +1,27 @@
package app.revanced.patches.shared.misc.pairip.license
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.returnEarly
import java.util.logging.Logger
@Suppress("unused")
val disableLicenseCheckPatch = bytecodePatch(
name = "Disable Pairip license check",
description = "Disable Play Integrity Protect (Pairip) client-side license check.",
use = false
) {
execute {
if (processLicenseResponseFingerprint.methodOrNull == null || validateLicenseResponseFingerprint.methodOrNull == null) {
return@execute Logger.getLogger(this::class.java.name)
.warning("Could not find Pairip licensing check. No changes applied.")
}
// Set first parameter (responseCode) to 0 (success status).
processLicenseResponseFingerprint.method.addInstruction(0, "const/4 p1, 0x0")
// Short-circuit the license response validation.
validateLicenseResponseFingerprint.method.returnEarly()
}
}

View File

@@ -0,0 +1,17 @@
package app.revanced.patches.shared.misc.pairip.license
import app.revanced.patcher.fingerprint
internal val processLicenseResponseFingerprint = fingerprint {
custom { method, classDef ->
classDef.type == "Lcom/pairip/licensecheck/LicenseClient;" &&
method.name == "processResponse"
}
}
internal val validateLicenseResponseFingerprint = fingerprint {
custom { method, classDef ->
classDef.type == "Lcom/pairip/licensecheck/ResponseValidator;" &&
method.name == "validateResponse"
}
}

View File

@@ -3,9 +3,9 @@ package app.revanced.patches.spotify.lite.ondemand
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
@Deprecated("Patch no longer works and will be deleted soon")
@Suppress("unused") @Suppress("unused")
val onDemandPatch = bytecodePatch( val onDemandPatch = bytecodePatch(
name = "Enable on demand",
description = "Enables listening to songs on-demand, allowing to play any song from playlists, albums or artists without limitations. This does not remove ads.", description = "Enables listening to songs on-demand, allowing to play any song from playlists, albums or artists without limitations. This does not remove ads.",
) { ) {
compatibleWith("com.spotify.lite") compatibleWith("com.spotify.lite")

View File

@@ -65,8 +65,15 @@ internal val protobufListsFingerprint = fingerprint {
custom { method, _ -> method.name == "emptyProtobufList" } custom { method, _ -> method.name == "emptyProtobufList" }
} }
internal val protobufListRemoveFingerprint = fingerprint { internal val abstractProtobufListEnsureIsMutableFingerprint = fingerprint {
custom { method, _ -> method.name == "remove" } accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
parameters()
returns("V")
custom { method, _ ->
method.indexOfFirstInstruction {
getReference<TypeReference>()?.type == "Ljava/lang/UnsupportedOperationException;"
} >= 0
}
} }
internal val homeSectionFingerprint = fingerprint { internal val homeSectionFingerprint = fingerprint {

View File

@@ -7,7 +7,5 @@ import app.revanced.patcher.patch.bytecodePatch
val spoofSignaturePatch = bytecodePatch( val spoofSignaturePatch = bytecodePatch(
description = "Spoofs the signature of the app fix various functions of the app.", description = "Spoofs the signature of the app fix various functions of the app.",
) { ) {
compatibleWith("com.spotify.music")
dependsOn(spoofPackageInfoPatch) dependsOn(spoofPackageInfoPatch)
} }

View File

@@ -0,0 +1,13 @@
package app.revanced.patches.spotify.misc.fix.login
import app.revanced.patcher.fingerprint
import app.revanced.util.literal
internal val katanaProxyLoginMethodHandlerClassFingerprint = fingerprint {
strings("katana_proxy_auth")
}
internal val katanaProxyLoginMethodTryAuthorizeFingerprint = fingerprint {
strings("e2e")
literal { 0 }
}

View File

@@ -0,0 +1,29 @@
package app.revanced.patches.spotify.misc.fix.login
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.returnEarly
@Suppress("unused")
val fixFacebookLoginPatch = bytecodePatch(
name = "Fix Facebook login",
description =
"Fix logging in with Facebook when the app is patched by always opening the login in a web browser window.",
) {
compatibleWith("com.spotify.music")
execute {
// The Facebook SDK tries to handle the login using the Facebook app in case it is installed.
// However, the Facebook app does signature checks with the app that is requesting the authentication,
// which ends up making the Facebook server reject with an invalid key hash for the app signature.
// Override the Faceboook SDK to always handle the login using the web browser, which does not perform
// signature checks.
val katanaProxyLoginMethodHandlerClass = katanaProxyLoginMethodHandlerClassFingerprint.originalClassDef
// Always return 0 (no Intent was launched) as the result of trying to authorize with the Facebook app to
// make the login fallback to a web browser window.
katanaProxyLoginMethodTryAuthorizeFingerprint
.match(katanaProxyLoginMethodHandlerClass)
.method
.returnEarly(0)
}
}

View File

@@ -46,7 +46,6 @@ private val hideAdsResourcePatch = resourcePatch {
SwitchPreference("revanced_hide_general_ads"), SwitchPreference("revanced_hide_general_ads"),
SwitchPreference("revanced_hide_end_screen_store_banner"), SwitchPreference("revanced_hide_end_screen_store_banner"),
SwitchPreference("revanced_hide_fullscreen_ads"), SwitchPreference("revanced_hide_fullscreen_ads"),
SwitchPreference("revanced_hide_buttoned_ads"),
SwitchPreference("revanced_hide_paid_promotion_label"), SwitchPreference("revanced_hide_paid_promotion_label"),
SwitchPreference("revanced_hide_player_store_shelf"), SwitchPreference("revanced_hide_player_store_shelf"),
SwitchPreference("revanced_hide_self_sponsor_ads"), SwitchPreference("revanced_hide_self_sponsor_ads"),

View File

@@ -48,7 +48,12 @@ private val swipeControlsResourcePatch = resourcePatch {
summaryKey = null, summaryKey = null,
), ),
TextPreference("revanced_swipe_overlay_background_opacity", inputType = InputType.NUMBER), TextPreference("revanced_swipe_overlay_background_opacity", inputType = InputType.NUMBER),
TextPreference("revanced_swipe_overlay_progress_color", inputType = InputType.TEXT_CAP_CHARACTERS), TextPreference("revanced_swipe_overlay_progress_brightness_color",
tag = "app.revanced.extension.shared.settings.preference.ColorPickerPreference",
inputType = InputType.TEXT_CAP_CHARACTERS),
TextPreference("revanced_swipe_overlay_progress_volume_color",
tag = "app.revanced.extension.shared.settings.preference.ColorPickerPreference",
inputType = InputType.TEXT_CAP_CHARACTERS),
TextPreference("revanced_swipe_text_overlay_size", inputType = InputType.NUMBER), TextPreference("revanced_swipe_text_overlay_size", inputType = InputType.NUMBER),
TextPreference("revanced_swipe_overlay_timeout", inputType = InputType.NUMBER), TextPreference("revanced_swipe_overlay_timeout", inputType = InputType.NUMBER),
TextPreference("revanced_swipe_threshold", inputType = InputType.NUMBER), TextPreference("revanced_swipe_threshold", inputType = InputType.NUMBER),

View File

@@ -143,6 +143,7 @@ val hideLayoutComponentsPatch = bytecodePatch(
key = "revanced_hide_description_components_screen", key = "revanced_hide_description_components_screen",
preferences = setOf( preferences = setOf(
SwitchPreference("revanced_hide_ai_generated_video_summary_section"), SwitchPreference("revanced_hide_ai_generated_video_summary_section"),
SwitchPreference("revanced_hide_ask_section"),
SwitchPreference("revanced_hide_attributes_section"), SwitchPreference("revanced_hide_attributes_section"),
SwitchPreference("revanced_hide_chapters_section"), SwitchPreference("revanced_hide_chapters_section"),
SwitchPreference("revanced_hide_info_cards_section"), SwitchPreference("revanced_hide_info_cards_section"),
@@ -224,6 +225,7 @@ val hideLayoutComponentsPatch = bytecodePatch(
SwitchPreference("revanced_hide_playables"), SwitchPreference("revanced_hide_playables"),
SwitchPreference("revanced_hide_search_result_recommendation_labels"), SwitchPreference("revanced_hide_search_result_recommendation_labels"),
SwitchPreference("revanced_hide_show_more_button"), SwitchPreference("revanced_hide_show_more_button"),
SwitchPreference("revanced_hide_ticket_shelf"),
SwitchPreference("revanced_hide_doodles"), SwitchPreference("revanced_hide_doodles"),
) )

View File

@@ -15,9 +15,6 @@ internal val openVideosFullscreenPortraitFingerprint = fingerprint {
} }
} }
/**
* Used to enable opening regular videos fullscreen.
*/
internal val openVideosFullscreenHookPatchExtensionFingerprint = fingerprint { internal val openVideosFullscreenHookPatchExtensionFingerprint = fingerprint {
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC) accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
returns("Z") returns("Z")

View File

@@ -53,4 +53,12 @@ internal val shortsPlaybackIntentFingerprint = fingerprint {
"ReelWatchFragmentArgs", "ReelWatchFragmentArgs",
"reels_fragment_descriptor" "reels_fragment_descriptor"
) )
}
internal val exitVideoPlayerFingerprint = fingerprint {
returns("V")
parameters()
literal {
mdx_drawer_layout_id
}
} }

View File

@@ -1,11 +1,16 @@
package app.revanced.patches.youtube.layout.shortsplayer package app.revanced.patches.youtube.layout.shortsplayer
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.mapping.get
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.mapping.resourceMappings
import app.revanced.patches.shared.misc.settings.preference.ListPreference import app.revanced.patches.shared.misc.settings.preference.ListPreference
import app.revanced.patches.youtube.layout.player.fullscreen.openVideosFullscreenHookPatch import app.revanced.patches.youtube.layout.player.fullscreen.openVideosFullscreenHookPatch
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
@@ -19,12 +24,29 @@ import app.revanced.patches.youtube.shared.mainActivityOnCreateFingerprint
import app.revanced.util.findFreeRegister import app.revanced.util.findFreeRegister
import app.revanced.util.getReference import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference import com.android.tools.smali.dexlib2.iface.reference.MethodReference
private const val EXTENSION_CLASS_DESCRIPTOR = private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/OpenShortsInRegularPlayerPatch;" "Lapp/revanced/extension/youtube/patches/OpenShortsInRegularPlayerPatch;"
internal var mdx_drawer_layout_id = -1L
private set
private val openShortsInRegularPlayerResourcePatch = resourcePatch {
dependsOn(resourceMappingPatch)
execute {
mdx_drawer_layout_id = resourceMappings[
"id",
"mdx_drawer_layout",
]
}
}
@Suppress("unused") @Suppress("unused")
val openShortsInRegularPlayerPatch = bytecodePatch( val openShortsInRegularPlayerPatch = bytecodePatch(
name = "Open Shorts in regular player", name = "Open Shorts in regular player",
@@ -36,7 +58,8 @@ val openShortsInRegularPlayerPatch = bytecodePatch(
addResourcesPatch, addResourcesPatch,
openVideosFullscreenHookPatch, openVideosFullscreenHookPatch,
navigationBarHookPatch, navigationBarHookPatch,
versionCheckPatch versionCheckPatch,
openShortsInRegularPlayerResourcePatch
) )
compatibleWith( compatibleWith(
@@ -127,5 +150,28 @@ val openShortsInRegularPlayerPatch = bytecodePatch(
${extensionInstructions(0, 1)} ${extensionInstructions(0, 1)}
""" """
) )
// Fix issue with back button exiting the app instead of minimizing the player.
// Without this change this issue can be difficult to reproduce, but seems to occur
// most often with 'open video in regular player' and not open in fullscreen player.
exitVideoPlayerFingerprint.method.apply {
// Method call for Activity.finish()
val finishIndex = indexOfFirstInstructionOrThrow {
val reference = getReference<MethodReference>()
reference?.name == "finish"
}
// Index of PlayerType.isWatchWhileMaximizedOrFullscreen()
val index = indexOfFirstInstructionReversedOrThrow(finishIndex, Opcode.MOVE_RESULT)
val register = getInstruction<OneRegisterInstruction>(index).registerA
addInstructions(
index + 1,
"""
invoke-static { v$register }, ${EXTENSION_CLASS_DESCRIPTOR}->overrideBackPressToExit(Z)Z
move-result v$register
"""
)
}
} }
} }

View File

@@ -89,14 +89,15 @@ val themePatch = bytecodePatch(
execute { execute {
val preferences = mutableSetOf<BasePreference>( val preferences = mutableSetOf<BasePreference>(
SwitchPreference("revanced_seekbar_custom_color"), SwitchPreference("revanced_seekbar_custom_color"),
TextPreference("revanced_seekbar_custom_color_primary", inputType = InputType.TEXT_CAP_CHARACTERS), TextPreference("revanced_seekbar_custom_color_primary",
tag = "app.revanced.extension.shared.settings.preference.ColorPickerPreference",
inputType = InputType.TEXT_CAP_CHARACTERS),
) )
if (is_19_25_or_greater) { if (is_19_25_or_greater) {
preferences += TextPreference( preferences += TextPreference("revanced_seekbar_custom_color_accent",
"revanced_seekbar_custom_color_accent", tag = "app.revanced.extension.shared.settings.preference.ColorPickerPreference",
inputType = InputType.TEXT_CAP_CHARACTERS inputType = InputType.TEXT_CAP_CHARACTERS)
)
} }
PreferenceScreen.SEEKBAR.addPreferences( PreferenceScreen.SEEKBAR.addPreferences(

View File

@@ -5,6 +5,7 @@ import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
@@ -23,7 +24,7 @@ private const val EXTENSION_CLASS_DESCRIPTOR =
val enableDebuggingPatch = bytecodePatch( val enableDebuggingPatch = bytecodePatch(
name = "Enable debugging", name = "Enable debugging",
description = "Adds options for debugging.", description = "Adds options for debugging and exporting ReVanced logs to the clipboard.",
) { ) {
dependsOn( dependsOn(
sharedExtensionPatch, sharedExtensionPatch,
@@ -56,6 +57,16 @@ val enableDebuggingPatch = bytecodePatch(
SwitchPreference("revanced_debug_protobuffer"), SwitchPreference("revanced_debug_protobuffer"),
SwitchPreference("revanced_debug_stacktrace"), SwitchPreference("revanced_debug_stacktrace"),
SwitchPreference("revanced_debug_toast_on_error"), SwitchPreference("revanced_debug_toast_on_error"),
NonInteractivePreference(
"revanced_debug_export_logs_to_clipboard",
tag = "app.revanced.extension.youtube.settings.preference.ExportLogToClipboardPreference",
selectable = true,
),
NonInteractivePreference(
"revanced_debug_logs_clear_buffer",
tag = "app.revanced.extension.youtube.settings.preference.ClearLogBufferPreference",
selectable = true,
),
), ),
), ),
) )
@@ -107,7 +118,6 @@ val enableDebuggingPatch = bytecodePatch(
return-wide v0 return-wide v0
""" """
) )
} }
experimentalStringFeatureFlagFingerprint.match( experimentalStringFeatureFlagFingerprint.match(

View File

@@ -0,0 +1,74 @@
package app.revanced.patches.youtube.misc.hapticfeedback
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/DisableHapticFeedbackPatch;"
@Suppress("unused")
val disableHapticFeedbackPatch = bytecodePatch(
name = "Disable haptic feedback",
description = "Adds an option to disable haptic feedback in the player for various actions.",
) {
dependsOn(
settingsPatch,
addResourcesPatch,
)
compatibleWith(
"com.google.android.youtube"(
"19.16.39",
"19.25.37",
"19.34.42",
"19.43.41",
"19.47.53",
"20.07.39",
"20.12.46",
)
)
execute {
addResources("youtube", "misc.hapticfeedback.disableHapticFeedbackPatch")
PreferenceScreen.PLAYER.addPreferences(
PreferenceScreenPreference(
"revanced_disable_haptic_feedback",
preferences = setOf(
SwitchPreference("revanced_disable_haptic_feedback_chapters"),
SwitchPreference("revanced_disable_haptic_feedback_precise_seeking"),
SwitchPreference("revanced_disable_haptic_feedback_seek_undo"),
SwitchPreference("revanced_disable_haptic_feedback_zoom"),
)
)
)
arrayOf(
markerHapticsFingerprint to "disableChapterVibrate",
scrubbingHapticsFingerprint to "disablePreciseSeekingVibrate",
seekUndoHapticsFingerprint to "disableSeekUndoVibrate",
zoomHapticsFingerprint to "disableZoomVibrate"
).forEach { (fingerprint, methodName) ->
fingerprint.method.apply {
addInstructionsWithLabels(
0,
"""
invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->$methodName()Z
move-result v0
if-eqz v0, :vibrate
return-void
""",
ExternalLabel("vibrate", getInstruction(0))
)
}
}
}
}

View File

@@ -0,0 +1,23 @@
package app.revanced.patches.youtube.misc.hapticfeedback
import app.revanced.patcher.fingerprint
internal val markerHapticsFingerprint = fingerprint {
returns("V")
strings("Failed to execute markers haptics vibrate.")
}
internal val scrubbingHapticsFingerprint = fingerprint {
returns("V")
strings("Failed to haptics vibrate for fine scrubbing.")
}
internal val seekUndoHapticsFingerprint = fingerprint {
returns("V")
strings("Failed to execute seek undo haptics vibrate.")
}
internal val zoomHapticsFingerprint = fingerprint {
returns("V")
strings("Failed to haptics vibrate for video zoom")
}

View File

@@ -74,6 +74,7 @@ private val settingsResourcePatch = resourcePatch {
arrayOf( arrayOf(
ResourceGroup("drawable", ResourceGroup("drawable",
"revanced_settings_circle_background.xml",
"revanced_settings_cursor.xml", "revanced_settings_cursor.xml",
"revanced_settings_icon.xml", "revanced_settings_icon.xml",
"revanced_settings_screen_00_about.xml", "revanced_settings_screen_00_about.xml",
@@ -91,6 +92,8 @@ private val settingsResourcePatch = resourcePatch {
"revanced_settings_screen_12_video.xml", "revanced_settings_screen_12_video.xml",
), ),
ResourceGroup("layout", ResourceGroup("layout",
"revanced_color_dot_widget.xml",
"revanced_color_picker.xml",
"revanced_preference_with_icon_no_search_result.xml", "revanced_preference_with_icon_no_search_result.xml",
"revanced_search_suggestion_item.xml", "revanced_search_suggestion_item.xml",
"revanced_settings_with_toolbar.xml"), "revanced_settings_with_toolbar.xml"),

View File

@@ -1,7 +0,0 @@
package app.revanced.patches.youtube.misc.zoomhaptics
import app.revanced.patcher.fingerprint
internal val zoomHapticsFingerprint = fingerprint {
strings("Failed to haptics vibrate for video zoom")
}

View File

@@ -1,54 +1,11 @@
package app.revanced.patches.youtube.misc.zoomhaptics package app.revanced.patches.youtube.misc.zoomhaptics
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patches.youtube.misc.hapticfeedback.disableHapticFeedbackPatch
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
@Deprecated("Superseded by disableHapticFeedbackPatch", ReplaceWith("disableHapticFeedbackPatch"))
val zoomHapticsPatch = bytecodePatch( val zoomHapticsPatch = bytecodePatch(
name = "Disable zoom haptics",
description = "Adds an option to disable haptics when zooming.", description = "Adds an option to disable haptics when zooming.",
) { ) {
dependsOn( dependsOn(disableHapticFeedbackPatch)
settingsPatch, }
addResourcesPatch,
)
compatibleWith(
"com.google.android.youtube"(
"19.16.39",
"19.25.37",
"19.34.42",
"19.43.41",
"19.47.53",
"20.07.39",
"20.12.46",
)
)
execute {
addResources("youtube", "misc.zoomhaptics.zoomHapticsPatch")
PreferenceScreen.MISC.addPreferences(
SwitchPreference("revanced_disable_zoom_haptics"),
)
zoomHapticsFingerprint.method.apply {
addInstructionsWithLabels(
0,
"""
invoke-static { }, Lapp/revanced/extension/youtube/patches/ZoomHapticsPatch;->shouldVibrate()Z
move-result v0
if-nez v0, :vibrate
return-void
""",
ExternalLabel("vibrate", getInstruction(0)),
)
}
}
}

View File

@@ -3,10 +3,10 @@ package app.revanced.patches.yuka.misc.unlockpremium
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
@Deprecated("This patch no longer works and will be removed in the future.")
@Suppress("unused") @Suppress("unused")
val unlockPremiumPatch = bytecodePatch( val unlockPremiumPatch = bytecodePatch {
name = "Unlock premium",
) {
compatibleWith("io.yuka.android"("4.29")) compatibleWith("io.yuka.android"("4.29"))
execute { execute {

View File

@@ -133,6 +133,7 @@ internal val Instruction.registersUsed: List<Int>
else -> listOf(registerC, registerD, registerE, registerF, registerG) else -> listOf(registerC, registerD, registerE, registerF, registerG)
} }
} }
is ThreeRegisterInstruction -> listOf(registerA, registerB, registerC) is ThreeRegisterInstruction -> listOf(registerA, registerB, registerC)
is TwoRegisterInstruction -> listOf(registerA, registerB) is TwoRegisterInstruction -> listOf(registerA, registerB)
is OneRegisterInstruction -> listOf(registerA) is OneRegisterInstruction -> listOf(registerA)
@@ -170,7 +171,7 @@ internal val Instruction.isReturnInstruction: Boolean
/** /**
* Adds public [AccessFlags] and removes private and protected flags (if present). * Adds public [AccessFlags] and removes private and protected flags (if present).
*/ */
internal fun Int.toPublicAccessFlags() : Int { internal fun Int.toPublicAccessFlags(): Int {
return this.or(AccessFlags.PUBLIC.value) return this.or(AccessFlags.PUBLIC.value)
.and(AccessFlags.PROTECTED.value.inv()) .and(AccessFlags.PROTECTED.value.inv())
.and(AccessFlags.PRIVATE.value.inv()) .and(AccessFlags.PRIVATE.value.inv())
@@ -489,9 +490,10 @@ fun Method.indexOfFirstInstruction(targetOpcode: Opcode): Int = indexOfFirstInst
* @return The index of the first opcode specified, or -1 if not found. * @return The index of the first opcode specified, or -1 if not found.
* @see indexOfFirstInstructionOrThrow * @see indexOfFirstInstructionOrThrow
*/ */
fun Method.indexOfFirstInstruction(startIndex: Int = 0, targetOpcode: Opcode): Int = indexOfFirstInstruction(startIndex) { fun Method.indexOfFirstInstruction(startIndex: Int = 0, targetOpcode: Opcode): Int =
opcode == targetOpcode indexOfFirstInstruction(startIndex) {
} opcode == targetOpcode
}
/** /**
* Get the index of the first [Instruction] that matches the predicate, starting from [startIndex]. * Get the index of the first [Instruction] that matches the predicate, starting from [startIndex].
@@ -526,9 +528,10 @@ fun Method.indexOfFirstInstructionOrThrow(targetOpcode: Opcode): Int = indexOfFi
* @throws PatchException * @throws PatchException
* @see indexOfFirstInstruction * @see indexOfFirstInstruction
*/ */
fun Method.indexOfFirstInstructionOrThrow(startIndex: Int = 0, targetOpcode: Opcode): Int = indexOfFirstInstructionOrThrow(startIndex) { fun Method.indexOfFirstInstructionOrThrow(startIndex: Int = 0, targetOpcode: Opcode): Int =
opcode == targetOpcode indexOfFirstInstructionOrThrow(startIndex) {
} opcode == targetOpcode
}
/** /**
* Get the index of the first [Instruction] that matches the predicate, starting from [startIndex]. * Get the index of the first [Instruction] that matches the predicate, starting from [startIndex].
@@ -554,9 +557,10 @@ fun Method.indexOfFirstInstructionOrThrow(startIndex: Int = 0, filter: Instructi
* @return -1 if the instruction is not found. * @return -1 if the instruction is not found.
* @see indexOfFirstInstructionReversedOrThrow * @see indexOfFirstInstructionReversedOrThrow
*/ */
fun Method.indexOfFirstInstructionReversed(startIndex: Int? = null, targetOpcode: Opcode): Int = indexOfFirstInstructionReversed(startIndex) { fun Method.indexOfFirstInstructionReversed(startIndex: Int? = null, targetOpcode: Opcode): Int =
opcode == targetOpcode indexOfFirstInstructionReversed(startIndex) {
} opcode == targetOpcode
}
/** /**
* Get the index of matching instruction, * Get the index of matching instruction,
@@ -593,9 +597,10 @@ fun Method.indexOfFirstInstructionReversed(targetOpcode: Opcode): Int = indexOfF
* @return The index of the instruction. * @return The index of the instruction.
* @see indexOfFirstInstructionReversed * @see indexOfFirstInstructionReversed
*/ */
fun Method.indexOfFirstInstructionReversedOrThrow(startIndex: Int? = null, targetOpcode: Opcode): Int = indexOfFirstInstructionReversedOrThrow(startIndex) { fun Method.indexOfFirstInstructionReversedOrThrow(startIndex: Int? = null, targetOpcode: Opcode): Int =
opcode == targetOpcode indexOfFirstInstructionReversedOrThrow(startIndex) {
} opcode == targetOpcode
}
/** /**
* Get the index of matching instruction, * Get the index of matching instruction,
@@ -652,7 +657,8 @@ fun Method.findInstructionIndicesReversedOrThrow(filter: Instruction.() -> Boole
* _Returns an empty list if no indices are found_ * _Returns an empty list if no indices are found_
* @see findInstructionIndicesReversedOrThrow * @see findInstructionIndicesReversedOrThrow
*/ */
fun Method.findInstructionIndicesReversed(opcode: Opcode): List<Int> = findInstructionIndicesReversed { this.opcode == opcode } fun Method.findInstructionIndicesReversed(opcode: Opcode): List<Int> =
findInstructionIndicesReversed { this.opcode == opcode }
/** /**
* @return An immutable list of indices of the opcode in reverse order. * @return An immutable list of indices of the opcode in reverse order.
@@ -726,43 +732,222 @@ fun BytecodePatchContext.forEachLiteralValueInstruction(
} }
} }
/** private const val RETURN_TYPE_MISMATCH = "Mismatch between override type and Method return type"
* Overrides the first instruction of a method with a constant return value.
* None of the method code will ever execute.
*/
fun MutableMethod.returnEarly(overrideValue: Boolean = false) = overrideReturnValue(overrideValue, false)
/** /**
* Overrides all return statements with a constant value. * Overrides the first instruction of a method with a constant `Boolean` return value.
* None of the method code will ever execute.
*
* For methods that return an object or any array type, calling this method with `false`
* will force the method to return a `null` value.
*/
fun MutableMethod.returnEarly(value: Boolean = false) {
val returnType = returnType.first()
check(returnType == 'Z' || (!value && (returnType in setOf('V', 'L', '[')))) { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toHexString(), false)
}
/**
* Overrides the first instruction of a method with a constant `Byte` return value.
* None of the method code will ever execute.
*/
fun MutableMethod.returnEarly(value: Byte) {
check(returnType.first() == 'B') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), false)
}
/**
* Overrides the first instruction of a method with a constant `Short` return value.
* None of the method code will ever execute.
*/
fun MutableMethod.returnEarly(value: Short) {
check(returnType.first() == 'S') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), false)
}
/**
* Overrides the first instruction of a method with a constant `Char` return value.
* None of the method code will ever execute.
*/
fun MutableMethod.returnEarly(value: Char) {
check(returnType.first() == 'C') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.code.toString(), false)
}
/**
* Overrides the first instruction of a method with a constant `Int` return value.
* None of the method code will ever execute.
*/
fun MutableMethod.returnEarly(value: Int) {
check(returnType.first() == 'I') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), false)
}
/**
* Overrides the first instruction of a method with a constant `Long` return value.
* None of the method code will ever execute.
*/
fun MutableMethod.returnEarly(value: Long) {
check(returnType.first() == 'J') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), false)
}
/**
* Overrides the first instruction of a method with a constant `Float` return value.
* None of the method code will ever execute.
*/
fun MutableMethod.returnEarly(value: Float) {
check(returnType.first() == 'F') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), false)
}
/**
* Overrides the first instruction of a method with a constant `Double` return value.
* None of the method code will ever execute.
*/
fun MutableMethod.returnEarly(value: Double) {
check(returnType.first() == 'J') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), false)
}
/**
* Overrides all return statements with a constant `Boolean` value.
* All method code is executed the same as unpatched.
*
* For methods that return an object or any array type, calling this method with `false`
* will force the method to return a `null` value.
*
* @see returnEarly
*/
fun MutableMethod.returnLate(value: Boolean) {
val returnType = returnType.first()
if (returnType == 'V') {
error("Cannot return late for Method of void type")
}
check(returnType == 'Z' || (!value && returnType in setOf('L', '['))) { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toHexString(), true)
}
/**
* Overrides all return statements with a constant `Byte` value.
* All method code is executed the same as unpatched. * All method code is executed the same as unpatched.
* *
* @see returnEarly * @see returnEarly
*/ */
internal fun MutableMethod.returnLate(overrideValue: Boolean = false) = overrideReturnValue(overrideValue, true) fun MutableMethod.returnLate(value: Byte) {
check(returnType.first() == 'B') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), true)
}
private fun MutableMethod.overrideReturnValue(bool: Boolean, returnLate: Boolean) { /**
val const = if (bool) "0x1" else "0x0" * Overrides all return statements with a constant `Short` value.
* All method code is executed the same as unpatched.
*
* @see returnEarly
*/
fun MutableMethod.returnLate(value: Short) {
check(returnType.first() == 'S') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), true)
}
/**
* Overrides all return statements with a constant `Char` value.
* All method code is executed the same as unpatched.
*
* @see returnEarly
*/
fun MutableMethod.returnLate(value: Char) {
check(returnType.first() == 'C') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.code.toString(), true)
}
/**
* Overrides all return statements with a constant `Int` value.
* All method code is executed the same as unpatched.
*
* @see returnEarly
*/
fun MutableMethod.returnLate(value: Int) {
check(returnType.first() == 'I') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), true)
}
/**
* Overrides all return statements with a constant `Long` value.
* All method code is executed the same as unpatched.
*
* @see returnEarly
*/
fun MutableMethod.returnLate(value: Long) {
check(returnType.first() == 'J') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), true)
}
/**
* Overrides all return statements with a constant `Float` value.
* All method code is executed the same as unpatched.
*
* @see returnEarly
*/
fun MutableMethod.returnLate(value: Float) {
check(returnType.first() == 'F') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), true)
}
/**
* Overrides all return statements with a constant `Double` value.
* All method code is executed the same as unpatched.
*
* @see returnEarly
*/
fun MutableMethod.returnLate(value: Double) {
check(returnType.first() == 'D') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), true)
}
private fun MutableMethod.overrideReturnValue(value: String, returnLate: Boolean) {
val instructions = when (returnType.first()) { val instructions = when (returnType.first()) {
'L' -> { // If return type is an object, always return null.
'L', '[' -> {
""" """
const/4 v0, $const const/4 v0, 0x0
return-object v0 return-object v0
""" """
} }
'V' -> { 'V' -> {
if (returnLate) throw IllegalArgumentException("Cannot return late for method of void type")
"return-void" "return-void"
} }
'I', 'Z' -> { 'B', 'Z' -> {
""" """
const/4 v0, $const const/4 v0, $value
return v0 return v0
""" """
} }
'S', 'C' -> {
"""
const/16 v0, $value
return v0
"""
}
'I', 'F' -> {
"""
const v0, $value
return v0
"""
}
'J', 'D' -> {
"""
const-wide v0, $value
return-wide v0
"""
}
else -> throw Exception("Return type is not supported: $this") else -> throw Exception("Return type is not supported: $this")
} }

View File

@@ -6,3 +6,5 @@ internal object Utils {
.joinToString("\n") { it.trimIndent() } // Remove the leading whitespace from each line. .joinToString("\n") { it.trimIndent() } // Remove the leading whitespace from each line.
.trimIndent() // Remove the leading newline. .trimIndent() // Remove the leading newline.
} }
internal fun Boolean.toHexString(): String = if (this) "0x1" else "0x0"

View File

@@ -44,7 +44,7 @@ Second \"item\" text"</string>
<!-- 'For you' should be translated using the same localized wording YouTube displays. --> <!-- 'For you' should be translated using the same localized wording YouTube displays. -->
<!-- 'Notify me' should be translated using the same localized wording YouTube displays. <!-- 'Notify me' should be translated using the same localized wording YouTube displays.
This item appear in the subscription feed for future livestreams or unreleased videos. --> This item appear in the subscription feed for future livestreams or unreleased videos. -->
<!-- 'People also watched' should be translated using the same localized wording YouTube displays. --> <!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. -->
<!-- 'Show more' should be translated with the same localized wording that YouTube displays. <!-- 'Show more' should be translated with the same localized wording that YouTube displays.
This button usually appears when searching for a YT creator. --> This button usually appears when searching for a YT creator. -->
<!-- https://logos.fandom.com/wiki/YouTube/Yoodles --> <!-- https://logos.fandom.com/wiki/YouTube/Yoodles -->
@@ -199,6 +199,8 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.gms.gmsCoreSupportResourcePatch"> <patch id="misc.gms.gmsCoreSupportResourcePatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
</patch>
<patch id="misc.gms.accountCredentialsInvalidTextPatch"> <patch id="misc.gms.accountCredentialsInvalidTextPatch">
</patch> </patch>
<patch id="misc.links.bypassURLRedirectsPatch"> <patch id="misc.links.bypassURLRedirectsPatch">
@@ -207,8 +209,6 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.privacy.removeTrackingQueryParameterPatch"> <patch id="misc.privacy.removeTrackingQueryParameterPatch">
</patch> </patch>
<patch id="misc.zoomhaptics.zoomHapticsPatch">
</patch>
<patch id="video.audio.forceOriginalAudioPatch"> <patch id="video.audio.forceOriginalAudioPatch">
<!-- 'Spoof video streams' should be the same translation used for revanced_spoof_video_streams_screen_title --> <!-- 'Spoof video streams' should be the same translation used for revanced_spoof_video_streams_screen_title -->
</patch> </patch>

View File

@@ -44,7 +44,7 @@ Second \"item\" text"</string>
<!-- 'For you' should be translated using the same localized wording YouTube displays. --> <!-- 'For you' should be translated using the same localized wording YouTube displays. -->
<!-- 'Notify me' should be translated using the same localized wording YouTube displays. <!-- 'Notify me' should be translated using the same localized wording YouTube displays.
This item appear in the subscription feed for future livestreams or unreleased videos. --> This item appear in the subscription feed for future livestreams or unreleased videos. -->
<!-- 'People also watched' should be translated using the same localized wording YouTube displays. --> <!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. -->
<!-- 'Show more' should be translated with the same localized wording that YouTube displays. <!-- 'Show more' should be translated with the same localized wording that YouTube displays.
This button usually appears when searching for a YT creator. --> This button usually appears when searching for a YT creator. -->
<!-- https://logos.fandom.com/wiki/YouTube/Yoodles --> <!-- https://logos.fandom.com/wiki/YouTube/Yoodles -->
@@ -199,6 +199,8 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.gms.gmsCoreSupportResourcePatch"> <patch id="misc.gms.gmsCoreSupportResourcePatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
</patch>
<patch id="misc.gms.accountCredentialsInvalidTextPatch"> <patch id="misc.gms.accountCredentialsInvalidTextPatch">
</patch> </patch>
<patch id="misc.links.bypassURLRedirectsPatch"> <patch id="misc.links.bypassURLRedirectsPatch">
@@ -207,8 +209,6 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.privacy.removeTrackingQueryParameterPatch"> <patch id="misc.privacy.removeTrackingQueryParameterPatch">
</patch> </patch>
<patch id="misc.zoomhaptics.zoomHapticsPatch">
</patch>
<patch id="video.audio.forceOriginalAudioPatch"> <patch id="video.audio.forceOriginalAudioPatch">
<!-- 'Spoof video streams' should be the same translation used for revanced_spoof_video_streams_screen_title --> <!-- 'Spoof video streams' should be the same translation used for revanced_spoof_video_streams_screen_title -->
</patch> </patch>

View File

@@ -35,6 +35,8 @@ Second \"item\" text"</string>
<string name="revanced_settings_submenu_title">الإعدادات</string> <string name="revanced_settings_submenu_title">الإعدادات</string>
<string name="revanced_settings_confirm_user_dialog_title">هل ترغب في المتابعة؟</string> <string name="revanced_settings_confirm_user_dialog_title">هل ترغب في المتابعة؟</string>
<string name="revanced_settings_reset">إعادة التعيين</string> <string name="revanced_settings_reset">إعادة التعيين</string>
<string name="revanced_settings_reset_color">إعادة تعيين اللون</string>
<string name="revanced_settings_color_invalid">لون غير صالح</string>
<string name="revanced_settings_restart_title">تحديث وإعادة التشغيل</string> <string name="revanced_settings_restart_title">تحديث وإعادة التشغيل</string>
<string name="revanced_settings_restart">إعادة التشغيل</string> <string name="revanced_settings_restart">إعادة التشغيل</string>
<string name="revanced_settings_import">استيراد</string> <string name="revanced_settings_import">استيراد</string>
@@ -42,6 +44,10 @@ Second \"item\" text"</string>
<string name="revanced_settings_import_reset">إعادة تعيين إعدادات ReVanced إلى الوضع الافتراضي</string> <string name="revanced_settings_import_reset">إعادة تعيين إعدادات ReVanced إلى الوضع الافتراضي</string>
<string name="revanced_settings_import_success">تم استيراد %d إعدادات</string> <string name="revanced_settings_import_success">تم استيراد %d إعدادات</string>
<string name="revanced_settings_import_failure_parse">فشل الاستيراد: %s</string> <string name="revanced_settings_import_failure_parse">فشل الاستيراد: %s</string>
<string name="revanced_settings_search_hint">بحث الإعدادات</string>
<string name="revanced_settings_search_no_results_title">لم يتم العثور على نتائج لـ \".%s\"</string>
<string name="revanced_settings_search_no_results_summary">جرّب كلمة مفتاحية أخرى</string>
<string name="revanced_settings_search_remove_message">إزالة من سجل البحث؟</string>
<string name="revanced_show_menu_icons_title">عرض أيقونات إعدادات ReVanced</string> <string name="revanced_show_menu_icons_title">عرض أيقونات إعدادات ReVanced</string>
<string name="revanced_show_menu_icons_summary_on">يتم عرض أيقونات الإعدادات</string> <string name="revanced_show_menu_icons_summary_on">يتم عرض أيقونات الإعدادات</string>
<string name="revanced_show_menu_icons_summary_off">لا يتم عرض أيقونات الإعدادات</string> <string name="revanced_show_menu_icons_summary_off">لا يتم عرض أيقونات الإعدادات</string>
@@ -93,6 +99,9 @@ Second \"item\" text"</string>
<string name="revanced_restore_old_settings_menus_title">استعادة قوائم الإعدادات القديمة</string> <string name="revanced_restore_old_settings_menus_title">استعادة قوائم الإعدادات القديمة</string>
<string name="revanced_restore_old_settings_menus_summary_on">يتم عرض قوائم الإعدادات القديمة</string> <string name="revanced_restore_old_settings_menus_summary_on">يتم عرض قوائم الإعدادات القديمة</string>
<string name="revanced_restore_old_settings_menus_summary_off">لا يتم عرض قوائم الإعدادات القديمة</string> <string name="revanced_restore_old_settings_menus_summary_off">لا يتم عرض قوائم الإعدادات القديمة</string>
<string name="revanced_settings_search_history_title">عرض سجل بحث الإعدادات</string>
<string name="revanced_settings_search_history_summary_on">يتم عرض سجل البحث في الإعدادات</string>
<string name="revanced_settings_search_history_summary_off">لا يتم عرض سجل البحث في الإعدادات</string>
</patch> </patch>
<patch id="misc.backgroundplayback.backgroundPlaybackPatch"> <patch id="misc.backgroundplayback.backgroundPlaybackPatch">
<string name="revanced_shorts_disable_background_playback_title">تعطيل تشغيل فيديوهات Shorts في الخلفية</string> <string name="revanced_shorts_disable_background_playback_title">تعطيل تشغيل فيديوهات Shorts في الخلفية</string>
@@ -108,6 +117,11 @@ Second \"item\" text"</string>
<string name="revanced_debug_protobuffer_title">سجل بروتوكول التخزين المؤقت</string> <string name="revanced_debug_protobuffer_title">سجل بروتوكول التخزين المؤقت</string>
<string name="revanced_debug_protobuffer_summary_on">تتضمن سجلات التصحيح التخزين المؤقت</string> <string name="revanced_debug_protobuffer_summary_on">تتضمن سجلات التصحيح التخزين المؤقت</string>
<string name="revanced_debug_protobuffer_summary_off">لا تتضمن سجلات التصحيح التخزين المؤقت</string> <string name="revanced_debug_protobuffer_summary_off">لا تتضمن سجلات التصحيح التخزين المؤقت</string>
<string name="revanced_debug_protobuffer_user_dialog_message">"سيؤدي تمكين هذا الإعداد إلى تسجيل بيانات تخطيط إضافية، بما في ذلك النص المعروض على الشاشة لبعض مكونات واجهة المستخدم.
يمكن أن يساعد هذا في تحديد المكونات عند إنشاء عوامل تصفية مخصصة.
ومع ذلك، سيؤدي تمكين هذا أيضًا إلى تسجيل بعض بيانات المستخدم مثل عنوان IP الخاص بك."</string>
<string name="revanced_debug_stacktrace_title">سجل تتبع المكدس</string> <string name="revanced_debug_stacktrace_title">سجل تتبع المكدس</string>
<string name="revanced_debug_stacktrace_summary_on">تتضمن سجلات التصحيح سجل تتبع المكدس</string> <string name="revanced_debug_stacktrace_summary_on">تتضمن سجلات التصحيح سجل تتبع المكدس</string>
<string name="revanced_debug_stacktrace_summary_off">لا تتضمن سجلات التصحيح سجل تتبع المكدس</string> <string name="revanced_debug_stacktrace_summary_off">لا تتضمن سجلات التصحيح سجل تتبع المكدس</string>
@@ -117,6 +131,15 @@ Second \"item\" text"</string>
<string name="revanced_debug_toast_on_error_user_dialog_message">"يؤدي إيقاف تشغيل ملاحظات الأخطاء إلى إخفاء كافة إشعارات أخطاء ReVanced. <string name="revanced_debug_toast_on_error_user_dialog_message">"يؤدي إيقاف تشغيل ملاحظات الأخطاء إلى إخفاء كافة إشعارات أخطاء ReVanced.
لن يتم إعلامك بأي أخطاء غير متوقعة."</string> لن يتم إعلامك بأي أخطاء غير متوقعة."</string>
<string name="revanced_debug_export_logs_to_clipboard_title">تصدير سجلات تصحيح الأخطاء</string>
<string name="revanced_debug_export_logs_to_clipboard_summary">نسخ سجلات تصحيح أخطاء ReVanced إلى الحافظة</string>
<string name="revanced_debug_logs_disabled">تم تعطيل تسجيلات تصحيح الأخطاء</string>
<string name="revanced_debug_logs_none_found">لم يتم العثور على سجلات</string>
<string name="revanced_debug_logs_copied_to_clipboard">تم نسخ السجلات</string>
<string name="revanced_debug_logs_failed_to_export">فشل تصدير السجلات: $s</string>
<string name="revanced_debug_logs_clear_buffer_title">مسح سجلات تصحيح الأخطاء</string>
<string name="revanced_debug_logs_clear_buffer_summary">يمسح جميع سجلات تصحيح أخطاء ReVanced المخزنة</string>
<string name="revanced_debug_logs_clear_toast">تم مسح السجلات</string>
</patch> </patch>
<patch id="layout.hide.general.hideLayoutComponentsPatch"> <patch id="layout.hide.general.hideLayoutComponentsPatch">
<string name="revanced_hide_album_cards_title">إخفاء بطاقات الألبوم</string> <string name="revanced_hide_album_cards_title">إخفاء بطاقات الألبوم</string>
@@ -153,15 +176,18 @@ Second \"item\" text"</string>
<string name="revanced_hide_notify_me_button_title">إخفاء زر \'تنبيهي\'</string> <string name="revanced_hide_notify_me_button_title">إخفاء زر \'تنبيهي\'</string>
<string name="revanced_hide_notify_me_button_summary_on">تم إخفاء الزر</string> <string name="revanced_hide_notify_me_button_summary_on">تم إخفاء الزر</string>
<string name="revanced_hide_notify_me_button_summary_off">يتم عرض الزر</string> <string name="revanced_hide_notify_me_button_summary_off">يتم عرض الزر</string>
<!-- 'People also watched' should be translated using the same localized wording YouTube displays. --> <!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. -->
<string name="revanced_hide_search_result_recommendations_title">إخفاء علامة \'الأشخاص الذين شاهدوا أيضًا\'</string> <string name="revanced_hide_search_result_recommendation_labels_title">إخفاء علامات اقتراحات الفيديو</string>
<string name="revanced_hide_search_result_recommendations_summary_on">تم إخفاء العلامة</string> <string name="revanced_hide_search_result_recommendation_labels_summary_on">تم إخفاء علامات \'اقتراحات للمشاهدة\' و\'قد يعجبك أيضًا\'</string>
<string name="revanced_hide_search_result_recommendations_summary_off">يتم عرض العلامة</string> <string name="revanced_hide_search_result_recommendation_labels_summary_off">يتم عرض علامات \'اقتراحات للمشاهدة\' و\'قد يعجبك أيضًا\'</string>
<!-- 'Show more' should be translated with the same localized wording that YouTube displays. <!-- 'Show more' should be translated with the same localized wording that YouTube displays.
This button usually appears when searching for a YT creator. --> This button usually appears when searching for a YT creator. -->
<string name="revanced_hide_show_more_button_title">إخفاء زر \'عرض المزيد\'</string> <string name="revanced_hide_show_more_button_title">إخفاء زر \'عرض المزيد\'</string>
<string name="revanced_hide_show_more_button_summary_on">تم إخفاء الزر</string> <string name="revanced_hide_show_more_button_summary_on">تم إخفاء الزر</string>
<string name="revanced_hide_show_more_button_summary_off">يتم عرض الزر</string> <string name="revanced_hide_show_more_button_summary_off">يتم عرض الزر</string>
<string name="revanced_hide_ticket_shelf_title">إخفاء رف التذاكر</string>
<string name="revanced_hide_ticket_shelf_summary_on">تم إخفاء رف التذاكر</string>
<string name="revanced_hide_ticket_shelf_summary_off">يتم عرض رف التذاكر</string>
<string name="revanced_hide_timed_reactions_title">إخفاء ردود الفعل المؤقتة</string> <string name="revanced_hide_timed_reactions_title">إخفاء ردود الفعل المؤقتة</string>
<string name="revanced_hide_timed_reactions_summary_on">تم إخفاء ردود الفعل المؤقتة</string> <string name="revanced_hide_timed_reactions_summary_on">تم إخفاء ردود الفعل المؤقتة</string>
<string name="revanced_hide_timed_reactions_summary_off">يتم عرض ردود الفعل المؤقتة</string> <string name="revanced_hide_timed_reactions_summary_off">يتم عرض ردود الفعل المؤقتة</string>
@@ -231,6 +257,9 @@ Second \"item\" text"</string>
<string name="revanced_hide_ai_generated_video_summary_section_title">إخفاء \'ملخص الفيديو الذي تم إنشاؤه بواسطة الذكاء الاصطناعي\'</string> <string name="revanced_hide_ai_generated_video_summary_section_title">إخفاء \'ملخص الفيديو الذي تم إنشاؤه بواسطة الذكاء الاصطناعي\'</string>
<string name="revanced_hide_ai_generated_video_summary_section_summary_on">تم إخفاء قسم ملخص الفيديو</string> <string name="revanced_hide_ai_generated_video_summary_section_summary_on">تم إخفاء قسم ملخص الفيديو</string>
<string name="revanced_hide_ai_generated_video_summary_section_summary_off">يتم عرض قسم ملخص الفيديو</string> <string name="revanced_hide_ai_generated_video_summary_section_summary_off">يتم عرض قسم ملخص الفيديو</string>
<string name="revanced_hide_ask_section_title">إخفاء \"Ask\"</string>
<string name="revanced_hide_ask_section_summary_on">تم إخفاء قسم \"Ask\"</string>
<string name="revanced_hide_ask_section_summary_off">يتم عرض قسم \"Ask\"</string>
<string name="revanced_hide_attributes_section_title">إخفاء الصفات</string> <string name="revanced_hide_attributes_section_title">إخفاء الصفات</string>
<string name="revanced_hide_attributes_section_summary_on">تم إخفاء أقسام الأماكن المميزة، الألعاب، الموسيقى والأشخاص المذكورون</string> <string name="revanced_hide_attributes_section_summary_on">تم إخفاء أقسام الأماكن المميزة، الألعاب، الموسيقى والأشخاص المذكورون</string>
<string name="revanced_hide_attributes_section_summary_off">يتم عرض أقسام الأماكن المميزة، الألعاب، الموسيقى والأشخاص المذكورون</string> <string name="revanced_hide_attributes_section_summary_off">يتم عرض أقسام الأماكن المميزة، الألعاب، الموسيقى والأشخاص المذكورون</string>
@@ -352,9 +381,6 @@ Second \"item\" text"</string>
هذه الميزة متاحة فقط للأجهزة القديمة"</string> هذه الميزة متاحة فقط للأجهزة القديمة"</string>
<string name="revanced_hide_fullscreen_ads_summary_off">يتم عرض إعلانات ملء الشاشة</string> <string name="revanced_hide_fullscreen_ads_summary_off">يتم عرض إعلانات ملء الشاشة</string>
<string name="revanced_hide_buttoned_ads_title">إخفاء الإعلانات الزرية</string>
<string name="revanced_hide_buttoned_ads_summary_on">تم إخفاء الإعلانات الزرية</string>
<string name="revanced_hide_buttoned_ads_summary_off">يتم عرض الإعلانات الزرية</string>
<string name="revanced_hide_paid_promotion_label_title">إخفاء تسمية الترقية المدفوعة</string> <string name="revanced_hide_paid_promotion_label_title">إخفاء تسمية الترقية المدفوعة</string>
<string name="revanced_hide_paid_promotion_label_summary_on">تم إخفاء تسمية الترقية المدفوعة</string> <string name="revanced_hide_paid_promotion_label_summary_on">تم إخفاء تسمية الترقية المدفوعة</string>
<string name="revanced_hide_paid_promotion_label_summary_off">يتم عرض تسمية الترقية المدفوعة</string> <string name="revanced_hide_paid_promotion_label_summary_off">يتم عرض تسمية الترقية المدفوعة</string>
@@ -465,9 +491,10 @@ Second \"item\" text"</string>
<string name="revanced_swipe_overlay_background_opacity_title">تعتيم خلفية واجهة التمرير السريع</string> <string name="revanced_swipe_overlay_background_opacity_title">تعتيم خلفية واجهة التمرير السريع</string>
<string name="revanced_swipe_overlay_background_opacity_summary">قيمة التعتيم بين 0-100</string> <string name="revanced_swipe_overlay_background_opacity_summary">قيمة التعتيم بين 0-100</string>
<string name="revanced_swipe_overlay_background_opacity_invalid_toast">يجب أن يكون تعتيم التمرير السريع بين 0-100</string> <string name="revanced_swipe_overlay_background_opacity_invalid_toast">يجب أن يكون تعتيم التمرير السريع بين 0-100</string>
<string name="revanced_swipe_overlay_progress_color_title">لون شريط تقدم واجهة التمرير</string> <string name="revanced_swipe_overlay_progress_brightness_color_title">لون سطوع واجهة التمرير</string>
<string name="revanced_swipe_overlay_progress_color_summary">لون شريط التقدم لعناصر التحكم في مستوى الصوت والسطوع</string> <string name="revanced_swipe_overlay_progress_brightness_color_summary">لون شريط التقدم لعناصر التحكم في السطوع</string>
<string name="revanced_swipe_overlay_progress_color_invalid_toast">لون شريط التقدم غير صالح</string> <string name="revanced_swipe_overlay_progress_volume_color_title">لون مستوى صوت واجهة التمرير</string>
<string name="revanced_swipe_overlay_progress_volume_color_summary">لون شريط التقدم لعناصر التحكم في مستوى الصوت</string>
<string name="revanced_swipe_text_overlay_size_title">حجم نص واجهة التمرير</string> <string name="revanced_swipe_text_overlay_size_title">حجم نص واجهة التمرير</string>
<string name="revanced_swipe_text_overlay_size_summary">حجم النص لواجهة التمرير بين 1-30</string> <string name="revanced_swipe_text_overlay_size_summary">حجم النص لواجهة التمرير بين 1-30</string>
<string name="revanced_swipe_text_overlay_size_invalid_toast">يجب أن يكون حجم النص بين 1-30</string> <string name="revanced_swipe_text_overlay_size_invalid_toast">يجب أن يكون حجم النص بين 1-30</string>
@@ -836,10 +863,10 @@ Second \"item\" text"</string>
<string name="revanced_ryd_failure_ryd_enabled_while_playing_video_then_user_voted">أعد تحميل الفيديو للتصويت بـ Return YouTube Dislike</string> <string name="revanced_ryd_failure_ryd_enabled_while_playing_video_then_user_voted">أعد تحميل الفيديو للتصويت بـ Return YouTube Dislike</string>
<!-- Video likes have been set to hidden by the video uploader. --> <!-- Video likes have been set to hidden by the video uploader. -->
<string name="revanced_ryd_video_likes_hidden_by_video_owner">مخفي بواسطة المالك</string> <string name="revanced_ryd_video_likes_hidden_by_video_owner">مخفي بواسطة المالك</string>
<string name="revanced_ryd_enable_summary_on">يتم عرض لم يعجبني</string> <string name="revanced_ryd_enabled_summary_on">يتم عرض لم يعجبني</string>
<string name="revanced_ryd_enable_summary_off">لا يتم عرض لم يعجبني</string> <string name="revanced_ryd_enabled_summary_off">لا يتم عرض لم يعجبني</string>
<string name="revanced_ryd_shorts_title">عرض لم يعجني في فيديوهات Shorts</string> <string name="revanced_ryd_shorts_title">عرض لم يعجني في فيديوهات Shorts</string>
<string name="revanced_ryd_shorts_summary_on_disclaimer">"يتم عرض مرات عدم الإعجاب في فيديوهات Shorts <string name="revanced_ryd_shorts_summary_on">"يتم عرض مرات عدم الإعجاب في فيديوهات Shorts
التقييد: قد لا تظهر مرات عدم الإعجاب في وضع التصفح المتخفي"</string> التقييد: قد لا تظهر مرات عدم الإعجاب في وضع التصفح المتخفي"</string>
<string name="revanced_ryd_shorts_summary_off">لا يتم عرض مرات عدم الإعجاب في فيديوهات Shorts</string> <string name="revanced_ryd_shorts_summary_off">لا يتم عرض مرات عدم الإعجاب في فيديوهات Shorts</string>
@@ -856,7 +883,6 @@ Second \"item\" text"</string>
<string name="revanced_ryd_toast_on_connection_error_title">عرض ملاحظة إذا كان API غير متاح</string> <string name="revanced_ryd_toast_on_connection_error_title">عرض ملاحظة إذا كان API غير متاح</string>
<string name="revanced_ryd_toast_on_connection_error_summary_on">يتم عرض الملاحظة في حالة عدم توفر Return YouTube Dislike</string> <string name="revanced_ryd_toast_on_connection_error_summary_on">يتم عرض الملاحظة في حالة عدم توفر Return YouTube Dislike</string>
<string name="revanced_ryd_toast_on_connection_error_summary_off">لا يتم عرض الملاحظة في حالة عدم توفر Return YouTube Dislike</string> <string name="revanced_ryd_toast_on_connection_error_summary_off">لا يتم عرض الملاحظة في حالة عدم توفر Return YouTube Dislike</string>
<string name="revanced_ryd_about">لمحة</string>
<string name="revanced_ryd_attribution_summary">يتم توفير البيانات بواسطة the Return YouTube Dislike API. اضغط هنا لمعرفة المزيد</string> <string name="revanced_ryd_attribution_summary">يتم توفير البيانات بواسطة the Return YouTube Dislike API. اضغط هنا لمعرفة المزيد</string>
<!-- Statistic strings are shown in the settings only when ReVanced debug mode is enabled. Typical users will never see these. --> <!-- Statistic strings are shown in the settings only when ReVanced debug mode is enabled. Typical users will never see these. -->
<string name="revanced_ryd_statistics_category_title">ReturnYouTubeDislike API إحصائيات هذا الجهاز</string> <string name="revanced_ryd_statistics_category_title">ReturnYouTubeDislike API إحصائيات هذا الجهاز</string>
@@ -1063,7 +1089,7 @@ Second \"item\" text"</string>
<string name="revanced_sb_new_segment_edit_by_hand_title">تعديل توقيت المقطع يدويًا</string> <string name="revanced_sb_new_segment_edit_by_hand_title">تعديل توقيت المقطع يدويًا</string>
<string name="revanced_sb_new_segment_edit_by_hand_content">هل تود تعديل التوقيت لبداية أو نهاية المقطع؟</string> <string name="revanced_sb_new_segment_edit_by_hand_content">هل تود تعديل التوقيت لبداية أو نهاية المقطع؟</string>
<string name="revanced_sb_new_segment_edit_by_hand_parse_error">الوقت المحدد غير صحيح</string> <string name="revanced_sb_new_segment_edit_by_hand_parse_error">الوقت المحدد غير صحيح</string>
<string name="revanced_sb_stats">إحصائيات</string> <string name="revanced_sb_stats_title">إحصائيات</string>
<!-- Shown in the settings preferences, and translations can be any text length. --> <!-- Shown in the settings preferences, and translations can be any text length. -->
<string name="revanced_sb_stats_connection_failure">الإحصائيات غير متوفرة مؤقتًا (الواجهة غير متوفرة)</string> <string name="revanced_sb_stats_connection_failure">الإحصائيات غير متوفرة مؤقتًا (الواجهة غير متوفرة)</string>
<string name="revanced_sb_stats_loading">جارٍ التحميل...</string> <string name="revanced_sb_stats_loading">جارٍ التحميل...</string>
@@ -1087,13 +1113,8 @@ Second \"item\" text"</string>
<string name="revanced_sb_stats_saved_second_format">%s ثانية</string> <string name="revanced_sb_stats_saved_second_format">%s ثانية</string>
<string name="revanced_sb_color_opacity_label">الشفافية:</string> <string name="revanced_sb_color_opacity_label">الشفافية:</string>
<string name="revanced_sb_color_dot_label">اللون:</string> <string name="revanced_sb_color_dot_label">اللون:</string>
<string name="revanced_sb_color_changed">تم تغيير اللون</string> <string name="revanced_sb_about_title">لمحة</string>
<string name="revanced_sb_color_reset">إعادة ضبط اللون</string> <string name="revanced_sb_about_api_summary">يتم توفير البيانات بواسطة SponsorBlock API. انقر هنا لمعرفة المزيد ومشاهدة التنزيلات لمنصات أخرى</string>
<string name="revanced_sb_color_invalid">رمز اللون غير صالح</string>
<string name="revanced_sb_reset_color">إعادة تعيين اللون</string>
<string name="revanced_sb_reset">إعادة التعيين</string>
<string name="revanced_sb_about">لمحة</string>
<string name="revanced_sb_about_api_sum">يتم توفير البيانات بواسطة SponsorBlock API. انقر هنا لمعرفة المزيد ومشاهدة التنزيلات لمنصات أخرى</string>
</patch> </patch>
<patch id="layout.formfactor.changeFormFactorPatch"> <patch id="layout.formfactor.changeFormFactorPatch">
<string name="revanced_change_form_factor_title">شكل نموذج التخطيط</string> <string name="revanced_change_form_factor_title">شكل نموذج التخطيط</string>
@@ -1316,6 +1337,22 @@ Second \"item\" text"</string>
<string name="microg_settings_title">إعدادات GmsCore</string> <string name="microg_settings_title">إعدادات GmsCore</string>
<string name="microg_settings_summary">إعدادات لـ GmsCore</string> <string name="microg_settings_summary">إعدادات لـ GmsCore</string>
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
<string name="revanced_disable_haptic_feedback_title">الاهتزاز عند الضغط</string>
<string name="revanced_disable_haptic_feedback_summary">تغيير الاهتزاز عند الضغط</string>
<string name="revanced_disable_haptic_feedback_chapters_title">تعطيل الاهتزاز للفصول</string>
<string name="revanced_disable_haptic_feedback_chapters_summary_on">تم تعطيل الاهتزاز للفصول</string>
<string name="revanced_disable_haptic_feedback_chapters_summary_off">تم تفعيل الاهتزاز للفصول</string>
<string name="revanced_disable_haptic_feedback_precise_seeking_title">تعطيل الاهتزاز عند التمرير الدقيق</string>
<string name="revanced_disable_haptic_feedback_precise_seeking_summary_on">تم تعطيل الاهتزاز الدقيق عند البحث</string>
<string name="revanced_disable_haptic_feedback_precise_seeking_summary_off">تم تفعيل الاهتزاز عند التمرير الدقيق</string>
<string name="revanced_disable_haptic_feedback_seek_undo_title">تعطيل الاهتزاز عند التراجع عن البحث</string>
<string name="revanced_disable_haptic_feedback_seek_undo_summary_on">تم تعطيل الاهتزاز عند التراجع عن البحث</string>
<string name="revanced_disable_haptic_feedback_seek_undo_summary_off">تم تمكين الاهتزاز عند التراجع عن البحث</string>
<string name="revanced_disable_haptic_feedback_zoom_title">تعطيل الاهتزاز عند التكبير</string>
<string name="revanced_disable_haptic_feedback_zoom_summary_on">تم تعطيل الاهتزاز عند التكبير</string>
<string name="revanced_disable_haptic_feedback_zoom_summary_off">تم تمكين الاهتزاز عند التكبير</string>
</patch>
<patch id="misc.gms.accountCredentialsInvalidTextPatch"> <patch id="misc.gms.accountCredentialsInvalidTextPatch">
<string name="microg_offline_account_login_error">إذا قمت مؤخرًا بتغيير تفاصيل تسجيل الدخول إلى حسابك، فأزل تثبيت MicroG ثم أعد تثبيته.</string> <string name="microg_offline_account_login_error">إذا قمت مؤخرًا بتغيير تفاصيل تسجيل الدخول إلى حسابك، فأزل تثبيت MicroG ثم أعد تثبيته.</string>
</patch> </patch>
@@ -1334,11 +1371,6 @@ Second \"item\" text"</string>
<string name="revanced_remove_tracking_query_parameter_summary_on">يتم إزالة معلمة استعلام التتبع من الروابط</string> <string name="revanced_remove_tracking_query_parameter_summary_on">يتم إزالة معلمة استعلام التتبع من الروابط</string>
<string name="revanced_remove_tracking_query_parameter_summary_off">لا يتم إزالة معلمة استعلام التتبع من الروابط</string> <string name="revanced_remove_tracking_query_parameter_summary_off">لا يتم إزالة معلمة استعلام التتبع من الروابط</string>
</patch> </patch>
<patch id="misc.zoomhaptics.zoomHapticsPatch">
<string name="revanced_disable_zoom_haptics_title">تعطيل الاهتزاز عند التكبير</string>
<string name="revanced_disable_zoom_haptics_summary_on">تم تعطيل الاهتزاز</string>
<string name="revanced_disable_zoom_haptics_summary_off">تم تمكين الاهتزاز</string>
</patch>
<patch id="video.audio.forceOriginalAudioPatch"> <patch id="video.audio.forceOriginalAudioPatch">
<string name="revanced_force_original_audio_title">فرض لغة الصوت الأصلية</string> <string name="revanced_force_original_audio_title">فرض لغة الصوت الأصلية</string>
<string name="revanced_force_original_audio_summary_on">استخدام لغة الصوت الأصلية</string> <string name="revanced_force_original_audio_summary_on">استخدام لغة الصوت الأصلية</string>

View File

@@ -44,7 +44,7 @@ Second \"item\" text"</string>
<!-- 'For you' should be translated using the same localized wording YouTube displays. --> <!-- 'For you' should be translated using the same localized wording YouTube displays. -->
<!-- 'Notify me' should be translated using the same localized wording YouTube displays. <!-- 'Notify me' should be translated using the same localized wording YouTube displays.
This item appear in the subscription feed for future livestreams or unreleased videos. --> This item appear in the subscription feed for future livestreams or unreleased videos. -->
<!-- 'People also watched' should be translated using the same localized wording YouTube displays. --> <!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. -->
<!-- 'Show more' should be translated with the same localized wording that YouTube displays. <!-- 'Show more' should be translated with the same localized wording that YouTube displays.
This button usually appears when searching for a YT creator. --> This button usually appears when searching for a YT creator. -->
<!-- https://logos.fandom.com/wiki/YouTube/Yoodles --> <!-- https://logos.fandom.com/wiki/YouTube/Yoodles -->
@@ -201,6 +201,8 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.gms.gmsCoreSupportResourcePatch"> <patch id="misc.gms.gmsCoreSupportResourcePatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
</patch>
<patch id="misc.gms.accountCredentialsInvalidTextPatch"> <patch id="misc.gms.accountCredentialsInvalidTextPatch">
</patch> </patch>
<patch id="misc.links.bypassURLRedirectsPatch"> <patch id="misc.links.bypassURLRedirectsPatch">
@@ -209,8 +211,6 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.privacy.removeTrackingQueryParameterPatch"> <patch id="misc.privacy.removeTrackingQueryParameterPatch">
</patch> </patch>
<patch id="misc.zoomhaptics.zoomHapticsPatch">
</patch>
<patch id="video.audio.forceOriginalAudioPatch"> <patch id="video.audio.forceOriginalAudioPatch">
<!-- 'Spoof video streams' should be the same translation used for revanced_spoof_video_streams_screen_title --> <!-- 'Spoof video streams' should be the same translation used for revanced_spoof_video_streams_screen_title -->
</patch> </patch>

View File

@@ -42,6 +42,10 @@ Second \"item\" text"</string>
<string name="revanced_settings_import_reset">ReVanced tənzimləmələr standarta təyin edildi</string> <string name="revanced_settings_import_reset">ReVanced tənzimləmələr standarta təyin edildi</string>
<string name="revanced_settings_import_success">%d tənzimləmə idxal edildi</string> <string name="revanced_settings_import_success">%d tənzimləmə idxal edildi</string>
<string name="revanced_settings_import_failure_parse">Uğursuz idxal prosesi: %s</string> <string name="revanced_settings_import_failure_parse">Uğursuz idxal prosesi: %s</string>
<string name="revanced_settings_search_hint">Tənzimləmələri axtar</string>
<string name="revanced_settings_search_no_results_title">%s üçün nəticə tapılmadı</string>
<string name="revanced_settings_search_no_results_summary">Başqa açar sözü yoxla</string>
<string name="revanced_settings_search_remove_message">Axtarış tarixçəsindən silinsin?</string>
<string name="revanced_show_menu_icons_title">ReVanced tənzimləmə nişanların göstər</string> <string name="revanced_show_menu_icons_title">ReVanced tənzimləmə nişanların göstər</string>
<string name="revanced_show_menu_icons_summary_on">Tənzimləmə nişanları göstərilir</string> <string name="revanced_show_menu_icons_summary_on">Tənzimləmə nişanları göstərilir</string>
<string name="revanced_show_menu_icons_summary_off">Tənzimləmə nişanları göstərilmir</string> <string name="revanced_show_menu_icons_summary_off">Tənzimləmə nişanları göstərilmir</string>
@@ -93,6 +97,9 @@ Davam et düyməsinə toxun və optimallaşdırma dəyişikliklərin qəbul et."
<string name="revanced_restore_old_settings_menus_title">Köhnə tənzimləmələr bölmələrin bərpa et</string> <string name="revanced_restore_old_settings_menus_title">Köhnə tənzimləmələr bölmələrin bərpa et</string>
<string name="revanced_restore_old_settings_menus_summary_on">Köhnə tənzimləmələr bölmələri göstərilir</string> <string name="revanced_restore_old_settings_menus_summary_on">Köhnə tənzimləmələr bölmələri göstərilir</string>
<string name="revanced_restore_old_settings_menus_summary_off">Köhnə tənzimləmələr bölmələri göstərilmir</string> <string name="revanced_restore_old_settings_menus_summary_off">Köhnə tənzimləmələr bölmələri göstərilmir</string>
<string name="revanced_settings_search_history_title">Axtarış tarixçəsi tənzimləməsin göstər</string>
<string name="revanced_settings_search_history_summary_on">Axtarış tarixçəsi tənzimləməsi göstərilir</string>
<string name="revanced_settings_search_history_summary_off">Axtarış tarixçəsi tənzimləməsi göstərilmir</string>
</patch> </patch>
<patch id="misc.backgroundplayback.backgroundPlaybackPatch"> <patch id="misc.backgroundplayback.backgroundPlaybackPatch">
<string name="revanced_shorts_disable_background_playback_title">Shorts arxa plan oynatmasın qapat</string> <string name="revanced_shorts_disable_background_playback_title">Shorts arxa plan oynatmasın qapat</string>
@@ -153,15 +160,18 @@ Gözlənilməz hallardan xəbərdar olmayacaqsınız."</string>
<string name="revanced_hide_notify_me_button_title">\"Mənə bildir\" düyməsini gizlət</string> <string name="revanced_hide_notify_me_button_title">\"Mənə bildir\" düyməsini gizlət</string>
<string name="revanced_hide_notify_me_button_summary_on">Düymə gizlidir</string> <string name="revanced_hide_notify_me_button_summary_on">Düymə gizlidir</string>
<string name="revanced_hide_notify_me_button_summary_off">Düymə göstərilir</string> <string name="revanced_hide_notify_me_button_summary_off">Düymə göstərilir</string>
<!-- 'People also watched' should be translated using the same localized wording YouTube displays. --> <!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. -->
<string name="revanced_hide_search_result_recommendations_title">\"İnsanlar həmçinin izləyiblər\" etiketin gizlət</string> <string name="revanced_hide_search_result_recommendation_labels_title">Video tövsiyə etiketlərini gizlət</string>
<string name="revanced_hide_search_result_recommendations_summary_on">Etiket gizlidir</string> <string name="revanced_hide_search_result_recommendation_labels_summary_on">\"İnsanlar həmçinin izləyiblər\" və \"Bunu da bəyənə bilərsiniz\" etiketləri gizlədilib</string>
<string name="revanced_hide_search_result_recommendations_summary_off">Etiket göstərilir</string> <string name="revanced_hide_search_result_recommendation_labels_summary_off">\"İnsanlar həmçinin izləyiblər\" və \"Bunu da bəyənə bilərsiniz\" etiketləri görünür</string>
<!-- 'Show more' should be translated with the same localized wording that YouTube displays. <!-- 'Show more' should be translated with the same localized wording that YouTube displays.
This button usually appears when searching for a YT creator. --> This button usually appears when searching for a YT creator. -->
<string name="revanced_hide_show_more_button_title">\'Daha çox göstər\' düyməsini gizlət</string> <string name="revanced_hide_show_more_button_title">\'Daha çox göstər\' düyməsini gizlət</string>
<string name="revanced_hide_show_more_button_summary_on">Düymə gizlidir</string> <string name="revanced_hide_show_more_button_summary_on">Düymə gizlidir</string>
<string name="revanced_hide_show_more_button_summary_off">Düymə göstərilir</string> <string name="revanced_hide_show_more_button_summary_off">Düymə göstərilir</string>
<string name="revanced_hide_ticket_shelf_title">Bilet bölməsin gizlət</string>
<string name="revanced_hide_ticket_shelf_summary_on">Bilet bölməsi gizlidir</string>
<string name="revanced_hide_ticket_shelf_summary_off">Bilet bölməsi görünür</string>
<string name="revanced_hide_timed_reactions_title">Vaxtlı reaksiyaları gizlət</string> <string name="revanced_hide_timed_reactions_title">Vaxtlı reaksiyaları gizlət</string>
<string name="revanced_hide_timed_reactions_summary_on">Zamanlanmış reaksiyalar gizlədilir</string> <string name="revanced_hide_timed_reactions_summary_on">Zamanlanmış reaksiyalar gizlədilir</string>
<string name="revanced_hide_timed_reactions_summary_off">Zamanlanmış reaksiyalar göstərilir</string> <string name="revanced_hide_timed_reactions_summary_off">Zamanlanmış reaksiyalar göstərilir</string>
@@ -231,6 +241,9 @@ Gözlənilməz hallardan xəbərdar olmayacaqsınız."</string>
<string name="revanced_hide_ai_generated_video_summary_section_title">\"AI ilə yaradılan video xülasəsini\" gizlət</string> <string name="revanced_hide_ai_generated_video_summary_section_title">\"AI ilə yaradılan video xülasəsini\" gizlət</string>
<string name="revanced_hide_ai_generated_video_summary_section_summary_on">Video xülasə bölməsi gizlədilib</string> <string name="revanced_hide_ai_generated_video_summary_section_summary_on">Video xülasə bölməsi gizlədilib</string>
<string name="revanced_hide_ai_generated_video_summary_section_summary_off">Video xülasə bölməsi göstərilir</string> <string name="revanced_hide_ai_generated_video_summary_section_summary_off">Video xülasə bölməsi göstərilir</string>
<string name="revanced_hide_ask_section_title">Soruş\'u Gizlət</string>
<string name="revanced_hide_ask_section_summary_on">Soruş bölməsi gizlidir</string>
<string name="revanced_hide_ask_section_summary_off">\"Soruş\" bölməsi göstərilir</string>
<string name="revanced_hide_attributes_section_title">Atributları Gizlət</string> <string name="revanced_hide_attributes_section_title">Atributları Gizlət</string>
<string name="revanced_hide_attributes_section_summary_on">Seçilən yerlər, Oyunlar, Musiqi və qeyd edilən insanlar bölmələri gizlədilir</string> <string name="revanced_hide_attributes_section_summary_on">Seçilən yerlər, Oyunlar, Musiqi və qeyd edilən insanlar bölmələri gizlədilir</string>
<string name="revanced_hide_attributes_section_summary_off">Seçilən yerlər, Oyunlar, Musiqi və qeyd edilən insanlar bölmələri görünür</string> <string name="revanced_hide_attributes_section_summary_off">Seçilən yerlər, Oyunlar, Musiqi və qeyd edilən insanlar bölmələri görünür</string>
@@ -352,9 +365,6 @@ Məhdudiyyətlər
Bu xüsusiyyət yalnız köhnə cihazlar üçün mövcuddur"</string> Bu xüsusiyyət yalnız köhnə cihazlar üçün mövcuddur"</string>
<string name="revanced_hide_fullscreen_ads_summary_off">Tam ekran reklamları göstərilir</string> <string name="revanced_hide_fullscreen_ads_summary_off">Tam ekran reklamları göstərilir</string>
<string name="revanced_hide_buttoned_ads_title">Düyməli reklamları gizlət</string>
<string name="revanced_hide_buttoned_ads_summary_on">Düyməli reklamlar gizlədilir</string>
<string name="revanced_hide_buttoned_ads_summary_off">Düyməli reklamlar göstərilir</string>
<string name="revanced_hide_paid_promotion_label_title">Ödənişli tanıtım etiketini gizlət</string> <string name="revanced_hide_paid_promotion_label_title">Ödənişli tanıtım etiketini gizlət</string>
<string name="revanced_hide_paid_promotion_label_summary_on">Ödənişli reklam etiketi gizlədilib</string> <string name="revanced_hide_paid_promotion_label_summary_on">Ödənişli reklam etiketi gizlədilib</string>
<string name="revanced_hide_paid_promotion_label_summary_off">Ödənişli reklam etiketi göstərilir</string> <string name="revanced_hide_paid_promotion_label_summary_off">Ödənişli reklam etiketi göstərilir</string>
@@ -465,9 +475,6 @@ Ekranın sağ tərəfində düzünə sürüşdürərək səs səviyyəsini tənz
<string name="revanced_swipe_overlay_background_opacity_title">Sürüşdürmə cildi arxa plan qeyri-şəffaflığı</string> <string name="revanced_swipe_overlay_background_opacity_title">Sürüşdürmə cildi arxa plan qeyri-şəffaflığı</string>
<string name="revanced_swipe_overlay_background_opacity_summary">0-100 arası qeyri-şəffaflıq dəyəri</string> <string name="revanced_swipe_overlay_background_opacity_summary">0-100 arası qeyri-şəffaflıq dəyəri</string>
<string name="revanced_swipe_overlay_background_opacity_invalid_toast">Sürüşmə qeyri-şəffaflığı 0-100 arası olmalıdır</string> <string name="revanced_swipe_overlay_background_opacity_invalid_toast">Sürüşmə qeyri-şəffaflığı 0-100 arası olmalıdır</string>
<string name="revanced_swipe_overlay_progress_color_title">Sürüşdürmə örtüyü irəliləyiş cizgisi rəngi</string>
<string name="revanced_swipe_overlay_progress_color_summary">Səs səviyyəsinə və parlaqlığa nəzarət üçün irəliləyiş cizgisi rəngi</string>
<string name="revanced_swipe_overlay_progress_color_invalid_toast">Yanlış irəliləyiş cizgisi rəngi</string>
<string name="revanced_swipe_text_overlay_size_title">Sürüşdürmə örtüyü mətn ölçüsü</string> <string name="revanced_swipe_text_overlay_size_title">Sürüşdürmə örtüyü mətn ölçüsü</string>
<string name="revanced_swipe_text_overlay_size_summary">Sürüşmə üçün mətn ölçüsü 1-30 arasındadır</string> <string name="revanced_swipe_text_overlay_size_summary">Sürüşmə üçün mətn ölçüsü 1-30 arasındadır</string>
<string name="revanced_swipe_text_overlay_size_invalid_toast">Mətn ölçüsü 1-30 arası olmalıdır</string> <string name="revanced_swipe_text_overlay_size_invalid_toast">Mətn ölçüsü 1-30 arası olmalıdır</string>
@@ -835,10 +842,10 @@ Avtomatik oynatma YouTube ayarlarında dəyişdirilə bilər: Ayarlar → Oxunu
<string name="revanced_ryd_failure_ryd_enabled_while_playing_video_then_user_voted">Ryd ilə səsvermə üçün videonu yenidən yüklə</string> <string name="revanced_ryd_failure_ryd_enabled_while_playing_video_then_user_voted">Ryd ilə səsvermə üçün videonu yenidən yüklə</string>
<!-- Video likes have been set to hidden by the video uploader. --> <!-- Video likes have been set to hidden by the video uploader. -->
<string name="revanced_ryd_video_likes_hidden_by_video_owner">Sahib tərəfindən gizlədilib</string> <string name="revanced_ryd_video_likes_hidden_by_video_owner">Sahib tərəfindən gizlədilib</string>
<string name="revanced_ryd_enable_summary_on">Bəyənməmələr göstərilir</string> <string name="revanced_ryd_enabled_summary_on">Bəyənməmələr göstərilir</string>
<string name="revanced_ryd_enable_summary_off">Bəyənməmələr göstərilmir</string> <string name="revanced_ryd_enabled_summary_off">Bəyənməmələr göstərilmir</string>
<string name="revanced_ryd_shorts_title">\"Shorts\"da bəyənməmə sayını göstər</string> <string name="revanced_ryd_shorts_title">\"Shorts\"da bəyənməmə sayını göstər</string>
<string name="revanced_ryd_shorts_summary_on_disclaimer">"Bəyənməmələr Shorts-da göstərilir <string name="revanced_ryd_shorts_summary_on">"Bəyənməmələr Shorts-da göstərilir
Məhdudiyyət: Bəyənməmələr gizli rejimdə görünməyə bilər"</string> Məhdudiyyət: Bəyənməmələr gizli rejimdə görünməyə bilər"</string>
<string name="revanced_ryd_shorts_summary_off">Bəyənməmələr Shorts-da göstərilmir</string> <string name="revanced_ryd_shorts_summary_off">Bəyənməmələr Shorts-da göstərilmir</string>
@@ -855,7 +862,6 @@ Məhdudiyyət: Bəyənməmələr gizli rejimdə görünməyə bilər"</string>
<string name="revanced_ryd_toast_on_connection_error_title">API əlçatan deyilsə ani bildiriş göstər</string> <string name="revanced_ryd_toast_on_connection_error_title">API əlçatan deyilsə ani bildiriş göstər</string>
<string name="revanced_ryd_toast_on_connection_error_summary_on">Return YouTube Dislike əlçatan deyilsə ani bildiriş göstər</string> <string name="revanced_ryd_toast_on_connection_error_summary_on">Return YouTube Dislike əlçatan deyilsə ani bildiriş göstər</string>
<string name="revanced_ryd_toast_on_connection_error_summary_off">Return YouTube Dislike əlçatan deyilsə ani bildiriş göstərmə</string> <string name="revanced_ryd_toast_on_connection_error_summary_off">Return YouTube Dislike əlçatan deyilsə ani bildiriş göstərmə</string>
<string name="revanced_ryd_about">Haqqında</string>
<string name="revanced_ryd_attribution_summary">Məlumat Return YouTube Dislike API vasitəsilə alınır. Ətraflı öyrənmək üçün bura toxun</string> <string name="revanced_ryd_attribution_summary">Məlumat Return YouTube Dislike API vasitəsilə alınır. Ətraflı öyrənmək üçün bura toxun</string>
<!-- Statistic strings are shown in the settings only when ReVanced debug mode is enabled. Typical users will never see these. --> <!-- Statistic strings are shown in the settings only when ReVanced debug mode is enabled. Typical users will never see these. -->
<string name="revanced_ryd_statistics_category_title">Bu cihazda Return YouTube Dislike API statistikası</string> <string name="revanced_ryd_statistics_category_title">Bu cihazda Return YouTube Dislike API statistikası</string>
@@ -1062,7 +1068,7 @@ Təqdim etməyə hazırdır?"</string>
<string name="revanced_sb_new_segment_edit_by_hand_title">Bölüm vaxtına əl ilə düzəliş et</string> <string name="revanced_sb_new_segment_edit_by_hand_title">Bölüm vaxtına əl ilə düzəliş et</string>
<string name="revanced_sb_new_segment_edit_by_hand_content">Bölümün başlanğıc və ya bitiş vaxtlarına düzəliş etmək istəyirsiniz?</string> <string name="revanced_sb_new_segment_edit_by_hand_content">Bölümün başlanğıc və ya bitiş vaxtlarına düzəliş etmək istəyirsiniz?</string>
<string name="revanced_sb_new_segment_edit_by_hand_parse_error">Yanlış vaxt verilmişdir</string> <string name="revanced_sb_new_segment_edit_by_hand_parse_error">Yanlış vaxt verilmişdir</string>
<string name="revanced_sb_stats">Statistikalar</string> <string name="revanced_sb_stats_title">Statistikalar</string>
<!-- Shown in the settings preferences, and translations can be any text length. --> <!-- Shown in the settings preferences, and translations can be any text length. -->
<string name="revanced_sb_stats_connection_failure">Statistikalar müvəqqəti mövcud deyil (API işləmir)</string> <string name="revanced_sb_stats_connection_failure">Statistikalar müvəqqəti mövcud deyil (API işləmir)</string>
<string name="revanced_sb_stats_loading">Yüklənir...</string> <string name="revanced_sb_stats_loading">Yüklənir...</string>
@@ -1086,13 +1092,8 @@ Təqdim etməyə hazırdır?"</string>
<string name="revanced_sb_stats_saved_second_format">%s saniyə</string> <string name="revanced_sb_stats_saved_second_format">%s saniyə</string>
<string name="revanced_sb_color_opacity_label">Qeyri-şəffaflıq:</string> <string name="revanced_sb_color_opacity_label">Qeyri-şəffaflıq:</string>
<string name="revanced_sb_color_dot_label">Rəng:</string> <string name="revanced_sb_color_dot_label">Rəng:</string>
<string name="revanced_sb_color_changed">Rəng dəyişdirildi</string> <string name="revanced_sb_about_title">Haqqında</string>
<string name="revanced_sb_color_reset">Rəngi sıfırla</string> <string name="revanced_sb_about_api_summary">Məlumat SponsorBlock API tərəfindən təqdim edilir. Daha ətraflı öyrənmək və digər platformalar üzrə yükləmələrə baxmaq üçün bura toxunun</string>
<string name="revanced_sb_color_invalid">Etibarsız rəng kodu</string>
<string name="revanced_sb_reset_color">Rəngi sıfırla</string>
<string name="revanced_sb_reset">Sıfırlayın</string>
<string name="revanced_sb_about">Haqqında</string>
<string name="revanced_sb_about_api_sum">Məlumat SponsorBlock API tərəfindən təqdim edilir. Daha ətraflı öyrənmək və digər platformalar üzrə yükləmələrə baxmaq üçün bura toxunun</string>
</patch> </patch>
<patch id="layout.formfactor.changeFormFactorPatch"> <patch id="layout.formfactor.changeFormFactorPatch">
<string name="revanced_change_form_factor_title">Tərtibat forma göstəricisi</string> <string name="revanced_change_form_factor_title">Tərtibat forma göstəricisi</string>
@@ -1315,6 +1316,9 @@ Bunu aktivləşdirmə daha yüksək video keyfiyyətləri əngəlin silə bilər
<string name="microg_settings_title">GmsCore Tənzimləmələri</string> <string name="microg_settings_title">GmsCore Tənzimləmələri</string>
<string name="microg_settings_summary">GmsCore üçün Tənzimləmələr</string> <string name="microg_settings_summary">GmsCore üçün Tənzimləmələr</string>
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
<string name="revanced_disable_haptic_feedback_zoom_title">Yaxınlaşdırma reaksiyasın qapat</string>
</patch>
<patch id="misc.gms.accountCredentialsInvalidTextPatch"> <patch id="misc.gms.accountCredentialsInvalidTextPatch">
<string name="microg_offline_account_login_error">Bu yaxınlarda hesabınıza giriş məlumatlarınızı dəyişmisinizsə, MicroG-ni silin və təkrar quraşdırın.</string> <string name="microg_offline_account_login_error">Bu yaxınlarda hesabınıza giriş məlumatlarınızı dəyişmisinizsə, MicroG-ni silin və təkrar quraşdırın.</string>
</patch> </patch>
@@ -1333,11 +1337,6 @@ Bunu aktivləşdirmə daha yüksək video keyfiyyətləri əngəlin silə bilər
<string name="revanced_remove_tracking_query_parameter_summary_on">İzləmə sorğusu faktoru bağlantılardan silinir</string> <string name="revanced_remove_tracking_query_parameter_summary_on">İzləmə sorğusu faktoru bağlantılardan silinir</string>
<string name="revanced_remove_tracking_query_parameter_summary_off">İzləmə sorğusu faktoru bağlantılardan silinmir</string> <string name="revanced_remove_tracking_query_parameter_summary_off">İzləmə sorğusu faktoru bağlantılardan silinmir</string>
</patch> </patch>
<patch id="misc.zoomhaptics.zoomHapticsPatch">
<string name="revanced_disable_zoom_haptics_title">Yaxınlaşdırma reaksiyasın qapat</string>
<string name="revanced_disable_zoom_haptics_summary_on">Reaksiya qapalıdır</string>
<string name="revanced_disable_zoom_haptics_summary_off">Reaksiya aktivdir</string>
</patch>
<patch id="video.audio.forceOriginalAudioPatch"> <patch id="video.audio.forceOriginalAudioPatch">
<string name="revanced_force_original_audio_title">Orijinal səs dilini zorla</string> <string name="revanced_force_original_audio_title">Orijinal səs dilini zorla</string>
<string name="revanced_force_original_audio_summary_on">Orijinal səs dilini istifadə</string> <string name="revanced_force_original_audio_summary_on">Orijinal səs dilini istifadə</string>

View File

@@ -35,6 +35,8 @@ Second \"item\" text"</string>
<string name="revanced_settings_submenu_title">Налады</string> <string name="revanced_settings_submenu_title">Налады</string>
<string name="revanced_settings_confirm_user_dialog_title">Вы хочаце працягнуць?</string> <string name="revanced_settings_confirm_user_dialog_title">Вы хочаце працягнуць?</string>
<string name="revanced_settings_reset">Скінуць</string> <string name="revanced_settings_reset">Скінуць</string>
<string name="revanced_settings_reset_color">Скінуць колер</string>
<string name="revanced_settings_color_invalid">Несапраўдны колер</string>
<string name="revanced_settings_restart_title">Абнавіце і перазагрузіце</string> <string name="revanced_settings_restart_title">Абнавіце і перазагрузіце</string>
<string name="revanced_settings_restart">Перазапуск</string> <string name="revanced_settings_restart">Перазапуск</string>
<string name="revanced_settings_import">Імпарт</string> <string name="revanced_settings_import">Імпарт</string>
@@ -42,6 +44,10 @@ Second \"item\" text"</string>
<string name="revanced_settings_import_reset">Налады ReVanced скінуты да стандартных</string> <string name="revanced_settings_import_reset">Налады ReVanced скінуты да стандартных</string>
<string name="revanced_settings_import_success">Імпартавана %d налад</string> <string name="revanced_settings_import_success">Імпартавана %d налад</string>
<string name="revanced_settings_import_failure_parse">Памылка імпарту: %s</string> <string name="revanced_settings_import_failure_parse">Памылка імпарту: %s</string>
<string name="revanced_settings_search_hint">Пошук налад</string>
<string name="revanced_settings_search_no_results_title">Нічога не знойдзена для \",%s\"</string>
<string name="revanced_settings_search_no_results_summary">Паспрабуйце іншае ключавое слова</string>
<string name="revanced_settings_search_remove_message">Выдаліць з гісторыі пошуку?</string>
<string name="revanced_show_menu_icons_title">Паказваць значкі налад ReVanced</string> <string name="revanced_show_menu_icons_title">Паказваць значкі налад ReVanced</string>
<string name="revanced_show_menu_icons_summary_on">Значкі налад паказваюцца</string> <string name="revanced_show_menu_icons_summary_on">Значкі налад паказваюцца</string>
<string name="revanced_show_menu_icons_summary_off">Значкі налад не паказваюцца</string> <string name="revanced_show_menu_icons_summary_off">Значкі налад не паказваюцца</string>
@@ -93,6 +99,9 @@ Second \"item\" text"</string>
<string name="revanced_restore_old_settings_menus_title">Аднавіць старое меню налад</string> <string name="revanced_restore_old_settings_menus_title">Аднавіць старое меню налад</string>
<string name="revanced_restore_old_settings_menus_summary_on">Старыя меню налад паказваюцца</string> <string name="revanced_restore_old_settings_menus_summary_on">Старыя меню налад паказваюцца</string>
<string name="revanced_restore_old_settings_menus_summary_off">Старыя меню налад не паказваюцца</string> <string name="revanced_restore_old_settings_menus_summary_off">Старыя меню налад не паказваюцца</string>
<string name="revanced_settings_search_history_title">Паказваць гісторыю пошуку ў наладах</string>
<string name="revanced_settings_search_history_summary_on">Паказваецца гісторыя пошуку ў наладах</string>
<string name="revanced_settings_search_history_summary_off">Гісторыя пошуку налад не паказваецца</string>
</patch> </patch>
<patch id="misc.backgroundplayback.backgroundPlaybackPatch"> <patch id="misc.backgroundplayback.backgroundPlaybackPatch">
<string name="revanced_shorts_disable_background_playback_title">Адключыць прайграванне Shorts у фонавым</string> <string name="revanced_shorts_disable_background_playback_title">Адключыць прайграванне Shorts у фонавым</string>
@@ -108,6 +117,11 @@ Second \"item\" text"</string>
<string name="revanced_debug_protobuffer_title">Буфер пратаколу часопіса</string> <string name="revanced_debug_protobuffer_title">Буфер пратаколу часопіса</string>
<string name="revanced_debug_protobuffer_summary_on">Журналы адладкі ўключаюць пратабуфер</string> <string name="revanced_debug_protobuffer_summary_on">Журналы адладкі ўключаюць пратабуфер</string>
<string name="revanced_debug_protobuffer_summary_off">Журналы адладкі не ўключаюць пратабуфер</string> <string name="revanced_debug_protobuffer_summary_off">Журналы адладкі не ўключаюць пратабуфер</string>
<string name="revanced_debug_protobuffer_user_dialog_message">"Уключэнне гэтага параметра будзе запісваць дадатковыя даныя макета, у тым ліку тэкст на экране для некаторых кампанентаў інтэрфейсу.
Гэта можа дапамагчы ідэнтыфікаваць кампаненты пры стварэнні карыстацкіх фільтраў.
Аднак уключэнне гэтага параметра таксама будзе запісваць некаторыя даныя карыстальніка, такія як ваш IP-адрас."</string>
<string name="revanced_debug_stacktrace_title">Сляды стэка журнала</string> <string name="revanced_debug_stacktrace_title">Сляды стэка журнала</string>
<string name="revanced_debug_stacktrace_summary_on">Журналы адладкі ўключаюць трасіроўку стэка</string> <string name="revanced_debug_stacktrace_summary_on">Журналы адладкі ўключаюць трасіроўку стэка</string>
<string name="revanced_debug_stacktrace_summary_off">Журналы адладкі не ўключаюць трасіроўку стэка</string> <string name="revanced_debug_stacktrace_summary_off">Журналы адладкі не ўключаюць трасіроўку стэка</string>
@@ -117,6 +131,15 @@ Second \"item\" text"</string>
<string name="revanced_debug_toast_on_error_user_dialog_message">"Адключэнне паведамленняў пра памылкі схавае ўсе апавяшчэнні ReVanced пра памылкі. <string name="revanced_debug_toast_on_error_user_dialog_message">"Адключэнне паведамленняў пра памылкі схавае ўсе апавяшчэнні ReVanced пра памылкі.
Вы не будзеце атрымліваць апавяшчэнні пра нечаканыя падзеі."</string> Вы не будзеце атрымліваць апавяшчэнні пра нечаканыя падзеі."</string>
<string name="revanced_debug_export_logs_to_clipboard_title">Экспартаваць адладачныя лагі</string>
<string name="revanced_debug_export_logs_to_clipboard_summary">Капіруе адладачныя лагі ReVanced у буфер абмену</string>
<string name="revanced_debug_logs_disabled">Адладачнае лагаванне адключана</string>
<string name="revanced_debug_logs_none_found">Лагі не знойдзены</string>
<string name="revanced_debug_logs_copied_to_clipboard">Лагі скапіяваны</string>
<string name="revanced_debug_logs_failed_to_export">Не атрымалася экспартаваць лагі: $s</string>
<string name="revanced_debug_logs_clear_buffer_title">Ачысціць адладачныя лагі</string>
<string name="revanced_debug_logs_clear_buffer_summary">Ачышчае ўсе захаваныя адладачныя лагі ReVanced</string>
<string name="revanced_debug_logs_clear_toast">Лагі ачышчаны</string>
</patch> </patch>
<patch id="layout.hide.general.hideLayoutComponentsPatch"> <patch id="layout.hide.general.hideLayoutComponentsPatch">
<string name="revanced_hide_album_cards_title">Схаваць карты альбома</string> <string name="revanced_hide_album_cards_title">Схаваць карты альбома</string>
@@ -153,15 +176,18 @@ Second \"item\" text"</string>
<string name="revanced_hide_notify_me_button_title">Схаваць кнопку \"Паведаміць мне\"</string> <string name="revanced_hide_notify_me_button_title">Схаваць кнопку \"Паведаміць мне\"</string>
<string name="revanced_hide_notify_me_button_summary_on">Кнопка схавана</string> <string name="revanced_hide_notify_me_button_summary_on">Кнопка схавана</string>
<string name="revanced_hide_notify_me_button_summary_off">Паказана кнопка</string> <string name="revanced_hide_notify_me_button_summary_off">Паказана кнопка</string>
<!-- 'People also watched' should be translated using the same localized wording YouTube displays. --> <!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. -->
<string name="revanced_hide_search_result_recommendations_title">Схаваць надпіс «Людзі таксама глядзелі»</string> <string name="revanced_hide_search_result_recommendation_labels_title">Схаваць надпісы з рэкамендацыямі відэа</string>
<string name="revanced_hide_search_result_recommendations_summary_on">Надпіс схаваны</string> <string name="revanced_hide_search_result_recommendation_labels_summary_on">Надпісы \"Людзі таксама глядзелі\" і \"Вам таксама можа спадабацца\" схаваныя</string>
<string name="revanced_hide_search_result_recommendations_summary_off">Надпіс паказаны</string> <string name="revanced_hide_search_result_recommendation_labels_summary_off">Надпісы \"Людзі таксама глядзелі\" і \"Вам таксама можа спадабацца\" паказаныя</string>
<!-- 'Show more' should be translated with the same localized wording that YouTube displays. <!-- 'Show more' should be translated with the same localized wording that YouTube displays.
This button usually appears when searching for a YT creator. --> This button usually appears when searching for a YT creator. -->
<string name="revanced_hide_show_more_button_title">Схаваць кнопку \"Паказаць больш\"</string> <string name="revanced_hide_show_more_button_title">Схаваць кнопку \"Паказаць больш\"</string>
<string name="revanced_hide_show_more_button_summary_on">Кнопка схавана</string> <string name="revanced_hide_show_more_button_summary_on">Кнопка схавана</string>
<string name="revanced_hide_show_more_button_summary_off">Паказана кнопка</string> <string name="revanced_hide_show_more_button_summary_off">Паказана кнопка</string>
<string name="revanced_hide_ticket_shelf_title">Схаваць паліцу білетаў</string>
<string name="revanced_hide_ticket_shelf_summary_on">Паліца білетаў схавана</string>
<string name="revanced_hide_ticket_shelf_summary_off">Паліца білетаў паказана</string>
<string name="revanced_hide_timed_reactions_title">Схаваць рэакцыі па часе</string> <string name="revanced_hide_timed_reactions_title">Схаваць рэакцыі па часе</string>
<string name="revanced_hide_timed_reactions_summary_on">Часовыя рэакцыі схаваныя</string> <string name="revanced_hide_timed_reactions_summary_on">Часовыя рэакцыі схаваныя</string>
<string name="revanced_hide_timed_reactions_summary_off">Паказваюцца рэакцыі па часе</string> <string name="revanced_hide_timed_reactions_summary_off">Паказваюцца рэакцыі па часе</string>
@@ -231,6 +257,9 @@ Second \"item\" text"</string>
<string name="revanced_hide_ai_generated_video_summary_section_title">Схаваць «Зводку відэа, згенэраваную штучным інтэлектам»</string> <string name="revanced_hide_ai_generated_video_summary_section_title">Схаваць «Зводку відэа, згенэраваную штучным інтэлектам»</string>
<string name="revanced_hide_ai_generated_video_summary_section_summary_on">Раздзел зводкі відэа схаваны</string> <string name="revanced_hide_ai_generated_video_summary_section_summary_on">Раздзел зводкі відэа схаваны</string>
<string name="revanced_hide_ai_generated_video_summary_section_summary_off">Раздзел зводкі відэа паказаны</string> <string name="revanced_hide_ai_generated_video_summary_section_summary_off">Раздзел зводкі відэа паказаны</string>
<string name="revanced_hide_ask_section_title">Схаваць «Спытаць»</string>
<string name="revanced_hide_ask_section_summary_on">Раздзел «Спытаць» схаваны</string>
<string name="revanced_hide_ask_section_summary_off">Раздзел «Спытаць» паказаны</string>
<string name="revanced_hide_attributes_section_title">Схаваць атрыбуты</string> <string name="revanced_hide_attributes_section_title">Схаваць атрыбуты</string>
<string name="revanced_hide_attributes_section_summary_on">Раздзелы «Папулярныя месцы», «Гульні», «Музыка» і «Людзі, якіх згадвалі» схаваныя</string> <string name="revanced_hide_attributes_section_summary_on">Раздзелы «Папулярныя месцы», «Гульні», «Музыка» і «Людзі, якіх згадвалі» схаваныя</string>
<string name="revanced_hide_attributes_section_summary_off">Раздзелы «Папулярныя месцы», «Гульні», «Музыка» і «Людзі, якіх згадвалі» паказаныя</string> <string name="revanced_hide_attributes_section_summary_off">Раздзелы «Папулярныя месцы», «Гульні», «Музыка» і «Людзі, якіх згадвалі» паказаныя</string>
@@ -352,9 +381,6 @@ Second \"item\" text"</string>
Гэтая функцыя даступная толькі для старых прылад"</string> Гэтая функцыя даступная толькі для старых прылад"</string>
<string name="revanced_hide_fullscreen_ads_summary_off">Адлюстроўваецца поўнаэкранная рэклама</string> <string name="revanced_hide_fullscreen_ads_summary_off">Адлюстроўваецца поўнаэкранная рэклама</string>
<string name="revanced_hide_buttoned_ads_title">Схаваць рэкламу на кнопках</string>
<string name="revanced_hide_buttoned_ads_summary_on">Аб\"явы на кнопках схаваныя</string>
<string name="revanced_hide_buttoned_ads_summary_off">Паказваюцца аб\"явы на кнопках</string>
<string name="revanced_hide_paid_promotion_label_title">Схаваць метку аплачанай акцыі</string> <string name="revanced_hide_paid_promotion_label_title">Схаваць метку аплачанай акцыі</string>
<string name="revanced_hide_paid_promotion_label_summary_on">Пазнака платнай акцыі схавана</string> <string name="revanced_hide_paid_promotion_label_summary_on">Пазнака платнай акцыі схавана</string>
<string name="revanced_hide_paid_promotion_label_summary_off">Адлюстроўваецца ярлык платнай акцыі</string> <string name="revanced_hide_paid_promotion_label_summary_off">Адлюстроўваецца ярлык платнай акцыі</string>
@@ -465,9 +491,10 @@ Second \"item\" text"</string>
<string name="revanced_swipe_overlay_background_opacity_title">Непразрыстасць фону накладкі пракруткі</string> <string name="revanced_swipe_overlay_background_opacity_title">Непразрыстасць фону накладкі пракруткі</string>
<string name="revanced_swipe_overlay_background_opacity_summary">Значэнне непразрыстасці паміж 0-100</string> <string name="revanced_swipe_overlay_background_opacity_summary">Значэнне непразрыстасці паміж 0-100</string>
<string name="revanced_swipe_overlay_background_opacity_invalid_toast">Непразрыстасць пракруткі павінна быць паміж 0-100</string> <string name="revanced_swipe_overlay_background_opacity_invalid_toast">Непразрыстасць пракруткі павінна быць паміж 0-100</string>
<string name="revanced_swipe_overlay_progress_color_title">Колер слупка прагрэсу накладкі правядзення</string> <string name="revanced_swipe_overlay_progress_brightness_color_title">Колер яркасці накладкі правядзення</string>
<string name="revanced_swipe_overlay_progress_color_summary">Колер слупка прагрэсу для рэгулявання гучнасці і яркасці</string> <string name="revanced_swipe_overlay_progress_brightness_color_summary">Колер шкалы прагрэсу для элементаў кіравання яркасцю</string>
<string name="revanced_swipe_overlay_progress_color_invalid_toast">Несапраўдны колер слупка прагрэсу</string> <string name="revanced_swipe_overlay_progress_volume_color_title">Колер гучнасці накладкі правядзення</string>
<string name="revanced_swipe_overlay_progress_volume_color_summary">Колер шкалы прагрэсу для элементаў кіравання гучнасцю</string>
<string name="revanced_swipe_text_overlay_size_title">Памер тэксту накладкі правядзення</string> <string name="revanced_swipe_text_overlay_size_title">Памер тэксту накладкі правядзення</string>
<string name="revanced_swipe_text_overlay_size_summary">Памер тэксту для накладкі правядзення ад 1 да 30</string> <string name="revanced_swipe_text_overlay_size_summary">Памер тэксту для накладкі правядзення ад 1 да 30</string>
<string name="revanced_swipe_text_overlay_size_invalid_toast">Памер тэксту павінен быць у межах ад 1 да 30</string> <string name="revanced_swipe_text_overlay_size_invalid_toast">Памер тэксту павінен быць у межах ад 1 да 30</string>
@@ -836,10 +863,10 @@ Second \"item\" text"</string>
<string name="revanced_ryd_failure_ryd_enabled_while_playing_video_then_user_voted">Перазагрузіце відэа, каб прагаласаваць з дапамогай функцыі \"Вярнуць не падабаецца YouTube\"</string> <string name="revanced_ryd_failure_ryd_enabled_while_playing_video_then_user_voted">Перазагрузіце відэа, каб прагаласаваць з дапамогай функцыі \"Вярнуць не падабаецца YouTube\"</string>
<!-- Video likes have been set to hidden by the video uploader. --> <!-- Video likes have been set to hidden by the video uploader. -->
<string name="revanced_ryd_video_likes_hidden_by_video_owner">Схавана ўладальнікам</string> <string name="revanced_ryd_video_likes_hidden_by_video_owner">Схавана ўладальнікам</string>
<string name="revanced_ryd_enable_summary_on">Дызлайкі паказаны</string> <string name="revanced_ryd_enabled_summary_on">Дызлайкі паказаны</string>
<string name="revanced_ryd_enable_summary_off">Дызлайкі не паказваюцца</string> <string name="revanced_ryd_enabled_summary_off">Дызлайкі не паказваюцца</string>
<string name="revanced_ryd_shorts_title">Паказвайце \"не падабаецца\" на Shorts</string> <string name="revanced_ryd_shorts_title">Паказвайце \"не падабаецца\" на Shorts</string>
<string name="revanced_ryd_shorts_summary_on_disclaimer">"Дызлайкі на Shorts паказаныя <string name="revanced_ryd_shorts_summary_on">"Дызлайкі на Shorts паказаныя
Абмежаванне: дызлайкі могуць не адлюстроўвацца ў рэжыме інкогніта"</string> Абмежаванне: дызлайкі могуць не адлюстроўвацца ў рэжыме інкогніта"</string>
<string name="revanced_ryd_shorts_summary_off">Дызлайкі на Shorts не паказаныя</string> <string name="revanced_ryd_shorts_summary_off">Дызлайкі на Shorts не паказаныя</string>
@@ -856,7 +883,6 @@ Second \"item\" text"</string>
<string name="revanced_ryd_toast_on_connection_error_title">Паказаць тост, калі API недаступны</string> <string name="revanced_ryd_toast_on_connection_error_title">Паказаць тост, калі API недаступны</string>
<string name="revanced_ryd_toast_on_connection_error_summary_on">Тост паказваецца, калі функцыя \"Вярнуць не падабаецца YouTube\" недаступная</string> <string name="revanced_ryd_toast_on_connection_error_summary_on">Тост паказваецца, калі функцыя \"Вярнуць не падабаецца YouTube\" недаступная</string>
<string name="revanced_ryd_toast_on_connection_error_summary_off">Тост не паказваецца, калі функцыя \"Вярнуць не падабаецца YouTube\" недаступная</string> <string name="revanced_ryd_toast_on_connection_error_summary_off">Тост не паказваецца, калі функцыя \"Вярнуць не падабаецца YouTube\" недаступная</string>
<string name="revanced_ryd_about">Пра праграму</string>
<string name="revanced_ryd_attribution_summary">Дадзеныя прадастаўляюцца Return YouTube Dislikes API. Націсніце тут, каб даведацца больш</string> <string name="revanced_ryd_attribution_summary">Дадзеныя прадастаўляюцца Return YouTube Dislikes API. Націсніце тут, каб даведацца больш</string>
<!-- Statistic strings are shown in the settings only when ReVanced debug mode is enabled. Typical users will never see these. --> <!-- Statistic strings are shown in the settings only when ReVanced debug mode is enabled. Typical users will never see these. -->
<string name="revanced_ryd_statistics_category_title">Статыстыка ReturnYouTubeDislike API гэтай прылады</string> <string name="revanced_ryd_statistics_category_title">Статыстыка ReturnYouTubeDislike API гэтай прылады</string>
@@ -1064,7 +1090,7 @@ Second \"item\" text"</string>
<string name="revanced_sb_new_segment_edit_by_hand_title">Рэдагаваць час сегмента ўручную</string> <string name="revanced_sb_new_segment_edit_by_hand_title">Рэдагаваць час сегмента ўручную</string>
<string name="revanced_sb_new_segment_edit_by_hand_content">Вы жадаеце змяніць час для пачатку або канца сегмента?</string> <string name="revanced_sb_new_segment_edit_by_hand_content">Вы жадаеце змяніць час для пачатку або канца сегмента?</string>
<string name="revanced_sb_new_segment_edit_by_hand_parse_error">Указаны няправільны час</string> <string name="revanced_sb_new_segment_edit_by_hand_parse_error">Указаны няправільны час</string>
<string name="revanced_sb_stats">Статыстыка</string> <string name="revanced_sb_stats_title">Статыстыка</string>
<!-- Shown in the settings preferences, and translations can be any text length. --> <!-- Shown in the settings preferences, and translations can be any text length. -->
<string name="revanced_sb_stats_connection_failure">Статыстыка часова недаступная (API не працуе)</string> <string name="revanced_sb_stats_connection_failure">Статыстыка часова недаступная (API не працуе)</string>
<string name="revanced_sb_stats_loading">Загрузка...</string> <string name="revanced_sb_stats_loading">Загрузка...</string>
@@ -1088,13 +1114,8 @@ Second \"item\" text"</string>
<string name="revanced_sb_stats_saved_second_format">%s секунд</string> <string name="revanced_sb_stats_saved_second_format">%s секунд</string>
<string name="revanced_sb_color_opacity_label">Непразрыстасць:</string> <string name="revanced_sb_color_opacity_label">Непразрыстасць:</string>
<string name="revanced_sb_color_dot_label">колер:</string> <string name="revanced_sb_color_dot_label">колер:</string>
<string name="revanced_sb_color_changed">Колер змяніўся</string> <string name="revanced_sb_about_title">Пра праграму</string>
<string name="revanced_sb_color_reset">Скід колеру</string> <string name="revanced_sb_about_api_summary">Дадзеныя прадастаўляюцца API SponsorBlock. Націсніце тут, каб даведацца больш і паглядзець спампоўкі для іншых платформаў</string>
<string name="revanced_sb_color_invalid">Няправільны код колеру</string>
<string name="revanced_sb_reset_color">Скінуць колер</string>
<string name="revanced_sb_reset">Скінуць</string>
<string name="revanced_sb_about">Пра праграму</string>
<string name="revanced_sb_about_api_sum">Дадзеныя прадастаўляюцца API SponsorBlock. Націсніце тут, каб даведацца больш і паглядзець спампоўкі для іншых платформаў</string>
</patch> </patch>
<patch id="layout.formfactor.changeFormFactorPatch"> <patch id="layout.formfactor.changeFormFactorPatch">
<string name="revanced_change_form_factor_title">Фармат экрана</string> <string name="revanced_change_form_factor_title">Фармат экрана</string>
@@ -1317,6 +1338,22 @@ Second \"item\" text"</string>
<string name="microg_settings_title">Налады GmsCore</string> <string name="microg_settings_title">Налады GmsCore</string>
<string name="microg_settings_summary">Налады для GmsCore</string> <string name="microg_settings_summary">Налады для GmsCore</string>
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
<string name="revanced_disable_haptic_feedback_title">Тактыльная зваротная сувязь</string>
<string name="revanced_disable_haptic_feedback_summary">Змяніць тактыльную зваротную сувязь</string>
<string name="revanced_disable_haptic_feedback_chapters_title">Адключыць тактыльныя эфекты раздзелаў</string>
<string name="revanced_disable_haptic_feedback_chapters_summary_on">Тактыльныя эфекты раздзелаў адключаны</string>
<string name="revanced_disable_haptic_feedback_chapters_summary_off">Тактыльныя эфекты раздзелаў уключаны</string>
<string name="revanced_disable_haptic_feedback_precise_seeking_title">Адключыць тактыльны эфект дакладнага пошуку</string>
<string name="revanced_disable_haptic_feedback_precise_seeking_summary_on">Дакладны тактыльны эфект пошуку адключаны</string>
<string name="revanced_disable_haptic_feedback_precise_seeking_summary_off">Тактыльны эфект дакладнага пошуку ўключаны</string>
<string name="revanced_disable_haptic_feedback_seek_undo_title">Адключыць тактыльны эфект адмены пошуку</string>
<string name="revanced_disable_haptic_feedback_seek_undo_summary_on">Тактыльны эфект адмены пошуку адключаны</string>
<string name="revanced_disable_haptic_feedback_seek_undo_summary_off">Тактыльны эфект адмены пошуку ўключаны</string>
<string name="revanced_disable_haptic_feedback_zoom_title">Адключыць тактыльны эфект маштабавання</string>
<string name="revanced_disable_haptic_feedback_zoom_summary_on">Тактыльны эфект маштабавання адключаны</string>
<string name="revanced_disable_haptic_feedback_zoom_summary_off">Тактыльны эфект маштабавання ўключаны</string>
</patch>
<patch id="misc.gms.accountCredentialsInvalidTextPatch"> <patch id="misc.gms.accountCredentialsInvalidTextPatch">
<string name="microg_offline_account_login_error">Калі вы нядаўна змянілі даныя для ўваходу ў свой уліковы запіс, выдаліце і пераўсталюйце MicroG.</string> <string name="microg_offline_account_login_error">Калі вы нядаўна змянілі даныя для ўваходу ў свой уліковы запіс, выдаліце і пераўсталюйце MicroG.</string>
</patch> </patch>
@@ -1335,11 +1372,6 @@ Second \"item\" text"</string>
<string name="revanced_remove_tracking_query_parameter_summary_on">Параметр запыту адсочвання выдалены са спасылак</string> <string name="revanced_remove_tracking_query_parameter_summary_on">Параметр запыту адсочвання выдалены са спасылак</string>
<string name="revanced_remove_tracking_query_parameter_summary_off">Параметр адсочвання запыту не выдаляецца са спасылак</string> <string name="revanced_remove_tracking_query_parameter_summary_off">Параметр адсочвання запыту не выдаляецца са спасылак</string>
</patch> </patch>
<patch id="misc.zoomhaptics.zoomHapticsPatch">
<string name="revanced_disable_zoom_haptics_title">Адключыць тактыльны эфект маштабавання</string>
<string name="revanced_disable_zoom_haptics_summary_on">Тактыльныя функцыі адключаны</string>
<string name="revanced_disable_zoom_haptics_summary_off">Тактыльныя сігналы ўключаны</string>
</patch>
<patch id="video.audio.forceOriginalAudioPatch"> <patch id="video.audio.forceOriginalAudioPatch">
<string name="revanced_force_original_audio_title">Вымушаная арыгінальная мова аўдыё</string> <string name="revanced_force_original_audio_title">Вымушаная арыгінальная мова аўдыё</string>
<string name="revanced_force_original_audio_summary_on">Выкарыстоўваць арыгінальную мову аўдыя</string> <string name="revanced_force_original_audio_summary_on">Выкарыстоўваць арыгінальную мову аўдыя</string>

View File

@@ -35,6 +35,8 @@ Second \"item\" text"</string>
<string name="revanced_settings_submenu_title">Настройки</string> <string name="revanced_settings_submenu_title">Настройки</string>
<string name="revanced_settings_confirm_user_dialog_title">Искате ли да продължите?</string> <string name="revanced_settings_confirm_user_dialog_title">Искате ли да продължите?</string>
<string name="revanced_settings_reset">Възстанови</string> <string name="revanced_settings_reset">Възстанови</string>
<string name="revanced_settings_reset_color">Нулиране на цвета</string>
<string name="revanced_settings_color_invalid">Невалиден цвят</string>
<string name="revanced_settings_restart_title">Рестартирай и опресни</string> <string name="revanced_settings_restart_title">Рестартирай и опресни</string>
<string name="revanced_settings_restart">Рестартиране</string> <string name="revanced_settings_restart">Рестартиране</string>
<string name="revanced_settings_import">Импортиране</string> <string name="revanced_settings_import">Импортиране</string>
@@ -42,6 +44,10 @@ Second \"item\" text"</string>
<string name="revanced_settings_import_reset">Настройките на ReVanced бяха нулирани</string> <string name="revanced_settings_import_reset">Настройките на ReVanced бяха нулирани</string>
<string name="revanced_settings_import_success">Следните настройки бяха импортирани успешно: %d</string> <string name="revanced_settings_import_success">Следните настройки бяха импортирани успешно: %d</string>
<string name="revanced_settings_import_failure_parse">Импортирането беше неуспешно: %s</string> <string name="revanced_settings_import_failure_parse">Импортирането беше неуспешно: %s</string>
<string name="revanced_settings_search_hint">Търсене на настройки</string>
<string name="revanced_settings_search_no_results_title">Няма намерени резултати за \",%s\"</string>
<string name="revanced_settings_search_no_results_summary">Опитайте друга ключова дума</string>
<string name="revanced_settings_search_remove_message">Премахване от историята на търсенията?</string>
<string name="revanced_show_menu_icons_title">Показване на иконите на настройките на ReVanced</string> <string name="revanced_show_menu_icons_title">Показване на иконите на настройките на ReVanced</string>
<string name="revanced_show_menu_icons_summary_on">Иконите на настройките се показват</string> <string name="revanced_show_menu_icons_summary_on">Иконите на настройките се показват</string>
<string name="revanced_show_menu_icons_summary_off">Иконите на настройките не се показват</string> <string name="revanced_show_menu_icons_summary_off">Иконите на настройките не се показват</string>
@@ -84,7 +90,7 @@ Second \"item\" text"</string>
<string name="revanced_settings_screen_01_ads_title">Реклами</string> <string name="revanced_settings_screen_01_ads_title">Реклами</string>
<string name="revanced_settings_screen_02_alt_thumbnails_title">Алтернативни миниатюри</string> <string name="revanced_settings_screen_02_alt_thumbnails_title">Алтернативни миниатюри</string>
<string name="revanced_settings_screen_03_feed_title">Поток</string> <string name="revanced_settings_screen_03_feed_title">Поток</string>
<string name="revanced_settings_screen_04_general_title">Общ</string> <string name="revanced_settings_screen_04_general_title">Общи</string>
<string name="revanced_settings_screen_05_player_title">Плеър</string> <string name="revanced_settings_screen_05_player_title">Плеър</string>
<string name="revanced_settings_screen_07_seekbar_title">Лента за прогрес на видеото</string> <string name="revanced_settings_screen_07_seekbar_title">Лента за прогрес на видеото</string>
<string name="revanced_settings_screen_08_swipe_controls_title">Контроли с плъзгане</string> <string name="revanced_settings_screen_08_swipe_controls_title">Контроли с плъзгане</string>
@@ -93,6 +99,9 @@ Second \"item\" text"</string>
<string name="revanced_restore_old_settings_menus_title">Възстановяване на старите менюта за настройки</string> <string name="revanced_restore_old_settings_menus_title">Възстановяване на старите менюта за настройки</string>
<string name="revanced_restore_old_settings_menus_summary_on">Старите менюта с настройки се показват</string> <string name="revanced_restore_old_settings_menus_summary_on">Старите менюта с настройки се показват</string>
<string name="revanced_restore_old_settings_menus_summary_off">Старите менюта с настройки не се показват</string> <string name="revanced_restore_old_settings_menus_summary_off">Старите менюта с настройки не се показват</string>
<string name="revanced_settings_search_history_title">Показване на историята на търсенията в настройките</string>
<string name="revanced_settings_search_history_summary_on">Историята на търсенията в настройките е показана</string>
<string name="revanced_settings_search_history_summary_off">Историята на търсенията в настройките не се показва</string>
</patch> </patch>
<patch id="misc.backgroundplayback.backgroundPlaybackPatch"> <patch id="misc.backgroundplayback.backgroundPlaybackPatch">
<string name="revanced_shorts_disable_background_playback_title">Възпроизвеждане на Shorts в фонов режим</string> <string name="revanced_shorts_disable_background_playback_title">Възпроизвеждане на Shorts в фонов режим</string>
@@ -108,6 +117,11 @@ Second \"item\" text"</string>
<string name="revanced_debug_protobuffer_title">Буфер на протокола за дневника</string> <string name="revanced_debug_protobuffer_title">Буфер на протокола за дневника</string>
<string name="revanced_debug_protobuffer_summary_on">Файлове с дневници за грешки включват буфера</string> <string name="revanced_debug_protobuffer_summary_on">Файлове с дневници за грешки включват буфера</string>
<string name="revanced_debug_protobuffer_summary_off">Файлове с дневници за грешки не включват буфера</string> <string name="revanced_debug_protobuffer_summary_off">Файлове с дневници за грешки не включват буфера</string>
<string name="revanced_debug_protobuffer_user_dialog_message">"Активирането на тази настройка ще регистрира допълнителни данни за оформлението, включително текст на екрана за някои компоненти на потребителския интерфейс.
Това може да помогне за идентифициране на компоненти при създаване на персонализирани филтри.
Активирането на тази настройка обаче ще регистрира и някои потребителски данни, като например вашия IP адрес."</string>
<string name="revanced_debug_stacktrace_title">Следи от стека на дневника</string> <string name="revanced_debug_stacktrace_title">Следи от стека на дневника</string>
<string name="revanced_debug_stacktrace_summary_on">Дневникът за отстраняване на грешки съдържа следи от стека</string> <string name="revanced_debug_stacktrace_summary_on">Дневникът за отстраняване на грешки съдържа следи от стека</string>
<string name="revanced_debug_stacktrace_summary_off">Дневникът за отстраняване на грешки не съдържа следи от стека</string> <string name="revanced_debug_stacktrace_summary_off">Дневникът за отстраняване на грешки не съдържа следи от стека</string>
@@ -117,6 +131,15 @@ Second \"item\" text"</string>
<string name="revanced_debug_toast_on_error_user_dialog_message">"Изключването на изскачащи съобщения за грешки крие всички известия за грешки на ReVanced. <string name="revanced_debug_toast_on_error_user_dialog_message">"Изключването на изскачащи съобщения за грешки крие всички известия за грешки на ReVanced.
Няма да бъдете уведомени за неочаквани събития."</string> Няма да бъдете уведомени за неочаквани събития."</string>
<string name="revanced_debug_export_logs_to_clipboard_title">Експортиране на логове за отстраняване на грешки</string>
<string name="revanced_debug_export_logs_to_clipboard_summary">Копира логовете за отстраняване на грешки на ReVanced в клипборда</string>
<string name="revanced_debug_logs_disabled">Отстраняването на грешки е деактивирано</string>
<string name="revanced_debug_logs_none_found">Не са намерени логове</string>
<string name="revanced_debug_logs_copied_to_clipboard">Логовете са копирани</string>
<string name="revanced_debug_logs_failed_to_export">Неуспешно експортиране на логове: $s</string>
<string name="revanced_debug_logs_clear_buffer_title">Изчистване на логовете за отстраняване на грешки</string>
<string name="revanced_debug_logs_clear_buffer_summary">Изчиства всички съхранени логове за отстраняване на грешки на ReVanced</string>
<string name="revanced_debug_logs_clear_toast">Логовете са изчистени</string>
</patch> </patch>
<patch id="layout.hide.general.hideLayoutComponentsPatch"> <patch id="layout.hide.general.hideLayoutComponentsPatch">
<string name="revanced_hide_album_cards_title">\"Карти на албумите\"</string> <string name="revanced_hide_album_cards_title">\"Карти на албумите\"</string>
@@ -153,15 +176,18 @@ Second \"item\" text"</string>
<string name="revanced_hide_notify_me_button_title">Скриване на бутона \"Уведоми ме\"</string> <string name="revanced_hide_notify_me_button_title">Скриване на бутона \"Уведоми ме\"</string>
<string name="revanced_hide_notify_me_button_summary_on">Бутона \"Уведоми ме\" е скрит</string> <string name="revanced_hide_notify_me_button_summary_on">Бутона \"Уведоми ме\" е скрит</string>
<string name="revanced_hide_notify_me_button_summary_off">Бутона \"Уведоми ме\" се показва</string> <string name="revanced_hide_notify_me_button_summary_off">Бутона \"Уведоми ме\" се показва</string>
<!-- 'People also watched' should be translated using the same localized wording YouTube displays. --> <!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. -->
<string name="revanced_hide_search_result_recommendations_title">Скриване на етикета \"Хората също гледаха\"</string> <string name="revanced_hide_search_result_recommendation_labels_title">Скриване на етикетите с видео препоръки</string>
<string name="revanced_hide_search_result_recommendations_summary_on">Етикетът е скрит</string> <string name="revanced_hide_search_result_recommendation_labels_summary_on">Етикетите „Други потребители също гледаха“ и „Може също да харесате“ са скрити</string>
<string name="revanced_hide_search_result_recommendations_summary_off">Етикетът е показан</string> <string name="revanced_hide_search_result_recommendation_labels_summary_off">Етикетите „Други потребители също гледаха“ и „Може също да харесате“ са показани</string>
<!-- 'Show more' should be translated with the same localized wording that YouTube displays. <!-- 'Show more' should be translated with the same localized wording that YouTube displays.
This button usually appears when searching for a YT creator. --> This button usually appears when searching for a YT creator. -->
<string name="revanced_hide_show_more_button_title">Скриване на бутона \"Покажи още\"</string> <string name="revanced_hide_show_more_button_title">Скриване на бутона \"Покажи още\"</string>
<string name="revanced_hide_show_more_button_summary_on">Бутона Покажи още е скрит</string> <string name="revanced_hide_show_more_button_summary_on">Бутона Покажи още е скрит</string>
<string name="revanced_hide_show_more_button_summary_off">Бутона Покажи още се показва</string> <string name="revanced_hide_show_more_button_summary_off">Бутона Покажи още се показва</string>
<string name="revanced_hide_ticket_shelf_title">Скриване на рафта за билети</string>
<string name="revanced_hide_ticket_shelf_summary_on">Рафтът за билети е скрит</string>
<string name="revanced_hide_ticket_shelf_summary_off">Рафтът за билети е показан</string>
<string name="revanced_hide_timed_reactions_title">Скриване на времевите реакции</string> <string name="revanced_hide_timed_reactions_title">Скриване на времевите реакции</string>
<string name="revanced_hide_timed_reactions_summary_on">Времевите реакции са скрити</string> <string name="revanced_hide_timed_reactions_summary_on">Времевите реакции са скрити</string>
<string name="revanced_hide_timed_reactions_summary_off">Времевите реакции се показват</string> <string name="revanced_hide_timed_reactions_summary_off">Времевите реакции се показват</string>
@@ -231,6 +257,9 @@ Second \"item\" text"</string>
<string name="revanced_hide_ai_generated_video_summary_section_title">Скриване на \"AI-генерирано видео резюме\"</string> <string name="revanced_hide_ai_generated_video_summary_section_title">Скриване на \"AI-генерирано видео резюме\"</string>
<string name="revanced_hide_ai_generated_video_summary_section_summary_on">Скрит е разделът с видео резюме</string> <string name="revanced_hide_ai_generated_video_summary_section_summary_on">Скрит е разделът с видео резюме</string>
<string name="revanced_hide_ai_generated_video_summary_section_summary_off">Показва се разделът с видео резюме</string> <string name="revanced_hide_ai_generated_video_summary_section_summary_off">Показва се разделът с видео резюме</string>
<string name="revanced_hide_ask_section_title">Скриване на „Попитай“</string>
<string name="revanced_hide_ask_section_summary_on">Скрит раздел „Попитай“</string>
<string name="revanced_hide_ask_section_summary_off">Показване на раздел „Попитай“</string>
<string name="revanced_hide_attributes_section_title">Скриване на атрибути</string> <string name="revanced_hide_attributes_section_title">Скриване на атрибути</string>
<string name="revanced_hide_attributes_section_summary_on">Секциите \"Препоръчани места\", \"Игри\", \"Музика\" и \"Споменати хора\" са скрити</string> <string name="revanced_hide_attributes_section_summary_on">Секциите \"Препоръчани места\", \"Игри\", \"Музика\" и \"Споменати хора\" са скрити</string>
<string name="revanced_hide_attributes_section_summary_off">Секциите \"Препоръчани места\", \"Игри\", \"Музика\" и \"Споменати хора\" са показани</string> <string name="revanced_hide_attributes_section_summary_off">Секциите \"Препоръчани места\", \"Игри\", \"Музика\" и \"Споменати хора\" са показани</string>
@@ -352,9 +381,6 @@ Second \"item\" text"</string>
Тази функция е налична само за по-стари устройства"</string> Тази функция е налична само за по-стари устройства"</string>
<string name="revanced_hide_fullscreen_ads_summary_off">Рекламите в режим на цял екран са показани</string> <string name="revanced_hide_fullscreen_ads_summary_off">Рекламите в режим на цял екран са показани</string>
<string name="revanced_hide_buttoned_ads_title">Скриване на рекламни бутони</string>
<string name="revanced_hide_buttoned_ads_summary_on">Бутонираните реклами са скрити</string>
<string name="revanced_hide_buttoned_ads_summary_off">Бутонираните реклами са показани</string>
<string name="revanced_hide_paid_promotion_label_title">Скриване на платените промоции</string> <string name="revanced_hide_paid_promotion_label_title">Скриване на платените промоции</string>
<string name="revanced_hide_paid_promotion_label_summary_on">Промоционалните етикети са скрити</string> <string name="revanced_hide_paid_promotion_label_summary_on">Промоционалните етикети са скрити</string>
<string name="revanced_hide_paid_promotion_label_summary_off">Промоционалните етикети се показват</string> <string name="revanced_hide_paid_promotion_label_summary_off">Промоционалните етикети се показват</string>
@@ -465,9 +491,10 @@ Second \"item\" text"</string>
<string name="revanced_swipe_overlay_background_opacity_title">Плъзгане на фона на наслагването непрозрачност</string> <string name="revanced_swipe_overlay_background_opacity_title">Плъзгане на фона на наслагването непрозрачност</string>
<string name="revanced_swipe_overlay_background_opacity_summary">Стойност на непрозрачността между 0-100</string> <string name="revanced_swipe_overlay_background_opacity_summary">Стойност на непрозрачността между 0-100</string>
<string name="revanced_swipe_overlay_background_opacity_invalid_toast">Непрозрачността на плъзгането трябва да е между 0-100</string> <string name="revanced_swipe_overlay_background_opacity_invalid_toast">Непрозрачността на плъзгането трябва да е между 0-100</string>
<string name="revanced_swipe_overlay_progress_color_title">Цвят на лентата за напредък при плъзгане</string> <string name="revanced_swipe_overlay_progress_brightness_color_title">Цвят на наслагването за яркост при плъзгане</string>
<string name="revanced_swipe_overlay_progress_color_summary">Цветът на лентата за напредък за контролите за сила на звука и яркост</string> <string name="revanced_swipe_overlay_progress_brightness_color_summary">Цветът на лентата за състояние при контролиране на яркостта</string>
<string name="revanced_swipe_overlay_progress_color_invalid_toast">Невалиден цвят на лентата за напредък</string> <string name="revanced_swipe_overlay_progress_volume_color_title">Цвят на наслагването за сила на звука при плъзгане</string>
<string name="revanced_swipe_overlay_progress_volume_color_summary">Цветът на лентата за състояние при контролиране на силата на звука</string>
<string name="revanced_swipe_text_overlay_size_title">Размер на текста на наслагването при плъзгане</string> <string name="revanced_swipe_text_overlay_size_title">Размер на текста на наслагването при плъзгане</string>
<string name="revanced_swipe_text_overlay_size_summary">Размерът на текста за наслагването при плъзгане между 1-30</string> <string name="revanced_swipe_text_overlay_size_summary">Размерът на текста за наслагването при плъзгане между 1-30</string>
<string name="revanced_swipe_text_overlay_size_invalid_toast">Размерът на текста трябва да е между 1-30</string> <string name="revanced_swipe_text_overlay_size_invalid_toast">Размерът на текста трябва да е между 1-30</string>
@@ -836,10 +863,10 @@ Second \"item\" text"</string>
<string name="revanced_ryd_failure_ryd_enabled_while_playing_video_then_user_voted">Презареди видеото за гласуване с ReturnYouTubeDislike</string> <string name="revanced_ryd_failure_ryd_enabled_while_playing_video_then_user_voted">Презареди видеото за гласуване с ReturnYouTubeDislike</string>
<!-- Video likes have been set to hidden by the video uploader. --> <!-- Video likes have been set to hidden by the video uploader. -->
<string name="revanced_ryd_video_likes_hidden_by_video_owner">Скрито от собственика</string> <string name="revanced_ryd_video_likes_hidden_by_video_owner">Скрито от собственика</string>
<string name="revanced_ryd_enable_summary_on">Нехаресванията се показват</string> <string name="revanced_ryd_enabled_summary_on">Нехаресванията се показват</string>
<string name="revanced_ryd_enable_summary_off">Нехаресванията не се показват</string> <string name="revanced_ryd_enabled_summary_off">Нехаресванията не се показват</string>
<string name="revanced_ryd_shorts_title">Пок. нехаресвания в кратките клипове</string> <string name="revanced_ryd_shorts_title">Пок. нехаресвания в кратките клипове</string>
<string name="revanced_ryd_shorts_summary_on_disclaimer">"Нехаресванията на Shorts са показани <string name="revanced_ryd_shorts_summary_on">"Нехаресванията на Shorts са показани
Ограничение: Нехаресванията може да не се показват в режим инкогнито"</string> Ограничение: Нехаресванията може да не се показват в режим инкогнито"</string>
<string name="revanced_ryd_shorts_summary_off">Нехаресванията на Shorts не са показани</string> <string name="revanced_ryd_shorts_summary_off">Нехаресванията на Shorts не са показани</string>
@@ -856,7 +883,6 @@ Second \"item\" text"</string>
<string name="revanced_ryd_toast_on_connection_error_title">Показване на известие, ако API не е наличен</string> <string name="revanced_ryd_toast_on_connection_error_title">Показване на известие, ако API не е наличен</string>
<string name="revanced_ryd_toast_on_connection_error_summary_on">Показва известие, ако Return YouTube Dislike не е наличен</string> <string name="revanced_ryd_toast_on_connection_error_summary_on">Показва известие, ако Return YouTube Dislike не е наличен</string>
<string name="revanced_ryd_toast_on_connection_error_summary_off">Не се показва известие, ако ReturnYouTube Dislike не е наличен</string> <string name="revanced_ryd_toast_on_connection_error_summary_off">Не се показва известие, ако ReturnYouTube Dislike не е наличен</string>
<string name="revanced_ryd_about">За програмата</string>
<string name="revanced_ryd_attribution_summary">Данните за нехаресване са от Return YouTube Dislike API. Докоснете за да научите повече</string> <string name="revanced_ryd_attribution_summary">Данните за нехаресване са от Return YouTube Dislike API. Докоснете за да научите повече</string>
<!-- Statistic strings are shown in the settings only when ReVanced debug mode is enabled. Typical users will never see these. --> <!-- Statistic strings are shown in the settings only when ReVanced debug mode is enabled. Typical users will never see these. -->
<string name="revanced_ryd_statistics_category_title">Статистика Return YouTube Dislike API на това устройство</string> <string name="revanced_ryd_statistics_category_title">Статистика Return YouTube Dislike API на това устройство</string>
@@ -1063,7 +1089,7 @@ Second \"item\" text"</string>
<string name="revanced_sb_new_segment_edit_by_hand_title">Ръчно редактиране на времената на частта</string> <string name="revanced_sb_new_segment_edit_by_hand_title">Ръчно редактиране на времената на частта</string>
<string name="revanced_sb_new_segment_edit_by_hand_content">Желаете ли да редактирате времената за начало или край на частта?</string> <string name="revanced_sb_new_segment_edit_by_hand_content">Желаете ли да редактирате времената за начало или край на частта?</string>
<string name="revanced_sb_new_segment_edit_by_hand_parse_error">Зададено е невалидно време</string> <string name="revanced_sb_new_segment_edit_by_hand_parse_error">Зададено е невалидно време</string>
<string name="revanced_sb_stats">Статистика</string> <string name="revanced_sb_stats_title">Статистика</string>
<!-- Shown in the settings preferences, and translations can be any text length. --> <!-- Shown in the settings preferences, and translations can be any text length. -->
<string name="revanced_sb_stats_connection_failure">Статистиките временно не са налични (API не работи)</string> <string name="revanced_sb_stats_connection_failure">Статистиките временно не са налични (API не работи)</string>
<string name="revanced_sb_stats_loading">Зареждане...</string> <string name="revanced_sb_stats_loading">Зареждане...</string>
@@ -1087,13 +1113,8 @@ Second \"item\" text"</string>
<string name="revanced_sb_stats_saved_second_format">%s секунди</string> <string name="revanced_sb_stats_saved_second_format">%s секунди</string>
<string name="revanced_sb_color_opacity_label">Непрозрачност:</string> <string name="revanced_sb_color_opacity_label">Непрозрачност:</string>
<string name="revanced_sb_color_dot_label">Цвят:</string> <string name="revanced_sb_color_dot_label">Цвят:</string>
<string name="revanced_sb_color_changed">Цветът е променен</string> <string name="revanced_sb_about_title">За програмата</string>
<string name="revanced_sb_color_reset">Възстанови цвета</string> <string name="revanced_sb_about_api_summary">Данните са предоставени от SponsorBlock API. Докоснете тук за повече информация и изтеглияния</string>
<string name="revanced_sb_color_invalid">Невалидна стойност за цвета</string>
<string name="revanced_sb_reset_color">Възстановяване на цвят</string>
<string name="revanced_sb_reset">Възстанови</string>
<string name="revanced_sb_about">За програмата</string>
<string name="revanced_sb_about_api_sum">Данните са предоставени от SponsorBlock API. Докоснете тук за повече информация и изтеглияния</string>
</patch> </patch>
<patch id="layout.formfactor.changeFormFactorPatch"> <patch id="layout.formfactor.changeFormFactorPatch">
<string name="revanced_change_form_factor_title">Формат на екрана /Таблет, Телфон, .../</string> <string name="revanced_change_form_factor_title">Формат на екрана /Таблет, Телфон, .../</string>
@@ -1316,6 +1337,22 @@ Second \"item\" text"</string>
<string name="microg_settings_title">GmsCore Настройки</string> <string name="microg_settings_title">GmsCore Настройки</string>
<string name="microg_settings_summary">Настройки на GmsCore</string> <string name="microg_settings_summary">Настройки на GmsCore</string>
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
<string name="revanced_disable_haptic_feedback_title">Вибрационна обратна връзка</string>
<string name="revanced_disable_haptic_feedback_summary">Промяна на вибрационната обратна връзка</string>
<string name="revanced_disable_haptic_feedback_chapters_title">Деактивиране на вибрацията при главите</string>
<string name="revanced_disable_haptic_feedback_chapters_summary_on">Вибрацията при главите е деактивирана</string>
<string name="revanced_disable_haptic_feedback_chapters_summary_off">Вибрацията при главите е активирана</string>
<string name="revanced_disable_haptic_feedback_precise_seeking_title">Деактивиране на вибрацията при прецизното търсене</string>
<string name="revanced_disable_haptic_feedback_precise_seeking_summary_on">Прецизната вибрация при търсене е деактивирана</string>
<string name="revanced_disable_haptic_feedback_precise_seeking_summary_off">Вибрацията при прецизното търсене е активирана</string>
<string name="revanced_disable_haptic_feedback_seek_undo_title">Деактивиране на вибрация при отмяна на търсене</string>
<string name="revanced_disable_haptic_feedback_seek_undo_summary_on">Вибрацията при отмяна на търсене е деактивирана</string>
<string name="revanced_disable_haptic_feedback_seek_undo_summary_off">Вибрацията при отмяна на търсене е активирана</string>
<string name="revanced_disable_haptic_feedback_zoom_title">Деактивиране на вибрация при мащабиране</string>
<string name="revanced_disable_haptic_feedback_zoom_summary_on">Вибрацията при мащабиране е деактивирана</string>
<string name="revanced_disable_haptic_feedback_zoom_summary_off">Вибрацията при мащабиране е активирана</string>
</patch>
<patch id="misc.gms.accountCredentialsInvalidTextPatch"> <patch id="misc.gms.accountCredentialsInvalidTextPatch">
<string name="microg_offline_account_login_error">Ако наскоро сте променили данните си за вход в профила, деинсталирайте и инсталирайте отново MicroG.</string> <string name="microg_offline_account_login_error">Ако наскоро сте променили данните си за вход в профила, деинсталирайте и инсталирайте отново MicroG.</string>
</patch> </patch>
@@ -1334,11 +1371,6 @@ Second \"item\" text"</string>
<string name="revanced_remove_tracking_query_parameter_summary_on">Параметърът на заявката за проследяване е премахнат от връзките</string> <string name="revanced_remove_tracking_query_parameter_summary_on">Параметърът на заявката за проследяване е премахнат от връзките</string>
<string name="revanced_remove_tracking_query_parameter_summary_off">Параметърът на заявката за проследяване не е премахнат от връзките</string> <string name="revanced_remove_tracking_query_parameter_summary_off">Параметърът на заявката за проследяване не е премахнат от връзките</string>
</patch> </patch>
<patch id="misc.zoomhaptics.zoomHapticsPatch">
<string name="revanced_disable_zoom_haptics_title">Деактивиране на вибрация при мащабиране</string>
<string name="revanced_disable_zoom_haptics_summary_on">Вибрациите са деактивирани</string>
<string name="revanced_disable_zoom_haptics_summary_off">Вибрациите са активирани</string>
</patch>
<patch id="video.audio.forceOriginalAudioPatch"> <patch id="video.audio.forceOriginalAudioPatch">
<string name="revanced_force_original_audio_title">Принудително оригинално аудио език</string> <string name="revanced_force_original_audio_title">Принудително оригинално аудио език</string>
<string name="revanced_force_original_audio_summary_on">Използване на оригиналния език на аудиото</string> <string name="revanced_force_original_audio_summary_on">Използване на оригиналния език на аудиото</string>

Some files were not shown because too many files have changed in this diff Show More