Compare commits

...

51 Commits

Author SHA1 Message Date
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
167 changed files with 3766 additions and 1494 deletions

View File

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

View File

@@ -13,24 +13,23 @@ jobs:
permissions:
contents: write
packages: write
id-token: write
attestations: write
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
# Make sure the release step uses its own credentials:
# https://github.com/cycjimmy/semantic-release-action#private-packages
persist-credentials: false
fetch-depth: 0
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: "temurin"
java-version: "17"
distribution: 'temurin'
java-version: '17'
- name: Cache Gradle
uses: burrunan/gradle-cache-action@v1
uses: burrunan/gradle-cache-action@v3
- name: Build
env:
@@ -40,7 +39,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "lts/*"
node-version: 'lts/*'
cache: 'npm'
- name: Install dependencies
@@ -54,6 +53,14 @@ jobs:
fingerprint: ${{ vars.GPG_FINGERPRINT }}
- name: Release
uses: cycjimmy/semantic-release-action@v4
id: release
env:
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": [
"CHANGELOG.md",
"gradle.properties",
"gradle.properties"
],
"message": "chore: Release v${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
@@ -33,16 +33,16 @@
"assets": [
{
"path": "patches/build/libs/patches-!(*sources*|*javadoc*).rvp?(.asc)"
},
}
],
successComment: false
"successComment": false
}
],
[
"@saithodev/semantic-release-backmerge",
{
backmergeBranches: [{"from": "main", "to": "dev"}],
clearWorkspace: true
"backmergeBranches": [{"from": "main", "to": "dev"}],
"clearWorkspace": true
}
]
]

View File

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

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;
import static app.revanced.extension.shared.settings.BaseSettings.DEBUG;
import static app.revanced.extension.shared.settings.BaseSettings.DEBUG_STACKTRACE;
import static app.revanced.extension.shared.settings.BaseSettings.DEBUG_TOAST_ON_ERROR;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.settings.BaseSettings;
import java.io.PrintWriter;
import java.io.StringWriter;
import 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 {
/**
@@ -17,99 +28,158 @@ public class Logger {
*/
@FunctionalInterface
public interface LogMessage {
/**
* @return Logger string message. This method is only called if logging is enabled.
*/
@NonNull
String buildMessageString();
}
/**
* @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 return 'SomethingView':
* <code>
* com.company.SomethingView
* com.company.SomethingView$StaticClass
* com.company.SomethingView$1
* </code>
*/
private String findOuterClassSimpleName() {
var selfClass = this.getClass();
private enum LogLevel {
DEBUG,
INFO,
ERROR
}
String fullClassName = selfClass.getName();
final int dollarSignIndex = fullClassName.indexOf('$');
if (dollarSignIndex < 0) {
return selfClass.getSimpleName(); // Already an outer class.
private static final String REVANCED_LOG_TAG = "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);
StringBuilder logBuilder = new StringBuilder(className.length() + 2
+ messageString.length());
logBuilder.append(className).append(": ").append(messageString);
String toastMessage = showToast ? logBuilder.toString() : null;
// Append exception message if present.
if (ex != null) {
var exceptionMessage = ex.getMessage();
if (exceptionMessage != null) {
logBuilder.append("\nException: ").append(exceptionMessage);
}
}
// 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);
if (includeStackTrace) {
var sw = new StringWriter();
new Throwable().printStackTrace(new PrintWriter(sw));
String stackTrace = sw.toString();
// Remove the stacktrace elements of this class.
final int loggerIndex = stackTrace.lastIndexOf(LOGGER_CLASS_NAME);
final int loggerBegins = stackTrace.indexOf('\n', loggerIndex);
logBuilder.append(stackTrace, loggerBegins, stackTrace.length());
}
String logText = logBuilder.toString();
LogBufferManager.appendToLogBuffer(logText);
switch (logLevel) {
case DEBUG:
if (ex == null) Log.d(REVANCED_LOG_TAG, logText);
else Log.d(REVANCED_LOG_TAG, logText, ex);
break;
case INFO:
if (ex == null) Log.i(REVANCED_LOG_TAG, logText);
else Log.i(REVANCED_LOG_TAG, logText, ex);
break;
case ERROR:
if (ex == null) Log.e(REVANCED_LOG_TAG, logText);
else Log.e(REVANCED_LOG_TAG, logText, ex);
break;
}
if (toastMessage != null) {
Utils.showToastLong(toastMessage);
}
}
private static final String REVANCED_LOG_PREFIX = "revanced: ";
/**
* Logs debug messages under the outer class name of the code calling this method.
* Whenever possible, the log string should be constructed entirely inside {@link LogMessage#buildMessageString()}
* so the performance cost of building strings is paid only if {@link BaseSettings#DEBUG} is enabled.
* <p>
* 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);
}
/**
* Logs debug messages under the outer class name of the code calling this method.
* Whenever possible, the log string should be constructed entirely inside {@link LogMessage#buildMessageString()}
* so the performance cost of building strings is paid only if {@link BaseSettings#DEBUG} is enabled.
* <p>
* 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()) {
String logMessage = message.buildMessageString();
String logTag = REVANCED_LOG_PREFIX + message.findOuterClassSimpleName();
if (DEBUG_STACKTRACE.get()) {
var builder = new StringBuilder(logMessage);
var sw = new StringWriter();
new Throwable().printStackTrace(new PrintWriter(sw));
builder.append('\n').append(sw);
logMessage = builder.toString();
}
if (ex == null) {
Log.d(logTag, logMessage);
} else {
Log.d(logTag, logMessage, ex);
}
logInternal(LogLevel.DEBUG, message, ex, DEBUG_STACKTRACE.get(), false);
}
}
/**
* 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);
}
/**
* Logs information messages using the outer class name of the code calling this method.
*/
public static void printInfo(@NonNull LogMessage message, @Nullable Exception ex) {
String logTag = REVANCED_LOG_PREFIX + message.findOuterClassSimpleName();
String logMessage = message.buildMessageString();
if (ex == null) {
Log.i(logTag, logMessage);
} else {
Log.i(logTag, logMessage, ex);
}
public static void printInfo(LogMessage message, @Nullable Exception ex) {
logInternal(LogLevel.INFO, message, ex, DEBUG_STACKTRACE.get(), false);
}
/**
* 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);
}
@@ -122,35 +192,23 @@ public class Logger {
* @param message log message
* @param ex exception (optional)
*/
public static void printException(@NonNull LogMessage message, @Nullable Throwable ex) {
String messageString = message.buildMessageString();
String outerClassSimpleName = message.findOuterClassSimpleName();
String logMessage = REVANCED_LOG_PREFIX + outerClassSimpleName;
if (ex == null) {
Log.e(logMessage, messageString);
} else {
Log.e(logMessage, messageString, ex);
}
if (DEBUG_TOAST_ON_ERROR.get()) {
Utils.showToastLong(outerClassSimpleName + ": " + messageString);
}
public static void printException(LogMessage message, @Nullable Throwable ex) {
logInternal(LogLevel.ERROR, message, ex, DEBUG_STACKTRACE.get(), DEBUG_TOAST_ON_ERROR.get());
}
/**
* Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#getContext()} may not be initialized.
* Normally this method should not be used.
*/
public static void initializationInfo(@NonNull Class<?> callingClass, @NonNull String message) {
Log.i(REVANCED_LOG_PREFIX + callingClass.getSimpleName(), message);
public static void initializationInfo(LogMessage message) {
logInternal(LogLevel.INFO, message, null, false, false);
}
/**
* Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#getContext()} may not be initialized.
* Normally this method should not be used.
*/
public static void initializationException(@NonNull Class<?> callingClass, @NonNull String message,
@Nullable Exception ex) {
Log.e(REVANCED_LOG_PREFIX + callingClass.getSimpleName(), message, ex);
public static void initializationException(LogMessage message, @Nullable Exception ex) {
logInternal(LogLevel.ERROR, message, ex, false, false);
}
}
}

View File

@@ -1,7 +1,11 @@
package app.revanced.extension.shared;
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.Intent;
import android.content.pm.ApplicationInfo;
@@ -18,6 +22,8 @@ import android.os.Looper;
import android.preference.Preference;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
import android.util.Pair;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
@@ -357,15 +363,17 @@ public class Utils {
public static Context getContext() {
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;
}
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.
context = appContext;
Logger.initializationInfo(Utils.class, "Set context: " + appContext);
AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get();
if (language != AppLanguage.DEFAULT) {
@@ -377,8 +385,9 @@ public class Utils {
}
}
public static void setClipboard(@NonNull String text) {
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
public static void setClipboard(CharSequence text) {
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context
.getSystemService(Context.CLIPBOARD_SERVICE);
android.content.ClipData clip = android.content.ClipData.newPlainText("ReVanced", text);
clipboard.setPrimaryClip(clip);
}
@@ -542,24 +551,25 @@ public class Utils {
private static void showToast(@NonNull String messageToToast, int toastDuration) {
Objects.requireNonNull(messageToToast);
runOnMainThreadNowOrLater(() -> {
if (context == null) {
Logger.initializationException(Utils.class, "Cannot show toast (context is null): " + messageToToast, null);
} else {
Logger.printDebug(() -> "Showing toast: " + messageToToast);
Toast.makeText(context, messageToToast, toastDuration).show();
}
}
);
Context currentContext = context;
if (currentContext == null) {
Logger.initializationException(() -> "Cannot show toast (context is null): " + messageToToast, null);
} else {
Logger.printDebug(() -> "Showing toast: " + messageToToast);
Toast.makeText(currentContext, messageToToast, toastDuration).show();
}
});
}
public static boolean isDarkModeEnabled(Context context) {
Configuration config = context.getResources().getConfiguration();
public static boolean isDarkModeEnabled() {
Configuration config = Resources.getSystem().getConfiguration();
final int currentNightMode = config.uiMode & Configuration.UI_MODE_NIGHT_MASK;
return currentNightMode == Configuration.UI_MODE_NIGHT_YES;
}
public static boolean isLandscapeOrientation() {
final int orientation = context.getResources().getConfiguration().orientation;
final int orientation = Resources.getSystem().getConfiguration().orientation;
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) {
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() {
return Looper.getMainLooper().isCurrentThread();
}
/**
* @throws IllegalStateException if the calling thread is _off_ the main thread
* @throws IllegalStateException if the calling thread is _off_ the main thread.
*/
public static void verifyOnMainThread() throws IllegalStateException {
if (!isCurrentlyOnMainThread()) {
@@ -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 {
if (isCurrentlyOnMainThread()) {
@@ -629,13 +639,23 @@ public class Utils {
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() {
NetworkType networkType = getNetworkType();
return networkType == NetworkType.MOBILE
|| 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() {
Context networkContext = getContext();
if (networkContext == null) {
@@ -738,9 +758,9 @@ public class Utils {
* then the preferences are left unsorted.
*/
@SuppressWarnings("deprecation")
public static void sortPreferenceGroups(@NonNull PreferenceGroup group) {
public static void sortPreferenceGroups(PreferenceGroup group) {
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++) {
Preference preference = group.getPreference(i);
@@ -769,17 +789,22 @@ public class Utils {
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;
for (Preference pref : preferences.values()) {
for (Pair<String, Preference> pair : preferences) {
int order = index++;
Preference pref = pair.second;
// Move any screens, intents, and the one off About preference to the top.
if (pref instanceof PreferenceScreen || pref instanceof ReVancedAboutPreference
|| pref.getIntent() != null) {
// Arbitrary high number.
// Any arbitrary large number.
order -= 1000;
}
@@ -843,6 +868,20 @@ public class Utils {
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) {
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.
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) -> {
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.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
@@ -54,7 +53,7 @@ public class ReVancedAboutPreference extends Preference {
}
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 java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
import app.revanced.extension.shared.Utils;
@@ -46,17 +45,25 @@ public class SortedListPreference extends ListPreference {
}
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++) {
Pair<CharSequence, CharSequence> pair = new Pair<>(entries[i], entryValues[i]);
if (i < firstEntriesToPreserve) {
firstEntries.add(pair);
} 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[] sortedEntryValues = new CharSequence[entrySize];
@@ -67,9 +74,10 @@ public class SortedListPreference extends ListPreference {
i++;
}
for (Pair<CharSequence, CharSequence> pair : lastEntries.values()) {
sortedEntries[i] = pair.first;
sortedEntryValues[i] = pair.second;
for (Pair<String, Pair<CharSequence, CharSequence>> outer : lastEntries) {
Pair<CharSequence, CharSequence> inner = outer.second;
sortedEntries[i] = inner.first;
sortedEntryValues[i] = inner.second;
i++;
}

View File

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

View File

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

View File

@@ -22,6 +22,6 @@ public class InputTextPreference extends EditTextPreference {
protected void onBindView(View 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) {
super.onBindView(view);
Utils.setTitleAndSummaryColor(getContext(), view);
Utils.setTitleAndSummaryColor(view);
}
@Override

View File

@@ -48,6 +48,6 @@ public class ReVancedTikTokAboutPreference extends ReVancedAboutPreference {
protected void onBindView(View 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) {
super.onBindView(view);
Utils.setTitleAndSummaryColor(getContext(), view);
Utils.setTitleAndSummaryColor(view);
}
}

View File

@@ -16,9 +16,7 @@ public class SpoofSimPatch {
return false;
}
Logger.initializationException(SpoofSimPatch.class,
"Context is not yet set, cannot spoof: " + fieldSpoofed, null);
Logger.initializationException(() -> "Context is not yet set, cannot spoof: " + fieldSpoofed, null);
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 Utils.isDarkModeEnabled(Utils.getContext())
return Utils.isDarkModeEnabled()
? !DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK
: !DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT;
}

View File

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

View File

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

View File

@@ -1,7 +1,5 @@
package app.revanced.extension.youtube.patches;
import android.content.res.Resources;
import android.util.TypedValue;
import android.view.View;
import app.revanced.extension.shared.Logger;
@@ -33,8 +31,7 @@ public final class WideSearchbarPatch {
final int paddingRight = searchBarView.getPaddingRight();
final int paddingTop = searchBarView.getPaddingTop();
final int paddingBottom = searchBarView.getPaddingBottom();
final int paddingStart = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
8, Resources.getSystem().getDisplayMetrics());
final int paddingStart = Utils.dipToPixels(8);
if (Utils.isRightToLeftLocale()) {
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"
);
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(
Settings.HIDE_GENERAL_ADS,
"_ad_with",
"_buttoned_layout",
"ads_video_with_context",
"banner_text_icon",
"square_image_layout",
"watch_metadata_app_promo",
"video_display_full_layout",
"hero_promo_image",
"statement_banner",
"brand_video_shelf",
"brand_video_singleton",
"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",
"product_details",
"composite_concurrent_carousel_layout",
"carousel_headered_layout",
"full_width_portrait_image_layout",
"brand_video_shelf",
"brand_video_singleton"
"square_image_layout",
"statement_banner",
"text_image_button_layout",
"text_image_no_button_layout", // Tablet layout search results.
"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(
Settings.HIDE_MOVIES_SECTION,
"browsy_bar",
"compact_movie",
"compact_tvfilm_item",
"horizontal_movie_shelf",
"movie_and_show_upsell_card",
"compact_tvfilm_item",
"offer_module_root"
);
@@ -160,7 +157,6 @@ public final class AdsFilter extends Filter {
addPathCallbacks(
generalAds,
buttonedAd,
merchandise,
viewProducts,
selfSponsor,
@@ -177,34 +173,30 @@ public final class AdsFilter extends Filter {
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (matchedGroup == playerShoppingShelf) {
if (contentIndex == 0 && playerShoppingShelfBuffer.check(protobufBufferArray).isFiltered()) {
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
return contentIndex == 0 && playerShoppingShelfBuffer.check(protobufBufferArray).isFiltered();
}
// Check for the index because of likelihood of false positives.
if (matchedGroup == shoppingLinks && contentIndex != 0) {
if (contentIndex != 0 && matchedGroup == shoppingLinks) {
return false;
}
if (exceptions.matches(path))
if (exceptions.matches(path)) {
return false;
}
if (matchedGroup == fullscreenAd) {
if (path.contains("|ImageType|")) closeFullscreenAd();
return false; // 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);
}
// Do not actually filter the fullscreen ad otherwise it will leave a dimmed screen.
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,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (matchedGroup == likeSubscribeGlow) {
if ((path.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX) || path.startsWith(COMPACT_CHANNEL_BAR_PATH_PREFIX))
&& path.contains(ANIMATED_VECTOR_TYPE_PATH)) {
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
return (path.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX) || path.startsWith(COMPACT_CHANNEL_BAR_PATH_PREFIX))
&& path.contains(ANIMATED_VECTOR_TYPE_PATH);
}
// If the current matched group is the action bar group,
// in case every filter group is enabled, hide the action bar.
if (matchedGroup == actionBarGroup) {
if (!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 isEveryFilterGroupEnabled();
}
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) {
// 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.
if (contentIndex == 0
return contentIndex == 0
&& path.endsWith(TIMESTAMP_OR_EMOJI_BUTTONS_ENDS_WITH_PATH)
&& emojiPickerBufferGroup.check(protobufBufferArray).isFiltered()) {
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
&& emojiPickerBufferGroup.check(protobufBufferArray).isFiltered();
}
if (matchedGroup == filterChipBar) {
if (aiCommentsSummary.check(protobufBufferArray).isFiltered()) {
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
return aiCommentsSummary.check(protobufBufferArray).isFiltered();
}
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) {
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"
);
final StringFilterGroup askSection = new StringFilterGroup(
Settings.HIDE_ASK_SECTION,
"youchat_entrypoint.eml"
);
final StringFilterGroup attributesSection = new StringFilterGroup(
Settings.HIDE_ATTRIBUTES_SECTION,
"gaming_section",
@@ -73,6 +78,7 @@ final class DescriptionComponentsFilter extends Filter {
addPathCallbacks(
aiGeneratedVideoSummarySection,
askSection,
attributesSection,
infoCardsSection,
howThisWasMadeSection,
@@ -88,13 +94,9 @@ final class DescriptionComponentsFilter extends Filter {
if (exceptions.matches(path)) return false;
if (matchedGroup == macroMarkersCarousel) {
if (contentIndex == 0 && macroMarkersCarouselGroupList.check(protobufBufferArray).isFiltered()) {
return super.isFiltered(path, identifier, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
return contentIndex == 0 && macroMarkersCarouselGroupList.check(protobufBufferArray).isFiltered();
}
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.List;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.settings.BaseSettings;
/**
* Filters litho based components.
*
@@ -62,10 +59,7 @@ abstract class Filter {
* Called after an enabled filter has been matched.
* Default implementation is to always filter the matched component and log the action.
* 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>
* Method is called off the main thread.
*
@@ -76,14 +70,6 @@ abstract class Filter {
*/
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
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;
}
}

View File

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

View File

@@ -38,6 +38,7 @@ public final class LayoutComponentsFilter extends Filter {
private final StringFilterGroup compactChannelBarInnerButton;
private final ByteArrayFilterGroup joinMembershipButton;
private final StringFilterGroup horizontalShelves;
private final ByteArrayFilterGroup ticketShelf;
public LayoutComponentsFilter() {
exceptions.addPatterns(
@@ -244,6 +245,11 @@ public final class LayoutComponentsFilter extends Filter {
"horizontal_tile_shelf.eml"
);
ticketShelf = new ByteArrayFilterGroup(
Settings.HIDE_TICKET_SHELF,
"ticket"
);
addPathCallbacks(
expandableMetadata,
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.
// Therefore, this identifier does not filter when the search bar is activated.
if (matchedGroup == singleItemInformationPanel) {
if (PlayerType.getCurrent().isMaximizedOrFullscreen() || !NavigationBar.isSearchBarActive()) {
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
return PlayerType.getCurrent().isMaximizedOrFullscreen() || !NavigationBar.isSearchBarActive();
}
// The groups are excluded from the filter due to the exceptions list below.
// Filter them separately here.
if (matchedGroup == notifyMe || matchedGroup == inFeedSurvey || matchedGroup == expandableMetadata)
{
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
if (matchedGroup == notifyMe || matchedGroup == inFeedSurvey || matchedGroup == expandableMetadata) {
return true;
}
if (exceptions.matches(path)) return false; // Exceptions are not filtered.
if (matchedGroup == compactChannelBarInner) {
if (compactChannelBarInnerButton.check(path).isFiltered()) {
// 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.
if (joinMembershipButton.check(protobufBufferArray).isFiltered()) {
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
}
return false;
return compactChannelBarInnerButton.check(path).isFiltered()
// 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.
&& joinMembershipButton.check(protobufBufferArray).isFiltered();
}
if (matchedGroup == horizontalShelves) {
if (contentIndex == 0 && hideShelves()) {
return super.isFiltered(path, identifier, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
return contentIndex == 0 && (hideShelves() || ticketShelf.check(protobufBufferArray).isFiltered());
}
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 app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.youtube.StringTrieSearch;
import app.revanced.extension.youtube.settings.Settings;
@@ -114,12 +115,29 @@ public final class LithoFilterPatch {
if (!group.includeInSearch()) {
continue;
}
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;
LithoFilterParameters parameters = (LithoFilterParameters) callbackParameter;
return filter.isFiltered(parameters.identifier, parameters.path, parameters.protoBuffer,
group, type, matchedStartIndex);
final boolean isFiltered = filter.isFiltered(parameters.identifier,
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,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (matchedGroup == videoQualityMenuFooter) {
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
return true;
}
if (contentIndex != 0) {
@@ -111,11 +111,6 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
return false;
}
if (flyoutFilterGroupList.check(protobufBufferArray).isFiltered()) {
// Super class handles logging.
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
return flyoutFilterGroupList.check(protobufBufferArray).isFiltered();
}
}

View File

@@ -278,27 +278,18 @@ public final class ShortsFilter extends Filter {
if (contentType == FilterContentType.PATH) {
if (matchedGroup == subscribeButton || matchedGroup == joinButton || matchedGroup == paidPromotionButton) {
// 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 super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
return path.startsWith(REEL_CHANNEL_BAR_PATH) || path.startsWith(REEL_METAPANEL_PATH);
}
if (matchedGroup == shortsCompactFeedVideoPath) {
if (shouldHideShortsFeedItems() && shortsCompactFeedVideoBuffer.check(protobufBufferArray).isFiltered()) {
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
return shouldHideShortsFeedItems() && shortsCompactFeedVideoBuffer.check(protobufBufferArray).isFiltered();
}
// Video action buttons (comment, share, remix) have the same path.
// Like and dislike are separate path filters and don't require buffer searching.
if (matchedGroup == shortsActionBar) {
if (actionButton.check(path).isFiltered()
&& videoActionButtonGroupList.check(protobufBufferArray).isFiltered()) {
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
return actionButton.check(path).isFiltered()
&& videoActionButtonGroupList.check(protobufBufferArray).isFiltered();
}
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
// under the assumption that the user wants all suggestions hidden.
if (isEverySuggestedActionFilterEnabled()) {
return super.isFiltered(path, identifier, protobufBufferArray, matchedGroup, contentType, contentIndex);
return true;
}
if (suggestedActionsGroupList.check(protobufBufferArray).isFiltered()) {
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
return suggestedActionsGroupList.check(protobufBufferArray).isFiltered();
}
} else {
// 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;
return true;
}
// Super class handles logging.
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
// 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;
}
return shouldHideShortsFeedItems();
}
private static boolean shouldHideShortsFeedItems() {

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,6 @@ import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.preference.PreferenceFragment;
import android.util.TypedValue;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toolbar;
@@ -119,8 +118,7 @@ public class LicenseActivityHook {
toolbar.setNavigationIcon(ReVancedPreferenceFragment.getBackButtonDrawable());
toolbar.setTitle(getResourceIdentifier("revanced_settings_title", "string"));
final int margin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16,
Utils.getContext().getResources().getDisplayMetrics());
final int margin = Utils.dipToPixels(16);
toolbar.setTitleMarginStart(margin);
toolbar.setTitleMarginEnd(margin);
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());
// 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_FULLSCREEN_ADS = new BooleanSetting("revanced_hide_fullscreen_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_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_TICKET_SHELF = new BooleanSetting("revanced_hide_ticket_shelf", FALSE);
// 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_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);
// 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_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_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);
@@ -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 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 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 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,
"revanced_spoof_device_dimensions_user_dialog_message");
/**
* When enabled, share the debug logs with care.
* 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));
public static final BooleanSetting DEBUG_PROTOBUFFER = new BooleanSetting("revanced_debug_protobuffer", FALSE, false,
"revanced_debug_protobuffer_user_dialog_message", parent(BaseSettings.DEBUG));
// Swipe controls
public static final BooleanSetting SWIPE_CHANGE_VIDEO = new BooleanSetting("revanced_swipe_change_video", FALSE, 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.TextUtils;
import android.text.style.BackgroundColorSpan;
import android.util.TypedValue;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.TextView;
@@ -245,9 +244,7 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
toolbar.setNavigationIcon(getBackButtonDrawable());
toolbar.setNavigationOnClickListener(view -> preferenceScreenDialog.dismiss());
final int margin = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 16, getResources().getDisplayMetrics()
);
final int margin = Utils.dipToPixels(16);
toolbar.setTitleMargin(margin, 0, margin, 0);
TextView toolbarTextView = Utils.getChildView(toolbar,
@@ -420,9 +417,11 @@ class PreferenceSearchData extends AbstractPreferenceSearchData<Preference> {
@CallSuper
void clearHighlighting() {
super.clearHighlighting();
if (highlightingApplied) {
preference.setSummary(originalSummary);
}
preference.setSummary(originalSummary);
super.clearHighlighting();
}
}
@@ -472,10 +471,12 @@ class SwitchPreferenceSearchData extends AbstractPreferenceSearchData<SwitchPref
@CallSuper
void clearHighlighting() {
super.clearHighlighting();
if (highlightingApplied) {
preference.setSummaryOn(originalSummaryOn);
preference.setSummaryOff(originalSummaryOff);
}
preference.setSummaryOn(originalSummaryOn);
preference.setSummaryOff(originalSummaryOff);
super.clearHighlighting();
}
}
@@ -529,8 +530,10 @@ class ListPreferenceSearchData extends AbstractPreferenceSearchData<ListPreferen
@CallSuper
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.Rect;
import android.text.TextUtils;
import android.util.TypedValue;
import androidx.annotation.NonNull;
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() {
if (highlightSegmentTimeBarScreenWidth == -1) {
highlightSegmentTimeBarScreenWidth = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, HIGHLIGHT_SEGMENT_DRAW_BAR_WIDTH,
Objects.requireNonNull(Utils.getContext()).getResources().getDisplayMetrics());
}
return highlightSegmentTimeBarScreenWidth;
}
/**
* Actual screen pixel width to use for the highlight segment time bar.
*/
private static final int highlightSegmentTimeBarScreenWidth
= Utils.dipToPixels(HIGHLIGHT_SEGMENT_DRAW_BAR_WIDTH);
/**
* Injection point.
@@ -757,7 +752,7 @@ public class SegmentPlaybackController {
final float left = leftPadding + segment.start * videoMillisecondsToPixels;
final float right;
if (segment.category == SegmentCategory.HIGHLIGHT) {
right = left + getHighlightSegmentTimeBarScreenWidth();
right = left + highlightSegmentTimeBarScreenWidth;
} else {
right = leftPadding + segment.end * videoMillisecondsToPixels;
}

View File

@@ -1,6 +1,7 @@
package app.revanced.extension.youtube.sponsorblock.objects;
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 android.graphics.Color;
@@ -9,7 +10,9 @@ import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.text.style.RelativeSizeSpan;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -134,7 +137,8 @@ public enum SegmentCategory {
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) {
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
*/
@NonNull
public final StringRef skippedToastBeginning;
/**
* Skipped segment toast, if the skip occurred in the middle half of the video
*/
@NonNull
public final StringRef skippedToastMiddle;
/**
* Skipped segment toast, if the skip occurred in the last quarter of the video
*/
@NonNull
public final StringRef skippedToastEnd;
@NonNull
public final Paint paint;
/**
* Category color with opacity applied.
*/
@ColorInt
private int color;
/**
* Value must be changed using {@link #setBehaviour(CategoryBehaviour)}.
* Caller must also {@link #updateEnabledCategories()}.
*/
@NonNull
public CategoryBehaviour behaviour = CategoryBehaviour.IGNORE;
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.behaviorSetting.save(behaviour.reVancedKeyValue);
}
@@ -273,6 +276,10 @@ public enum SegmentCategory {
return opacitySetting.get();
}
public float getOpacityDefault() {
return opacitySetting.defaultValue;
}
public void resetColorAndOpacity() {
setColor(colorSetting.defaultValue);
setOpacity(opacitySetting.defaultValue);
@@ -291,10 +298,19 @@ public enum SegmentCategory {
/**
* @return Integer color of #RRGGBB format.
*/
@ColorInt
public int getColorNoOpacity() {
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.
*/
@@ -302,22 +318,27 @@ public enum SegmentCategory {
return String.format(Locale.US, "#%06X", getColorNoOpacity());
}
private static SpannableString getCategoryColorDotSpan(String text, int color) {
SpannableString dotSpan = new SpannableString('⬤' + text);
private static SpannableString getCategoryColorDotSpan(String text, @ColorInt int color) {
SpannableString dotSpan = new SpannableString(COLOR_DOT_STRING + text);
dotSpan.setSpan(new ForegroundColorSpan(color), 0, 1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return dotSpan;
}
public static SpannableString getCategoryColorDot(int color) {
return getCategoryColorDotSpan("", color);
public static SpannableString getCategoryColorDot(@ColorInt int 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() {
return getCategoryColorDot(color);
}
public SpannableString getTitleWithColorDot(int categoryColor) {
public SpannableString getTitleWithColorDot(@ColorInt int categoryColor) {
return getCategoryColorDotSpan(" " + title, categoryColor);
}

View File

@@ -1,35 +1,46 @@
package app.revanced.extension.youtube.sponsorblock.objects;
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 android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Bundle;
import android.preference.ListPreference;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.GridLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import java.util.Locale;
import java.util.Objects;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.preference.ColorPickerPreference;
import app.revanced.extension.shared.settings.preference.ColorPickerView;
@SuppressWarnings("deprecation")
public class SegmentCategoryListPreference extends ListPreference {
private final SegmentCategory category;
private TextView colorDotView;
private EditText colorEditText;
private EditText opacityEditText;
/**
* #RRGGBB
* RGB format (no alpha).
*/
@ColorInt
private int categoryColor;
/**
* [0, 1]
@@ -37,6 +48,11 @@ public class SegmentCategoryListPreference extends ListPreference {
private float categoryOpacity;
private int selectedDialogEntryIndex;
private TextView dialogColorDotView;
private EditText dialogColorEditText;
private EditText dialogOpacityEditText;
private ColorPickerView dialogColorPickerView;
public SegmentCategoryListPreference(Context context, SegmentCategory category) {
super(context);
this.category = Objects.requireNonNull(category);
@@ -53,7 +69,7 @@ public class SegmentCategoryListPreference extends ListPreference {
setEntryValues(isHighlightCategory
? CategoryBehaviour.getBehaviorKeyValuesWithoutSkipOnce()
: CategoryBehaviour.getBehaviorKeyValues());
setSummary(category.description.toString());
super.setSummary(category.description.toString());
updateUI();
}
@@ -67,8 +83,20 @@ public class SegmentCategoryListPreference extends ListPreference {
categoryOpacity = category.getOpacity();
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.setPadding(70, 0, 150, 0); // Padding for the entire layout.
gridLayout.setColumnCount(3);
gridLayout.setRowCount(2);
@@ -84,19 +112,22 @@ public class SegmentCategoryListPreference extends ListPreference {
gridParams.rowSpec = GridLayout.spec(0); // First row.
gridParams.columnSpec = GridLayout.spec(1); // Second column.
gridParams.setMargins(0, 0, 10, 0);
colorDotView = new TextView(context);
colorDotView.setLayoutParams(gridParams);
gridLayout.addView(colorDotView);
dialogColorDotView = new TextView(context);
dialogColorDotView.setLayoutParams(gridParams);
gridLayout.addView(dialogColorDotView);
updateCategoryColorDot();
gridParams = new GridLayout.LayoutParams();
gridParams.rowSpec = GridLayout.spec(0); // First row.
gridParams.columnSpec = GridLayout.spec(2); // Third column.
colorEditText = new EditText(context);
colorEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS);
colorEditText.setTextLocale(Locale.US);
colorEditText.setText(category.getColorString());
colorEditText.addTextChangedListener(new TextWatcher() {
dialogColorEditText = new EditText(context);
dialogColorEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
dialogColorEditText.setAutofillHints((String) null);
dialogColorEditText.setTypeface(Typeface.MONOSPACE);
dialogColorEditText.setTextLocale(Locale.US);
dialogColorEditText.setText(getColorString(categoryColor));
dialogColorEditText.addTextChangedListener(new TextWatcher() {
@Override
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) {
try {
String colorString = edit.toString();
final int colorStringLength = colorString.length();
String normalizedColorString = ColorPickerPreference.cleanupColorCodeString(colorString);
if (!colorString.startsWith("#")) {
edit.insert(0, "#"); // Recursively calls back into this method.
if (!normalizedColorString.equals(colorString)) {
edit.replace(0, colorString.length(), normalizedColorString);
return;
}
final int maxColorStringLength = 7; // #RRGGBB
if (colorStringLength > maxColorStringLength) {
edit.delete(maxColorStringLength, colorStringLength);
if (normalizedColorString.length() != ColorPickerPreference.COLOR_STRING_LENGTH) {
// User is still typing out the color.
return;
}
categoryColor = Color.parseColor(colorString);
updateCategoryColorDot();
} catch (IllegalArgumentException ex) {
// Ignore.
// Remove the alpha channel.
final int newColor = Color.parseColor(colorString) & 0x00FFFFFF;
// Changing view color causes callback into this class.
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);
gridLayout.addView(colorEditText);
dialogColorEditText.setLayoutParams(gridParams);
gridLayout.addView(dialogColorEditText);
gridParams = new GridLayout.LayoutParams();
gridParams.rowSpec = GridLayout.spec(1); // Second row.
@@ -143,9 +176,13 @@ public class SegmentCategoryListPreference extends ListPreference {
gridParams = new GridLayout.LayoutParams();
gridParams.rowSpec = GridLayout.spec(1); // Second row.
gridParams.columnSpec = GridLayout.spec(2); // Third column.
opacityEditText = new EditText(context);
opacityEditText.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
opacityEditText.addTextChangedListener(new TextWatcher() {
dialogOpacityEditText = new EditText(context);
dialogOpacityEditText.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
dialogOpacityEditText.setAutofillHints((String) null);
dialogOpacityEditText.setTypeface(Typeface.MONOSPACE);
dialogOpacityEditText.setTextLocale(Locale.US);
dialogOpacityEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@@ -183,31 +220,40 @@ public class SegmentCategoryListPreference extends ListPreference {
}
updateCategoryColorDot();
} catch (NumberFormatException ex) {
} catch (Exception ex) {
// Should never happen.
Logger.printException(() -> "Could not parse opacity string", ex);
Logger.printException(() -> "opacityEditText afterTextChanged failure", ex);
}
}
});
opacityEditText.setLayoutParams(gridParams);
gridLayout.addView(opacityEditText);
dialogOpacityEditText.setLayoutParams(gridParams);
gridLayout.addView(dialogOpacityEditText);
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.setPositiveButton(android.R.string.ok, (dialog, which) -> {
onClick(dialog, DialogInterface.BUTTON_POSITIVE);
});
builder.setNeutralButton(str("revanced_sb_reset_color"), (dialog, which) -> {
try {
category.resetColorAndOpacity();
updateUI();
Utils.showToastShort(str("revanced_sb_color_reset"));
} catch (Exception ex) {
Logger.printException(() -> "setNeutralButton failure", ex);
}
});
builder.setNeutralButton(str("revanced_settings_reset_color"), null);
builder.setNegativeButton(android.R.string.cancel, null);
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
protected void onDialogClosed(boolean positiveResult) {
try {
@@ -230,42 +295,50 @@ public class SegmentCategoryListPreference extends ListPreference {
}
try {
String colorString = colorEditText.getText().toString();
if (!colorString.equals(category.getColorString()) || categoryOpacity != category.getOpacity()) {
category.setColor(colorString);
category.setOpacity(categoryOpacity);
Utils.showToastShort(str("revanced_sb_color_changed"));
}
category.setColor(dialogColorEditText.getText().toString());
category.setOpacity(categoryOpacity);
} catch (IllegalArgumentException ex) {
Utils.showToastShort(str("revanced_sb_color_invalid"));
Utils.showToastShort(str("revanced_settings_color_invalid"));
}
updateUI();
}
} catch (Exception ex) {
Logger.printException(() -> "onDialogClosed failure", ex);
} finally {
dialogColorDotView = null;
dialogColorEditText = null;
dialogOpacityEditText = null;
dialogColorPickerView = null;
}
}
private void applyOpacityToCategoryColor() {
categoryColor = applyOpacityToColor(categoryColor, categoryOpacity);
@ColorInt
private int applyOpacityToCategoryColor() {
return applyOpacityToColor(categoryColor, categoryOpacity);
}
public void updateUI() {
categoryColor = category.getColorNoOpacity();
categoryOpacity = category.getOpacity();
applyOpacityToCategoryColor();
setTitle(category.getTitleWithColorDot(categoryColor));
setTitle(category.getTitleWithColorDot(applyOpacityToCategoryColor()));
}
private void updateCategoryColorDot() {
applyOpacityToCategoryColor();
colorDotView.setText(SegmentCategory.getCategoryColorDot(categoryColor));
dialogColorDotView.setText(SegmentCategory.getCategoryColorDot(applyOpacityToCategoryColor()));
}
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);
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())
.setView(editText)
.setNegativeButton(android.R.string.cancel, null)
.setNeutralButton(str("revanced_sb_reset"), urlChangeListener)
.setNeutralButton(str("revanced_settings_reset"), urlChangeListener)
.setPositiveButton(android.R.string.ok, urlChangeListener)
.show();
return true;
@@ -433,7 +437,11 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
Utils.setEditTextDialogTheme(builder);
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

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

View File

@@ -284,6 +284,14 @@ public final class app/revanced/patches/messenger/inputfield/DisableTypingIndica
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 static final fun getRemoveMetaAITabPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
@@ -652,6 +660,10 @@ public final class app/revanced/patches/shared/misc/mapping/ResourceMappingPatch
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 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;
@@ -872,6 +884,10 @@ public final class app/revanced/patches/spotify/misc/fix/SpoofSignaturePatchKt {
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 static final fun getSanitizeSharingLinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
@@ -1383,6 +1399,10 @@ public final class app/revanced/patches/youtube/misc/gms/GmsCoreSupportPatchKt {
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 static final fun addImageUrlErrorCallbackHook (Ljava/lang/String;)V
public static final fun addImageUrlHook (Ljava/lang/String;Z)V
@@ -1615,8 +1635,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 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 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 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 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 }
?.getReference<MethodReference>()
?.takeIf {
it.anyMethodSignatureMatches(it,
it.anyMethodSignatureMatches(
SETTINGS_GLOBAL_GET_INT_OR_THROW_METHOD_REFERENCE,
SETTINGS_GLOBAL_GET_INT_OR_DEFAULT_METHOD_REFERENCE
)

View File

@@ -17,7 +17,8 @@ val removeShareTargetsPatch = resourcePatch(
try {
document("res/xml/shortcuts.xml")
} 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 ->
val rootNode = document.getNode("shortcuts") as? Element ?: return@use

View File

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

View File

@@ -5,9 +5,14 @@ import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch
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")
val disableSwitchingEmojiToStickerPatch = bytecodePatch(
name = "Disable switching emoji to sticker",
description = "Disables switching from emoji to sticker search mode in message input field.",
) {
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
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import app.revanced.patches.messenger.metaai.removeMetaAIPatch
@Deprecated("Superseded by removeMetaAIPatch", ReplaceWith("removeMetaAIPatch"))
@Suppress("unused")
val removeMetaAITabPatch = bytecodePatch(
name = "Remove Meta AI tab",
description = "Removes the 'Meta AI' tab from the navbar.",
) {
compatibleWith("com.facebook.orca")
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"
)
}
}
dependsOn(removeMetaAIPatch)
}

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.
if (googlePlayUtilityFingerprint.methodOrNull != null) {
googlePlayUtilityFingerprint.method.returnEarly()
googlePlayUtilityFingerprint.method.returnEarly(0)
}
// 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.patch.bytecodePatch
@Deprecated("Patch no longer works and will be deleted soon")
@Suppress("unused")
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.",
) {
compatibleWith("com.spotify.lite")

View File

@@ -7,7 +7,5 @@ import app.revanced.patcher.patch.bytecodePatch
val spoofSignaturePatch = bytecodePatch(
description = "Spoofs the signature of the app fix various functions of the app.",
) {
compatibleWith("com.spotify.music")
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_end_screen_store_banner"),
SwitchPreference("revanced_hide_fullscreen_ads"),
SwitchPreference("revanced_hide_buttoned_ads"),
SwitchPreference("revanced_hide_paid_promotion_label"),
SwitchPreference("revanced_hide_player_store_shelf"),
SwitchPreference("revanced_hide_self_sponsor_ads"),

View File

@@ -48,7 +48,9 @@ private val swipeControlsResourcePatch = resourcePatch {
summaryKey = null,
),
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_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_overlay_timeout", 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",
preferences = setOf(
SwitchPreference("revanced_hide_ai_generated_video_summary_section"),
SwitchPreference("revanced_hide_ask_section"),
SwitchPreference("revanced_hide_attributes_section"),
SwitchPreference("revanced_hide_chapters_section"),
SwitchPreference("revanced_hide_info_cards_section"),
@@ -224,6 +225,7 @@ val hideLayoutComponentsPatch = bytecodePatch(
SwitchPreference("revanced_hide_playables"),
SwitchPreference("revanced_hide_search_result_recommendation_labels"),
SwitchPreference("revanced_hide_show_more_button"),
SwitchPreference("revanced_hide_ticket_shelf"),
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 {
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
returns("Z")

View File

@@ -53,4 +53,12 @@ internal val shortsPlaybackIntentFingerprint = fingerprint {
"ReelWatchFragmentArgs",
"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
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.getInstruction
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.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.youtube.layout.player.fullscreen.openVideosFullscreenHookPatch
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.getReference
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.reference.MethodReference
private const val EXTENSION_CLASS_DESCRIPTOR =
"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")
val openShortsInRegularPlayerPatch = bytecodePatch(
name = "Open Shorts in regular player",
@@ -36,7 +58,8 @@ val openShortsInRegularPlayerPatch = bytecodePatch(
addResourcesPatch,
openVideosFullscreenHookPatch,
navigationBarHookPatch,
versionCheckPatch
versionCheckPatch,
openShortsInRegularPlayerResourcePatch
)
compatibleWith(
@@ -127,5 +150,28 @@ val openShortsInRegularPlayerPatch = bytecodePatch(
${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 {
val preferences = mutableSetOf<BasePreference>(
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) {
preferences += TextPreference(
"revanced_seekbar_custom_color_accent",
inputType = InputType.TEXT_CAP_CHARACTERS
)
preferences += TextPreference("revanced_seekbar_custom_color_accent",
tag = "app.revanced.extension.shared.settings.preference.ColorPickerPreference",
inputType = InputType.TEXT_CAP_CHARACTERS)
}
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.patches.all.misc.resources.addResources
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.Sorting
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
@@ -23,7 +24,7 @@ private const val EXTENSION_CLASS_DESCRIPTOR =
val enableDebuggingPatch = bytecodePatch(
name = "Enable debugging",
description = "Adds options for debugging.",
description = "Adds options for debugging and exporting ReVanced logs to the clipboard.",
) {
dependsOn(
sharedExtensionPatch,
@@ -56,6 +57,16 @@ val enableDebuggingPatch = bytecodePatch(
SwitchPreference("revanced_debug_protobuffer"),
SwitchPreference("revanced_debug_stacktrace"),
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
"""
)
}
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(
ResourceGroup("drawable",
"revanced_settings_circle_background.xml",
"revanced_settings_cursor.xml",
"revanced_settings_icon.xml",
"revanced_settings_screen_00_about.xml",
@@ -91,6 +92,8 @@ private val settingsResourcePatch = resourcePatch {
"revanced_settings_screen_12_video.xml",
),
ResourceGroup("layout",
"revanced_color_dot_widget.xml",
"revanced_color_picker.xml",
"revanced_preference_with_icon_no_search_result.xml",
"revanced_search_suggestion_item.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
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.SwitchPreference
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.misc.hapticfeedback.disableHapticFeedbackPatch
@Deprecated("Superseded by disableHapticFeedbackPatch", ReplaceWith("disableHapticFeedbackPatch"))
val zoomHapticsPatch = bytecodePatch(
name = "Disable zoom haptics",
description = "Adds an option to disable haptics when zooming.",
) {
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.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)),
)
}
}
}
dependsOn(disableHapticFeedbackPatch)
}

View File

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

View File

@@ -133,6 +133,7 @@ internal val Instruction.registersUsed: List<Int>
else -> listOf(registerC, registerD, registerE, registerF, registerG)
}
}
is ThreeRegisterInstruction -> listOf(registerA, registerB, registerC)
is TwoRegisterInstruction -> listOf(registerA, registerB)
is OneRegisterInstruction -> listOf(registerA)
@@ -170,7 +171,7 @@ internal val Instruction.isReturnInstruction: Boolean
/**
* 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)
.and(AccessFlags.PROTECTED.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.
* @see indexOfFirstInstructionOrThrow
*/
fun Method.indexOfFirstInstruction(startIndex: Int = 0, targetOpcode: Opcode): Int = indexOfFirstInstruction(startIndex) {
opcode == targetOpcode
}
fun Method.indexOfFirstInstruction(startIndex: Int = 0, targetOpcode: Opcode): Int =
indexOfFirstInstruction(startIndex) {
opcode == targetOpcode
}
/**
* 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
* @see indexOfFirstInstruction
*/
fun Method.indexOfFirstInstructionOrThrow(startIndex: Int = 0, targetOpcode: Opcode): Int = indexOfFirstInstructionOrThrow(startIndex) {
opcode == targetOpcode
}
fun Method.indexOfFirstInstructionOrThrow(startIndex: Int = 0, targetOpcode: Opcode): Int =
indexOfFirstInstructionOrThrow(startIndex) {
opcode == targetOpcode
}
/**
* 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.
* @see indexOfFirstInstructionReversedOrThrow
*/
fun Method.indexOfFirstInstructionReversed(startIndex: Int? = null, targetOpcode: Opcode): Int = indexOfFirstInstructionReversed(startIndex) {
opcode == targetOpcode
}
fun Method.indexOfFirstInstructionReversed(startIndex: Int? = null, targetOpcode: Opcode): Int =
indexOfFirstInstructionReversed(startIndex) {
opcode == targetOpcode
}
/**
* Get the index of matching instruction,
@@ -593,9 +597,10 @@ fun Method.indexOfFirstInstructionReversed(targetOpcode: Opcode): Int = indexOfF
* @return The index of the instruction.
* @see indexOfFirstInstructionReversed
*/
fun Method.indexOfFirstInstructionReversedOrThrow(startIndex: Int? = null, targetOpcode: Opcode): Int = indexOfFirstInstructionReversedOrThrow(startIndex) {
opcode == targetOpcode
}
fun Method.indexOfFirstInstructionReversedOrThrow(startIndex: Int? = null, targetOpcode: Opcode): Int =
indexOfFirstInstructionReversedOrThrow(startIndex) {
opcode == targetOpcode
}
/**
* 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_
* @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.
@@ -726,43 +732,222 @@ fun BytecodePatchContext.forEachLiteralValueInstruction(
}
}
/**
* 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)
private const val RETURN_TYPE_MISMATCH = "Mismatch between override type and Method return type"
/**
* 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.
*
* @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()) {
'L' -> {
// If return type is an object, always return null.
'L', '[' -> {
"""
const/4 v0, $const
const/4 v0, 0x0
return-object v0
"""
}
'V' -> {
if (returnLate) throw IllegalArgumentException("Cannot return late for method of void type")
"return-void"
}
'I', 'Z' -> {
'B', 'Z' -> {
"""
const/4 v0, $const
const/4 v0, $value
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")
}

View File

@@ -6,3 +6,5 @@ internal object Utils {
.joinToString("\n") { it.trimIndent() } // Remove the leading whitespace from each line.
.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. -->
<!-- '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. -->
<!-- '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.
This button usually appears when searching for a YT creator. -->
<!-- https://logos.fandom.com/wiki/YouTube/Yoodles -->

View File

@@ -44,7 +44,7 @@ Second \"item\" text"</string>
<!-- 'For you' 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. -->
<!-- '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.
This button usually appears when searching for a YT creator. -->
<!-- https://logos.fandom.com/wiki/YouTube/Yoodles -->

View File

@@ -35,6 +35,8 @@ Second \"item\" text"</string>
<string name="revanced_settings_submenu_title">الإعدادات</string>
<string name="revanced_settings_confirm_user_dialog_title">هل ترغب في المتابعة؟</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">إعادة التشغيل</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_success">تم استيراد %d إعدادات</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_summary_on">يتم عرض أيقونات الإعدادات</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_summary_on">يتم عرض قوائم الإعدادات القديمة</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 id="misc.backgroundplayback.backgroundPlaybackPatch">
<string name="revanced_shorts_disable_background_playback_title">تعطيل تشغيل فيديوهات Shorts في الخلفية</string>
@@ -153,15 +162,18 @@ Second \"item\" text"</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_off">يتم عرض الزر</string>
<!-- 'People also watched' 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_recommendations_summary_on">تم إخفاء العلامة</string>
<string name="revanced_hide_search_result_recommendations_summary_off">يتم عرض العلامة</string>
<!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. -->
<string name="revanced_hide_search_result_recommendation_labels_title">إخفاء علامات اقتراحات الفيديو</string>
<string name="revanced_hide_search_result_recommendation_labels_summary_on">تم إخفاء علامات \'اقتراحات للمشاهدة\' و\'قد يعجبك أيضًا\'</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.
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_summary_on">تم إخفاء الزر</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_summary_on">تم إخفاء ردود الفعل المؤقتة</string>
<string name="revanced_hide_timed_reactions_summary_off">يتم عرض ردود الفعل المؤقتة</string>
@@ -231,6 +243,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_summary_on">تم إخفاء قسم ملخص الفيديو</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_summary_on">تم إخفاء أقسام الأماكن المميزة، الألعاب، الموسيقى والأشخاص المذكورون</string>
<string name="revanced_hide_attributes_section_summary_off">يتم عرض أقسام الأماكن المميزة، الألعاب، الموسيقى والأشخاص المذكورون</string>
@@ -352,9 +367,6 @@ Second \"item\" text"</string>
هذه الميزة متاحة فقط للأجهزة القديمة"</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_summary_on">تم إخفاء تسمية الترقية المدفوعة</string>
<string name="revanced_hide_paid_promotion_label_summary_off">يتم عرض تسمية الترقية المدفوعة</string>
@@ -836,10 +848,10 @@ Second \"item\" text"</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. -->
<string name="revanced_ryd_video_likes_hidden_by_video_owner">مخفي بواسطة المالك</string>
<string name="revanced_ryd_enable_summary_on">يتم عرض لم يعجبني</string>
<string name="revanced_ryd_enable_summary_off">لا يتم عرض لم يعجبني</string>
<string name="revanced_ryd_enabled_summary_on">يتم عرض لم يعجبني</string>
<string name="revanced_ryd_enabled_summary_off">لا يتم عرض لم يعجبني</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 name="revanced_ryd_shorts_summary_off">لا يتم عرض مرات عدم الإعجاب في فيديوهات Shorts</string>
@@ -856,7 +868,6 @@ Second \"item\" text"</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_off">لا يتم عرض الملاحظة في حالة عدم توفر Return YouTube Dislike</string>
<string name="revanced_ryd_about">لمحة</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. -->
<string name="revanced_ryd_statistics_category_title">ReturnYouTubeDislike API إحصائيات هذا الجهاز</string>
@@ -1063,7 +1074,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_content">هل تود تعديل التوقيت لبداية أو نهاية المقطع؟</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. -->
<string name="revanced_sb_stats_connection_failure">الإحصائيات غير متوفرة مؤقتًا (الواجهة غير متوفرة)</string>
<string name="revanced_sb_stats_loading">جارٍ التحميل...</string>
@@ -1087,13 +1098,8 @@ Second \"item\" text"</string>
<string name="revanced_sb_stats_saved_second_format">%s ثانية</string>
<string name="revanced_sb_color_opacity_label">الشفافية:</string>
<string name="revanced_sb_color_dot_label">اللون:</string>
<string name="revanced_sb_color_changed">تم تغيير اللون</string>
<string name="revanced_sb_color_reset">إعادة ضبط اللون</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>
<string name="revanced_sb_about_title">لمحة</string>
<string name="revanced_sb_about_api_summary">يتم توفير البيانات بواسطة SponsorBlock API. انقر هنا لمعرفة المزيد ومشاهدة التنزيلات لمنصات أخرى</string>
</patch>
<patch id="layout.formfactor.changeFormFactorPatch">
<string name="revanced_change_form_factor_title">شكل نموذج التخطيط</string>

View File

@@ -44,7 +44,7 @@ Second \"item\" text"</string>
<!-- 'For you' 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. -->
<!-- '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.
This button usually appears when searching for a YT creator. -->
<!-- https://logos.fandom.com/wiki/YouTube/Yoodles -->

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_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_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_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>
@@ -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_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_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 id="misc.backgroundplayback.backgroundPlaybackPatch">
<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_summary_on">Düymə gizlidir</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. -->
<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_recommendations_summary_on">Etiket gizlidir</string>
<string name="revanced_hide_search_result_recommendations_summary_off">Etiket göstərilir</string>
<!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. -->
<string name="revanced_hide_search_result_recommendation_labels_title">Video tövsiyə etiketlərini gizlət</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_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.
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_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_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_summary_on">Zamanlanmış reaksiyalar gizlədilir</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_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_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_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>
@@ -352,9 +365,6 @@ Məhdudiyyətlər
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_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_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>
@@ -835,10 +845,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>
<!-- 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_enable_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_on">Bəyənməmələr göstərilir</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_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>
<string name="revanced_ryd_shorts_summary_off">Bəyənməmələr Shorts-da göstərilmir</string>
@@ -855,7 +865,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_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_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>
<!-- 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>
@@ -1062,7 +1071,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_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_stats">Statistikalar</string>
<string name="revanced_sb_stats_title">Statistikalar</string>
<!-- 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_loading">Yüklənir...</string>
@@ -1086,13 +1095,8 @@ Təqdim etməyə hazırdır?"</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_dot_label">Rəng:</string>
<string name="revanced_sb_color_changed">Rəng dəyişdirildi</string>
<string name="revanced_sb_color_reset">Rəngi sıfırla</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>
<string name="revanced_sb_about_title">Haqqında</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>
</patch>
<patch id="layout.formfactor.changeFormFactorPatch">
<string name="revanced_change_form_factor_title">Tərtibat forma göstəricisi</string>

View File

@@ -35,6 +35,8 @@ Second \"item\" text"</string>
<string name="revanced_settings_submenu_title">Налады</string>
<string name="revanced_settings_confirm_user_dialog_title">Вы хочаце працягнуць?</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">Перазапуск</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_success">Імпартавана %d налад</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_summary_on">Значкі налад паказваюцца</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_summary_on">Старыя меню налад паказваюцца</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 id="misc.backgroundplayback.backgroundPlaybackPatch">
<string name="revanced_shorts_disable_background_playback_title">Адключыць прайграванне Shorts у фонавым</string>
@@ -153,15 +162,18 @@ Second \"item\" text"</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_off">Паказана кнопка</string>
<!-- 'People also watched' 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_recommendations_summary_on">Надпіс схаваны</string>
<string name="revanced_hide_search_result_recommendations_summary_off">Надпіс паказаны</string>
<!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. -->
<string name="revanced_hide_search_result_recommendation_labels_title">Схаваць надпісы з рэкамендацыямі відэа</string>
<string name="revanced_hide_search_result_recommendation_labels_summary_on">Надпісы \"Людзі таксама глядзелі\" і \"Вам таксама можа спадабацца\" схаваныя</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.
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_summary_on">Кнопка схавана</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_summary_on">Часовыя рэакцыі схаваныя</string>
<string name="revanced_hide_timed_reactions_summary_off">Паказваюцца рэакцыі па часе</string>
@@ -231,6 +243,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_summary_on">Раздзел зводкі відэа схаваны</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_summary_on">Раздзелы «Папулярныя месцы», «Гульні», «Музыка» і «Людзі, якіх згадвалі» схаваныя</string>
<string name="revanced_hide_attributes_section_summary_off">Раздзелы «Папулярныя месцы», «Гульні», «Музыка» і «Людзі, якіх згадвалі» паказаныя</string>
@@ -352,9 +367,6 @@ Second \"item\" text"</string>
Гэтая функцыя даступная толькі для старых прылад"</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_summary_on">Пазнака платнай акцыі схавана</string>
<string name="revanced_hide_paid_promotion_label_summary_off">Адлюстроўваецца ярлык платнай акцыі</string>
@@ -836,10 +848,10 @@ Second \"item\" text"</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. -->
<string name="revanced_ryd_video_likes_hidden_by_video_owner">Схавана ўладальнікам</string>
<string name="revanced_ryd_enable_summary_on">Дызлайкі паказаны</string>
<string name="revanced_ryd_enable_summary_off">Дызлайкі не паказваюцца</string>
<string name="revanced_ryd_enabled_summary_on">Дызлайкі паказаны</string>
<string name="revanced_ryd_enabled_summary_off">Дызлайкі не паказваюцца</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 name="revanced_ryd_shorts_summary_off">Дызлайкі на Shorts не паказаныя</string>
@@ -856,7 +868,6 @@ Second \"item\" text"</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_off">Тост не паказваецца, калі функцыя \"Вярнуць не падабаецца YouTube\" недаступная</string>
<string name="revanced_ryd_about">Пра праграму</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. -->
<string name="revanced_ryd_statistics_category_title">Статыстыка ReturnYouTubeDislike API гэтай прылады</string>
@@ -1064,7 +1075,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_content">Вы жадаеце змяніць час для пачатку або канца сегмента?</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. -->
<string name="revanced_sb_stats_connection_failure">Статыстыка часова недаступная (API не працуе)</string>
<string name="revanced_sb_stats_loading">Загрузка...</string>
@@ -1088,13 +1099,8 @@ Second \"item\" text"</string>
<string name="revanced_sb_stats_saved_second_format">%s секунд</string>
<string name="revanced_sb_color_opacity_label">Непразрыстасць:</string>
<string name="revanced_sb_color_dot_label">колер:</string>
<string name="revanced_sb_color_changed">Колер змяніўся</string>
<string name="revanced_sb_color_reset">Скід колеру</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>
<string name="revanced_sb_about_title">Пра праграму</string>
<string name="revanced_sb_about_api_summary">Дадзеныя прадастаўляюцца API SponsorBlock. Націсніце тут, каб даведацца больш і паглядзець спампоўкі для іншых платформаў</string>
</patch>
<patch id="layout.formfactor.changeFormFactorPatch">
<string name="revanced_change_form_factor_title">Фармат экрана</string>

View File

@@ -35,6 +35,8 @@ Second \"item\" text"</string>
<string name="revanced_settings_submenu_title">Настройки</string>
<string name="revanced_settings_confirm_user_dialog_title">Искате ли да продължите?</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">Рестартиране</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_success">Следните настройки бяха импортирани успешно: %d</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_summary_on">Иконите на настройките се показват</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_summary_on">Старите менюта с настройки се показват</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 id="misc.backgroundplayback.backgroundPlaybackPatch">
<string name="revanced_shorts_disable_background_playback_title">Възпроизвеждане на Shorts в фонов режим</string>
@@ -153,15 +162,18 @@ Second \"item\" text"</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_off">Бутона \"Уведоми ме\" се показва</string>
<!-- 'People also watched' 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_recommendations_summary_on">Етикетът е скрит</string>
<string name="revanced_hide_search_result_recommendations_summary_off">Етикетът е показан</string>
<!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. -->
<string name="revanced_hide_search_result_recommendation_labels_title">Скриване на етикетите с видео препоръки</string>
<string name="revanced_hide_search_result_recommendation_labels_summary_on">Етикетите „Други потребители също гледаха“ и „Може също да харесате“ са скрити</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.
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_summary_on">Бутона Покажи още е скрит</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_summary_on">Времевите реакции са скрити</string>
<string name="revanced_hide_timed_reactions_summary_off">Времевите реакции се показват</string>
@@ -231,6 +243,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_summary_on">Скрит е разделът с видео резюме</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_summary_on">Секциите \"Препоръчани места\", \"Игри\", \"Музика\" и \"Споменати хора\" са скрити</string>
<string name="revanced_hide_attributes_section_summary_off">Секциите \"Препоръчани места\", \"Игри\", \"Музика\" и \"Споменати хора\" са показани</string>
@@ -352,9 +367,6 @@ Second \"item\" text"</string>
Тази функция е налична само за по-стари устройства"</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_summary_on">Промоционалните етикети са скрити</string>
<string name="revanced_hide_paid_promotion_label_summary_off">Промоционалните етикети се показват</string>
@@ -836,10 +848,10 @@ Second \"item\" text"</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. -->
<string name="revanced_ryd_video_likes_hidden_by_video_owner">Скрито от собственика</string>
<string name="revanced_ryd_enable_summary_on">Нехаресванията се показват</string>
<string name="revanced_ryd_enable_summary_off">Нехаресванията не се показват</string>
<string name="revanced_ryd_enabled_summary_on">Нехаресванията се показват</string>
<string name="revanced_ryd_enabled_summary_off">Нехаресванията не се показват</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 name="revanced_ryd_shorts_summary_off">Нехаресванията на Shorts не са показани</string>
@@ -856,7 +868,6 @@ Second \"item\" text"</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_off">Не се показва известие, ако ReturnYouTube Dislike не е наличен</string>
<string name="revanced_ryd_about">За програмата</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. -->
<string name="revanced_ryd_statistics_category_title">Статистика Return YouTube Dislike API на това устройство</string>
@@ -1063,7 +1074,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_content">Желаете ли да редактирате времената за начало или край на частта?</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. -->
<string name="revanced_sb_stats_connection_failure">Статистиките временно не са налични (API не работи)</string>
<string name="revanced_sb_stats_loading">Зареждане...</string>
@@ -1087,13 +1098,8 @@ Second \"item\" text"</string>
<string name="revanced_sb_stats_saved_second_format">%s секунди</string>
<string name="revanced_sb_color_opacity_label">Непрозрачност:</string>
<string name="revanced_sb_color_dot_label">Цвят:</string>
<string name="revanced_sb_color_changed">Цветът е променен</string>
<string name="revanced_sb_color_reset">Възстанови цвета</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>
<string name="revanced_sb_about_title">За програмата</string>
<string name="revanced_sb_about_api_summary">Данните са предоставени от SponsorBlock API. Докоснете тук за повече информация и изтеглияния</string>
</patch>
<patch id="layout.formfactor.changeFormFactorPatch">
<string name="revanced_change_form_factor_title">Формат на екрана /Таблет, Телфон, .../</string>

View File

@@ -35,6 +35,8 @@ Second \"item\" text"</string>
<string name="revanced_settings_submenu_title">সেটিংস</string>
<string name="revanced_settings_confirm_user_dialog_title">আপনি কি এগিয়ে যেতে ইচ্ছুক?</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">আবার চালু করুন</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_success">%d সেটিং আমদানি হয়েছে</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_summary_on">সেটিং আইকন দেখানো হয়েছে</string>
<string name="revanced_show_menu_icons_summary_off">সেটিং আইকন দেখানো হচ্ছে না</string>
@@ -93,6 +99,9 @@ MicroG-এর জন্য ব্যাটারি অপ্টিমাইজ
<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_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 id="misc.backgroundplayback.backgroundPlaybackPatch">
<string name="revanced_shorts_disable_background_playback_title">Shorts ব্যাকগ্রাউন্ড প্লে অক্ষম করুন</string>
@@ -153,15 +162,18 @@ MicroG-এর জন্য ব্যাটারি অপ্টিমাইজ
<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_off">বোতাম দেখানো আছে</string>
<!-- 'People also watched' should be translated using the same localized wording YouTube displays. -->
<string name="revanced_hide_search_result_recommendations_title">\'People also watched\' লেবেল লুকান</string>
<string name="revanced_hide_search_result_recommendations_summary_on">লেবেল লুকানো আছে</string>
<string name="revanced_hide_search_result_recommendations_summary_off">লেবেল দেখানো হয়েছে</string>
<!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. -->
<string name="revanced_hide_search_result_recommendation_labels_title">ভিডিও প্রস্তাবনা লেবেল লুকান</string>
<string name="revanced_hide_search_result_recommendation_labels_summary_on">\"\'লোকেরা আরও দেখেছে\' এবং \'আপনি সম্ভবত পছন্দ করতে পারেন\' লেবেলগুলি লুকানো আছে\"</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.
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_summary_on">বোতাম লুকানো আছে</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_summary_on">সময় অনুযায়ী প্রতিক্রিয়া লুকিয়ে রয়েছে</string>
<string name="revanced_hide_timed_reactions_summary_off">সময় অনুযায়ী প্রতিক্রিয়া প্রদর্শিত হয়েছে</string>
@@ -231,6 +243,9 @@ MicroG-এর জন্য ব্যাটারি অপ্টিমাইজ
<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_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_summary_on">বৈশিষ্ট্যযুক্ত স্থান, গেমস, সঙ্গীত, এবং উল্লিখিত ব্যক্তি বিভাগগুলি লুকানো আছে</string>
<string name="revanced_hide_attributes_section_summary_off">বৈশিষ্ট্যযুক্ত স্থান, গেমস, সঙ্গীত, এবং উল্লিখিত ব্যক্তি বিভাগগুলি দেখানো হয়েছে</string>
@@ -352,9 +367,6 @@ MicroG-এর জন্য ব্যাটারি অপ্টিমাইজ
এই বৈশিষ্ট্যটি কেবল পুরনো ডিভাইসের জন্য উপলব্ধ"</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_summary_on">অর্থের বিনিময়ে প্রচার অন্তর্ভুক্ত রয়েছে ব্যানার লুকিয়ে রয়েছে</string>
<string name="revanced_hide_paid_promotion_label_summary_off">অর্থের বিনিময়ে প্রচার অন্তর্ভুক্ত রয়েছে ব্যানার প্রদর্শিত হয়েছে</string>
@@ -836,10 +848,10 @@ YouTube সেটিংসে অটো প্লে পরিবর্তন
<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. -->
<string name="revanced_ryd_video_likes_hidden_by_video_owner">মালিক কর্তৃক লুকানো</string>
<string name="revanced_ryd_enable_summary_on">অপছন্দগুলো প্রদর্শিত হয়েছে</string>
<string name="revanced_ryd_enable_summary_off">অপছন্দগুলো প্রদর্শিত হয়নি</string>
<string name="revanced_ryd_enabled_summary_on">অপছন্দগুলো প্রদর্শিত হয়েছে</string>
<string name="revanced_ryd_enabled_summary_off">অপছন্দগুলো প্রদর্শিত হয়নি</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 name="revanced_ryd_shorts_summary_off">Shorts-এ অপছন্দগুলি দেখানো হয়নি</string>
@@ -856,7 +868,6 @@ YouTube সেটিংসে অটো প্লে পরিবর্তন
<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_off">Return YouTube Dislike উপলভ্য না থাকলে টোস্ট দেখানো হবে না</string>
<string name="revanced_ryd_about">সম্পর্কিত</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. -->
<string name="revanced_ryd_statistics_category_title">এই ডিভাইসের ReturnYouTubeDislike API পরিসংখ্যান</string>
@@ -1063,7 +1074,7 @@ YouTube সেটিংসে অটো প্লে পরিবর্তন
<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_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. -->
<string name="revanced_sb_stats_connection_failure">পরিসংখ্যান সাময়িকভাবে উপলব্ধ নেই (API ডাউন)</string>
<string name="revanced_sb_stats_loading">লোড হচ্ছে...</string>
@@ -1087,13 +1098,8 @@ YouTube সেটিংসে অটো প্লে পরিবর্তন
<string name="revanced_sb_stats_saved_second_format">%s সেকেন্ড</string>
<string name="revanced_sb_color_opacity_label">স্বচ্ছতা:</string>
<string name="revanced_sb_color_dot_label">রং:</string>
<string name="revanced_sb_color_changed">রং পরিবর্তন করা হয়েছে</string>
<string name="revanced_sb_color_reset">রং আবার সেট করুন</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>
<string name="revanced_sb_about_title">সম্পর্কিত</string>
<string name="revanced_sb_about_api_summary">ডেটা SponsorBlock API দ্বারা সরবরাহ করা হয়। আরও জানতে এবং অন্যান্য প্ল্যাটফর্মের ডাউনলোড দেখতে এখানে ট্যাপ করুন</string>
</patch>
<patch id="layout.formfactor.changeFormFactorPatch">
<string name="revanced_change_form_factor_title">লেআউট ফর্ম ফ্যাক্টর</string>

View File

@@ -44,7 +44,7 @@ Second \"item\" text"</string>
<!-- 'For you' 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. -->
<!-- '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.
This button usually appears when searching for a YT creator. -->
<!-- https://logos.fandom.com/wiki/YouTube/Yoodles -->

View File

@@ -35,6 +35,8 @@ Second \"item\" text"</string>
<string name="revanced_settings_submenu_title">Configuració</string>
<string name="revanced_settings_confirm_user_dialog_title">Vols continuar?</string>
<string name="revanced_settings_reset">Restablir</string>
<string name="revanced_settings_reset_color">Restablir el color</string>
<string name="revanced_settings_color_invalid">Color no vàlid</string>
<string name="revanced_settings_restart_title">Actualitza i reinicia</string>
<string name="revanced_settings_restart">Reinicia</string>
<string name="revanced_settings_import">Importa</string>
@@ -42,6 +44,10 @@ Second \"item\" text"</string>
<string name="revanced_settings_import_reset">La configuració de ReVanced s\'ha restablert als valors predeterminats</string>
<string name="revanced_settings_import_success">S\'han importat %d configuracions</string>
<string name="revanced_settings_import_failure_parse">No s\'ha pogut importar: %s</string>
<string name="revanced_settings_search_hint">Cerca a la configuració</string>
<string name="revanced_settings_search_no_results_title">No s\'han trobat resultats per a «%s»</string>
<string name="revanced_settings_search_no_results_summary">Prova una altra paraula clau</string>
<string name="revanced_settings_search_remove_message">Voleu suprimir-ho de l\'historial de cerca?</string>
<string name="revanced_show_menu_icons_title">Mostra les icones de configuració de ReVanced</string>
<string name="revanced_show_menu_icons_summary_on">Es mostren les icones de configuració</string>
<string name="revanced_show_menu_icons_summary_off">No es mostren les icones de configuració</string>
@@ -93,6 +99,9 @@ Toca el botó Continua i permet els canvis d'optimització."</string>
<string name="revanced_restore_old_settings_menus_title">Restaurar els menús de configuració antics</string>
<string name="revanced_restore_old_settings_menus_summary_on">Es mostren els menús de configuració antics</string>
<string name="revanced_restore_old_settings_menus_summary_off">No es mostren els menús de configuració antics</string>
<string name="revanced_settings_search_history_title">Mostra l\'historial de cerca de la configuració</string>
<string name="revanced_settings_search_history_summary_on">Es mostra l\'historial de cerca de la configuració</string>
<string name="revanced_settings_search_history_summary_off">L\'historial de cerca de la configuració no es mostra</string>
</patch>
<patch id="misc.backgroundplayback.backgroundPlaybackPatch">
<string name="revanced_shorts_disable_background_playback_title">Desactivar la reproducció en segon pla de Shorts</string>
@@ -153,15 +162,18 @@ No se t'informarà de cap esdeveniment inesperat."</string>
<string name="revanced_hide_notify_me_button_title">Amaga el botó \"M\'avisa\"</string>
<string name="revanced_hide_notify_me_button_summary_on">El botó està amagat</string>
<string name="revanced_hide_notify_me_button_summary_off">El botó es mostra</string>
<!-- 'People also watched' should be translated using the same localized wording YouTube displays. -->
<string name="revanced_hide_search_result_recommendations_title">Amaga l\'etiqueta \"La gent també ha mirat\"</string>
<string name="revanced_hide_search_result_recommendations_summary_on">L\'etiqueta s\'amaga</string>
<string name="revanced_hide_search_result_recommendations_summary_off">L\'etiqueta es mostra</string>
<!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. -->
<string name="revanced_hide_search_result_recommendation_labels_title">Amaga les etiquetes de recomanació de vídeos</string>
<string name="revanced_hide_search_result_recommendation_labels_summary_on">Les etiquetes «Altres usuaris també han vist» i «Potser també t\'agradarà» estan ocultes</string>
<string name="revanced_hide_search_result_recommendation_labels_summary_off">Les etiquetes «Altres usuaris també han vist» i «Potser també t\'agradarà» es mostren</string>
<!-- 'Show more' should be translated with the same localized wording that YouTube displays.
This button usually appears when searching for a YT creator. -->
<string name="revanced_hide_show_more_button_title">Amaga el botó \'Mostra més\'</string>
<string name="revanced_hide_show_more_button_summary_on">El botó està amagat</string>
<string name="revanced_hide_show_more_button_summary_off">El botó es mostra</string>
<string name="revanced_hide_ticket_shelf_title">Amaga la secció de tiquets</string>
<string name="revanced_hide_ticket_shelf_summary_on">La secció de tiquets està amagada</string>
<string name="revanced_hide_ticket_shelf_summary_off">La secció de tiquets es mostra</string>
<string name="revanced_hide_timed_reactions_title">Amaga les reaccions temporitzades</string>
<string name="revanced_hide_timed_reactions_summary_on">S\'han amagat les reaccions temporitzades</string>
<string name="revanced_hide_timed_reactions_summary_off">Es mostren les reaccions temporitzades</string>
@@ -231,6 +243,9 @@ No se t'informarà de cap esdeveniment inesperat."</string>
<string name="revanced_hide_ai_generated_video_summary_section_title">Amaga «Resum de vídeo generat per IA»</string>
<string name="revanced_hide_ai_generated_video_summary_section_summary_on">S\'ha amagat la secció de resum del vídeo</string>
<string name="revanced_hide_ai_generated_video_summary_section_summary_off">Es mostra la secció de resum del vídeo</string>
<string name="revanced_hide_ask_section_title">Amaga la secció «Pregunta»</string>
<string name="revanced_hide_ask_section_summary_on">La secció «Pregunta» està amagada</string>
<string name="revanced_hide_ask_section_summary_off">La secció «Pregunta» es mostra</string>
<string name="revanced_hide_attributes_section_title">Amaga els atributs</string>
<string name="revanced_hide_attributes_section_summary_on">Les seccions Llocs destacats, Jocs, Música i Persones esmentades s\'amaguen</string>
<string name="revanced_hide_attributes_section_summary_off">Les seccions Llocs destacats, Jocs, Música i Persones esmentades es mostren</string>
@@ -352,9 +367,6 @@ Limitacions
Aquesta funció només està disponible per a dispositius antics"</string>
<string name="revanced_hide_fullscreen_ads_summary_off">Els anuncis de pantalla completa es mostren</string>
<string name="revanced_hide_buttoned_ads_title">Amaga els anuncis amb botó</string>
<string name="revanced_hide_buttoned_ads_summary_on">Els anuncis amb botó estan amagats</string>
<string name="revanced_hide_buttoned_ads_summary_off">Els anuncis amb botó es mostren</string>
<string name="revanced_hide_paid_promotion_label_title">Amaga l\'etiqueta de promoció de pagament</string>
<string name="revanced_hide_paid_promotion_label_summary_on">L\'etiqueta de promoció de pagament està amagada</string>
<string name="revanced_hide_paid_promotion_label_summary_off">Es mostra l\'etiqueta de promoció de pagament</string>
@@ -836,10 +848,10 @@ Configuració → Reproducció → Reprodueix el vídeo següent automàticament
<string name="revanced_ryd_failure_ryd_enabled_while_playing_video_then_user_voted">Recarrega el vídeo per votar utilitzant Return YouTube Dislike</string>
<!-- Video likes have been set to hidden by the video uploader. -->
<string name="revanced_ryd_video_likes_hidden_by_video_owner">Amagat per l\'amo</string>
<string name="revanced_ryd_enable_summary_on">Els \"no m\'agrada\" es mostren</string>
<string name="revanced_ryd_enable_summary_off">Els \"no m\'agrada\" no es mostren</string>
<string name="revanced_ryd_enabled_summary_on">Els \"no m\'agrada\" es mostren</string>
<string name="revanced_ryd_enabled_summary_off">Els \"no m\'agrada\" no es mostren</string>
<string name="revanced_ryd_shorts_title">Mostrar \"no m\'agrada\" a Shorts</string>
<string name="revanced_ryd_shorts_summary_on_disclaimer">"Els \"no m'agrada\" als Shorts es mostren
<string name="revanced_ryd_shorts_summary_on">"Els \"no m'agrada\" als Shorts es mostren
Limitació: és possible que els \"no m'agrada\" no apareguin en mode d'incògnit"</string>
<string name="revanced_ryd_shorts_summary_off">Els \"no m\'agrada\" als Shorts no es mostren</string>
@@ -856,7 +868,6 @@ Limitació: és possible que els \"no m'agrada\" no apareguin en mode d'incògni
<string name="revanced_ryd_toast_on_connection_error_title">Mostrar una \"toast\" si l\'API no està disponible</string>
<string name="revanced_ryd_toast_on_connection_error_summary_on">La \"toast\" es mostra si Return YouTube Dislike no està disponible</string>
<string name="revanced_ryd_toast_on_connection_error_summary_off">La \"toast\" no es mostra si Return YouTube Dislike no està disponible</string>
<string name="revanced_ryd_about">Quant a</string>
<string name="revanced_ryd_attribution_summary">Les dades són proporcionades per l\'API de Return YouTube Dislike. Toca aquí per obtenir més informació</string>
<!-- 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">Estadístiques de l\'API de ReturnYouTubeDislike d\'aquest dispositiu</string>
@@ -1062,7 +1073,7 @@ Preparat per enviar?"</string>
<string name="revanced_sb_new_segment_edit_by_hand_title">Editeu el temps del segment manualment</string>
<string name="revanced_sb_new_segment_edit_by_hand_content">Voleu editar el temps per a l\'inici o el final del segment?</string>
<string name="revanced_sb_new_segment_edit_by_hand_parse_error">Temps invàlid proporcionat</string>
<string name="revanced_sb_stats">Estadístiques</string>
<string name="revanced_sb_stats_title">Estadístiques</string>
<!-- Shown in the settings preferences, and translations can be any text length. -->
<string name="revanced_sb_stats_connection_failure">Les estadístiques no estan disponibles temporalment (l\'API no funciona)</string>
<string name="revanced_sb_stats_loading">S\'està carregant...</string>
@@ -1086,13 +1097,8 @@ Preparat per enviar?"</string>
<string name="revanced_sb_stats_saved_second_format">%s segons</string>
<string name="revanced_sb_color_opacity_label">Opacitat:</string>
<string name="revanced_sb_color_dot_label">Color:</string>
<string name="revanced_sb_color_changed">Color canviat</string>
<string name="revanced_sb_color_reset">Color restablert</string>
<string name="revanced_sb_color_invalid">Codi de color invàlid</string>
<string name="revanced_sb_reset_color">Restableix el color</string>
<string name="revanced_sb_reset">Restablir</string>
<string name="revanced_sb_about">Quant a</string>
<string name="revanced_sb_about_api_sum">Les dades són proporcionades per l\'API de SponsorBlock. Toca aquí per a saber-ne més i veure les descàrregues per a altres plataformes</string>
<string name="revanced_sb_about_title">Quant a</string>
<string name="revanced_sb_about_api_summary">Les dades són proporcionades per l\'API de SponsorBlock. Toca aquí per a saber-ne més i veure les descàrregues per a altres plataformes</string>
</patch>
<patch id="layout.formfactor.changeFormFactorPatch">
<string name="revanced_change_form_factor_title">Factor de forma del disseny</string>

View File

@@ -35,6 +35,8 @@ Second \"item\" text"</string>
<string name="revanced_settings_submenu_title">Nastavení</string>
<string name="revanced_settings_confirm_user_dialog_title">Přejete si pokračovat?</string>
<string name="revanced_settings_reset">Výchozí</string>
<string name="revanced_settings_reset_color">Obnovit barvu</string>
<string name="revanced_settings_color_invalid">Neplatná barva</string>
<string name="revanced_settings_restart_title">Obnovit a restartovat</string>
<string name="revanced_settings_restart">Restartovat</string>
<string name="revanced_settings_import">Importovat</string>
@@ -42,6 +44,10 @@ Second \"item\" text"</string>
<string name="revanced_settings_import_reset">Nastavení ReVanced obnoveno do výchozího stavu</string>
<string name="revanced_settings_import_success">Importováno %d nastavení</string>
<string name="revanced_settings_import_failure_parse">Importováni selhalo: %s</string>
<string name="revanced_settings_search_hint">Hledat nastavení</string>
<string name="revanced_settings_search_no_results_title">Nebyly nalezeny žádné výsledky pro \",%s\"</string>
<string name="revanced_settings_search_no_results_summary">Zkuste jiné klíčové slovo</string>
<string name="revanced_settings_search_remove_message">Odebrat z historie vyhledávání?</string>
<string name="revanced_show_menu_icons_title">Zobrazit ikony nastavení ReVanced</string>
<string name="revanced_show_menu_icons_summary_on">Ikony nastavení se zobrazují</string>
<string name="revanced_show_menu_icons_summary_off">Ikony nastavení se nezobrazují</string>
@@ -93,6 +99,9 @@ Klepněte na tlačítko Pokračovat a povolte změny optimalizace."</string>
<string name="revanced_restore_old_settings_menus_title">Obnovit staré menu nastavení</string>
<string name="revanced_restore_old_settings_menus_summary_on">Staré menu nastavení se zobrazují</string>
<string name="revanced_restore_old_settings_menus_summary_off">Staré menu nastavení se nezobrazují</string>
<string name="revanced_settings_search_history_title">Zobrazit historii vyhledávání v nastavení</string>
<string name="revanced_settings_search_history_summary_on">Historie vyhledávání v nastavení je zobrazena</string>
<string name="revanced_settings_search_history_summary_off">Historie vyhledávání nastavení se nezobrazuje</string>
</patch>
<patch id="misc.backgroundplayback.backgroundPlaybackPatch">
<string name="revanced_shorts_disable_background_playback_title">Zakázat automatické přehrávání Shorts v pozadí</string>
@@ -153,15 +162,18 @@ Nebudete informováni o žádné neočekávané události."</string>
<string name="revanced_hide_notify_me_button_title">Skrýt tlačitko \'Upozorněte mě\'</string>
<string name="revanced_hide_notify_me_button_summary_on">Tlačítko je skryté</string>
<string name="revanced_hide_notify_me_button_summary_off">Tlačítko je zobrazeno</string>
<!-- 'People also watched' should be translated using the same localized wording YouTube displays. -->
<string name="revanced_hide_search_result_recommendations_title">Skrýt štítek „Lidé také sledovali“</string>
<string name="revanced_hide_search_result_recommendations_summary_on">Štítek je skrytý</string>
<string name="revanced_hide_search_result_recommendations_summary_off">Štítek je zobrazen</string>
<!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. -->
<string name="revanced_hide_search_result_recommendation_labels_title">Skrýt popisky doporučení videí</string>
<string name="revanced_hide_search_result_recommendation_labels_summary_on">Popisky „Lidé také sledovali“ a „Mohlo by se vám také líbit“ jsou skryté</string>
<string name="revanced_hide_search_result_recommendation_labels_summary_off">Popisky „Lidé také sledovali“ a „Mohlo by se vám také líbit“ jsou zobrazené</string>
<!-- 'Show more' should be translated with the same localized wording that YouTube displays.
This button usually appears when searching for a YT creator. -->
<string name="revanced_hide_show_more_button_title">Skrýt tlačítko „Zobrazit více“</string>
<string name="revanced_hide_show_more_button_summary_on">Tlačítko je skryto</string>
<string name="revanced_hide_show_more_button_summary_off">Tlačítko je zobrazeno</string>
<string name="revanced_hide_ticket_shelf_title">Skrýt panel Vstupenek</string>
<string name="revanced_hide_ticket_shelf_summary_on">Panel Vstupenek je skrytý</string>
<string name="revanced_hide_ticket_shelf_summary_off">Panel Vstupenek je zobrazen</string>
<string name="revanced_hide_timed_reactions_title">Skrýt reakce zobrazené po čase</string>
<string name="revanced_hide_timed_reactions_summary_on">Reakce zobrazené po čase jsou skryty</string>
<string name="revanced_hide_timed_reactions_summary_off">Reakce se zobrazují po čase</string>
@@ -231,6 +243,9 @@ Nebudete informováni o žádné neočekávané události."</string>
<string name="revanced_hide_ai_generated_video_summary_section_title">Skrýt „Souhrn videa generovaný pomocí AI“</string>
<string name="revanced_hide_ai_generated_video_summary_section_summary_on">Sekce se souhrnem videa je skrytá</string>
<string name="revanced_hide_ai_generated_video_summary_section_summary_off">Sekce se souhrnem videa je zobrazena</string>
<string name="revanced_hide_ask_section_title">Skrýt sekci Zeptat se</string>
<string name="revanced_hide_ask_section_summary_on">Sekce Zeptat se je skrytá</string>
<string name="revanced_hide_ask_section_summary_off">Sekce Zeptat se je zobrazena</string>
<string name="revanced_hide_attributes_section_title">Skrýt atributy</string>
<string name="revanced_hide_attributes_section_summary_on">Sekce Doporučená místa, Hry, Hudba a Zmínění lidé jsou skryté</string>
<string name="revanced_hide_attributes_section_summary_off">Sekce Doporučená místa, Hry, Hudba a Zmínění lidé jsou zobrazené</string>
@@ -352,9 +367,6 @@ Omezení:
Tato funkce je dostupná pouze pro starší zařízení"</string>
<string name="revanced_hide_fullscreen_ads_summary_off">Celostránkové reklamy jsou zobrazeny</string>
<string name="revanced_hide_buttoned_ads_title">Skrýt reklamy s tlačítky</string>
<string name="revanced_hide_buttoned_ads_summary_on">Reklamy s tlačítky jsou skryty</string>
<string name="revanced_hide_buttoned_ads_summary_off">Reklamy s tlačítky jsou zobrazeny</string>
<string name="revanced_hide_paid_promotion_label_title">Skrýt štítek placené propagace</string>
<string name="revanced_hide_paid_promotion_label_summary_on">Štítek placené propagace je skryt</string>
<string name="revanced_hide_paid_promotion_label_summary_off">Štítek placené propagace je zobrazen</string>
@@ -836,10 +848,10 @@ Nastavení → Přehrávání → Automatické přehrávání dalšího videa"</
<string name="revanced_ryd_failure_ryd_enabled_while_playing_video_then_user_voted">Načtěte video znovu, abyste hlasovali pomocí Return YouTube Dislike</string>
<!-- Video likes have been set to hidden by the video uploader. -->
<string name="revanced_ryd_video_likes_hidden_by_video_owner">Skryto vlastníkem</string>
<string name="revanced_ryd_enable_summary_on">Nelíbí se se zobrazují</string>
<string name="revanced_ryd_enable_summary_off">Nelíbí se se nezobrazují</string>
<string name="revanced_ryd_enabled_summary_on">Nelíbí se se zobrazují</string>
<string name="revanced_ryd_enabled_summary_off">Nelíbí se se nezobrazují</string>
<string name="revanced_ryd_shorts_title">Zobrazit nelíbí se v Shorts</string>
<string name="revanced_ryd_shorts_summary_on_disclaimer">"Počty „Nelíbí se mi“ u Shorts jsou zobrazeny
<string name="revanced_ryd_shorts_summary_on">"Počty „Nelíbí se mi“ u Shorts jsou zobrazeny
Omezení: Počty „Nelíbí se mi“ se nemusí zobrazit v anonymním režimu"</string>
<string name="revanced_ryd_shorts_summary_off">Počty „Nelíbí se mi“ u Shorts nejsou zobrazeny</string>
@@ -856,7 +868,6 @@ Omezení: Počty „Nelíbí se mi“ se nemusí zobrazit v anonymním režimu"<
<string name="revanced_ryd_toast_on_connection_error_title">Zobrazit toast, pokud API není dostupné</string>
<string name="revanced_ryd_toast_on_connection_error_summary_on">Toast se zobrazí, pokud Return YouTube Dislike není dostupný</string>
<string name="revanced_ryd_toast_on_connection_error_summary_off">Toast se nezobrazí, pokud Return YouTube Dislike není dostupný</string>
<string name="revanced_ryd_about">O aplikaci</string>
<string name="revanced_ryd_attribution_summary">Data jsou poskytována API Return YouTube Dislike. Klepnutím se dozvíte více</string>
<!-- 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">Statistiky API ReturnYouTubeDislike tohoto zařízení</string>
@@ -1062,7 +1073,7 @@ Jste připraveni k odeslání?"</string>
<string name="revanced_sb_new_segment_edit_by_hand_title">Upravit načasování segmentu ručně</string>
<string name="revanced_sb_new_segment_edit_by_hand_content">Chcete upravit načasování pro začátek nebo konec segmentu?</string>
<string name="revanced_sb_new_segment_edit_by_hand_parse_error">Zadán neplatný čas</string>
<string name="revanced_sb_stats">Statistiky</string>
<string name="revanced_sb_stats_title">Statistiky</string>
<!-- Shown in the settings preferences, and translations can be any text length. -->
<string name="revanced_sb_stats_connection_failure">Statistiky jsou dočasně nedostupné (server API je mimo provoz)</string>
<string name="revanced_sb_stats_loading">Načítání...</string>
@@ -1086,13 +1097,8 @@ Jste připraveni k odeslání?"</string>
<string name="revanced_sb_stats_saved_second_format">%s sekund</string>
<string name="revanced_sb_color_opacity_label">Průhlednost:</string>
<string name="revanced_sb_color_dot_label">Barva:</string>
<string name="revanced_sb_color_changed">Barva změněna</string>
<string name="revanced_sb_color_reset">Barva resetována</string>
<string name="revanced_sb_color_invalid">Neplatný kód barvy</string>
<string name="revanced_sb_reset_color">Resetovat barvu</string>
<string name="revanced_sb_reset">Výchozí</string>
<string name="revanced_sb_about">O aplikaci</string>
<string name="revanced_sb_about_api_sum">Data poskytuje rozhraní API SponsorBlock. Klepněte zde, abyste se dozvěděli více a zobrazili si soubory ke stažení pro další platformy</string>
<string name="revanced_sb_about_title">O aplikaci</string>
<string name="revanced_sb_about_api_summary">Data poskytuje rozhraní API SponsorBlock. Klepněte zde, abyste se dozvěděli více a zobrazili si soubory ke stažení pro další platformy</string>
</patch>
<patch id="layout.formfactor.changeFormFactorPatch">
<string name="revanced_change_form_factor_title">Rozvržení formuláře</string>

View File

@@ -35,6 +35,8 @@ Second \"item\" text"</string>
<string name="revanced_settings_submenu_title">Indstillinger</string>
<string name="revanced_settings_confirm_user_dialog_title">Ønsker du at fortsætte?</string>
<string name="revanced_settings_reset">Nulstil</string>
<string name="revanced_settings_reset_color">Nulstil farve</string>
<string name="revanced_settings_color_invalid">Ugyldig farve</string>
<string name="revanced_settings_restart_title">Opdater og genstart</string>
<string name="revanced_settings_restart">Genstart</string>
<string name="revanced_settings_import">Importer</string>
@@ -42,6 +44,10 @@ Second \"item\" text"</string>
<string name="revanced_settings_import_reset">ReVanced-indstillinger nulstillet til standard</string>
<string name="revanced_settings_import_success">%d indstillinger importeret</string>
<string name="revanced_settings_import_failure_parse">Import mislykkedes: %s</string>
<string name="revanced_settings_search_hint">Søg i indstillinger</string>
<string name="revanced_settings_search_no_results_title">Ingen resultater fundet for \'%s\'</string>
<string name="revanced_settings_search_no_results_summary">Prøv et andet søgeord</string>
<string name="revanced_settings_search_remove_message">Fjern fra søgehistorik?</string>
<string name="revanced_show_menu_icons_title">Vis ReVanced-indstillingsikoner</string>
<string name="revanced_show_menu_icons_summary_on">Indstillingsikoner vises</string>
<string name="revanced_show_menu_icons_summary_off">Indstillingsikoner vises ikke</string>
@@ -93,6 +99,9 @@ Tap på knappen Fortsæt, og tillad optimeringsændringer."</string>
<string name="revanced_restore_old_settings_menus_title">Gendan gamle indstillingsmenuer</string>
<string name="revanced_restore_old_settings_menus_summary_on">Gamle indstillingsmenuer er vist</string>
<string name="revanced_restore_old_settings_menus_summary_off">Gamle indstillingsmenuer er ikke vist</string>
<string name="revanced_settings_search_history_title">Vis indstillingssøgehistorik</string>
<string name="revanced_settings_search_history_summary_on">Indstillingssøgehistorik vises</string>
<string name="revanced_settings_search_history_summary_off">Søgehistorik for indstillinger vises ikke</string>
</patch>
<patch id="misc.backgroundplayback.backgroundPlaybackPatch">
<string name="revanced_shorts_disable_background_playback_title">Deaktiver baggrundsafspilning af Shorts</string>
@@ -151,12 +160,18 @@ Du modtager ikke notifikationer om uventede hændelser."</string>
<string name="revanced_hide_notify_me_button_title">Skjul knappen \'Underret mig\'</string>
<string name="revanced_hide_notify_me_button_summary_on">Knappen er skjult</string>
<string name="revanced_hide_notify_me_button_summary_off">Knappen er vist</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_recommendation_labels_title">Skjul etiketter for videoanbefalinger</string>
<string name="revanced_hide_search_result_recommendation_labels_summary_on">Etiketterne \"Folk så også\" og \"Du kan måske også lide\" er skjulte</string>
<string name="revanced_hide_search_result_recommendation_labels_summary_off">Etiketterne \"Folk så også\" og \"Du kan måske også lide\" vises</string>
<!-- 'Show more' should be translated with the same localized wording that YouTube displays.
This button usually appears when searching for a YT creator. -->
<string name="revanced_hide_show_more_button_title">Skjul knappen \'Vis mere\'</string>
<string name="revanced_hide_show_more_button_summary_on">Knappen er skjult</string>
<string name="revanced_hide_show_more_button_summary_off">Knappen er vist</string>
<string name="revanced_hide_ticket_shelf_title">Skjul billetreolen</string>
<string name="revanced_hide_ticket_shelf_summary_on">Billetreolen er skjult</string>
<string name="revanced_hide_ticket_shelf_summary_off">Billetreolen vises</string>
<string name="revanced_hide_timed_reactions_title">Skjul tidsreaktioner</string>
<string name="revanced_hide_timed_reactions_summary_on">Tidsreaktioner skjules</string>
<string name="revanced_hide_timed_reactions_summary_off">Tidsreaktioner vises</string>
@@ -223,6 +238,9 @@ Du modtager ikke notifikationer om uventede hændelser."</string>
<string name="revanced_hide_ai_generated_video_summary_section_title">Skjul \"AI-genereret videooversigt\"</string>
<string name="revanced_hide_ai_generated_video_summary_section_summary_on">Videooversigtssektionen er skjult</string>
<string name="revanced_hide_ai_generated_video_summary_section_summary_off">Videooversigtssektionen vises</string>
<string name="revanced_hide_ask_section_title">Skjul Spørg</string>
<string name="revanced_hide_ask_section_summary_on">Spørgsmål sektionen er skjult</string>
<string name="revanced_hide_ask_section_summary_off">Spørgsmål sektionen vises</string>
<string name="revanced_hide_chapters_section_summary_on">Kapitler sektion er skjult</string>
<string name="revanced_hide_chapters_section_summary_off">Kapitel afsnit er vist</string>
<string name="revanced_hide_info_cards_section_summary_on">Info-kort sektion er skjult</string>
@@ -322,9 +340,6 @@ Begrænsninger
Denne funktion er kun tilgængelig for ældre enheder"</string>
<string name="revanced_hide_fullscreen_ads_summary_off">Fuldskærms annoncer vises</string>
<string name="revanced_hide_buttoned_ads_title">Skjul knapfyldte annoncer</string>
<string name="revanced_hide_buttoned_ads_summary_on">Knappede annoncer er skjult</string>
<string name="revanced_hide_buttoned_ads_summary_off">Knappede annoncer vises</string>
<string name="revanced_hide_paid_promotion_label_title">Skjul betalt kampagneetiket</string>
<string name="revanced_hide_paid_promotion_label_summary_on">Betalt reklamemærke er skjult</string>
<string name="revanced_hide_paid_promotion_label_summary_off">Betalt salgsfremmende mærke er vist</string>
@@ -798,8 +813,8 @@ Indstillinger → Afspilning → Afspil næste video automatisk"</string>
<string name="revanced_ryd_failure_ryd_enabled_while_playing_video_then_user_voted">Genindlæs video for at stemme ved hjælp af Return YouTube Dislike</string>
<!-- Video likes have been set to hidden by the video uploader. -->
<string name="revanced_ryd_video_likes_hidden_by_video_owner">Skjult af ejer</string>
<string name="revanced_ryd_enable_summary_on">Dislikationer vises</string>
<string name="revanced_ryd_enable_summary_off">Dislikationer vises ikke</string>
<string name="revanced_ryd_enabled_summary_on">Dislikationer vises</string>
<string name="revanced_ryd_enabled_summary_off">Dislikationer vises ikke</string>
<string name="revanced_ryd_shorts_title">Vis ikke på Shorts</string>
<string name="revanced_ryd_dislike_percentage_title">Synes ikke som procent</string>
<!-- Translations should use language similar to 'revanced_sb_enable_compact_skip_button' -->
@@ -812,7 +827,6 @@ Indstillinger → Afspilning → Afspil næste video automatisk"</string>
<string name="revanced_ryd_toast_on_connection_error_title">Vis en toast hvis API ikke er tilgængelig</string>
<string name="revanced_ryd_toast_on_connection_error_summary_on">Toast vises, hvis Return YouTube Dislike ikke er tilgængelig</string>
<string name="revanced_ryd_toast_on_connection_error_summary_off">Toast vises ikke, hvis Return YouTube Dislike ikke er tilgængelig</string>
<string name="revanced_ryd_about">Om</string>
<string name="revanced_ryd_attribution_summary">Data leveres af Return YouTube Dislike API. Tryk her for at få mere at vide</string>
<!-- 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 statistikker for denne enhed</string>
@@ -1010,7 +1024,7 @@ Er du klar til at indsende?"</string>
<string name="revanced_sb_new_segment_edit_by_hand_title">Rediger timing af segment manuelt</string>
<string name="revanced_sb_new_segment_edit_by_hand_content">Vil du redigere timingen for start eller afslutning af segmentet?</string>
<string name="revanced_sb_new_segment_edit_by_hand_parse_error">Ugyldig tid givet</string>
<string name="revanced_sb_stats">Statistik</string>
<string name="revanced_sb_stats_title">Statistik</string>
<!-- Shown in the settings preferences, and translations can be any text length. -->
<string name="revanced_sb_stats_loading">Indlæser...</string>
<string name="revanced_sb_stats_sb_disabled">SponsorBloker er deaktiveret</string>
@@ -1033,13 +1047,8 @@ Er du klar til at indsende?"</string>
<string name="revanced_sb_stats_saved_second_format">%s sekunder</string>
<string name="revanced_sb_color_opacity_label">Opacitet:</string>
<string name="revanced_sb_color_dot_label">Farve:</string>
<string name="revanced_sb_color_changed">Farve ændret</string>
<string name="revanced_sb_color_reset">Nulstil farve</string>
<string name="revanced_sb_color_invalid">Ugyldig farvekode</string>
<string name="revanced_sb_reset_color">Nulstil farve</string>
<string name="revanced_sb_reset">Nulstil</string>
<string name="revanced_sb_about">Om</string>
<string name="revanced_sb_about_api_sum">Data leveres af SponsorBlock API. Tryk her for at få flere oplysninger og se downloads til andre platforme</string>
<string name="revanced_sb_about_title">Om</string>
<string name="revanced_sb_about_api_summary">Data leveres af SponsorBlock API. Tryk her for at få flere oplysninger og se downloads til andre platforme</string>
</patch>
<patch id="layout.formfactor.changeFormFactorPatch">
<string name="revanced_change_form_factor_title">Layout-formfaktor</string>

View File

@@ -35,6 +35,8 @@ Second \"item\" text"</string>
<string name="revanced_settings_submenu_title">Einstellungen</string>
<string name="revanced_settings_confirm_user_dialog_title">Möchtest du fortfahren?</string>
<string name="revanced_settings_reset">Zurücksetzen</string>
<string name="revanced_settings_reset_color">Farbe zurücksetzen</string>
<string name="revanced_settings_color_invalid">Ungültige Farbe</string>
<string name="revanced_settings_restart_title">Aktualisieren und neu starten</string>
<string name="revanced_settings_restart">Neustart</string>
<string name="revanced_settings_import">Importieren</string>
@@ -42,6 +44,10 @@ Second \"item\" text"</string>
<string name="revanced_settings_import_reset">ReVanced-Einstellungen auf Standardwerte zurückgesetzt</string>
<string name="revanced_settings_import_success">%d Einstellungen importiert</string>
<string name="revanced_settings_import_failure_parse">Import fehlgeschlagen: %s</string>
<string name="revanced_settings_search_hint">Sucheinstellungen</string>
<string name="revanced_settings_search_no_results_title">Keine Ergebnisse für „%s“ gefunden</string>
<string name="revanced_settings_search_no_results_summary">Versuchen Sie ein anderes Schlüsselwort</string>
<string name="revanced_settings_search_remove_message">Aus Suchverlauf entfernen?</string>
<string name="revanced_show_menu_icons_title">ReVanced-Einstellungssymbole anzeigen</string>
<string name="revanced_show_menu_icons_summary_on">Einstellungssymbole werden angezeigt</string>
<string name="revanced_show_menu_icons_summary_off">Einstellungssymbole werden nicht angezeigt</string>
@@ -89,6 +95,9 @@ Tippen Sie auf die Schaltfläche \"Fortfahren\" und erlauben Sie die Optimierung
<string name="revanced_restore_old_settings_menus_title">Alte Einstellungsmenüs wiederherstellen</string>
<string name="revanced_restore_old_settings_menus_summary_on">Alte Einstellungsmenüs werden angezeigt</string>
<string name="revanced_restore_old_settings_menus_summary_off">Alte Einstellungsmenüs werden nicht angezeigt</string>
<string name="revanced_settings_search_history_title">Suchverlauf der Einstellungen anzeigen</string>
<string name="revanced_settings_search_history_summary_on">Suchverlauf der Einstellungen wird angezeigt</string>
<string name="revanced_settings_search_history_summary_off">Der Suchverlauf der Einstellungen wird nicht angezeigt</string>
</patch>
<patch id="misc.backgroundplayback.backgroundPlaybackPatch">
<string name="revanced_shorts_disable_background_playback_title">Shorts-Hintergrundwiedergabe deaktivieren</string>
@@ -149,15 +158,18 @@ Sie werden nicht über unerwartete Ereignisse informiert."</string>
<string name="revanced_hide_notify_me_button_title">\'Benachrichtigungen\' Button ausblenden</string>
<string name="revanced_hide_notify_me_button_summary_on">Button ist ausgeblendet</string>
<string name="revanced_hide_notify_me_button_summary_off">Button wird angezeigt</string>
<!-- 'People also watched' should be translated using the same localized wording YouTube displays. -->
<string name="revanced_hide_search_result_recommendations_title">\'Leute haben auch angeschaut\'-Label ausblenden</string>
<string name="revanced_hide_search_result_recommendations_summary_on">Label ist ausgeblendet</string>
<string name="revanced_hide_search_result_recommendations_summary_off">Label wird angezeigt</string>
<!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. -->
<string name="revanced_hide_search_result_recommendation_labels_title">Videovorschlag-Labels ausblenden</string>
<string name="revanced_hide_search_result_recommendation_labels_summary_on">Die Labels „Nutzer haben sich auch angesehen“ und „Ihnen könnte auch gefallen“ sind ausgeblendet</string>
<string name="revanced_hide_search_result_recommendation_labels_summary_off">Die Labels „Nutzer haben sich auch angesehen“ und „Ihnen könnte auch gefallen“ werden angezeigt</string>
<!-- 'Show more' should be translated with the same localized wording that YouTube displays.
This button usually appears when searching for a YT creator. -->
<string name="revanced_hide_show_more_button_title">\'Mehr anzeigen\' Button ausblenden</string>
<string name="revanced_hide_show_more_button_summary_on">Button ist ausgeblendet</string>
<string name="revanced_hide_show_more_button_summary_off">Button wird angezeigt</string>
<string name="revanced_hide_ticket_shelf_title">Ticket-Reihe ausblenden</string>
<string name="revanced_hide_ticket_shelf_summary_on">Ticket-Reihe ist ausgeblendet</string>
<string name="revanced_hide_ticket_shelf_summary_off">Ticket-Reihe wird angezeigt</string>
<string name="revanced_hide_timed_reactions_title">Zeitliche Reaktionen ausblenden</string>
<string name="revanced_hide_timed_reactions_summary_on">Zeitgesteuerte Reaktionen sind ausgeblendet</string>
<string name="revanced_hide_timed_reactions_summary_off">Zeitgesteuerte Reaktionen werden angezeigt</string>
@@ -227,6 +239,9 @@ Sie werden nicht über unerwartete Ereignisse informiert."</string>
<string name="revanced_hide_ai_generated_video_summary_section_title">\'KI-generierte Videozusammenfassung\' ausblenden</string>
<string name="revanced_hide_ai_generated_video_summary_section_summary_on">Videozusammenfassungsbereich ist ausgeblendet</string>
<string name="revanced_hide_ai_generated_video_summary_section_summary_off">Videozusammenfassungsbereich wird angezeigt</string>
<string name="revanced_hide_ask_section_title">Ask ausblenden</string>
<string name="revanced_hide_ask_section_summary_on">Ask Abschnitt ist ausgeblendet</string>
<string name="revanced_hide_ask_section_summary_off">Ask Abschnitt wird angezeigt</string>
<string name="revanced_hide_attributes_section_title">Attribute ausblenden</string>
<string name="revanced_hide_attributes_section_summary_on">Empfohlene Orte, Spiele, Musik und Erwähnungen von Personen sind ausgeblendet</string>
<string name="revanced_hide_attributes_section_summary_off">Empfohlene Orte, Spiele, Musik und Erwähnungen von Personen werden angezeigt</string>
@@ -347,9 +362,6 @@ Einschränkungen
Diese Funktion ist nur für ältere Geräte verfügbar"</string>
<string name="revanced_hide_fullscreen_ads_summary_off">Vollbild-Anzeigen werden angezeigt</string>
<string name="revanced_hide_buttoned_ads_title">Verknüpfte Werbung ausblenden</string>
<string name="revanced_hide_buttoned_ads_summary_on">Verknüpfte Anzeigen sind ausgeblendet</string>
<string name="revanced_hide_buttoned_ads_summary_off">Verknüpfte Werbung wird angezeigt</string>
<string name="revanced_hide_paid_promotion_label_title">Bezahltes Werbe-Label ausblenden</string>
<string name="revanced_hide_paid_promotion_label_summary_on">Bezahltes Werbelabel ist ausgeblendet</string>
<string name="revanced_hide_paid_promotion_label_summary_off">Bezahltes Werbe-Label wird angezeigt</string>
@@ -829,10 +841,10 @@ Einstellungen → Wiedergabe → Nächstes Video automatisch abspielen"</string>
<string name="revanced_ryd_failure_ryd_enabled_while_playing_video_then_user_voted">Video neu laden, um mit Return YouTube Dislike abzustimmen</string>
<!-- Video likes have been set to hidden by the video uploader. -->
<string name="revanced_ryd_video_likes_hidden_by_video_owner">Vom Eigentümer verborgen</string>
<string name="revanced_ryd_enable_summary_on">Dislikes werden angezeigt</string>
<string name="revanced_ryd_enable_summary_off">Dislikes werden nicht angezeigt</string>
<string name="revanced_ryd_enabled_summary_on">Dislikes werden angezeigt</string>
<string name="revanced_ryd_enabled_summary_off">Dislikes werden nicht angezeigt</string>
<string name="revanced_ryd_shorts_title">Dislikes auf Shorts anzeigen</string>
<string name="revanced_ryd_shorts_summary_on_disclaimer">"Dislikes für Shorts werden angezeigt
<string name="revanced_ryd_shorts_summary_on">"Dislikes für Shorts werden angezeigt
Einschränkung: Dislikes werden möglicherweise nicht im Inkognito-Modus angezeigt"</string>
<string name="revanced_ryd_shorts_summary_off">Dislikes für Shorts werden nicht angezeigt</string>
@@ -849,7 +861,6 @@ Einschränkung: Dislikes werden möglicherweise nicht im Inkognito-Modus angezei
<string name="revanced_ryd_toast_on_connection_error_title">Einen Toast anzeigen, wenn die API nicht verfügbar ist</string>
<string name="revanced_ryd_toast_on_connection_error_summary_on">Toast wird angezeigt, wenn YouTube-Ablehnung nicht verfügbar ist</string>
<string name="revanced_ryd_toast_on_connection_error_summary_off">Toast wird nicht angezeigt, wenn YouTube-Dislike nicht verfügbar ist</string>
<string name="revanced_ryd_about">Über</string>
<string name="revanced_ryd_attribution_summary">Daten werden von der YouTube Dislike API zurückgegeben. Tippe hier, um mehr zu erfahren</string>
<!-- 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">Gibt die API-Statistiken dieses Geräts zurück</string>
@@ -1056,7 +1067,7 @@ Bereit zum Einreichen?"</string>
<string name="revanced_sb_new_segment_edit_by_hand_title">Timing des Segments manuell bearbeiten</string>
<string name="revanced_sb_new_segment_edit_by_hand_content">Möchtest du den Zeitpunkt für den Anfang oder das Ende des Segments bearbeiten?</string>
<string name="revanced_sb_new_segment_edit_by_hand_parse_error">Ungültige Zeit angegeben</string>
<string name="revanced_sb_stats">Statistiken</string>
<string name="revanced_sb_stats_title">Statistiken</string>
<!-- Shown in the settings preferences, and translations can be any text length. -->
<string name="revanced_sb_stats_connection_failure">Statistiken sind vorübergehend nicht verfügbar (API ist ausgefallen)</string>
<string name="revanced_sb_stats_loading">Lädt...</string>
@@ -1080,13 +1091,8 @@ Bereit zum Einreichen?"</string>
<string name="revanced_sb_stats_saved_second_format">%s Sekunden</string>
<string name="revanced_sb_color_opacity_label">Deckkraft:</string>
<string name="revanced_sb_color_dot_label">Farbe:</string>
<string name="revanced_sb_color_changed">Farbe geändert</string>
<string name="revanced_sb_color_reset">Farbe zurücksetzen</string>
<string name="revanced_sb_color_invalid">Ungültiger Farbcode</string>
<string name="revanced_sb_reset_color">Farbe zurücksetzen</string>
<string name="revanced_sb_reset">Zurücksetzen</string>
<string name="revanced_sb_about">Über</string>
<string name="revanced_sb_about_api_sum">Daten werden von der SponsorBlock API bereitgestellt. Tippe hier, um mehr zu erfahren und Downloads für andere Plattformen zu sehen</string>
<string name="revanced_sb_about_title">Über</string>
<string name="revanced_sb_about_api_summary">Daten werden von der SponsorBlock API bereitgestellt. Tippe hier, um mehr zu erfahren und Downloads für andere Plattformen zu sehen</string>
</patch>
<patch id="layout.formfactor.changeFormFactorPatch">
<string name="revanced_change_form_factor_title">Anordnungsformfactor</string>

View File

@@ -35,6 +35,8 @@ Second \"item\" text"</string>
<string name="revanced_settings_submenu_title">Ρυθμίσεις</string>
<string name="revanced_settings_confirm_user_dialog_title">Θέλετε να συνεχίσετε;</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">Επανεκκίνηση</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_success">Έγινε εισαγωγή %d ρυθμίσεων</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_summary_on">Τα εικονίδια ρυθμίσεων εμφανίζονται</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_summary_on">Τα παλιά μενού ρυθμίσεων εμφανίζονται</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 id="misc.backgroundplayback.backgroundPlaybackPatch">
<string name="revanced_shorts_disable_background_playback_title">Απενεργοποίηση αναπαραγωγής παρασκηνίου για τα Shorts</string>
@@ -155,15 +164,18 @@ Second \"item\" text"</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_off">Εμφανίζεται</string>
<!-- 'People also watched' 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_recommendations_summary_on">Κρυμμένη</string>
<string name="revanced_hide_search_result_recommendations_summary_off">Εμφανίζεται</string>
<!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. -->
<string name="revanced_hide_search_result_recommendation_labels_title">Ετικέτες προτάσεων βίντεο</string>
<string name="revanced_hide_search_result_recommendation_labels_summary_on">Κρυμμένες\n\nΑφορά τις ετικέτες «Άλλοι χρήστες παρακολούθησαν επίσης» και «Ενδέχεται επίσης να σας αρέσει»</string>
<string name="revanced_hide_search_result_recommendation_labels_summary_off">Εμφανίζονται\n\nΑφορά τις ετικέτες «Άλλοι χρήστες παρακολούθησαν επίσης» και «Ενδέχεται επίσης να σας αρέσει»</string>
<!-- 'Show more' should be translated with the same localized wording that YouTube displays.
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_summary_on">Κρυμμένο</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_summary_on">Κρυμμένες</string>
<string name="revanced_hide_timed_reactions_summary_off">Εμφανίζονται</string>
@@ -233,6 +245,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_summary_on">Κρυμμένη</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_summary_on">Κρυμμένη.\n\nΑφορά τις ιδιότητες: Επιλεγμένα μέρη, Παιχνίδια, Μουσική, και «Οι χρήστες ανέφεραν»</string>
<string name="revanced_hide_attributes_section_summary_off">Εμφανίζεται.\n\nΑφορά τις ιδιότητες: Επιλεγμένα μέρη, Παιχνίδια, Μουσική, και «Οι χρήστες ανέφεραν»</string>
@@ -354,9 +369,6 @@ Second \"item\" text"</string>
Αυτή η λειτουργία είναι διαθέσιμη μόνο για παλιότερες συσκευές"</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_summary_on">Κρυμμένες</string>
<string name="revanced_hide_paid_promotion_label_summary_off">Εμφανίζονται</string>
@@ -797,9 +809,9 @@ Second \"item\" text"</string>
<string name="revanced_end_screen_suggested_video_summary_off">Εμφανίζεται</string>
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
<string name="revanced_hide_related_video_overlay_title">Απόκρυψη επικάλυψης σχετικού βίντεο σε πλήρη οθόνη</string>
<string name="revanced_hide_related_video_overlay_summary_on">Η επικάλυψη σχετικού βίντεο είναι κρυφή</string>
<string name="revanced_hide_related_video_overlay_summary_off">Εμφανίζεται η επικάλυψη σχετικού βίντεο</string>
<string name="revanced_hide_related_video_overlay_title">Σχετιζόμενο βίντεο σε πλήρη οθόνη</string>
<string name="revanced_hide_related_video_overlay_summary_on">Κρυμμένο</string>
<string name="revanced_hide_related_video_overlay_summary_off">Εμφανίζεται</string>
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
<string name="revanced_hide_timestamp_title">Χρονική πρόοδος βίντεο</string>
@@ -838,10 +850,10 @@ Second \"item\" text"</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. -->
<string name="revanced_ryd_video_likes_hidden_by_video_owner">Κρυμμένα από τον ιδιοκτήτη</string>
<string name="revanced_ryd_enable_summary_on">Τα «Δεν μου αρέσει» εμφανίζονται</string>
<string name="revanced_ryd_enable_summary_off">Τα «Δεν μου αρέσει» δεν εμφανίζονται</string>
<string name="revanced_ryd_enabled_summary_on">Τα «Δεν μου αρέσει» εμφανίζονται</string>
<string name="revanced_ryd_enabled_summary_off">Τα «Δεν μου αρέσει» δεν εμφανίζονται</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 name="revanced_ryd_shorts_summary_off">Τα «Δεν μου αρέσει» δεν εμφανίζονται στα Shorts</string>
@@ -858,7 +870,6 @@ Second \"item\" text"</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_off">Δεν εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης αν το Return YouTube Dislike δεν είναι διαθέσιμο</string>
<string name="revanced_ryd_about">Σχετικά με</string>
<string name="revanced_ryd_attribution_summary">Τα δεδομένα Dislike παρέχονται από το API του Return YouTube Dislike. Πατήστε εδώ για να μάθετε περισσότερα</string>
<!-- 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>
@@ -1064,7 +1075,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_content">Θέλετε να επεξεργαστείτε τον χρονισμό του τμήματος από την αρχή ή από το τέλος του τμήματος;</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. -->
<string name="revanced_sb_stats_connection_failure">Τα στατιστικά είναι προσωρινά μη διαθέσιμα (το API είναι εκτός λειτουργίας)</string>
<string name="revanced_sb_stats_loading">Φόρτωση...</string>
@@ -1088,13 +1099,8 @@ Second \"item\" text"</string>
<string name="revanced_sb_stats_saved_second_format">%s δευτερόλεπτα</string>
<string name="revanced_sb_color_opacity_label">Αδιαφάνεια:</string>
<string name="revanced_sb_color_dot_label">Χρώμα:</string>
<string name="revanced_sb_color_changed">Το χρώμα άλλαξε</string>
<string name="revanced_sb_color_reset">Το χρώμα επαναφέρθηκε</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>
<string name="revanced_sb_about_title">Σχετικά με</string>
<string name="revanced_sb_about_api_summary">Τα δεδομένα παρέχονται από το SponsorBlock API. Πατήστε για να μάθετε περισσότερα και να δείτε λήψεις για άλλες πλατφόρμες</string>
</patch>
<patch id="layout.formfactor.changeFormFactorPatch">
<string name="revanced_change_form_factor_title">Αλλαγή μορφής διάταξης</string>

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