mirror of
https://github.com/ReVanced/revanced-patches.git
synced 2026-01-13 22:43:14 +00:00
Compare commits
70 Commits
v5.31.0-de
...
v5.32.0-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63859f0ef9 | ||
|
|
1c9000dbda | ||
|
|
8ec857a175 | ||
|
|
f56c7868f5 | ||
|
|
cfd77800d6 | ||
|
|
707deaef0b | ||
|
|
9ddb3ac39d | ||
|
|
a7d3b7c287 | ||
|
|
30bac0397e | ||
|
|
c5fc187a35 | ||
|
|
f46dbcd084 | ||
|
|
2136573cb6 | ||
|
|
86ec08993c | ||
|
|
44da5a71c5 | ||
|
|
e4e81b89ea | ||
|
|
165df659a1 | ||
|
|
bb87afe0f6 | ||
|
|
ac5fb17937 | ||
|
|
e88356b3c5 | ||
|
|
dead9c2d94 | ||
|
|
ca640b2839 | ||
|
|
c972267cd8 | ||
|
|
d0d2c13d16 | ||
|
|
e7b4ab53cf | ||
|
|
f994264d9c | ||
|
|
eb61c1f5d1 | ||
|
|
e578347277 | ||
|
|
294b2dce2e | ||
|
|
aa37105ea3 | ||
|
|
eb57a2697b | ||
|
|
19bc5b63c5 | ||
|
|
2b93ff6cfc | ||
|
|
cc6984e919 | ||
|
|
8bf575e778 | ||
|
|
2e625ee1a2 | ||
|
|
6bcba48ee7 | ||
|
|
c3034edc43 | ||
|
|
82255a09d3 | ||
|
|
594dce13cd | ||
|
|
479e205808 | ||
|
|
3d1b7e8101 | ||
|
|
e951184b7a | ||
|
|
d088b1e7ed | ||
|
|
a38f635514 | ||
|
|
b3e6c215cc | ||
|
|
c9cc3d5c41 | ||
|
|
536e64565c | ||
|
|
65cbf3c1eb | ||
|
|
61c1a7a75a | ||
|
|
1e39db06b8 | ||
|
|
e019f83232 | ||
|
|
3b57a5f8c0 | ||
|
|
eafe3dfc45 | ||
|
|
d56d8d990c | ||
|
|
37a8682901 | ||
|
|
11ba7d4e3e | ||
|
|
6833d37c26 | ||
|
|
e6f72bcb7d | ||
|
|
e8a227c082 | ||
|
|
0472ec2830 | ||
|
|
6412a5cb1a | ||
|
|
cc548689ac | ||
|
|
a3d47e72e3 | ||
|
|
f37482443a | ||
|
|
cc4aef89d3 | ||
|
|
1c0a0eb4b5 | ||
|
|
b1d6c46763 | ||
|
|
42195b9f63 | ||
|
|
a4e08ea13d | ||
|
|
bd2a939a72 |
2
.github/workflows/build_pull_request.yml
vendored
2
.github/workflows/build_pull_request.yml
vendored
@@ -13,8 +13,6 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
|
||||
1
.github/workflows/pull_strings.yml
vendored
1
.github/workflows/pull_strings.yml
vendored
@@ -17,7 +17,6 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: dev
|
||||
fetch-depth: 0
|
||||
clean: true
|
||||
|
||||
- name: Pull strings
|
||||
|
||||
2
.github/workflows/push_strings.yml
vendored
2
.github/workflows/push_strings.yml
vendored
@@ -15,8 +15,6 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Preprocess strings
|
||||
env:
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -19,8 +19,6 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
|
||||
201
CHANGELOG.md
201
CHANGELOG.md
@@ -1,3 +1,204 @@
|
||||
# [5.32.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.32.0-dev.3...v5.32.0-dev.4) (2025-07-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Messenger - Hide inbox ads:** Support the latest app version ([2959c02](https://github.com/ReVanced/revanced-patches/commit/2959c0214dfa703ee623ef1f89bded7f78c9d252))
|
||||
|
||||
# [5.32.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.32.0-dev.2...v5.32.0-dev.3) (2025-07-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - External downloads:** Improve the selection of the external downloader package ([#5504](https://github.com/ReVanced/revanced-patches/issues/5504)) ([5de9aa9](https://github.com/ReVanced/revanced-patches/commit/5de9aa9fad4f24186da045fb188f8718d6f63d7a))
|
||||
|
||||
# [5.32.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.32.0-dev.1...v5.32.0-dev.2) (2025-07-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Hide layout components:** Fix "Hide ticket shelf" ([#5516](https://github.com/ReVanced/revanced-patches/issues/5516)) ([3b85c71](https://github.com/ReVanced/revanced-patches/commit/3b85c71433325fff49e01c77c7b9ff8ddd0a7068))
|
||||
|
||||
# [5.32.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.31.3-dev.1...v5.32.0-dev.1) (2025-07-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Prime Video:** Add `Playback speed` patch ([#5444](https://github.com/ReVanced/revanced-patches/issues/5444)) ([22cf313](https://github.com/ReVanced/revanced-patches/commit/22cf313a7b99b69e17b9d488c514802043a5dc10))
|
||||
|
||||
## [5.31.3-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.31.2...v5.31.3-dev.1) (2025-07-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - GmsCore support:** Fix search suggestions when logged out by using correct search provider ([#5483](https://github.com/ReVanced/revanced-patches/issues/5483)) ([e86fdc8](https://github.com/ReVanced/revanced-patches/commit/e86fdc86b161a6077960b85149e83bacbac664e7))
|
||||
|
||||
## [5.31.2](https://github.com/ReVanced/revanced-patches/compare/v5.31.1...v5.31.2) (2025-07-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Spotify - Spoof client:** Fix login failing by spoofing login request in addition ([#5448](https://github.com/ReVanced/revanced-patches/issues/5448)) ([4e59ddc](https://github.com/ReVanced/revanced-patches/commit/4e59ddc62388d09f71b89593fc8b76933d9facea))
|
||||
* **YouTube - Disable double tap actions:** Remove old incompatible targets ([857053e](https://github.com/ReVanced/revanced-patches/commit/857053e29b72ded10a84b0ac693fa107705342d9))
|
||||
* **YouTube - Hide layout components:** Hide quick actions does not work ([#5423](https://github.com/ReVanced/revanced-patches/issues/5423)) ([9c66729](https://github.com/ReVanced/revanced-patches/commit/9c6672946d44001e106bdac9041e2d79ef3f6ab2))
|
||||
* **YouTube - Hide layout components:** Show correct custom header logo if 'Hide YouTube Doodles' is enabled ([#5431](https://github.com/ReVanced/revanced-patches/issues/5431)) ([20cc141](https://github.com/ReVanced/revanced-patches/commit/20cc141e61f75de1a1749247c4f4aed167dee8ea))
|
||||
* **YouTube - Settings:** Back button/gesture closes search instead of exiting ([#5418](https://github.com/ReVanced/revanced-patches/issues/5418)) ([134b278](https://github.com/ReVanced/revanced-patches/commit/134b278baa7b90d2c4b06200cabacabf55ebc055))
|
||||
|
||||
## [5.31.2-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.31.2-dev.4...v5.31.2-dev.5) (2025-07-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Spotify - Spoof client:** Fix login failing by spoofing login request in addition ([#5448](https://github.com/ReVanced/revanced-patches/issues/5448)) ([4e59ddc](https://github.com/ReVanced/revanced-patches/commit/4e59ddc62388d09f71b89593fc8b76933d9facea))
|
||||
|
||||
## [5.31.2-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.31.2-dev.3...v5.31.2-dev.4) (2025-07-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Settings:** Back button/gesture closes search instead of exiting ([#5418](https://github.com/ReVanced/revanced-patches/issues/5418)) ([134b278](https://github.com/ReVanced/revanced-patches/commit/134b278baa7b90d2c4b06200cabacabf55ebc055))
|
||||
|
||||
## [5.31.2-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.31.2-dev.2...v5.31.2-dev.3) (2025-07-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Disable double tap actions:** Remove old incompatible targets ([857053e](https://github.com/ReVanced/revanced-patches/commit/857053e29b72ded10a84b0ac693fa107705342d9))
|
||||
|
||||
## [5.31.2-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.31.2-dev.1...v5.31.2-dev.2) (2025-07-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Hide layout components:** Show correct custom header logo if 'Hide YouTube Doodles' is enabled ([#5431](https://github.com/ReVanced/revanced-patches/issues/5431)) ([20cc141](https://github.com/ReVanced/revanced-patches/commit/20cc141e61f75de1a1749247c4f4aed167dee8ea))
|
||||
|
||||
## [5.31.2-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.31.1...v5.31.2-dev.1) (2025-07-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Hide layout components:** Hide quick actions does not work ([#5423](https://github.com/ReVanced/revanced-patches/issues/5423)) ([9c66729](https://github.com/ReVanced/revanced-patches/commit/9c6672946d44001e106bdac9041e2d79ef3f6ab2))
|
||||
|
||||
## [5.31.1](https://github.com/ReVanced/revanced-patches/compare/v5.31.0...v5.31.1) (2025-07-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Spotify - Unlock Premium:** Fix hiding context menu ads for latest version ([#5415](https://github.com/ReVanced/revanced-patches/issues/5415)) ([dcde393](https://github.com/ReVanced/revanced-patches/commit/dcde3935bde3172576d0f9f5ff9eb62ecfff7dfe))
|
||||
|
||||
## [5.31.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.31.0...v5.31.1-dev.1) (2025-07-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Spotify - Unlock Premium:** Fix hiding context menu ads for latest version ([#5415](https://github.com/ReVanced/revanced-patches/issues/5415)) ([dcde393](https://github.com/ReVanced/revanced-patches/commit/dcde3935bde3172576d0f9f5ff9eb62ecfff7dfe))
|
||||
|
||||
# [5.31.0](https://github.com/ReVanced/revanced-patches/compare/v5.30.0...v5.31.0) (2025-07-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Bacon Reader - Spoof client:** Use www instead of ssl API to fix auth related issues ([#5402](https://github.com/ReVanced/revanced-patches/issues/5402)) ([72459bb](https://github.com/ReVanced/revanced-patches/commit/72459bb2eaf4691e32822dfdd1db3240e2fe98dd))
|
||||
* Correctly name `Enable ROM signature spoofing` patch ([d85881a](https://github.com/ReVanced/revanced-patches/commit/d85881a6768232a999534677bebb248e640fe5ab))
|
||||
* Fix accidental changes ([e2ac841](https://github.com/ReVanced/revanced-patches/commit/e2ac8419756e3c7d62e2c0430a2918a3c1c63666))
|
||||
* Fix refactoring typo ([ec0ae42](https://github.com/ReVanced/revanced-patches/commit/ec0ae42496628cdeb2a639020fce94316b41b751))
|
||||
* Handle empty list of announcements ([de9d720](https://github.com/ReVanced/revanced-patches/commit/de9d7209f4e818a618a7fd9000013ae8ebd728f2))
|
||||
* **SoundCloud:** Constrain patches to last working app target ([e8ea89f](https://github.com/ReVanced/revanced-patches/commit/e8ea89fc1a3f0531a0af7529663f13328aca4fe7))
|
||||
* **Spotify - Unlock Premium:** Remove wrongfully hidden non ad browse sections ([#5403](https://github.com/ReVanced/revanced-patches/issues/5403)) ([8633544](https://github.com/ReVanced/revanced-patches/commit/8633544decc0814d7a548fbc5576b4bdd1d7eee0))
|
||||
* **Spotify:** Remove other ads type from the browse screen ([#5333](https://github.com/ReVanced/revanced-patches/issues/5333)) ([c68533a](https://github.com/ReVanced/revanced-patches/commit/c68533a33a399ca813380b5c9ccddce434ceadf8))
|
||||
* **Sync for Reddit - Spoof client:** Use www instead of ssl API to fix auth related issues ([#5392](https://github.com/ReVanced/revanced-patches/issues/5392)) ([47e6b62](https://github.com/ReVanced/revanced-patches/commit/47e6b62f3d8b07960cfb2963f441222d3e67df92))
|
||||
* **YouTube - Hide ads:** Hide new type of general ad ([#5345](https://github.com/ReVanced/revanced-patches/issues/5345)) ([f23716b](https://github.com/ReVanced/revanced-patches/commit/f23716bc52c03d8d0271bfe38b19247e6de7021d))
|
||||
* **YouTube - Hide layout components:** Do not hide playlist sort button if 'Hide AI comments summary' is on ([5f3e48e](https://github.com/ReVanced/revanced-patches/commit/5f3e48ec5853f6439800ef58239291c34bcab5f6))
|
||||
* **YouTube - Playback speed:** Allow custom speeds with 0.01x precision ([#5360](https://github.com/ReVanced/revanced-patches/issues/5360)) ([0eecef0](https://github.com/ReVanced/revanced-patches/commit/0eecef00fc93d2a217944978e29dce82e3134e35))
|
||||
* **YouTube - Slide to seek:** Show tap and hold 2x speed overlay when active ([#5398](https://github.com/ReVanced/revanced-patches/issues/5398)) ([dbc9c5f](https://github.com/ReVanced/revanced-patches/commit/dbc9c5f00c1f5bbb95f8822667cc1ac3c613fa00))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Cricbuzz - Hide ads:** Hide Cricbuzz11 UI elements ([#5381](https://github.com/ReVanced/revanced-patches/issues/5381)) ([a42c98f](https://github.com/ReVanced/revanced-patches/commit/a42c98f8b51fd37d815fd38b75a2b7ccc4fb049b))
|
||||
* **Lightroom:** Constrain patches to last working version ([#5335](https://github.com/ReVanced/revanced-patches/issues/5335)) ([32ce70e](https://github.com/ReVanced/revanced-patches/commit/32ce70e994f354b9a569376bb89eb38b3190e6f9))
|
||||
* **Spotify - Spoof client:** Fix issues like songs skipping by spoofing to iOS ([#5388](https://github.com/ReVanced/revanced-patches/issues/5388)) ([e36d4c1](https://github.com/ReVanced/revanced-patches/commit/e36d4c1986b58815c7659e6ef44011166873f9c8))
|
||||
* **Spotify:** Remove support for old versions ([#5404](https://github.com/ReVanced/revanced-patches/issues/5404)) ([9d31238](https://github.com/ReVanced/revanced-patches/commit/9d31238803a45e957472760fc40c3862da2cf3f0))
|
||||
* **YouTube - Change header:** Add in-app setting to change the app header ([#5346](https://github.com/ReVanced/revanced-patches/issues/5346)) ([9ba45b6](https://github.com/ReVanced/revanced-patches/commit/9ba45b6680595d732b47e8fa54bee98b7c7af179))
|
||||
* **YouTube - Hide layout components:** Add `Hide channel links preview` and `Hide 'Visit Community' button` in channel page ([#5320](https://github.com/ReVanced/revanced-patches/issues/5320)) ([9d9cce3](https://github.com/ReVanced/revanced-patches/commit/9d9cce3ec5550b2fea88df745f1700bb2f17eb9e))
|
||||
* **YouTube:** Disable two-finger tap gesture for skipping chapters ([#5374](https://github.com/ReVanced/revanced-patches/issues/5374)) ([71db0a2](https://github.com/ReVanced/revanced-patches/commit/71db0a2661b5f76eb5048cdeed83f26fbfdf4fee))
|
||||
|
||||
# [5.31.0-dev.17](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.16...v5.31.0-dev.17) (2025-07-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Spotify - Unlock Premium:** Remove wrongfully hidden non ad browse sections ([#5403](https://github.com/ReVanced/revanced-patches/issues/5403)) ([8633544](https://github.com/ReVanced/revanced-patches/commit/8633544decc0814d7a548fbc5576b4bdd1d7eee0))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Spotify:** Remove support for old versions ([#5404](https://github.com/ReVanced/revanced-patches/issues/5404)) ([9d31238](https://github.com/ReVanced/revanced-patches/commit/9d31238803a45e957472760fc40c3862da2cf3f0))
|
||||
|
||||
# [5.31.0-dev.16](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.15...v5.31.0-dev.16) (2025-07-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Spotify - Spoof client:** Fix issues like songs skipping by spoofing to iOS ([#5388](https://github.com/ReVanced/revanced-patches/issues/5388)) ([e36d4c1](https://github.com/ReVanced/revanced-patches/commit/e36d4c1986b58815c7659e6ef44011166873f9c8))
|
||||
* **YouTube:** Disable two-finger tap gesture for skipping chapters ([#5374](https://github.com/ReVanced/revanced-patches/issues/5374)) ([71db0a2](https://github.com/ReVanced/revanced-patches/commit/71db0a2661b5f76eb5048cdeed83f26fbfdf4fee))
|
||||
|
||||
# [5.31.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.14...v5.31.0-dev.15) (2025-07-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Handle empty list of announcements ([de9d720](https://github.com/ReVanced/revanced-patches/commit/de9d7209f4e818a618a7fd9000013ae8ebd728f2))
|
||||
|
||||
# [5.31.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.13...v5.31.0-dev.14) (2025-07-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Bacon Reader - Spoof client:** Use www instead of ssl API to fix auth related issues ([#5402](https://github.com/ReVanced/revanced-patches/issues/5402)) ([72459bb](https://github.com/ReVanced/revanced-patches/commit/72459bb2eaf4691e32822dfdd1db3240e2fe98dd))
|
||||
|
||||
# [5.31.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.12...v5.31.0-dev.13) (2025-07-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Slide to seek:** Show tap and hold 2x speed overlay when active ([#5398](https://github.com/ReVanced/revanced-patches/issues/5398)) ([dbc9c5f](https://github.com/ReVanced/revanced-patches/commit/dbc9c5f00c1f5bbb95f8822667cc1ac3c613fa00))
|
||||
|
||||
# [5.31.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.11...v5.31.0-dev.12) (2025-07-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Sync for Reddit - Spoof client:** Use www instead of ssl API to fix auth related issues ([#5392](https://github.com/ReVanced/revanced-patches/issues/5392)) ([47e6b62](https://github.com/ReVanced/revanced-patches/commit/47e6b62f3d8b07960cfb2963f441222d3e67df92))
|
||||
|
||||
# [5.31.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.10...v5.31.0-dev.11) (2025-07-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Cricbuzz - Hide ads:** Hide Cricbuzz11 UI elements ([#5381](https://github.com/ReVanced/revanced-patches/issues/5381)) ([a42c98f](https://github.com/ReVanced/revanced-patches/commit/a42c98f8b51fd37d815fd38b75a2b7ccc4fb049b))
|
||||
|
||||
# [5.31.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.9...v5.31.0-dev.10) (2025-07-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Hide layout components:** Do not hide playlist sort button if 'Hide AI comments summary' is on ([5f3e48e](https://github.com/ReVanced/revanced-patches/commit/5f3e48ec5853f6439800ef58239291c34bcab5f6))
|
||||
|
||||
# [5.31.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.8...v5.31.0-dev.9) (2025-07-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fix accidental changes ([e2ac841](https://github.com/ReVanced/revanced-patches/commit/e2ac8419756e3c7d62e2c0430a2918a3c1c63666))
|
||||
|
||||
# [5.31.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.7...v5.31.0-dev.8) (2025-07-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Correctly name `Enable ROM signature spoofing` patch ([d85881a](https://github.com/ReVanced/revanced-patches/commit/d85881a6768232a999534677bebb248e640fe5ab))
|
||||
|
||||
# [5.31.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.6...v5.31.0-dev.7) (2025-07-06)
|
||||
|
||||
|
||||
|
||||
4
extensions/cricbuzz/build.gradle.kts
Normal file
4
extensions/cricbuzz/build.gradle.kts
Normal file
@@ -0,0 +1,4 @@
|
||||
dependencies {
|
||||
compileOnly(project(":extensions:shared:library"))
|
||||
compileOnly(project(":extensions:cricbuzz:stub"))
|
||||
}
|
||||
1
extensions/cricbuzz/src/main/AndroidManifest.xml
Normal file
1
extensions/cricbuzz/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest/>
|
||||
@@ -0,0 +1,28 @@
|
||||
package app.revanced.extension.cricbuzz.ads;
|
||||
|
||||
import com.cricbuzz.android.data.rest.model.BottomBar;
|
||||
import java.util.List;
|
||||
import java.util.Iterator;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class HideAdsPatch {
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void filterCb11(List<BottomBar> list) {
|
||||
try {
|
||||
Iterator<BottomBar> iterator = list.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
BottomBar bar = iterator.next();
|
||||
if (bar.getName().equals("Cricbuzz11")) {
|
||||
Logger.printInfo(() -> "Removing Cricbuzz11 bar: " + bar);
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "filterCb11 failure", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
17
extensions/cricbuzz/stub/build.gradle.kts
Normal file
17
extensions/cricbuzz/stub/build.gradle.kts
Normal file
@@ -0,0 +1,17 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "app.revanced.extension"
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
}
|
||||
1
extensions/cricbuzz/stub/src/main/AndroidManifest.xml
Normal file
1
extensions/cricbuzz/stub/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest/>
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.cricbuzz.android.data.rest.model;
|
||||
|
||||
public final class BottomBar {
|
||||
public final String getName() { throw new UnsupportedOperationException(); }
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
package app.revanced.extension.primevideo.videoplayer;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.graphics.RectF;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.PixelFormat;
|
||||
import java.util.Arrays;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
|
||||
import com.amazon.video.sdk.player.Player;
|
||||
|
||||
public class PlaybackSpeedPatch {
|
||||
private static Player player;
|
||||
private static final float[] SPEED_VALUES = {0.5f, 0.7f, 0.8f, 0.9f, 0.95f, 1.0f, 1.05f, 1.1f, 1.2f, 1.3f, 1.5f, 2.0f};
|
||||
private static final String SPEED_BUTTON_TAG = "speed_overlay";
|
||||
|
||||
public static void setPlayer(Player playerInstance) {
|
||||
player = playerInstance;
|
||||
if (player != null) {
|
||||
// Reset playback rate when switching between episodes to ensure correct display.
|
||||
player.setPlaybackRate(1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public static void initializeSpeedOverlay(View userControlsView) {
|
||||
try {
|
||||
LinearLayout buttonContainer = Utils.getChildViewByResourceName(userControlsView, "ButtonContainerPlayerTop");
|
||||
|
||||
// If the speed overlay exists we should return early.
|
||||
if (Utils.getChildView(buttonContainer, false, child ->
|
||||
child instanceof ImageView && SPEED_BUTTON_TAG.equals(child.getTag())) != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImageView speedButton = createSpeedButton(userControlsView.getContext());
|
||||
speedButton.setOnClickListener(v -> changePlaybackSpeed(speedButton));
|
||||
buttonContainer.addView(speedButton, 0);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
Logger.printException(() -> "initializeSpeedOverlay, no button container found", e);
|
||||
} catch (Exception e) {
|
||||
Logger.printException(() -> "initializeSpeedOverlay failure", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static ImageView createSpeedButton(Context context) {
|
||||
ImageView speedButton = new ImageView(context);
|
||||
speedButton.setContentDescription("Playback Speed");
|
||||
speedButton.setTag(SPEED_BUTTON_TAG);
|
||||
speedButton.setClickable(true);
|
||||
speedButton.setFocusable(true);
|
||||
speedButton.setScaleType(ImageView.ScaleType.CENTER);
|
||||
|
||||
SpeedIconDrawable speedIcon = new SpeedIconDrawable();
|
||||
speedButton.setImageDrawable(speedIcon);
|
||||
|
||||
int buttonSize = Utils.dipToPixels(48);
|
||||
speedButton.setMinimumWidth(buttonSize);
|
||||
speedButton.setMinimumHeight(buttonSize);
|
||||
|
||||
return speedButton;
|
||||
}
|
||||
|
||||
private static String[] getSpeedOptions() {
|
||||
String[] options = new String[SPEED_VALUES.length];
|
||||
for (int i = 0; i < SPEED_VALUES.length; i++) {
|
||||
options[i] = SPEED_VALUES[i] + "x";
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
private static void changePlaybackSpeed(ImageView imageView) {
|
||||
if (player == null) {
|
||||
Logger.printException(() -> "Player not available");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
player.pause();
|
||||
AlertDialog dialog = createSpeedPlaybackDialog(imageView);
|
||||
dialog.setOnDismissListener(dialogInterface -> player.play());
|
||||
dialog.show();
|
||||
|
||||
} catch (Exception e) {
|
||||
Logger.printException(() -> "changePlaybackSpeed", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static AlertDialog createSpeedPlaybackDialog(ImageView imageView) {
|
||||
Context context = imageView.getContext();
|
||||
int currentSelection = getCurrentSpeedSelection();
|
||||
|
||||
return new AlertDialog.Builder(context)
|
||||
.setTitle("Select Playback Speed")
|
||||
.setSingleChoiceItems(getSpeedOptions(), currentSelection,
|
||||
PlaybackSpeedPatch::handleSpeedSelection)
|
||||
.create();
|
||||
}
|
||||
|
||||
private static int getCurrentSpeedSelection() {
|
||||
try {
|
||||
float currentRate = player.getPlaybackRate();
|
||||
int index = Arrays.binarySearch(SPEED_VALUES, currentRate);
|
||||
return Math.max(index, 0); // Use slowest speed if not found.
|
||||
} catch (Exception e) {
|
||||
Logger.printException(() -> "getCurrentSpeedSelection error getting current playback speed", e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleSpeedSelection(android.content.DialogInterface dialog, int selectedIndex) {
|
||||
try {
|
||||
float selectedSpeed = SPEED_VALUES[selectedIndex];
|
||||
player.setPlaybackRate(selectedSpeed);
|
||||
player.play();
|
||||
} catch (Exception e) {
|
||||
Logger.printException(() -> "handleSpeedSelection error setting playback speed", e);
|
||||
} finally {
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SpeedIconDrawable extends Drawable {
|
||||
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
int w = getBounds().width();
|
||||
int h = getBounds().height();
|
||||
float centerX = w / 2f;
|
||||
// Position gauge in lower portion.
|
||||
float centerY = h * 0.7f;
|
||||
float radius = Math.min(w, h) / 2f * 0.8f;
|
||||
|
||||
paint.setColor(Color.WHITE);
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
paint.setStrokeWidth(radius * 0.1f);
|
||||
|
||||
// Draw semicircle.
|
||||
RectF oval = new RectF(centerX - radius, centerY - radius, centerX + radius, centerY + radius);
|
||||
canvas.drawArc(oval, 180, 180, false, paint);
|
||||
|
||||
// Draw three tick marks.
|
||||
paint.setStrokeWidth(radius * 0.06f);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
float angle = 180 + (i * 45); // 180°, 225°, 270°.
|
||||
float angleRad = (float) Math.toRadians(angle);
|
||||
|
||||
float startX = centerX + (radius * 0.8f) * (float) Math.cos(angleRad);
|
||||
float startY = centerY + (radius * 0.8f) * (float) Math.sin(angleRad);
|
||||
float endX = centerX + radius * (float) Math.cos(angleRad);
|
||||
float endY = centerY + radius * (float) Math.sin(angleRad);
|
||||
|
||||
canvas.drawLine(startX, startY, endX, endY, paint);
|
||||
}
|
||||
|
||||
// Draw needle.
|
||||
paint.setStrokeWidth(radius * 0.08f);
|
||||
float needleAngle = 200; // Slightly right of center.
|
||||
float needleAngleRad = (float) Math.toRadians(needleAngle);
|
||||
|
||||
float needleEndX = centerX + (radius * 0.6f) * (float) Math.cos(needleAngleRad);
|
||||
float needleEndY = centerY + (radius * 0.6f) * (float) Math.sin(needleAngleRad);
|
||||
|
||||
canvas.drawLine(centerX, centerY, needleEndX, needleEndY, paint);
|
||||
|
||||
// Center dot.
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
canvas.drawCircle(centerX, centerY, radius * 0.06f, paint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
paint.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter colorFilter) {
|
||||
paint.setColorFilter(colorFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return Utils.dipToPixels(32);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return Utils.dipToPixels(32);
|
||||
}
|
||||
}
|
||||
@@ -4,4 +4,10 @@ public interface VideoPlayer {
|
||||
long getCurrentPosition();
|
||||
|
||||
void seekTo(long positionMs);
|
||||
|
||||
void pause();
|
||||
|
||||
void play();
|
||||
|
||||
boolean isPlaying();
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.amazon.video.sdk.player;
|
||||
|
||||
public interface Player {
|
||||
float getPlaybackRate();
|
||||
|
||||
void setPlaybackRate(float rate);
|
||||
|
||||
void play();
|
||||
|
||||
void pause();
|
||||
}
|
||||
@@ -1438,6 +1438,28 @@ public class Utils {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a percentage of the screen height to actual device pixels.
|
||||
*
|
||||
* @param percentage The percentage of the screen height (e.g., 30 for 30%).
|
||||
* @return The device pixel value corresponding to the percentage of screen height.
|
||||
*/
|
||||
public static int percentageHeightToPixels(int percentage) {
|
||||
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
||||
return (int) (metrics.heightPixels * (percentage / 100.0f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a percentage of the screen width to actual device pixels.
|
||||
*
|
||||
* @param percentage The percentage of the screen width (e.g., 30 for 30%).
|
||||
* @return The device pixel value corresponding to the percentage of screen width.
|
||||
*/
|
||||
public static int percentageWidthToPixels(int percentage) {
|
||||
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
||||
return (int) (metrics.widthPixels * (percentage / 100.0f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the brightness of a color by lightening or darkening it based on the given factor.
|
||||
* <p>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package app.revanced.extension.shared.settings.preference;
|
||||
|
||||
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
@@ -26,7 +24,7 @@ public class CustomDialogListPreference extends ListPreference {
|
||||
/**
|
||||
* Custom ArrayAdapter to handle checkmark visibility.
|
||||
*/
|
||||
private static class ListPreferenceArrayAdapter extends ArrayAdapter<CharSequence> {
|
||||
public static class ListPreferenceArrayAdapter extends ArrayAdapter<CharSequence> {
|
||||
private static class SubViewDataContainer {
|
||||
ImageView checkIcon;
|
||||
View placeholder;
|
||||
|
||||
@@ -1,28 +1,15 @@
|
||||
package app.revanced.extension.shared.settings.preference;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.graphics.drawable.shapes.RectShape;
|
||||
import android.graphics.drawable.shapes.RoundRectShape;
|
||||
import android.graphics.drawable.ShapeDrawable;
|
||||
import android.graphics.Paint.Style;
|
||||
import android.os.Bundle;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Pair;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ dependencies {
|
||||
compileOnly(project(":extensions:spotify:stub"))
|
||||
compileOnly(libs.annotation)
|
||||
|
||||
implementation(project(":extensions:spotify:utils"))
|
||||
implementation(libs.nanohttpd)
|
||||
implementation(libs.protobuf.javalite)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package app.revanced.extension.spotify.layout.hide.createbutton;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.spotify.shared.ComponentFilters.*;
|
||||
import app.revanced.extension.spotify.shared.ComponentFilters.ComponentFilter;
|
||||
import app.revanced.extension.spotify.shared.ComponentFilters.ResourceIdComponentFilter;
|
||||
import app.revanced.extension.spotify.shared.ComponentFilters.StringComponentFilter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class HideCreateButtonPatch {
|
||||
@@ -53,7 +55,9 @@ public final class HideCreateButtonPatch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
} catch (Throwable ex) {
|
||||
// Catch Throwable as calling toString can cause crashes with wrongfully generated code that throws
|
||||
// NoSuchMethod errors.
|
||||
Logger.printException(() -> "returnNullIfIsCreateButton failure", ex);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
package app.revanced.extension.spotify.misc.fix;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.spotify.misc.fix.clienttoken.data.v0.ClienttokenHttp.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
import static app.revanced.extension.spotify.misc.fix.Constants.*;
|
||||
|
||||
class ClientTokenService {
|
||||
private static final String IOS_CLIENT_ID = "58bd3c95768941ea9eb4350aaa033eb3";
|
||||
private static final String IOS_USER_AGENT;
|
||||
|
||||
static {
|
||||
String clientVersion = getClientVersion();
|
||||
int commitHashIndex = clientVersion.lastIndexOf(".");
|
||||
String version = clientVersion.substring(
|
||||
clientVersion.indexOf("-") + 1,
|
||||
clientVersion.lastIndexOf(".", commitHashIndex - 1)
|
||||
);
|
||||
|
||||
IOS_USER_AGENT = "Spotify/" + version + " iOS/" + getSystemVersion() + " (" + getHardwareMachine() + ")";
|
||||
}
|
||||
|
||||
private static final ConnectivitySdkData.Builder IOS_CONNECTIVITY_SDK_DATA =
|
||||
ConnectivitySdkData.newBuilder()
|
||||
.setPlatformSpecificData(PlatformSpecificData.newBuilder()
|
||||
.setIos(NativeIOSData.newBuilder()
|
||||
.setHwMachine(getHardwareMachine())
|
||||
.setSystemVersion(getSystemVersion())
|
||||
)
|
||||
);
|
||||
|
||||
private static final ClientDataRequest.Builder IOS_CLIENT_DATA_REQUEST =
|
||||
ClientDataRequest.newBuilder()
|
||||
.setClientVersion(getClientVersion())
|
||||
.setClientId(IOS_CLIENT_ID);
|
||||
|
||||
private static final ClientTokenRequest.Builder IOS_CLIENT_TOKEN_REQUEST =
|
||||
ClientTokenRequest.newBuilder()
|
||||
.setRequestType(ClientTokenRequestType.REQUEST_CLIENT_DATA_REQUEST);
|
||||
|
||||
|
||||
@NonNull
|
||||
static ClientTokenRequest newIOSClientTokenRequest(String deviceId) {
|
||||
Logger.printInfo(() -> "Creating new iOS client token request with device ID: " + deviceId);
|
||||
|
||||
return IOS_CLIENT_TOKEN_REQUEST
|
||||
.setClientData(IOS_CLIENT_DATA_REQUEST
|
||||
.setConnectivitySdkData(IOS_CONNECTIVITY_SDK_DATA
|
||||
.setDeviceId(deviceId)
|
||||
)
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static ClientTokenResponse getClientTokenResponse(@NonNull ClientTokenRequest request) {
|
||||
if (request.getRequestType() == ClientTokenRequestType.REQUEST_CLIENT_DATA_REQUEST) {
|
||||
Logger.printInfo(() -> "Requesting iOS client token");
|
||||
String deviceId = request.getClientData().getConnectivitySdkData().getDeviceId();
|
||||
request = newIOSClientTokenRequest(deviceId);
|
||||
}
|
||||
|
||||
ClientTokenResponse response;
|
||||
try {
|
||||
response = requestClientToken(request);
|
||||
} catch (IOException ex) {
|
||||
Logger.printException(() -> "Failed to handle request", ex);
|
||||
return null;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static ClientTokenResponse requestClientToken(@NonNull ClientTokenRequest request) throws IOException {
|
||||
HttpURLConnection urlConnection = (HttpURLConnection) new URL(CLIENT_TOKEN_API_URL).openConnection();
|
||||
urlConnection.setRequestMethod("POST");
|
||||
urlConnection.setDoOutput(true);
|
||||
urlConnection.setRequestProperty("Content-Type", "application/x-protobuf");
|
||||
urlConnection.setRequestProperty("Accept", "application/x-protobuf");
|
||||
urlConnection.setRequestProperty("User-Agent", IOS_USER_AGENT);
|
||||
|
||||
byte[] requestArray = request.toByteArray();
|
||||
urlConnection.setFixedLengthStreamingMode(requestArray.length);
|
||||
urlConnection.getOutputStream().write(requestArray);
|
||||
|
||||
try (InputStream inputStream = urlConnection.getInputStream()) {
|
||||
return ClientTokenResponse.parseFrom(inputStream);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static ClientTokenResponse serveClientTokenRequest(@NonNull InputStream inputStream) {
|
||||
ClientTokenRequest request;
|
||||
try {
|
||||
request = ClientTokenRequest.parseFrom(inputStream);
|
||||
} catch (IOException ex) {
|
||||
Logger.printException(() -> "Failed to parse request from input stream", ex);
|
||||
return null;
|
||||
}
|
||||
Logger.printInfo(() -> "Request of type: " + request.getRequestType());
|
||||
|
||||
ClientTokenResponse response = getClientTokenResponse(request);
|
||||
if (response != null) Logger.printInfo(() -> "Response of type: " + response.getResponseType());
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package app.revanced.extension.spotify.misc.fix;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
class Constants {
|
||||
static final String CLIENT_TOKEN_API_PATH = "/v1/clienttoken";
|
||||
static final String CLIENT_TOKEN_API_URL = "https://clienttoken.spotify.com" + CLIENT_TOKEN_API_PATH;
|
||||
|
||||
// Modified by a patch. Do not touch.
|
||||
@NonNull
|
||||
static String getClientVersion() {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Modified by a patch. Do not touch.
|
||||
@NonNull
|
||||
static String getSystemVersion() {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Modified by a patch. Do not touch.
|
||||
@NonNull
|
||||
static String getHardwareMachine() {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
package app.revanced.extension.spotify.misc.fix;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.spotify.login5.v4.proto.Login5.*;
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.MessageLite;
|
||||
import fi.iki.elonen.NanoHTTPD;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Objects;
|
||||
|
||||
import static app.revanced.extension.spotify.misc.fix.Session.FAILED_TO_RENEW_SESSION;
|
||||
import static fi.iki.elonen.NanoHTTPD.Response.Status.INTERNAL_ERROR;
|
||||
|
||||
class LoginRequestListener extends NanoHTTPD {
|
||||
LoginRequestListener(int port) {
|
||||
super(port);
|
||||
|
||||
try {
|
||||
start();
|
||||
} catch (IOException ex) {
|
||||
Logger.printException(() -> "Failed to start login request listener on port " + port, ex);
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Response serve(IHTTPSession request) {
|
||||
Logger.printInfo(() -> "Serving request for URI: " + request.getUri());
|
||||
|
||||
InputStream requestBodyInputStream = getRequestBodyInputStream(request);
|
||||
|
||||
LoginRequest loginRequest;
|
||||
try {
|
||||
loginRequest = LoginRequest.parseFrom(requestBodyInputStream);
|
||||
} catch (IOException ex) {
|
||||
Logger.printException(() -> "Failed to parse LoginRequest", ex);
|
||||
return newResponse(INTERNAL_ERROR);
|
||||
}
|
||||
|
||||
MessageLite loginResponse;
|
||||
|
||||
// A request may be made concurrently by Spotify,
|
||||
// however a webview can only handle one request at a time due to singleton cookie manager.
|
||||
// Therefore, synchronize to ensure that only one webview handles the request at a time.
|
||||
synchronized (this) {
|
||||
try {
|
||||
loginResponse = getLoginResponse(loginRequest);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "Failed to get login response", ex);
|
||||
return newResponse(INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
return newResponse(Response.Status.OK, loginResponse);
|
||||
}
|
||||
|
||||
|
||||
private static LoginResponse getLoginResponse(@NonNull LoginRequest loginRequest) {
|
||||
Session session;
|
||||
|
||||
if (!loginRequest.hasStoredCredential()) {
|
||||
Logger.printInfo(() -> "Received request for initial login");
|
||||
session = WebApp.currentSession; // Session obtained from WebApp.launchLogin, can be null if still in progress.
|
||||
} else {
|
||||
Logger.printInfo(() -> "Received request to restore saved session");
|
||||
session = Session.read(loginRequest.getStoredCredential().getUsername());
|
||||
}
|
||||
|
||||
return toLoginResponse(session);
|
||||
}
|
||||
|
||||
private static LoginResponse toLoginResponse(@Nullable Session session) {
|
||||
LoginResponse.Builder builder = LoginResponse.newBuilder();
|
||||
|
||||
if (session == null) {
|
||||
Logger.printException(() -> "Session is null. An initial login may still be in progress, returning try again later error");
|
||||
builder.setError(LoginError.TRY_AGAIN_LATER);
|
||||
} else if (session.accessTokenExpired()) {
|
||||
Logger.printInfo(() -> "Access token expired, renewing session");
|
||||
WebApp.renewSessionBlocking(session.cookies);
|
||||
return toLoginResponse(WebApp.currentSession);
|
||||
} else if (session.username == null) {
|
||||
Logger.printException(() -> "Session username is null, likely caused by invalid cookies, returning invalid credentials error");
|
||||
session.delete();
|
||||
builder.setError(LoginError.INVALID_CREDENTIALS);
|
||||
} else if (session == FAILED_TO_RENEW_SESSION) {
|
||||
Logger.printException(() -> "Failed to renew session, likely caused by a timeout, returning try again later error");
|
||||
builder.setError(LoginError.TRY_AGAIN_LATER);
|
||||
} else {
|
||||
session.save();
|
||||
Logger.printInfo(() -> "Returning session for username: " + session.username);
|
||||
builder.setOk(LoginOk.newBuilder()
|
||||
.setUsername(session.username)
|
||||
.setAccessToken(session.accessToken)
|
||||
.setStoredCredential(ByteString.fromHex("00")) // Placeholder, as it cannot be null or empty.
|
||||
.setAccessTokenExpiresIn(session.accessTokenExpiresInSeconds())
|
||||
.build());
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static InputStream limitedInputStream(InputStream inputStream, long contentLength) {
|
||||
return new FilterInputStream(inputStream) {
|
||||
private long remaining = contentLength;
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if (remaining <= 0) return -1;
|
||||
int result = super.read();
|
||||
if (result != -1) remaining--;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
if (remaining <= 0) return -1;
|
||||
len = (int) Math.min(len, remaining);
|
||||
int result = super.read(b, off, len);
|
||||
if (result != -1) remaining -= result;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static InputStream getRequestBodyInputStream(@NonNull IHTTPSession request) {
|
||||
long requestContentLength =
|
||||
Long.parseLong(Objects.requireNonNull(request.getHeaders().get("content-length")));
|
||||
return limitedInputStream(request.getInputStream(), requestContentLength);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
@NonNull
|
||||
private static Response newResponse(Response.Status status) {
|
||||
return newResponse(status, null);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static Response newResponse(Response.IStatus status, MessageLite messageLite) {
|
||||
if (messageLite == null) {
|
||||
return newFixedLengthResponse(status, "application/x-protobuf", null);
|
||||
}
|
||||
|
||||
byte[] messageBytes = messageLite.toByteArray();
|
||||
InputStream stream = new ByteArrayInputStream(messageBytes);
|
||||
return newFixedLengthResponse(status, "application/x-protobuf", stream, messageBytes.length);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package app.revanced.extension.spotify.misc.fix;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.spotify.misc.fix.clienttoken.data.v0.ClienttokenHttp.ClientTokenResponse;
|
||||
import com.google.protobuf.MessageLite;
|
||||
import fi.iki.elonen.NanoHTTPD;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Objects;
|
||||
|
||||
import static app.revanced.extension.spotify.misc.fix.ClientTokenService.serveClientTokenRequest;
|
||||
import static app.revanced.extension.spotify.misc.fix.Constants.CLIENT_TOKEN_API_PATH;
|
||||
import static fi.iki.elonen.NanoHTTPD.Response.Status.INTERNAL_ERROR;
|
||||
|
||||
class RequestListener extends NanoHTTPD {
|
||||
RequestListener(int port) {
|
||||
super(port);
|
||||
|
||||
try {
|
||||
start();
|
||||
} catch (IOException ex) {
|
||||
Logger.printException(() -> "Failed to start request listener on port " + port, ex);
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Response serve(@NonNull IHTTPSession session) {
|
||||
String uri = session.getUri();
|
||||
if (!uri.equals(CLIENT_TOKEN_API_PATH)) return INTERNAL_ERROR_RESPONSE;
|
||||
|
||||
Logger.printInfo(() -> "Serving request for URI: " + uri);
|
||||
|
||||
ClientTokenResponse response = serveClientTokenRequest(getInputStream(session));
|
||||
if (response != null) return newResponse(Response.Status.OK, response);
|
||||
|
||||
Logger.printException(() -> "Failed to serve client token request");
|
||||
return INTERNAL_ERROR_RESPONSE;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static InputStream newLimitedInputStream(InputStream inputStream, long contentLength) {
|
||||
return new FilterInputStream(inputStream) {
|
||||
private long remaining = contentLength;
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if (remaining <= 0) return -1;
|
||||
int result = super.read();
|
||||
if (result != -1) remaining--;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
if (remaining <= 0) return -1;
|
||||
len = (int) Math.min(len, remaining);
|
||||
int result = super.read(b, off, len);
|
||||
if (result != -1) remaining -= result;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static InputStream getInputStream(@NonNull IHTTPSession session) {
|
||||
long requestContentLength = Long.parseLong(Objects.requireNonNull(session.getHeaders().get("content-length")));
|
||||
return newLimitedInputStream(session.getInputStream(), requestContentLength);
|
||||
}
|
||||
|
||||
private static final Response INTERNAL_ERROR_RESPONSE = newResponse(INTERNAL_ERROR);
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
@NonNull
|
||||
private static Response newResponse(Response.Status status) {
|
||||
return newResponse(status, null);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static Response newResponse(Response.IStatus status, MessageLite messageLite) {
|
||||
if (messageLite == null) {
|
||||
return newFixedLengthResponse(status, "application/x-protobuf", null);
|
||||
}
|
||||
|
||||
byte[] messageBytes = messageLite.toByteArray();
|
||||
InputStream stream = new ByteArrayInputStream(messageBytes);
|
||||
return newFixedLengthResponse(status, "application/x-protobuf", stream, messageBytes.length);
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
package app.revanced.extension.spotify.misc.fix;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import static android.content.Context.MODE_PRIVATE;
|
||||
|
||||
class Session {
|
||||
/**
|
||||
* Username of the account. Null if this session does not have an authenticated user.
|
||||
*/
|
||||
@Nullable
|
||||
final String username;
|
||||
/**
|
||||
* Access token for this session.
|
||||
*/
|
||||
final String accessToken;
|
||||
/**
|
||||
* Session expiration timestamp in milliseconds.
|
||||
*/
|
||||
final Long expirationTime;
|
||||
/**
|
||||
* Authentication cookies for this session.
|
||||
*/
|
||||
final String cookies;
|
||||
|
||||
/**
|
||||
* Session that represents a failed attempt to renew the session.
|
||||
*/
|
||||
static final Session FAILED_TO_RENEW_SESSION = new Session("", "", "");
|
||||
|
||||
/**
|
||||
* @param username Username of the account. Empty if this session does not have an authenticated user.
|
||||
* @param accessToken Access token for this session.
|
||||
* @param cookies Authentication cookies for this session.
|
||||
*/
|
||||
Session(@Nullable String username, String accessToken, String cookies) {
|
||||
this(username, accessToken, System.currentTimeMillis() + 60 * 60 * 1000, cookies);
|
||||
}
|
||||
|
||||
private Session(@Nullable String username, String accessToken, long expirationTime, String cookies) {
|
||||
this.username = username;
|
||||
this.accessToken = accessToken;
|
||||
this.expirationTime = expirationTime;
|
||||
this.cookies = cookies;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The number of milliseconds until the access token expires.
|
||||
*/
|
||||
long accessTokenExpiresInMillis() {
|
||||
long currentTime = System.currentTimeMillis();
|
||||
return expirationTime - currentTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The number of seconds until the access token expires.
|
||||
*/
|
||||
int accessTokenExpiresInSeconds() {
|
||||
return (int) accessTokenExpiresInMillis() / 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if the access token has expired, false otherwise.
|
||||
*/
|
||||
boolean accessTokenExpired() {
|
||||
return accessTokenExpiresInMillis() <= 0;
|
||||
}
|
||||
|
||||
void save() {
|
||||
Logger.printInfo(() -> "Saving session: " + this);
|
||||
|
||||
SharedPreferences.Editor editor = Utils.getContext().getSharedPreferences("revanced", MODE_PRIVATE).edit();
|
||||
|
||||
String json;
|
||||
try {
|
||||
json = new JSONObject()
|
||||
.put("accessToken", accessToken)
|
||||
.put("expirationTime", expirationTime)
|
||||
.put("cookies", cookies).toString();
|
||||
} catch (JSONException ex) {
|
||||
Logger.printException(() -> "Failed to convert session to stored credential", ex);
|
||||
return;
|
||||
}
|
||||
|
||||
editor.putString("session_" + username, json);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
void delete() {
|
||||
Logger.printInfo(() -> "Deleting saved session for username: " + username);
|
||||
SharedPreferences.Editor editor = Utils.getContext().getSharedPreferences("revanced", MODE_PRIVATE).edit();
|
||||
editor.remove("session_" + username);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static Session read(String username) {
|
||||
Logger.printInfo(() -> "Reading saved session for username: " + username);
|
||||
|
||||
SharedPreferences sharedPreferences = Utils.getContext().getSharedPreferences("revanced", MODE_PRIVATE);
|
||||
String savedJson = sharedPreferences.getString("session_" + username, null);
|
||||
if (savedJson == null) {
|
||||
Logger.printInfo(() -> "No session found in shared preferences");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
JSONObject json = new JSONObject(savedJson);
|
||||
String accessToken = json.getString("accessToken");
|
||||
long expirationTime = json.getLong("expirationTime");
|
||||
String cookies = json.getString("cookies");
|
||||
|
||||
return new Session(username, accessToken, expirationTime, cookies);
|
||||
} catch (JSONException ex) {
|
||||
Logger.printException(() -> "Failed to read session from shared preferences", ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Session(" +
|
||||
"username=" + username +
|
||||
", accessToken=" + accessToken +
|
||||
", expirationTime=" + expirationTime +
|
||||
", cookies=" + cookies +
|
||||
')';
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,15 @@
|
||||
package app.revanced.extension.spotify.misc.fix;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class SpoofClientPatch {
|
||||
private static LoginRequestListener listener;
|
||||
private static RequestListener listener;
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* <br>
|
||||
* Launch login server.
|
||||
* Injection point. Launch requests listener server.
|
||||
*/
|
||||
public static void launchListener(int port) {
|
||||
public synchronized static void launchListener(int port) {
|
||||
if (listener != null) {
|
||||
Logger.printInfo(() -> "Listener already running on port " + port);
|
||||
return;
|
||||
@@ -21,34 +17,9 @@ public class SpoofClientPatch {
|
||||
|
||||
try {
|
||||
Logger.printInfo(() -> "Launching listener on port " + port);
|
||||
listener = new LoginRequestListener(port);
|
||||
listener = new RequestListener(port);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "launchListener failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* <br>
|
||||
* Launch login web view.
|
||||
*/
|
||||
public static void launchLogin(LayoutInflater inflater) {
|
||||
try {
|
||||
WebApp.launchLogin(inflater.getContext());
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "launchLogin failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* <br>
|
||||
* Set handler to call the native login after the webview login.
|
||||
*/
|
||||
public static void setNativeLoginHandler(View startLoginButton) {
|
||||
WebApp.nativeLoginHandler = (() -> {
|
||||
startLoginButton.setSoundEffectsEnabled(false);
|
||||
startLoginButton.performClick();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,297 +0,0 @@
|
||||
package app.revanced.extension.spotify.misc.fix;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Build;
|
||||
import android.view.Window;
|
||||
import android.view.WindowInsets;
|
||||
import android.webkit.*;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.spotify.UserAgent;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static app.revanced.extension.spotify.misc.fix.Session.FAILED_TO_RENEW_SESSION;
|
||||
|
||||
class WebApp {
|
||||
private static final String OPEN_SPOTIFY_COM = "open.spotify.com";
|
||||
private static final String OPEN_SPOTIFY_COM_URL = "https://" + OPEN_SPOTIFY_COM;
|
||||
private static final String OPEN_SPOTIFY_COM_PREFERENCES_URL = OPEN_SPOTIFY_COM_URL + "/preferences";
|
||||
private static final String ACCOUNTS_SPOTIFY_COM_LOGIN_URL = "https://accounts.spotify.com/login?allow_password=1"
|
||||
+ "&continue=https%3A%2F%2Fopen.spotify.com%2Fpreferences";
|
||||
|
||||
private static final int GET_SESSION_TIMEOUT_SECONDS = 10;
|
||||
private static final String JAVASCRIPT_INTERFACE_NAME = "androidInterface";
|
||||
private static final String USER_AGENT = getWebUserAgent();
|
||||
|
||||
/**
|
||||
* A session obtained from the webview after logging in.
|
||||
*/
|
||||
@Nullable
|
||||
static volatile Session currentSession = null;
|
||||
|
||||
/**
|
||||
* Current webview in use. Any use of the object must be done on the main thread.
|
||||
*/
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private static volatile WebView currentWebView;
|
||||
|
||||
interface NativeLoginHandler {
|
||||
void login();
|
||||
}
|
||||
|
||||
static NativeLoginHandler nativeLoginHandler;
|
||||
|
||||
static void launchLogin(Context context) {
|
||||
final Dialog dialog = newDialog(context);
|
||||
|
||||
Utils.runOnBackgroundThread(() -> {
|
||||
Logger.printInfo(() -> "Launching login");
|
||||
|
||||
// A session must be obtained from a login. Repeat until a session is acquired.
|
||||
boolean isAcquired = false;
|
||||
do {
|
||||
CountDownLatch onLoggedInLatch = new CountDownLatch(1);
|
||||
CountDownLatch getSessionLatch = new CountDownLatch(1);
|
||||
|
||||
// Can't use Utils.getContext() here, because autofill won't work.
|
||||
// See https://stackoverflow.com/a/79182053/11213244.
|
||||
launchWebView(context, ACCOUNTS_SPOTIFY_COM_LOGIN_URL, new WebViewCallback() {
|
||||
@Override
|
||||
void onInitialized(WebView webView) {
|
||||
super.onInitialized(webView);
|
||||
|
||||
dialog.setContentView(webView);
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
void onLoggedIn(String cookies) {
|
||||
onLoggedInLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
void onReceivedSession(Session session) {
|
||||
super.onReceivedSession(session);
|
||||
|
||||
getSessionLatch.countDown();
|
||||
dialog.dismiss();
|
||||
|
||||
try {
|
||||
nativeLoginHandler.login();
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "nativeLoginHandler failure", ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
// Wait indefinitely until the user logs in.
|
||||
onLoggedInLatch.await();
|
||||
// Wait until the session is received, or timeout.
|
||||
isAcquired = getSessionLatch.await(GET_SESSION_TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException ex) {
|
||||
Logger.printException(() -> "Login interrupted", ex);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
} while (!isAcquired);
|
||||
});
|
||||
}
|
||||
|
||||
static void renewSessionBlocking(String cookies) {
|
||||
Logger.printInfo(() -> "Renewing session with cookies: " + cookies);
|
||||
|
||||
CountDownLatch getSessionLatch = new CountDownLatch(1);
|
||||
|
||||
launchWebView(Utils.getContext(), OPEN_SPOTIFY_COM_PREFERENCES_URL, new WebViewCallback() {
|
||||
@Override
|
||||
public void onInitialized(WebView webView) {
|
||||
setCookies(cookies);
|
||||
super.onInitialized(webView);
|
||||
}
|
||||
|
||||
public void onReceivedSession(Session session) {
|
||||
super.onReceivedSession(session);
|
||||
getSessionLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
boolean isAcquired = false;
|
||||
try {
|
||||
isAcquired = getSessionLatch.await(GET_SESSION_TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException ex) {
|
||||
Logger.printException(() -> "Session renewal interrupted", ex);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
if (!isAcquired) {
|
||||
Logger.printException(() -> "Failed to retrieve session within " + GET_SESSION_TIMEOUT_SECONDS + " seconds");
|
||||
currentSession = FAILED_TO_RENEW_SESSION;
|
||||
destructWebView();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* All methods are called on the main thread.
|
||||
*/
|
||||
abstract static class WebViewCallback {
|
||||
void onInitialized(WebView webView) {
|
||||
currentWebView = webView;
|
||||
currentSession = null; // Reset current session.
|
||||
}
|
||||
|
||||
void onLoggedIn(String cookies) {
|
||||
}
|
||||
|
||||
void onReceivedSession(Session session) {
|
||||
Logger.printInfo(() -> "Received session: " + session);
|
||||
currentSession = session;
|
||||
|
||||
destructWebView();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
private static void launchWebView(
|
||||
Context context,
|
||||
String initialUrl,
|
||||
WebViewCallback webViewCallback
|
||||
) {
|
||||
Utils.runOnMainThreadNowOrLater(() -> {
|
||||
WebView webView = new WebView(context);
|
||||
WebSettings settings = webView.getSettings();
|
||||
settings.setDomStorageEnabled(true);
|
||||
settings.setJavaScriptEnabled(true);
|
||||
settings.setUserAgentString(USER_AGENT);
|
||||
|
||||
// WebViewClient is always called off the main thread,
|
||||
// but callback interface methods are called on the main thread.
|
||||
webView.setWebViewClient(new WebViewClient() {
|
||||
@Override
|
||||
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
|
||||
if (OPEN_SPOTIFY_COM.equals(request.getUrl().getHost())) {
|
||||
Utils.runOnMainThread(() -> webViewCallback.onLoggedIn(getCurrentCookies()));
|
||||
}
|
||||
|
||||
return super.shouldInterceptRequest(view, request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
||||
Logger.printInfo(() -> "Page started loading: " + url);
|
||||
|
||||
if (!url.startsWith(OPEN_SPOTIFY_COM_URL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.printInfo(() -> "Evaluating script to get session on url: " + url);
|
||||
String getSessionScript = "Object.defineProperty(Object.prototype, \"_username\", {" +
|
||||
" configurable: true," +
|
||||
" set(username) {" +
|
||||
" accessToken = this._builder?.accessToken;" +
|
||||
" if (accessToken) {" +
|
||||
" " + JAVASCRIPT_INTERFACE_NAME + ".getSession(username, accessToken);" +
|
||||
" delete Object.prototype._username;" +
|
||||
" }" +
|
||||
" " +
|
||||
" Object.defineProperty(this, \"_username\", {" +
|
||||
" configurable: true," +
|
||||
" enumerable: true," +
|
||||
" writable: true," +
|
||||
" value: username" +
|
||||
" })" +
|
||||
" " +
|
||||
" }" +
|
||||
"});" +
|
||||
"if (new URLSearchParams(window.location.search).get('_authfailed') != null) {" +
|
||||
" " + JAVASCRIPT_INTERFACE_NAME + ".getSession(null, null);" +
|
||||
"}";
|
||||
|
||||
view.evaluateJavascript(getSessionScript, null);
|
||||
}
|
||||
});
|
||||
|
||||
webView.addJavascriptInterface(new Object() {
|
||||
@SuppressWarnings("unused")
|
||||
@JavascriptInterface
|
||||
public void getSession(String username, String accessToken) {
|
||||
Session session = new Session(username, accessToken, getCurrentCookies());
|
||||
Utils.runOnMainThread(() -> webViewCallback.onReceivedSession(session));
|
||||
}
|
||||
}, JAVASCRIPT_INTERFACE_NAME);
|
||||
|
||||
CookieManager.getInstance().removeAllCookies((anyRemoved) -> {
|
||||
Logger.printInfo(() -> "Loading URL: " + initialUrl);
|
||||
webView.loadUrl(initialUrl);
|
||||
|
||||
Logger.printInfo(() -> "WebView initialized with user agent: " + USER_AGENT);
|
||||
webViewCallback.onInitialized(webView);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static void destructWebView() {
|
||||
Utils.runOnMainThreadNowOrLater(() -> {
|
||||
currentWebView.stopLoading();
|
||||
currentWebView.destroy();
|
||||
currentWebView = null;
|
||||
});
|
||||
}
|
||||
|
||||
private static String getWebUserAgent() {
|
||||
String userAgentString = WebSettings.getDefaultUserAgent(Utils.getContext());
|
||||
try {
|
||||
return new UserAgent(userAgentString)
|
||||
.withCommentReplaced("Android", "Windows NT 10.0; Win64; x64")
|
||||
.withoutProduct("Mobile")
|
||||
.toString();
|
||||
} catch (IllegalArgumentException ex) {
|
||||
userAgentString = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edge/137.0.0.0";
|
||||
String fallback = userAgentString;
|
||||
Logger.printException(() -> "Failed to get user agent, falling back to " + fallback, ex);
|
||||
}
|
||||
|
||||
return userAgentString;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static Dialog newDialog(Context context) {
|
||||
Dialog dialog = new Dialog(context, android.R.style.Theme_Black_NoTitleBar_Fullscreen);
|
||||
dialog.setCancelable(false);
|
||||
|
||||
// Ensure that the keyboard does not cover the webview content.
|
||||
Window window = dialog.getWindow();
|
||||
//noinspection StatementWithEmptyBody
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
window.getDecorView().setOnApplyWindowInsetsListener((v, insets) -> {
|
||||
v.setPadding(0, 0, 0, insets.getInsets(WindowInsets.Type.ime()).bottom);
|
||||
|
||||
return WindowInsets.CONSUMED;
|
||||
});
|
||||
} else {
|
||||
// TODO: Implement for lower Android versions.
|
||||
}
|
||||
return dialog;
|
||||
}
|
||||
|
||||
private static String getCurrentCookies() {
|
||||
CookieManager cookieManager = CookieManager.getInstance();
|
||||
return cookieManager.getCookie(OPEN_SPOTIFY_COM_URL);
|
||||
}
|
||||
|
||||
private static void setCookies(@NonNull String cookies) {
|
||||
CookieManager cookieManager = CookieManager.getInstance();
|
||||
|
||||
String[] cookiesList = cookies.split(";");
|
||||
for (String cookie : cookiesList) {
|
||||
cookieManager.setCookie(OPEN_SPOTIFY_COM_URL, cookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package spotify.clienttoken.data.v0;
|
||||
|
||||
option optimize_for = LITE_RUNTIME;
|
||||
option java_package = "app.revanced.extension.spotify.misc.fix.clienttoken.data.v0";
|
||||
|
||||
message ClientTokenRequest {
|
||||
ClientTokenRequestType request_type = 1;
|
||||
|
||||
oneof request {
|
||||
ClientDataRequest client_data = 2;
|
||||
}
|
||||
}
|
||||
|
||||
enum ClientTokenRequestType {
|
||||
REQUEST_UNKNOWN = 0;
|
||||
REQUEST_CLIENT_DATA_REQUEST = 1;
|
||||
REQUEST_CHALLENGE_ANSWERS_REQUEST = 2;
|
||||
}
|
||||
|
||||
message ClientDataRequest {
|
||||
string client_version = 1;
|
||||
string client_id = 2;
|
||||
|
||||
oneof data {
|
||||
ConnectivitySdkData connectivity_sdk_data = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message ConnectivitySdkData {
|
||||
PlatformSpecificData platform_specific_data = 1;
|
||||
string device_id = 2;
|
||||
}
|
||||
|
||||
message PlatformSpecificData {
|
||||
oneof data {
|
||||
NativeIOSData ios = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message NativeIOSData {
|
||||
int32 user_interface_idiom = 1;
|
||||
bool target_iphone_simulator = 2;
|
||||
string hw_machine = 3;
|
||||
string system_version = 4;
|
||||
string simulator_model_identifier = 5;
|
||||
}
|
||||
|
||||
message ClientTokenResponse {
|
||||
ClientTokenResponseType response_type = 1;
|
||||
|
||||
oneof response {
|
||||
GrantedTokenResponse granted_token = 2;
|
||||
}
|
||||
}
|
||||
|
||||
enum ClientTokenResponseType {
|
||||
RESPONSE_UNKNOWN = 0;
|
||||
RESPONSE_GRANTED_TOKEN_RESPONSE = 1;
|
||||
RESPONSE_CHALLENGES_RESPONSE = 2;
|
||||
}
|
||||
|
||||
message GrantedTokenResponse {
|
||||
string token = 1;
|
||||
int32 expires_after_seconds = 2;
|
||||
int32 refresh_after_seconds = 3;
|
||||
repeated TokenDomain domains = 4;
|
||||
}
|
||||
|
||||
message TokenDomain {
|
||||
string domain = 1;
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package spotify.login5.v4;
|
||||
|
||||
option optimize_for = LITE_RUNTIME;
|
||||
option java_package = "app.revanced.extension.spotify.login5.v4.proto";
|
||||
|
||||
message StoredCredential {
|
||||
string username = 1;
|
||||
bytes data = 2;
|
||||
}
|
||||
|
||||
message LoginRequest {
|
||||
oneof login_method {
|
||||
StoredCredential stored_credential = 100;
|
||||
}
|
||||
}
|
||||
|
||||
message LoginOk {
|
||||
string username = 1;
|
||||
string access_token = 2;
|
||||
bytes stored_credential = 3;
|
||||
int32 access_token_expires_in = 4;
|
||||
}
|
||||
|
||||
message LoginResponse {
|
||||
oneof response {
|
||||
LoginOk ok = 1;
|
||||
LoginError error = 2;
|
||||
}
|
||||
}
|
||||
|
||||
enum LoginError {
|
||||
UNKNOWN_ERROR = 0;
|
||||
INVALID_CREDENTIALS = 1;
|
||||
BAD_REQUEST = 2;
|
||||
UNSUPPORTED_LOGIN_PROTOCOL = 3;
|
||||
TIMEOUT = 4;
|
||||
UNKNOWN_IDENTIFIER = 5;
|
||||
TOO_MANY_ATTEMPTS = 6;
|
||||
INVALID_PHONENUMBER = 7;
|
||||
TRY_AGAIN_LATER = 8;
|
||||
}
|
||||
@@ -2,7 +2,5 @@ package com.spotify.browsita.v1.resolved;
|
||||
|
||||
public final class Section {
|
||||
public static final int BRAND_ADS_FIELD_NUMBER = 6;
|
||||
public static final int PROMOTION_V1_FIELD_NUMBER = 3;
|
||||
public static final int PROMOTION_V3_FIELD_NUMBER = 5;
|
||||
public int sectionTypeCase_;
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package com.spotify.useraccount.v1;
|
||||
|
||||
/**
|
||||
* Used for target 8.6.98.900. Class is still present in newer app targets.
|
||||
*/
|
||||
public class AccountAttribute {
|
||||
public Object value_;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
plugins {
|
||||
java
|
||||
antlr
|
||||
}
|
||||
|
||||
dependencies {
|
||||
antlr(libs.antlr4)
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
tasks {
|
||||
generateGrammarSource {
|
||||
arguments = listOf("-visitor")
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
grammar UserAgent;
|
||||
|
||||
@header { package app.revanced.extension.spotify; }
|
||||
|
||||
userAgent
|
||||
: product (WS product)* EOF
|
||||
;
|
||||
|
||||
product
|
||||
: name ('/' version)? (WS comment)?
|
||||
;
|
||||
|
||||
name
|
||||
: STRING
|
||||
;
|
||||
|
||||
version
|
||||
: STRING ('.' STRING)*
|
||||
;
|
||||
|
||||
comment
|
||||
: COMMENT
|
||||
;
|
||||
|
||||
COMMENT
|
||||
: '(' ~ ')'* ')'
|
||||
;
|
||||
|
||||
STRING
|
||||
: [a-zA-Z0-9]+
|
||||
;
|
||||
|
||||
WS
|
||||
: [ \r\n]+
|
||||
;
|
||||
@@ -1,60 +0,0 @@
|
||||
package app.revanced.extension.spotify;
|
||||
|
||||
import org.antlr.v4.runtime.CharStream;
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.CommonTokenStream;
|
||||
import org.antlr.v4.runtime.TokenStreamRewriter;
|
||||
import org.antlr.v4.runtime.tree.ParseTreeWalker;
|
||||
|
||||
public class UserAgent {
|
||||
private final UserAgentParser.UserAgentContext tree;
|
||||
private final TokenStreamRewriter rewriter;
|
||||
private final ParseTreeWalker walker;
|
||||
|
||||
public UserAgent(String userAgentString) {
|
||||
CharStream input = CharStreams.fromString(userAgentString);
|
||||
UserAgentLexer lexer = new UserAgentLexer(input);
|
||||
CommonTokenStream tokens = new CommonTokenStream(lexer);
|
||||
|
||||
tree = new UserAgentParser(tokens).userAgent();
|
||||
walker = new ParseTreeWalker();
|
||||
rewriter = new TokenStreamRewriter(tokens);
|
||||
}
|
||||
|
||||
public UserAgent withoutProduct(String name) {
|
||||
walker.walk(new UserAgentBaseListener() {
|
||||
@Override
|
||||
public void exitProduct(UserAgentParser.ProductContext ctx) {
|
||||
if (!ctx.name().getText().contains(name)) return;
|
||||
|
||||
int startIndex = ctx.getStart().getTokenIndex();
|
||||
if (startIndex != 0) startIndex -= 1; // Also remove the preceding whitespace.
|
||||
|
||||
int stopIndex = ctx.getStop().getTokenIndex();
|
||||
|
||||
|
||||
rewriter.delete(startIndex, stopIndex);
|
||||
}
|
||||
}, tree);
|
||||
|
||||
return new UserAgent(rewriter.getText().trim());
|
||||
}
|
||||
|
||||
public UserAgent withCommentReplaced(String containing, String replacement) {
|
||||
walker.walk(new UserAgentBaseListener() {
|
||||
@Override
|
||||
public void exitComment(UserAgentParser.CommentContext ctx) {
|
||||
if (ctx.getText().contains(containing)) {
|
||||
rewriter.replace(ctx.getStart(), ctx.getStop(), "(" + replacement + ")");
|
||||
}
|
||||
}
|
||||
}, tree);
|
||||
|
||||
return new UserAgent(rewriter.getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return rewriter.getText();
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
package app.revanced.extension.youtube.patches;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
@@ -11,18 +14,21 @@ import app.revanced.extension.youtube.settings.Settings;
|
||||
public class ChangeHeaderPatch {
|
||||
|
||||
public enum HeaderLogo {
|
||||
DEFAULT(null),
|
||||
REGULAR("ytWordmarkHeader"),
|
||||
PREMIUM("ytPremiumWordmarkHeader"),
|
||||
REVANCED("revanced_header_logo"),
|
||||
REVANCED_MINIMAL("revanced_header_logo_minimal"),
|
||||
CUSTOM("custom_header");
|
||||
DEFAULT(null, null),
|
||||
REGULAR("ytWordmarkHeader", "yt_ringo2_wordmark_header"),
|
||||
PREMIUM("ytPremiumWordmarkHeader", "yt_ringo2_premium_wordmark_header"),
|
||||
REVANCED("revanced_header_logo", "revanced_header_logo"),
|
||||
REVANCED_MINIMAL("revanced_header_logo_minimal", "revanced_header_logo_minimal"),
|
||||
CUSTOM("custom_header", "custom_header");
|
||||
|
||||
@Nullable
|
||||
private final String resourceName;
|
||||
private final String attributeName;
|
||||
@Nullable
|
||||
private final String drawableName;
|
||||
|
||||
HeaderLogo(@Nullable String resourceName) {
|
||||
this.resourceName = resourceName;
|
||||
HeaderLogo(@Nullable String attributeName, @Nullable String drawableName) {
|
||||
this.attributeName = attributeName;
|
||||
this.drawableName = drawableName;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -30,24 +36,66 @@ public class ChangeHeaderPatch {
|
||||
*/
|
||||
@Nullable
|
||||
private Integer getAttributeId() {
|
||||
if (resourceName == null) {
|
||||
if (attributeName == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final int identifier = Utils.getResourceIdentifier(resourceName, "attr");
|
||||
// Identifier is zero if custom header setting was included in imported settings
|
||||
// and a custom image was not included during patching.
|
||||
return identifier == 0 ? null : identifier;
|
||||
final int identifier = Utils.getResourceIdentifier(attributeName, "attr");
|
||||
if (identifier == 0) {
|
||||
// Identifier is zero if custom header setting was included in imported settings
|
||||
// and a custom image was not included during patching.
|
||||
Logger.printDebug(() -> "Could not find attribute: " + drawableName);
|
||||
Settings.HEADER_LOGO.resetToDefault();
|
||||
return null;
|
||||
}
|
||||
|
||||
return identifier;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Drawable getDrawable() {
|
||||
if (drawableName == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String drawableFullName = drawableName + (Utils.isDarkModeEnabled()
|
||||
? "_dark"
|
||||
: "_light");
|
||||
|
||||
final int identifier = Utils.getResourceIdentifier(drawableFullName, "drawable");
|
||||
if (identifier == 0) {
|
||||
Logger.printDebug(() -> "Could not find drawable: " + drawableFullName);
|
||||
Settings.HEADER_LOGO.resetToDefault();
|
||||
return null;
|
||||
}
|
||||
return Utils.getContext().getDrawable(identifier);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static final Integer headerLogoResource = Settings.HEADER_LOGO.get().getAttributeId();
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static int getHeaderAttributeId(int original) {
|
||||
return Objects.requireNonNullElse(headerLogoResource, original);
|
||||
return Objects.requireNonNullElse(Settings.HEADER_LOGO.get().getAttributeId(), original);
|
||||
}
|
||||
|
||||
public static Drawable getDrawable(Drawable original) {
|
||||
Drawable logo = Settings.HEADER_LOGO.get().getDrawable();
|
||||
if (logo != null) {
|
||||
return logo;
|
||||
}
|
||||
|
||||
// TODO: If 'Hide Doodles' is enabled, this will force the regular logo regardless
|
||||
// what account the user has. This can be improved the next time a Doodle is
|
||||
// active and the attribute id is passed to this method so the correct
|
||||
// regular/premium logo is returned.
|
||||
logo = HeaderLogo.REGULAR.getDrawable();
|
||||
if (logo != null) {
|
||||
return logo;
|
||||
}
|
||||
|
||||
// Should never happen.
|
||||
Logger.printException(() -> "Could not find regular header logo resource");
|
||||
return original;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package app.revanced.extension.youtube.patches;
|
||||
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class DisableDoubleTapActionsPatch {
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*
|
||||
* @return If "should skip to chapter start" flag is set.
|
||||
*/
|
||||
public static boolean disableDoubleTapChapters(boolean original) {
|
||||
return original && !Settings.DISABLE_CHAPTER_SKIP_DOUBLE_TAP.get();
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,15 @@
|
||||
package app.revanced.extension.youtube.patches;
|
||||
|
||||
import static app.revanced.extension.youtube.settings.preference.ExternalDownloaderPreference.showDialogIfAppIsNotInstalled;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Objects;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.StringRef;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
@@ -36,7 +34,7 @@ public final class DownloadsPatch {
|
||||
*
|
||||
* Appears to always be called from the main thread.
|
||||
*/
|
||||
public static boolean inAppDownloadButtonOnClick(@NonNull String videoId) {
|
||||
public static boolean inAppDownloadButtonOnClick(String videoId) {
|
||||
try {
|
||||
if (!Settings.EXTERNAL_DOWNLOADER_ACTION_BUTTON.get()) {
|
||||
return false;
|
||||
@@ -48,6 +46,9 @@ public final class DownloadsPatch {
|
||||
boolean isActivityContext = true;
|
||||
if (context == null) {
|
||||
// Utils context is the application context, and not an activity context.
|
||||
//
|
||||
// Edit: This check may no longer be needed since YT can now
|
||||
// only be launched from the main Activity (embedded usage in other apps no longer works).
|
||||
context = Utils.getContext();
|
||||
isActivityContext = false;
|
||||
}
|
||||
@@ -64,8 +65,7 @@ public final class DownloadsPatch {
|
||||
* @param isActivityContext If the context parameter is for an Activity. If this is false, then
|
||||
* the downloader is opened as a new task (which forces YT to minimize).
|
||||
*/
|
||||
public static void launchExternalDownloader(@NonNull String videoId,
|
||||
@NonNull Context context, boolean isActivityContext) {
|
||||
public static void launchExternalDownloader(String videoId, Context context, boolean isActivityContext) {
|
||||
try {
|
||||
Objects.requireNonNull(videoId);
|
||||
Logger.printDebug(() -> "Launching external downloader with context: " + context);
|
||||
@@ -73,16 +73,8 @@ public final class DownloadsPatch {
|
||||
// Trim string to avoid any accidental whitespace.
|
||||
var downloaderPackageName = Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME.get().trim();
|
||||
|
||||
boolean packageEnabled = false;
|
||||
try {
|
||||
packageEnabled = context.getPackageManager().getApplicationInfo(downloaderPackageName, 0).enabled;
|
||||
} catch (PackageManager.NameNotFoundException error) {
|
||||
Logger.printDebug(() -> "External downloader could not be found: " + error);
|
||||
}
|
||||
|
||||
// If the package is not installed, show the toast
|
||||
if (!packageEnabled) {
|
||||
Utils.showToastLong(StringRef.str("revanced_external_downloader_not_installed_warning", downloaderPackageName));
|
||||
// If the package is not installed, show a dialog.
|
||||
if (showDialogIfAppIsNotInstalled(context, downloaderPackageName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,10 +59,11 @@ public final class AnnouncementsPatch {
|
||||
int id = Settings.ANNOUNCEMENT_LAST_ID.defaultValue;
|
||||
try {
|
||||
final var announcementIds = new JSONArray(jsonString);
|
||||
if (announcementIds.length() == 0) return true;
|
||||
|
||||
id = announcementIds.getJSONObject(0).getInt("id");
|
||||
|
||||
} catch (Throwable ex) {
|
||||
Logger.printException(() -> "Failed to parse announcement IDs", ex);
|
||||
Logger.printException(() -> "Failed to parse announcement ID", ex);
|
||||
}
|
||||
|
||||
// Do not show the announcement, if the last announcement id is the same as the current one.
|
||||
|
||||
@@ -10,8 +10,8 @@ import static app.revanced.extension.shared.requests.Route.Method.GET;
|
||||
|
||||
public class AnnouncementsRoutes {
|
||||
private static final String ANNOUNCEMENTS_PROVIDER = "https://api.revanced.app/v4";
|
||||
public static final Route GET_LATEST_ANNOUNCEMENT_IDS = new Route(GET, "/announcements/latest/id?tag=youtube");
|
||||
public static final Route GET_LATEST_ANNOUNCEMENTS = new Route(GET, "/announcements/latest?tag=youtube");
|
||||
public static final Route GET_LATEST_ANNOUNCEMENT_IDS = new Route(GET, "/announcements/latest/id?tag=\uD83C\uDF9E\uFE0F%20YouTube");
|
||||
public static final Route GET_LATEST_ANNOUNCEMENTS = new Route(GET, "/announcements/latest?tag=\uD83C\uDF9E\uFE0F%20YouTube");
|
||||
|
||||
private AnnouncementsRoutes() {
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package app.revanced.extension.youtube.patches.components;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.shared.PlayerType;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
final class CommentsFilter extends Filter {
|
||||
@@ -89,7 +90,9 @@ final class CommentsFilter extends Filter {
|
||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
if (matchedGroup == chipBar) {
|
||||
return aiCommentsSummary.check(protobufBufferArray).isFiltered();
|
||||
// Playlist sort button uses same components and must only filter if the player is opened.
|
||||
return PlayerType.getCurrent().isMaximizedOrFullscreen()
|
||||
&& aiCommentsSummary.check(protobufBufferArray).isFiltered();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -4,12 +4,14 @@ import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButt
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.youtube.StringTrieSearch;
|
||||
import app.revanced.extension.youtube.patches.ChangeHeaderPatch;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.shared.NavigationBar;
|
||||
import app.revanced.extension.youtube.shared.PlayerType;
|
||||
@@ -30,7 +32,7 @@ public final class LayoutComponentsFilter extends Filter {
|
||||
);
|
||||
|
||||
private final StringTrieSearch exceptions = new StringTrieSearch();
|
||||
private final StringFilterGroup inFeedSurvey;
|
||||
private final StringFilterGroup surveys;
|
||||
private final StringFilterGroup notifyMe;
|
||||
private final StringFilterGroup singleItemInformationPanel;
|
||||
private final StringFilterGroup expandableMetadata;
|
||||
@@ -108,8 +110,8 @@ public final class LayoutComponentsFilter extends Filter {
|
||||
"chip_bar"
|
||||
);
|
||||
|
||||
inFeedSurvey = new StringFilterGroup(
|
||||
Settings.HIDE_FEED_SURVEY,
|
||||
surveys = new StringFilterGroup(
|
||||
Settings.HIDE_SURVEYS,
|
||||
"in_feed_survey",
|
||||
"slimline_survey",
|
||||
"feed_nudge"
|
||||
@@ -264,7 +266,7 @@ public final class LayoutComponentsFilter extends Filter {
|
||||
|
||||
ticketShelf = new ByteArrayFilterGroup(
|
||||
Settings.HIDE_TICKET_SHELF,
|
||||
"ticket.eml"
|
||||
"ticket_item.eml"
|
||||
);
|
||||
|
||||
addPathCallbacks(
|
||||
@@ -284,17 +286,18 @@ public final class LayoutComponentsFilter extends Filter {
|
||||
forYouShelf,
|
||||
horizontalShelves,
|
||||
imageShelf,
|
||||
inFeedSurvey,
|
||||
infoPanel,
|
||||
latestPosts,
|
||||
medicalPanel,
|
||||
notifyMe,
|
||||
paidPromotion,
|
||||
playables,
|
||||
quickActions,
|
||||
relatedVideos,
|
||||
singleItemInformationPanel,
|
||||
subscribersCommunityGuidelines,
|
||||
subscriptionsChipBar,
|
||||
surveys,
|
||||
timedReactions,
|
||||
videoRecommendationLabels
|
||||
);
|
||||
@@ -314,7 +317,7 @@ public final class LayoutComponentsFilter extends Filter {
|
||||
|
||||
// The groups are excluded from the filter due to the exceptions list below.
|
||||
// Filter them separately here.
|
||||
if (matchedGroup == notifyMe || matchedGroup == inFeedSurvey || matchedGroup == expandableMetadata) {
|
||||
if (matchedGroup == notifyMe || matchedGroup == surveys || matchedGroup == expandableMetadata) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -436,13 +439,11 @@ public final class LayoutComponentsFilter extends Filter {
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
@Nullable
|
||||
public static Drawable hideYoodles(Drawable animatedYoodle) {
|
||||
if (HIDE_DOODLES_ENABLED) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return animatedYoodle;
|
||||
public static void setDoodleDrawable(ImageView imageView, Drawable original) {
|
||||
Drawable replacement = HIDE_DOODLES_ENABLED
|
||||
? ChangeHeaderPatch.getDrawable(original)
|
||||
: original;
|
||||
imageView.setImageDrawable(replacement);
|
||||
}
|
||||
|
||||
private static final boolean HIDE_SHOW_MORE_BUTTON_ENABLED = Settings.HIDE_SHOW_MORE_BUTTON.get();
|
||||
|
||||
@@ -5,7 +5,9 @@ import static app.revanced.extension.shared.Utils.getResourceIdentifier;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.util.TypedValue;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toolbar;
|
||||
@@ -24,12 +26,15 @@ import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFrag
|
||||
* This class is responsible for injecting our own fragment by replacing the LicenseActivity.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class LicenseActivityHook {
|
||||
public class LicenseActivityHook extends Activity {
|
||||
|
||||
private static int currentThemeValueOrdinal = -1; // Must initially be a non-valid enum ordinal value.
|
||||
|
||||
private static ViewGroup.LayoutParams toolbarLayoutParams;
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
public static SearchViewController searchViewController;
|
||||
|
||||
public static void setToolbarLayoutParams(Toolbar toolbar) {
|
||||
if (toolbarLayoutParams != null) {
|
||||
toolbar.setLayoutParams(toolbarLayoutParams);
|
||||
@@ -126,12 +131,13 @@ public class LicenseActivityHook {
|
||||
view -> view instanceof TextView);
|
||||
if (toolbarTextView != null) {
|
||||
toolbarTextView.setTextColor(Utils.getAppForegroundColor());
|
||||
toolbarTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
|
||||
}
|
||||
setToolbarLayoutParams(toolbar);
|
||||
|
||||
// Add Search Icon and EditText for ReVancedPreferenceFragment only.
|
||||
// Add Search bar only for ReVancedPreferenceFragment.
|
||||
if (fragment instanceof ReVancedPreferenceFragment) {
|
||||
SearchViewController.addSearchViewComponents(activity, toolbar, (ReVancedPreferenceFragment) fragment);
|
||||
searchViewController = SearchViewController.addSearchViewComponents(activity, toolbar, (ReVancedPreferenceFragment) fragment);
|
||||
}
|
||||
|
||||
toolBarParent.addView(toolbar, 0);
|
||||
@@ -166,4 +172,10 @@ public class LicenseActivityHook {
|
||||
Utils.setIsDarkModeEnabled(themeOrdinal == 1);
|
||||
}
|
||||
}
|
||||
|
||||
public static void handleConfigurationChanged(Activity activity, Configuration newConfig) {
|
||||
if (searchViewController != null) {
|
||||
searchViewController.handleOrientationChange(newConfig.orientation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.graphics.drawable.GradientDrawable;
|
||||
import android.util.Pair;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
@@ -51,6 +52,7 @@ public class SearchViewController {
|
||||
private final Deque<String> searchHistory;
|
||||
private final AutoCompleteTextView autoCompleteTextView;
|
||||
private final boolean showSettingsSearchHistory;
|
||||
private int currentOrientation;
|
||||
|
||||
/**
|
||||
* Creates a background drawable for the SearchView with rounded corners.
|
||||
@@ -83,8 +85,8 @@ public class SearchViewController {
|
||||
/**
|
||||
* Adds search view components to the activity.
|
||||
*/
|
||||
public static void addSearchViewComponents(Activity activity, Toolbar toolbar, ReVancedPreferenceFragment fragment) {
|
||||
new SearchViewController(activity, toolbar, fragment);
|
||||
public static SearchViewController addSearchViewComponents(Activity activity, Toolbar toolbar, ReVancedPreferenceFragment fragment) {
|
||||
return new SearchViewController(activity, toolbar, fragment);
|
||||
}
|
||||
|
||||
private SearchViewController(Activity activity, Toolbar toolbar, ReVancedPreferenceFragment fragment) {
|
||||
@@ -93,6 +95,7 @@ public class SearchViewController {
|
||||
this.originalTitle = toolbar.getTitle();
|
||||
this.showSettingsSearchHistory = Settings.SETTINGS_SEARCH_HISTORY.get();
|
||||
this.searchHistory = new LinkedList<>();
|
||||
this.currentOrientation = activity.getResources().getConfiguration().orientation;
|
||||
StringSetting searchEntries = Settings.SETTINGS_SEARCH_ENTRIES;
|
||||
if (showSettingsSearchHistory) {
|
||||
String entries = searchEntries.get();
|
||||
@@ -115,6 +118,9 @@ public class SearchViewController {
|
||||
searchView.getContext().getResources().getIdentifier(
|
||||
"android:id/search_src_text", null, null));
|
||||
|
||||
// Disable fullscreen keyboard mode.
|
||||
autoCompleteTextView.setImeOptions(autoCompleteTextView.getImeOptions() | EditorInfo.IME_FLAG_NO_EXTRACT_UI);
|
||||
|
||||
// Set background and query hint.
|
||||
searchView.setBackground(createBackgroundDrawable(toolbar.getContext()));
|
||||
searchView.setQueryHint(str("revanced_settings_search_hint"));
|
||||
@@ -197,7 +203,7 @@ public class SearchViewController {
|
||||
if (isSearchActive) {
|
||||
closeSearch();
|
||||
} else {
|
||||
activity.onBackPressed();
|
||||
activity.finish();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "navigation click failure", ex);
|
||||
@@ -285,6 +291,16 @@ public class SearchViewController {
|
||||
}
|
||||
}
|
||||
|
||||
public void handleOrientationChange(int newOrientation) {
|
||||
if (newOrientation != currentOrientation) {
|
||||
currentOrientation = newOrientation;
|
||||
if (autoCompleteTextView != null) {
|
||||
autoCompleteTextView.dismissDropDown();
|
||||
Logger.printDebug(() -> "Orientation changed, search history dismissed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the search view and shows the keyboard.
|
||||
*/
|
||||
@@ -313,7 +329,7 @@ public class SearchViewController {
|
||||
/**
|
||||
* Closes the search view and hides the keyboard.
|
||||
*/
|
||||
private void closeSearch() {
|
||||
public void closeSearch() {
|
||||
isSearchActive = false;
|
||||
toolbar.getMenu().findItem(getResourceIdentifier(
|
||||
"action_search", "id")).setVisible(true);
|
||||
@@ -326,6 +342,19 @@ public class SearchViewController {
|
||||
imm.hideSoftInputFromWindow(searchView.getWindowToken(), 0);
|
||||
}
|
||||
|
||||
public static boolean handleBackPress() {
|
||||
if (LicenseActivityHook.searchViewController != null
|
||||
&& LicenseActivityHook.searchViewController.isSearchActive()) {
|
||||
LicenseActivityHook.searchViewController.closeSearch();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isSearchActive() {
|
||||
return isSearchActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom ArrayAdapter for search history.
|
||||
*/
|
||||
|
||||
@@ -100,7 +100,6 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting HIDE_CROWDFUNDING_BOX = new BooleanSetting("revanced_hide_crowdfunding_box", FALSE, true);
|
||||
public static final BooleanSetting HIDE_DOODLES = new BooleanSetting("revanced_hide_doodles", FALSE, true, "revanced_hide_doodles_user_dialog_message");
|
||||
public static final BooleanSetting HIDE_EXPANDABLE_CARD = new BooleanSetting("revanced_hide_expandable_card", TRUE);
|
||||
public static final BooleanSetting HIDE_FEED_SURVEY = new BooleanSetting("revanced_hide_feed_survey", TRUE);
|
||||
public static final BooleanSetting HIDE_FILTER_BAR_FEED_IN_FEED = new BooleanSetting("revanced_hide_filter_bar_feed_in_feed", FALSE, true);
|
||||
public static final BooleanSetting HIDE_FILTER_BAR_FEED_IN_HISTORY = new BooleanSetting("revanced_hide_filter_bar_feed_in_history", FALSE);
|
||||
public static final BooleanSetting HIDE_FILTER_BAR_FEED_IN_RELATED_VIDEOS = new BooleanSetting("revanced_hide_filter_bar_feed_in_related_videos", FALSE, true);
|
||||
@@ -113,6 +112,7 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting HIDE_NOTIFY_ME_BUTTON = new BooleanSetting("revanced_hide_notify_me_button", TRUE);
|
||||
public static final BooleanSetting HIDE_PLAYABLES = new BooleanSetting("revanced_hide_playables", TRUE);
|
||||
public static final BooleanSetting HIDE_SHOW_MORE_BUTTON = new BooleanSetting("revanced_hide_show_more_button", TRUE, true);
|
||||
public static final BooleanSetting HIDE_SURVEYS = new BooleanSetting("revanced_hide_surveys", TRUE);
|
||||
public static final BooleanSetting HIDE_TICKET_SHELF = new BooleanSetting("revanced_hide_ticket_shelf", FALSE);
|
||||
public static final BooleanSetting HIDE_VIDEO_RECOMMENDATION_LABELS = new BooleanSetting("revanced_hide_video_recommendation_labels", TRUE);
|
||||
|
||||
@@ -146,6 +146,7 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting COPY_VIDEO_URL = new BooleanSetting("revanced_copy_video_url", FALSE);
|
||||
public static final BooleanSetting COPY_VIDEO_URL_TIMESTAMP = new BooleanSetting("revanced_copy_video_url_timestamp", TRUE);
|
||||
public static final BooleanSetting DISABLE_AUTO_CAPTIONS = new BooleanSetting("revanced_disable_auto_captions", FALSE, true);
|
||||
public static final BooleanSetting DISABLE_CHAPTER_SKIP_DOUBLE_TAP = new BooleanSetting("revanced_disable_chapter_skip_double_tap", FALSE);
|
||||
public static final BooleanSetting DISABLE_FULLSCREEN_AMBIENT_MODE = new BooleanSetting("revanced_disable_fullscreen_ambient_mode", TRUE, true);
|
||||
public static final BooleanSetting DISABLE_ROLLING_NUMBER_ANIMATIONS = new BooleanSetting("revanced_disable_rolling_number_animations", FALSE);
|
||||
public static final EnumSetting<FullscreenMode> EXIT_FULLSCREEN = new EnumSetting<>("revanced_exit_fullscreen", FullscreenMode.DISABLED);
|
||||
@@ -190,7 +191,7 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting EXTERNAL_DOWNLOADER = new BooleanSetting("revanced_external_downloader", FALSE);
|
||||
public static final BooleanSetting EXTERNAL_DOWNLOADER_ACTION_BUTTON = new BooleanSetting("revanced_external_downloader_action_button", FALSE);
|
||||
public static final StringSetting EXTERNAL_DOWNLOADER_PACKAGE_NAME = new StringSetting("revanced_external_downloader_name",
|
||||
"org.schabi.newpipe" /* NewPipe */, parentsAny(EXTERNAL_DOWNLOADER, EXTERNAL_DOWNLOADER_ACTION_BUTTON));
|
||||
"com.deniscerri.ytdl" /* YTDLnis */, parentsAny(EXTERNAL_DOWNLOADER, EXTERNAL_DOWNLOADER_ACTION_BUTTON));
|
||||
|
||||
// Comments
|
||||
public static final BooleanSetting HIDE_COMMENTS_AI_CHAT_SUMMARY = new BooleanSetting("revanced_hide_comments_ai_chat_summary", FALSE);
|
||||
|
||||
@@ -16,10 +16,8 @@ import app.revanced.extension.youtube.settings.Settings;
|
||||
@SuppressWarnings({"unused", "deprecation"})
|
||||
public final class CustomVideoSpeedListPreference extends CustomDialogListPreference {
|
||||
|
||||
/**
|
||||
* Initialize a settings preference list with the available playback speeds.
|
||||
*/
|
||||
private void initializeEntryValues() {
|
||||
{
|
||||
// Initialize a settings preference list with the available playback speeds.
|
||||
float[] customPlaybackSpeeds = CustomPlaybackSpeedPatch.customPlaybackSpeeds;
|
||||
final int numberOfEntries = customPlaybackSpeeds.length + 1;
|
||||
String[] preferenceListEntries = new String[numberOfEntries];
|
||||
@@ -41,10 +39,6 @@ public final class CustomVideoSpeedListPreference extends CustomDialogListPrefer
|
||||
setEntryValues(preferenceListEntryValues);
|
||||
}
|
||||
|
||||
{
|
||||
initializeEntryValues();
|
||||
}
|
||||
|
||||
public CustomVideoSpeedListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,444 @@
|
||||
package app.revanced.extension.youtube.settings.preference;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.sf;
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.drawable.ShapeDrawable;
|
||||
import android.graphics.drawable.shapes.RoundRectShape;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Pair;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.ListView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.preference.CustomDialogListPreference;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
/**
|
||||
* A custom ListPreference for selecting an external downloader package with checkmarks and EditText for custom package names.
|
||||
*/
|
||||
@SuppressWarnings({"unused", "deprecation"})
|
||||
public class ExternalDownloaderPreference extends CustomDialogListPreference {
|
||||
|
||||
/**
|
||||
* Enum representing supported external downloaders with their display names, package names, and download URLs.
|
||||
*/
|
||||
private enum Downloader {
|
||||
YTDLNIS("YTDLnis",
|
||||
"com.deniscerri.ytdl",
|
||||
"https://ytdlnis.org",
|
||||
true),
|
||||
SEAL("Seal",
|
||||
"com.junkfood.seal",
|
||||
"https://github.com/JunkFood02/Seal/releases/latest",
|
||||
true),
|
||||
GRAYJAY("Grayjay",
|
||||
"com.futo.platformplayer",
|
||||
"https://grayjay.app"),
|
||||
LIBRETUBE("LibreTube",
|
||||
"com.github.libretube",
|
||||
"https://libretube.dev"),
|
||||
NEWPIPE("NewPipe",
|
||||
"org.schabi.newpipe",
|
||||
"https://newpipe.net"),
|
||||
PIPEPIPE("PipePipe",
|
||||
"InfinityLoop1309.NewPipeEnhanced",
|
||||
"https://pipepipe.dev"),
|
||||
TUBULAR("Tubular",
|
||||
"org.polymorphicshade.tubular",
|
||||
"https://github.com/polymorphicshade/Tubular/releases/latest"),
|
||||
OTHER(sf("revanced_external_downloader_other_item").toString(),
|
||||
null,
|
||||
null,
|
||||
true);
|
||||
|
||||
private static final Map<String, Downloader> PACKAGE_TO_ENUM = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (Downloader downloader : values()) {
|
||||
String packageName = downloader.packageName;
|
||||
if (packageName != null) {
|
||||
PACKAGE_TO_ENUM.put(packageName, downloader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a Downloader by its package name. This method can never return {@link #OTHER}.
|
||||
* @return The Downloader enum or null if not found.
|
||||
*/
|
||||
@Nullable
|
||||
public static Downloader findByPackageName(String packageName) {
|
||||
return PACKAGE_TO_ENUM.get(Objects.requireNonNull(packageName));
|
||||
}
|
||||
|
||||
public final String name;
|
||||
@Nullable
|
||||
public final String packageName;
|
||||
@Nullable
|
||||
public final String downloadUrl;
|
||||
/**
|
||||
* If a downloader app should be shown in the preference settings
|
||||
* if the app is not currently installed.
|
||||
*/
|
||||
public final boolean isPreferred;
|
||||
|
||||
Downloader(String name, String packageName, String downloadUrl) {
|
||||
this(name, packageName, downloadUrl, false);
|
||||
}
|
||||
|
||||
Downloader(String name, @Nullable String packageName, @Nullable String downloadUrl, boolean isPreferred) {
|
||||
this.name = name;
|
||||
this.packageName = packageName;
|
||||
this.downloadUrl = downloadUrl;
|
||||
this.isPreferred = isPreferred;
|
||||
}
|
||||
|
||||
public boolean isInstalled() {
|
||||
return packageName != null && isAppInstalledAndEnabled(packageName);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isAppInstalledAndEnabled(String packageName) {
|
||||
try {
|
||||
if (Utils.getContext().getPackageManager().getApplicationInfo(packageName, 0).enabled) {
|
||||
Logger.printDebug(() -> "App installed: " + packageName);
|
||||
return true;
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException error) {
|
||||
Logger.printDebug(() -> "App not installed: " + packageName);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private EditText editText;
|
||||
private CustomDialogListPreference.ListPreferenceArrayAdapter adapter;
|
||||
|
||||
public ExternalDownloaderPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
public ExternalDownloaderPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public ExternalDownloaderPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public ExternalDownloaderPreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
private void updateEntries() {
|
||||
List<CharSequence> entries = new ArrayList<>();
|
||||
List<CharSequence> entryValues = new ArrayList<>();
|
||||
|
||||
for (Downloader downloader : Downloader.values()) {
|
||||
if (downloader.isPreferred || downloader.isInstalled()) {
|
||||
String packageName = downloader.packageName;
|
||||
|
||||
entries.add(downloader.name);
|
||||
entryValues.add(packageName != null
|
||||
? packageName
|
||||
: Downloader.OTHER.name);
|
||||
}
|
||||
}
|
||||
|
||||
setEntries(entries.toArray(new CharSequence[0]));
|
||||
setEntryValues(entryValues.toArray(new CharSequence[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the summary for this ListPreference.
|
||||
*/
|
||||
@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.
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a custom dialog with a ListView for predefined downloader packages and EditText for custom package input.
|
||||
*/
|
||||
@Override
|
||||
protected void showDialog(@Nullable Bundle state) {
|
||||
// Must set entries before showing the dialog, to handle if
|
||||
// an app is installed while the settings are open in the background.
|
||||
updateEntries();
|
||||
|
||||
Context context = getContext();
|
||||
String packageName = Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME.get();
|
||||
|
||||
// Create the main layout for the dialog content.
|
||||
LinearLayout contentLayout = new LinearLayout(context);
|
||||
contentLayout.setOrientation(LinearLayout.VERTICAL);
|
||||
|
||||
// Create ListView for predefined downloader apps.
|
||||
ListView listView = new ListView(context);
|
||||
listView.setId(android.R.id.list);
|
||||
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
|
||||
|
||||
// Create custom adapter for the ListView.
|
||||
final boolean usingCustomDownloader = Downloader.findByPackageName(packageName) == null;
|
||||
adapter = new CustomDialogListPreference.ListPreferenceArrayAdapter(
|
||||
context,
|
||||
Utils.getResourceIdentifier("revanced_custom_list_item_checked", "layout"),
|
||||
getEntries(),
|
||||
getEntryValues(),
|
||||
usingCustomDownloader
|
||||
? Downloader.OTHER.name
|
||||
: packageName
|
||||
);
|
||||
listView.setAdapter(adapter);
|
||||
|
||||
Function<String, Void> updateListViewSelection = (updatedPackageName) -> {
|
||||
String entryValueName = Downloader.findByPackageName(updatedPackageName) == null
|
||||
? Downloader.OTHER.name
|
||||
: updatedPackageName;
|
||||
CharSequence[] entryValues = getEntryValues();
|
||||
|
||||
for (int i = 0, length = entryValues.length; i < length; i++) {
|
||||
String entryString = entryValues[i].toString();
|
||||
if (entryString.equals(entryValueName)) {
|
||||
listView.setItemChecked(i, true);
|
||||
listView.setSelection(i);
|
||||
adapter.setSelectedValue(entryString);
|
||||
adapter.notifyDataSetChanged();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
updateListViewSelection.apply(packageName);
|
||||
|
||||
// Handle item click to select value.
|
||||
listView.setOnItemClickListener((parent, view, position, id) -> {
|
||||
String selectedValue = getEntryValues()[position].toString();
|
||||
Downloader selectedApp = Downloader.findByPackageName(selectedValue);
|
||||
|
||||
if (selectedApp != null) {
|
||||
editText.setText(selectedApp.packageName);
|
||||
editText.setEnabled(false); // Disable editing for predefined options.
|
||||
} else {
|
||||
String savedPackageName = Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME.get();
|
||||
editText.setText(Downloader.findByPackageName(savedPackageName) == null
|
||||
? savedPackageName // If the user is clicking thru options then retain existing other app.
|
||||
: ""
|
||||
);
|
||||
editText.setEnabled(true); // Enable editing for Custom.
|
||||
editText.requestFocus();
|
||||
}
|
||||
editText.setSelection(editText.getText().length());
|
||||
adapter.setSelectedValue(selectedValue);
|
||||
adapter.notifyDataSetChanged();
|
||||
});
|
||||
|
||||
// Add ListView to content layout with initial height.
|
||||
LinearLayout.LayoutParams listViewParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
0 // Initial height, will be updated.
|
||||
);
|
||||
listViewParams.bottomMargin = dipToPixels(16);
|
||||
contentLayout.addView(listView, listViewParams);
|
||||
|
||||
// Add EditText for custom package name.
|
||||
editText = new EditText(context);
|
||||
editText.setText(packageName);
|
||||
editText.setSelection(packageName.length());
|
||||
editText.setHint(str("revanced_external_downloader_other_item_hint"));
|
||||
editText.setSingleLine(true); // Restrict EditText to a single line.
|
||||
editText.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
|
||||
// Set initial EditText state based on selected downloader.
|
||||
editText.setEnabled(usingCustomDownloader);
|
||||
editText.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable edit) {
|
||||
String updatedPackageName = edit.toString().trim();
|
||||
updateListViewSelection.apply(updatedPackageName);
|
||||
}
|
||||
});
|
||||
|
||||
ShapeDrawable editTextBackground = new ShapeDrawable(new RoundRectShape(
|
||||
Utils.createCornerRadii(10), null, null));
|
||||
editTextBackground.getPaint().setColor(Utils.getEditTextBackground());
|
||||
final int dip8 = dipToPixels(8);
|
||||
editText.setPadding(dip8, dip8, dip8, dip8);
|
||||
editText.setBackground(editTextBackground);
|
||||
editText.setClipToOutline(true);
|
||||
contentLayout.addView(editText);
|
||||
|
||||
// Create the custom dialog.
|
||||
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||
context,
|
||||
getTitle() != null ? getTitle().toString() : "",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
() -> {
|
||||
String newValue = editText.getText().toString().trim();
|
||||
if (newValue.isEmpty()) {
|
||||
// Show dialog if EditText is empty.
|
||||
Utils.createCustomDialog(
|
||||
context,
|
||||
str("revanced_external_downloader_name_title"),
|
||||
str("revanced_external_downloader_empty_warning"),
|
||||
null,
|
||||
null,
|
||||
() -> {}, // OK button does nothing (dismiss only).
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
false
|
||||
).first.show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (showDialogIfAppIsNotInstalled(getContext(), newValue)) {
|
||||
return; // Invalid package. Do not save.
|
||||
}
|
||||
|
||||
// Save custom package name.
|
||||
if (callChangeListener(newValue)) {
|
||||
setValue(newValue);
|
||||
}
|
||||
},
|
||||
() -> {}, // Cancel button action (dismiss only).
|
||||
str("revanced_settings_reset"),
|
||||
() -> { // Reset action.
|
||||
String defaultValue = Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME.defaultValue;
|
||||
editText.setText(defaultValue);
|
||||
editText.setSelection(defaultValue.length());
|
||||
editText.setEnabled(false); // Disable editing on reset.
|
||||
updateListViewSelection.apply(defaultValue);
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
// Add the content layout directly to the dialog's main layout.
|
||||
LinearLayout dialogMainLayout = dialogPair.second;
|
||||
dialogMainLayout.addView(contentLayout, dialogMainLayout.getChildCount() - 1);
|
||||
|
||||
// Update ListView height dynamically based on orientation.
|
||||
//noinspection ExtractMethodRecommender
|
||||
Runnable updateListViewHeight = () -> {
|
||||
int totalHeight = 0;
|
||||
ListAdapter listAdapter = listView.getAdapter();
|
||||
if (listAdapter != null) {
|
||||
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
||||
final int listAdapterCount = listAdapter.getCount();
|
||||
for (int i = 0; i < listAdapterCount; i++) {
|
||||
View item = listAdapter.getView(i, null, listView);
|
||||
item.measure(
|
||||
View.MeasureSpec.makeMeasureSpec(metrics.widthPixels, View.MeasureSpec.AT_MOST),
|
||||
View.MeasureSpec.UNSPECIFIED
|
||||
);
|
||||
totalHeight += item.getMeasuredHeight();
|
||||
}
|
||||
totalHeight += listView.getDividerHeight() * (listAdapterCount - 1);
|
||||
}
|
||||
|
||||
final int orientation = context.getResources().getConfiguration().orientation;
|
||||
if (orientation == android.content.res.Configuration.ORIENTATION_PORTRAIT) {
|
||||
// In portrait orientation, use WRAP_CONTENT for ListView height.
|
||||
listViewParams.height = LinearLayout.LayoutParams.WRAP_CONTENT;
|
||||
} else {
|
||||
// In landscape orientation, limit ListView height to 30% of screen height.
|
||||
final int maxHeight = Utils.percentageHeightToPixels(30);
|
||||
listViewParams.height = Math.min(totalHeight, maxHeight);
|
||||
}
|
||||
listView.setLayoutParams(listViewParams);
|
||||
};
|
||||
|
||||
// Initial height calculation.
|
||||
updateListViewHeight.run();
|
||||
|
||||
// Listen for configuration changes (e.g., orientation).
|
||||
View dialogView = dialogPair.second;
|
||||
// Recalculate height when layout changes (e.g., orientation change).
|
||||
dialogView.getViewTreeObserver().addOnGlobalLayoutListener(updateListViewHeight::run);
|
||||
|
||||
// Show the dialog.
|
||||
dialogPair.first.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If the app is not installed and a dialog was shown.
|
||||
*/
|
||||
public static boolean showDialogIfAppIsNotInstalled(Context context, String packageName) {
|
||||
if (isAppInstalledAndEnabled(packageName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Downloader downloader = Downloader.findByPackageName(packageName);
|
||||
String downloadUrl = downloader != null
|
||||
? downloader.downloadUrl
|
||||
: null;
|
||||
String okButtonText = downloadUrl != null
|
||||
? str("gms_core_dialog_open_website_text") // Open website.
|
||||
: null; // Ok.
|
||||
// Show a dialog if the recommended app is not installed or if the custom package cannot be found.
|
||||
String message = downloader != null
|
||||
? str("revanced_external_downloader_not_installed_warning", downloader.name)
|
||||
: str("revanced_external_downloader_package_not_found_warning", packageName);
|
||||
|
||||
Utils.createCustomDialog(
|
||||
context,
|
||||
str("revanced_external_downloader_not_found_title"),
|
||||
message,
|
||||
null,
|
||||
okButtonText,
|
||||
() -> {
|
||||
try {
|
||||
// OK button action: open the downloader's URL if available.
|
||||
if (downloadUrl != null) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(downloadUrl));
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "Failed to open downloader URL: " + downloader, ex);
|
||||
}
|
||||
},
|
||||
() -> {}, // Cancel button action (dismiss only).
|
||||
null,
|
||||
null,
|
||||
false
|
||||
).first.show();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ 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.Window;
|
||||
import android.view.WindowInsets;
|
||||
@@ -248,7 +249,15 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
||||
rootView.setOnApplyWindowInsetsListener((v, insets) -> {
|
||||
Insets statusInsets = insets.getInsets(WindowInsets.Type.statusBars());
|
||||
Insets navInsets = insets.getInsets(WindowInsets.Type.navigationBars());
|
||||
v.setPadding(0, statusInsets.top, 0, navInsets.bottom);
|
||||
Insets cutoutInsets = insets.getInsets(WindowInsets.Type.displayCutout());
|
||||
|
||||
// Apply padding for display cutout in landscape.
|
||||
int leftPadding = cutoutInsets.left;
|
||||
int rightPadding = cutoutInsets.right;
|
||||
int topPadding = statusInsets.top;
|
||||
int bottomPadding = navInsets.bottom;
|
||||
|
||||
v.setPadding(leftPadding, topPadding, rightPadding, bottomPadding);
|
||||
return insets;
|
||||
});
|
||||
}
|
||||
@@ -265,10 +274,16 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
||||
true, TextView.class::isInstance);
|
||||
if (toolbarTextView != null) {
|
||||
toolbarTextView.setTextColor(Utils.getAppForegroundColor());
|
||||
toolbarTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
|
||||
}
|
||||
|
||||
LicenseActivityHook.setToolbarLayoutParams(toolbar);
|
||||
|
||||
if (LicenseActivityHook.searchViewController != null
|
||||
&& LicenseActivityHook.searchViewController.isSearchActive()) {
|
||||
toolbar.post(() -> LicenseActivityHook.searchViewController.closeSearch());
|
||||
}
|
||||
|
||||
rootView.addView(toolbar, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -830,11 +830,10 @@ public class SegmentPlaybackController {
|
||||
WindowManager.LayoutParams params = window.getAttributes();
|
||||
params.gravity = Gravity.BOTTOM;
|
||||
params.y = dipToPixels(72);
|
||||
DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
|
||||
int portraitWidth = (int) (displayMetrics.widthPixels * 0.6);
|
||||
int portraitWidth = Utils.percentageWidthToPixels(60); // 60% of the screen width.
|
||||
|
||||
if (Resources.getSystem().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
portraitWidth = (int) Math.min(portraitWidth, displayMetrics.heightPixels * 0.6);
|
||||
portraitWidth = Math.min(portraitWidth, Utils.percentageHeightToPixels(60)); // 60% of the screen height.
|
||||
}
|
||||
params.width = portraitWidth;
|
||||
params.dimAmount = 0.0f;
|
||||
|
||||
@@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M
|
||||
org.gradle.parallel = true
|
||||
android.useAndroidX = true
|
||||
kotlin.code.style = official
|
||||
version = 5.31.0-dev.7
|
||||
version = 5.32.0-dev.4
|
||||
|
||||
@@ -116,8 +116,8 @@ public final class app/revanced/patches/all/misc/shortcut/sharetargets/RemoveSha
|
||||
public static final fun getRemoveShareTargetsPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/all/misc/spoof/SignatureSpoofPatchKt {
|
||||
public static final fun getSignatureSpoofPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
|
||||
public final class app/revanced/patches/all/misc/spoof/EnableRomSignatureSpoofingKt {
|
||||
public static final fun getEnableRomSignatureSpoofing ()Lapp/revanced/patcher/patch/ResourcePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/all/misc/targetSdk/SetTargetSdkVersion34Kt {
|
||||
@@ -168,6 +168,10 @@ public final class app/revanced/patches/cricbuzz/ads/DisableAdsPatchKt {
|
||||
public static final fun getDisableAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/cricbuzz/misc/extension/ExtensionPatchKt {
|
||||
public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/crunchyroll/ads/HideAdsPatchKt {
|
||||
public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
@@ -472,6 +476,10 @@ public final class app/revanced/patches/primevideo/misc/permissions/RenamePermis
|
||||
public static final fun getRenamePermissionsPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/primevideo/video/speed/PlaybackSpeedPatchKt {
|
||||
public static final fun getPlaybackSpeedPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/protonmail/account/RemoveFreeAccountsLimitPatchKt {
|
||||
public static final fun getRemoveFreeAccountsLimitPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
|
||||
}
|
||||
@@ -900,6 +908,10 @@ public final class app/revanced/patches/shared/misc/spoof/UserAgentClientSpoofPa
|
||||
public static final fun userAgentClientSpoofPatch (Ljava/lang/String;)Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/shared/misc/string/ReplaceStringPatchKt {
|
||||
public static final fun replaceStringPatch (Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/solidexplorer2/functionality/filesize/RemoveFileSizeLimitPatchKt {
|
||||
public static final fun getRemoveFileSizeLimitPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
@@ -1228,6 +1240,11 @@ public final class app/revanced/patches/youtube/interaction/dialog/RemoveViewerD
|
||||
public static final fun getRemoveViewerDiscretionDialogPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/interaction/doubletap/DisableChapterSkipDoubleTapPatchKt {
|
||||
public static final fun getDisableChapterSkipDoubleTapPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
public static final fun getDisableDoubleTapActionsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/interaction/downloads/DownloadsPatchKt {
|
||||
public static final fun getDownloadsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package app.revanced.patches.all.misc.spoof
|
||||
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patcher.patch.stringOption
|
||||
import app.revanced.util.getNode
|
||||
import com.android.apksig.ApkVerifier
|
||||
import com.android.apksig.apk.ApkFormatException
|
||||
import org.w3c.dom.Element
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.nio.file.InvalidPathException
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.cert.CertificateException
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.util.*
|
||||
|
||||
@Suppress("unused")
|
||||
val enableRomSignatureSpoofing = resourcePatch(
|
||||
name = "Enable ROM signature spoofing",
|
||||
description = "Spoofs the signature via the manifest meta-data \"fake-signature\". " +
|
||||
"This patch only works with ROMs that support signature spoofing.",
|
||||
use = false,
|
||||
) {
|
||||
val signatureOrPath by stringOption(
|
||||
key = "signatureOrApkFilePath",
|
||||
title = "Signature or APK file path",
|
||||
validator = validator@{ signature ->
|
||||
signature ?: return@validator false
|
||||
|
||||
parseSignature(signature) != null
|
||||
},
|
||||
description = "The hex-encoded signature or path to an APK file with the desired signature.",
|
||||
required = true,
|
||||
)
|
||||
execute {
|
||||
document("AndroidManifest.xml").use { document ->
|
||||
val permission = document.createElement("uses-permission").apply {
|
||||
setAttribute("android:name", "android.permission.FAKE_PACKAGE_SIGNATURE")
|
||||
}
|
||||
val manifest = document.getNode("manifest").appendChild(permission)
|
||||
|
||||
|
||||
val fakeSignatureMetadata = document.createElement("meta-data").apply {
|
||||
setAttribute("android:name", "fake-signature")
|
||||
setAttribute("android:value", parseSignature(signatureOrPath!!))
|
||||
}
|
||||
document.getNode("application").appendChild(fakeSignatureMetadata)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseSignature(optionValue: String): String? {
|
||||
// Parse as a hex-encoded signature.
|
||||
try {
|
||||
// TODO: Replace with signature.hexToByteArray when stable in kotlin
|
||||
val signatureBytes = HexFormat.of().parseHex(optionValue)
|
||||
CertificateFactory.getInstance("X.509").generateCertificate(signatureBytes.inputStream())
|
||||
|
||||
return optionValue
|
||||
} catch (_: IllegalArgumentException) {
|
||||
} catch (_: CertificateException) {
|
||||
}
|
||||
|
||||
// Parse as a path to an APK file.
|
||||
try {
|
||||
val apkFile = File(optionValue)
|
||||
if (!apkFile.isFile) return null
|
||||
|
||||
val result = ApkVerifier.Builder(apkFile).build().verify()
|
||||
|
||||
val hexFormat = HexFormat.of()
|
||||
|
||||
val signature = (if (result.isVerifiedUsingV3Scheme) {
|
||||
result.v3SchemeSigners[0].certificate
|
||||
} else if (result.isVerifiedUsingV2Scheme) {
|
||||
result.v2SchemeSigners[0].certificate
|
||||
} else if (result.isVerifiedUsingV1Scheme) {
|
||||
result.v1SchemeSigners[0].certificate
|
||||
} else {
|
||||
return null
|
||||
}).encoded
|
||||
|
||||
return hexFormat.formatHex(signature)
|
||||
} catch (_: IOException) {
|
||||
} catch (_: InvalidPathException) {
|
||||
} catch (_: ApkFormatException) {
|
||||
} catch (_: NoSuchAlgorithmException) {
|
||||
} catch (_: IllegalArgumentException) {
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
package app.revanced.patches.all.misc.spoof
|
||||
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patcher.patch.stringOption
|
||||
import app.revanced.util.getNode
|
||||
import com.android.apksig.ApkVerifier
|
||||
import com.android.apksig.apk.ApkFormatException
|
||||
import org.w3c.dom.Element
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.IOException
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.InvalidPathException
|
||||
import java.nio.file.attribute.BasicFileAttributes
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.cert.CertificateException
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.util.*
|
||||
import kotlin.io.path.Path
|
||||
|
||||
val signatureSpoofPatch = resourcePatch(
|
||||
name = "Spoof app signature",
|
||||
description = "Spoofs the app signature via the \"fake-signature\" meta key. " +
|
||||
"This patch only works with patched device roms.",
|
||||
use = false,
|
||||
) {
|
||||
val signature by stringOption(
|
||||
key = "spoofedAppSignature",
|
||||
title = "Signature",
|
||||
validator = { signature ->
|
||||
optionToSignature(signature) != null
|
||||
},
|
||||
description = "The hex-encoded signature or path to an apk file with the desired signature",
|
||||
required = true,
|
||||
)
|
||||
execute {
|
||||
document("AndroidManifest.xml").use { document ->
|
||||
val manifest = document.getNode("manifest") as Element
|
||||
|
||||
val fakeSignaturePermission = document.createElement("uses-permission")
|
||||
fakeSignaturePermission.setAttribute("android:name", "android.permission.FAKE_PACKAGE_SIGNATURE")
|
||||
manifest.appendChild(fakeSignaturePermission)
|
||||
|
||||
val application = document.getNode("application") ?: {
|
||||
val child = document.createElement("application")
|
||||
manifest.appendChild(child)
|
||||
child
|
||||
} as Element;
|
||||
|
||||
val fakeSignatureMetadata = document.createElement("meta-data")
|
||||
fakeSignatureMetadata.setAttribute("android:name", "fake-signature")
|
||||
fakeSignatureMetadata.setAttribute("android:value", optionToSignature(signature))
|
||||
application.appendChild(fakeSignatureMetadata)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun optionToSignature(signature: String?): String? {
|
||||
if (signature == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
// TODO: Replace with signature.hexToByteArray when stable in kotlin
|
||||
val signatureBytes = HexFormat.of()
|
||||
.parseHex(signature)
|
||||
val factory = CertificateFactory.getInstance("X.509")
|
||||
factory.generateCertificate(ByteArrayInputStream(signatureBytes))
|
||||
return signature;
|
||||
} catch (_: IllegalArgumentException) {
|
||||
} catch (_: CertificateException) {
|
||||
}
|
||||
try {
|
||||
val signaturePath = Path(signature)
|
||||
if (!Files.readAttributes(signaturePath, BasicFileAttributes::class.java).isRegularFile) {
|
||||
return null;
|
||||
}
|
||||
val verifier = ApkVerifier.Builder(signaturePath.toFile())
|
||||
.build()
|
||||
|
||||
val result = verifier.verify()
|
||||
if (result.isVerifiedUsingV3Scheme) {
|
||||
return HexFormat.of().formatHex(result.v3SchemeSigners[0].certificate.encoded)
|
||||
} else if (result.isVerifiedUsingV2Scheme) {
|
||||
return HexFormat.of().formatHex(result.v2SchemeSigners[0].certificate.encoded)
|
||||
} else if (result.isVerifiedUsingV1Scheme) {
|
||||
return HexFormat.of().formatHex(result.v1SchemeSigners[0].certificate.encoded)
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (_: IOException) {
|
||||
} catch (_: InvalidPathException) {
|
||||
} catch (_: ApkFormatException) {
|
||||
} catch (_: NoSuchAlgorithmException) {
|
||||
} catch (_: IllegalArgumentException) {}
|
||||
return null;
|
||||
}
|
||||
@@ -3,24 +3,38 @@ package app.revanced.patches.cricbuzz.ads
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.cricbuzz.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.returnEarly
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/cricbuzz/ads/HideAdsPatch;"
|
||||
|
||||
@Suppress("unused")
|
||||
val disableAdsPatch = bytecodePatch (
|
||||
name = "Hide ads",
|
||||
) {
|
||||
compatibleWith("com.cricbuzz.android"("6.23.02"))
|
||||
compatibleWith("com.cricbuzz.android"("6.24.01"))
|
||||
|
||||
dependsOn(sharedExtensionPatch)
|
||||
|
||||
execute {
|
||||
userStateSwitchFingerprint.method.apply {
|
||||
val opcodeIndex = indexOfFirstInstructionOrThrow(Opcode.MOVE_RESULT_OBJECT)
|
||||
val register = getInstruction<OneRegisterInstruction>(opcodeIndex).registerA
|
||||
userStateSwitchFingerprint.method.returnEarly(true)
|
||||
|
||||
addInstruction(
|
||||
opcodeIndex + 1,
|
||||
"const-string v$register, \"ACTIVE\""
|
||||
// Remove region-specific Cricbuzz11 elements.
|
||||
cb11ConstructorFingerprint.method.addInstruction(0, "const/4 p7, 0x0")
|
||||
getBottomBarFingerprint.method.apply {
|
||||
val getIndex = indexOfFirstInstructionOrThrow() {
|
||||
opcode == Opcode.IGET_OBJECT && getReference<FieldReference>()?.name == "bottomBar"
|
||||
}
|
||||
val getRegister = getInstruction<TwoRegisterInstruction>(getIndex).registerA
|
||||
|
||||
addInstruction(getIndex + 1,
|
||||
"invoke-static { v$getRegister }, $EXTENSION_CLASS_DESCRIPTOR->filterCb11(Ljava/util/List;)V"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,30 @@ import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal val userStateSwitchFingerprint = fingerprint {
|
||||
strings("key.user.state", "NA")
|
||||
opcodes(Opcode.SPARSE_SWITCH)
|
||||
strings("key.user.state", "NA")
|
||||
}
|
||||
|
||||
internal val cb11ConstructorFingerprint = fingerprint {
|
||||
parameters(
|
||||
"Ljava/lang/String;",
|
||||
"Ljava/lang/String;",
|
||||
"Ljava/lang/String;",
|
||||
"I",
|
||||
"Ljava/lang/String;",
|
||||
"Ljava/lang/String;",
|
||||
"Z",
|
||||
"Ljava/lang/String;",
|
||||
"Ljava/lang/String;",
|
||||
"L"
|
||||
)
|
||||
custom { _, classDef ->
|
||||
classDef.endsWith("CB11Details;")
|
||||
}
|
||||
}
|
||||
|
||||
internal val getBottomBarFingerprint = fingerprint {
|
||||
custom { method, classDef ->
|
||||
method.name == "getBottomBar" && classDef.endsWith("HomeMenu;")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package app.revanced.patches.cricbuzz.misc.extension
|
||||
|
||||
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
|
||||
|
||||
val sharedExtensionPatch = sharedExtensionPatch("cricbuzz", applicationInitHook)
|
||||
@@ -0,0 +1,9 @@
|
||||
package app.revanced.patches.cricbuzz.misc.extension
|
||||
|
||||
import app.revanced.patches.shared.misc.extension.extensionHook
|
||||
|
||||
internal val applicationInitHook = extensionHook {
|
||||
custom { method, classDef ->
|
||||
method.name == "onCreate" && classDef.endsWith("/NyitoActivity;")
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,6 @@ internal val createInboxSubTabsFingerprint = fingerprint {
|
||||
}
|
||||
|
||||
internal val loadInboxAdsFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||
returns("V")
|
||||
strings(
|
||||
"ads_load_begin",
|
||||
|
||||
@@ -12,7 +12,7 @@ val skipAdsPatch = bytecodePatch(
|
||||
name = "Skip ads",
|
||||
description = "Automatically skips video stream ads.",
|
||||
) {
|
||||
compatibleWith("com.amazon.avod.thirdpartyclient"("3.0.403.257"))
|
||||
compatibleWith("com.amazon.avod.thirdpartyclient"("3.0.412.2947"))
|
||||
|
||||
dependsOn(sharedExtensionPatch)
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package app.revanced.patches.primevideo.video.speed
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal val playbackUserControlsInitializeFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC)
|
||||
parameters("Lcom/amazon/avod/playbackclient/PlaybackInitializationContext;")
|
||||
returns("V")
|
||||
custom { method, classDef ->
|
||||
method.name == "initialize" && classDef.type == "Lcom/amazon/avod/playbackclient/activity/feature/PlaybackUserControlsFeature;"
|
||||
}
|
||||
}
|
||||
|
||||
internal val playbackUserControlsPrepareForPlaybackFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC)
|
||||
parameters("Lcom/amazon/avod/playbackclient/PlaybackContext;")
|
||||
returns("V")
|
||||
custom { method, classDef ->
|
||||
method.name == "prepareForPlayback" &&
|
||||
classDef.type == "Lcom/amazon/avod/playbackclient/activity/feature/PlaybackUserControlsFeature;"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package app.revanced.patches.primevideo.video.speed
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.primevideo.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/primevideo/videoplayer/PlaybackSpeedPatch;"
|
||||
|
||||
val playbackSpeedPatch = bytecodePatch(
|
||||
name = "Playback speed",
|
||||
description = "Adds playback speed controls to the video player.",
|
||||
) {
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
)
|
||||
|
||||
compatibleWith(
|
||||
"com.amazon.avod.thirdpartyclient"("3.0.412.2947")
|
||||
)
|
||||
|
||||
execute {
|
||||
playbackUserControlsInitializeFingerprint.method.apply {
|
||||
val getIndex = indexOfFirstInstructionOrThrow {
|
||||
opcode == Opcode.IPUT_OBJECT &&
|
||||
getReference<FieldReference>()?.name == "mUserControls"
|
||||
}
|
||||
|
||||
val getRegister = getInstruction<OneRegisterInstruction>(getIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
getIndex + 1,
|
||||
"""
|
||||
invoke-static { v$getRegister }, $EXTENSION_CLASS_DESCRIPTOR->initializeSpeedOverlay(Landroid/view/View;)V
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
playbackUserControlsPrepareForPlaybackFingerprint.method.apply {
|
||||
addInstructions(
|
||||
0,
|
||||
"""
|
||||
invoke-virtual { p1 }, Lcom/amazon/avod/playbackclient/PlaybackContext;->getPlayer()Lcom/amazon/video/sdk/player/Player;
|
||||
move-result-object v0
|
||||
invoke-static { v0 }, $EXTENSION_CLASS_DESCRIPTOR->setPlayer(Lcom/amazon/video/sdk/player/Player;)V
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,16 @@ import app.revanced.patcher.Fingerprint
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patches.reddit.customclients.spoofClientPatch
|
||||
import app.revanced.patches.shared.misc.string.replaceStringPatch
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
val spoofClientPatch = spoofClientPatch(redirectUri = "http://baconreader.com/auth") { clientIdOption ->
|
||||
dependsOn(
|
||||
// Redirects from SSL to WWW domain are bugged causing auth problems.
|
||||
// Manually rewrite the URLs to fix this.
|
||||
replaceStringPatch("ssl.reddit.com", "www.reddit.com")
|
||||
)
|
||||
|
||||
compatibleWith(
|
||||
"com.onelouder.baconreader",
|
||||
"com.onelouder.baconreader.premium",
|
||||
|
||||
@@ -61,7 +61,7 @@ val spoofClientPatch = spoofClientPatch(redirectUri = "redditisfun://auth") { cl
|
||||
// region Patch miscellaneous.
|
||||
|
||||
// Reddit messed up and does not append a redirect uri to the authorization url to old.reddit.com/login.
|
||||
// Replace old.reddit.com with ssl.reddit.com to fix this.
|
||||
// Replace old.reddit.com with www.reddit.com to fix this.
|
||||
buildAuthorizationStringFingerprint.method.apply {
|
||||
val index = indexOfFirstInstructionOrThrow {
|
||||
getReference<StringReference>()?.contains("old.reddit.com") == true
|
||||
@@ -70,7 +70,7 @@ val spoofClientPatch = spoofClientPatch(redirectUri = "redditisfun://auth") { cl
|
||||
val targetRegister = getInstruction<OneRegisterInstruction>(index).registerA
|
||||
replaceInstruction(
|
||||
index,
|
||||
"const-string v$targetRegister, \"https://ssl.reddit.com/api/v1/authorize.compact\"",
|
||||
"const-string v$targetRegister, \"https://www.reddit.com/api/v1/authorize.compact\"",
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patches.reddit.customclients.spoofClientPatch
|
||||
import app.revanced.patches.reddit.customclients.sync.detection.piracy.disablePiracyDetectionPatch
|
||||
import app.revanced.patches.shared.misc.string.replaceStringPatch
|
||||
import app.revanced.util.returnEarly
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
@@ -13,7 +14,12 @@ import java.util.Base64
|
||||
val spoofClientPatch = spoofClientPatch(
|
||||
redirectUri = "http://redditsync/auth",
|
||||
) { clientIdOption ->
|
||||
dependsOn(disablePiracyDetectionPatch)
|
||||
dependsOn(
|
||||
disablePiracyDetectionPatch,
|
||||
// Redirects from SSL to WWW domain are bugged causing auth problems.
|
||||
// Manually rewrite the URLs to fix this.
|
||||
replaceStringPatch("ssl.reddit.com", "www.reddit.com")
|
||||
)
|
||||
|
||||
compatibleWith(
|
||||
"com.laurencedawson.reddit_sync",
|
||||
|
||||
@@ -148,7 +148,7 @@ fun gmsCoreSupportPatch(
|
||||
|
||||
fun packageNameTransform(fromPackageName: String, toPackageName: String): (String) -> String? = { string ->
|
||||
when (string) {
|
||||
"$fromPackageName.SuggestionsProvider",
|
||||
"$fromPackageName.SuggestionProvider",
|
||||
"$fromPackageName.fileprovider",
|
||||
-> string.replace(fromPackageName, toPackageName)
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package app.revanced.patches.shared.misc.string
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
|
||||
import app.revanced.util.getReference
|
||||
import com.android.tools.smali.dexlib2.ReferenceType
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||
import kotlin.text.contains
|
||||
|
||||
fun replaceStringPatch(
|
||||
from: String,
|
||||
to: String
|
||||
) = bytecodePatch(
|
||||
description = "Replaces occurrences of '$from' with '$to' in string references.",
|
||||
) {
|
||||
dependsOn(
|
||||
transformInstructionsPatch(
|
||||
filterMap = filterMap@{ _, _, instruction, instructionIndex ->
|
||||
if (instruction.opcode.referenceType != ReferenceType.STRING) return@filterMap null
|
||||
|
||||
val stringReference = instruction.getReference<StringReference>()!!.string
|
||||
if (from !in stringReference) return@filterMap null
|
||||
|
||||
Triple(instructionIndex, instruction as OneRegisterInstruction, stringReference)
|
||||
},
|
||||
transform = transform@{ mutableMethod, entry ->
|
||||
val (instructionIndex, instruction, stringReference) = entry
|
||||
|
||||
val newString = stringReference.replace(from, to)
|
||||
mutableMethod.replaceInstruction(
|
||||
instructionIndex,
|
||||
"${instruction.opcode.name} v${instruction.registerA}, \"$newString\"",
|
||||
)
|
||||
},
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -6,12 +6,10 @@ import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.util.smali.ExternalLabel
|
||||
import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import java.util.logging.Logger
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/spotify/layout/hide/createbutton/HideCreateButtonPatch;"
|
||||
@@ -26,13 +24,6 @@ val hideCreateButtonPatch = bytecodePatch(
|
||||
dependsOn(sharedExtensionPatch)
|
||||
|
||||
execute {
|
||||
if (IS_SPOTIFY_LEGACY_APP_TARGET) {
|
||||
Logger.getLogger(this::class.java.name).warning(
|
||||
"Create button does not exist in legacy app target. No changes applied."
|
||||
)
|
||||
return@execute
|
||||
}
|
||||
|
||||
val oldNavigationBarAddItemMethod = oldNavigationBarAddItemFingerprint.originalMethodOrNull
|
||||
// Only throw the fingerprint error when oldNavigationBarAddItemMethod does not exist.
|
||||
val navigationBarItemSetClassDef = if (oldNavigationBarAddItemMethod == null) {
|
||||
|
||||
@@ -7,8 +7,8 @@ import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patcher.patch.stringOption
|
||||
import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
|
||||
import app.revanced.util.*
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import org.w3c.dom.Element
|
||||
@@ -19,12 +19,6 @@ private val customThemeBytecodePatch = bytecodePatch {
|
||||
dependsOn(sharedExtensionPatch)
|
||||
|
||||
execute {
|
||||
if (IS_SPOTIFY_LEGACY_APP_TARGET) {
|
||||
// Bytecode changes are not needed for legacy app target.
|
||||
// Player background color is changed with existing resource patch.
|
||||
return@execute
|
||||
}
|
||||
|
||||
val colorSpaceUtilsClassDef = colorSpaceUtilsClassFingerprint.originalClassDef
|
||||
|
||||
// Hook a util method that converts ARGB to RGBA in the sRGB color space to replace hardcoded accent colors.
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
package app.revanced.patches.spotify.lite.ondemand
|
||||
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val onDemandFingerprint = fingerprint(fuzzyPatternScanThreshold = 2) {
|
||||
returns("L")
|
||||
parameters()
|
||||
opcodes(
|
||||
Opcode.INVOKE_STATIC,
|
||||
Opcode.MOVE_RESULT,
|
||||
Opcode.INVOKE_STATIC,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.IF_EQZ,
|
||||
Opcode.SGET_OBJECT,
|
||||
Opcode.GOTO,
|
||||
Opcode.SGET_OBJECT,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.MOVE_RESULT,
|
||||
Opcode.IPUT,
|
||||
Opcode.RETURN_OBJECT,
|
||||
)
|
||||
}
|
||||
@@ -1,21 +1,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(
|
||||
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")
|
||||
|
||||
execute {
|
||||
// Spoof a premium account
|
||||
|
||||
onDemandFingerprint.method.addInstruction(
|
||||
onDemandFingerprint.patternMatch!!.endIndex - 1,
|
||||
"const/4 v0, 0x2",
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -2,7 +2,6 @@ package app.revanced.patches.spotify.misc
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstruction
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
@@ -13,25 +12,13 @@ import com.android.tools.smali.dexlib2.iface.reference.TypeReference
|
||||
|
||||
context(BytecodePatchContext)
|
||||
internal val accountAttributeFingerprint get() = fingerprint {
|
||||
custom { _, classDef ->
|
||||
classDef.type == if (IS_SPOTIFY_LEGACY_APP_TARGET) {
|
||||
"Lcom/spotify/useraccount/v1/AccountAttribute;"
|
||||
} else {
|
||||
"Lcom/spotify/remoteconfig/internal/AccountAttribute;"
|
||||
}
|
||||
}
|
||||
custom { _, classDef -> classDef.type == "Lcom/spotify/remoteconfig/internal/AccountAttribute;" }
|
||||
}
|
||||
|
||||
context(BytecodePatchContext)
|
||||
internal val productStateProtoGetMapFingerprint get() = fingerprint {
|
||||
returns("Ljava/util/Map;")
|
||||
custom { _, classDef ->
|
||||
classDef.type == if (IS_SPOTIFY_LEGACY_APP_TARGET) {
|
||||
"Lcom/spotify/ucs/proto/v0/UcsResponseWrapper${'$'}AccountAttributesResponse;"
|
||||
} else {
|
||||
"Lcom/spotify/remoteconfig/internal/ProductStateProto;"
|
||||
}
|
||||
}
|
||||
custom { _, classDef -> classDef.type == "Lcom/spotify/remoteconfig/internal/ProductStateProto;" }
|
||||
}
|
||||
|
||||
internal val buildQueryParametersFingerprint = fingerprint {
|
||||
@@ -62,8 +49,8 @@ internal val contextMenuViewModelConstructorFingerprint = fingerprint {
|
||||
/**
|
||||
* Used to find the interface name of a context menu item.
|
||||
*/
|
||||
internal val browsePodcastsContextMenuItemClassFingerprint = fingerprint {
|
||||
strings("browse_podcast_item", "ui_navigate")
|
||||
internal val removeAdsContextMenuItemClassFingerprint = fingerprint {
|
||||
strings("remove_ads_item", "ui_navigate")
|
||||
}
|
||||
|
||||
internal const val CONTEXT_MENU_ITEM_CLASS_DESCRIPTOR_PLACEHOLDER = "Lapp/revanced/ContextMenuItemPlaceholder;"
|
||||
@@ -90,14 +77,14 @@ internal val contextFromJsonFingerprint = fingerprint {
|
||||
)
|
||||
custom { method, classDef ->
|
||||
method.name == "fromJson" &&
|
||||
classDef.endsWith("voiceassistants/playermodels/ContextJsonAdapter;")
|
||||
classDef.type.endsWith("voiceassistants/playermodels/ContextJsonAdapter;")
|
||||
}
|
||||
}
|
||||
|
||||
internal val readPlayerOptionOverridesFingerprint = fingerprint {
|
||||
custom { method, classDef ->
|
||||
method.name == "readPlayerOptionOverrides" &&
|
||||
classDef.endsWith("voiceassistants/playermodels/PreparePlayOptionsJsonAdapter;")
|
||||
classDef.type.endsWith("voiceassistants/playermodels/PreparePlayOptionsJsonAdapter;")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,21 +106,21 @@ internal val abstractProtobufListEnsureIsMutableFingerprint = fingerprint {
|
||||
|
||||
internal fun structureGetSectionsFingerprint(className: String) = fingerprint {
|
||||
custom { method, classDef ->
|
||||
classDef.endsWith(className) && method.indexOfFirstInstruction {
|
||||
classDef.type.endsWith(className) && method.indexOfFirstInstruction {
|
||||
opcode == Opcode.IGET_OBJECT && getReference<FieldReference>()?.name == "sections_"
|
||||
} >= 0
|
||||
}
|
||||
}
|
||||
|
||||
internal val homeSectionFingerprint = fingerprint {
|
||||
custom { _, classDef -> classDef.endsWith("homeapi/proto/Section;") }
|
||||
custom { _, classDef -> classDef.type.endsWith("homeapi/proto/Section;") }
|
||||
}
|
||||
|
||||
internal val homeStructureGetSectionsFingerprint =
|
||||
structureGetSectionsFingerprint("homeapi/proto/HomeStructure;")
|
||||
|
||||
internal val browseSectionFingerprint = fingerprint {
|
||||
custom { _, classDef-> classDef.endsWith("browsita/v1/resolved/Section;") }
|
||||
custom { _, classDef-> classDef.type.endsWith("browsita/v1/resolved/Section;") }
|
||||
}
|
||||
|
||||
internal val browseStructureGetSectionsFingerprint =
|
||||
|
||||
@@ -7,37 +7,28 @@ import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
internal val getPackageInfoFingerprint = fingerprint {
|
||||
strings(
|
||||
"Failed to get the application signatures"
|
||||
)
|
||||
}
|
||||
|
||||
internal val loadOrbitLibraryFingerprint = fingerprint {
|
||||
strings("/liborbit-jni-spotify.so")
|
||||
}
|
||||
|
||||
internal val startupPageLayoutInflateFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("Landroid/view/View;")
|
||||
parameters("Landroid/view/LayoutInflater;", "Landroid/view/ViewGroup;", "Landroid/os/Bundle;")
|
||||
strings("blueprintContainer", "gradient", "valuePropositionTextView")
|
||||
internal val setClientIdFingerprint = fingerprint {
|
||||
parameters("Ljava/lang/String;")
|
||||
custom { method, classDef ->
|
||||
classDef.type == "Lcom/spotify/connectivity/ApplicationScopeConfiguration;"
|
||||
&& method.name == "setClientId"
|
||||
}
|
||||
}
|
||||
|
||||
internal val renderStartLoginScreenFingerprint = fingerprint {
|
||||
strings("authenticationButtonFactory", "MORE_OPTIONS")
|
||||
internal val setUserAgentFingerprint = fingerprint {
|
||||
parameters("Ljava/lang/String;")
|
||||
custom { method, classDef ->
|
||||
classDef.type == "Lcom/spotify/connectivity/ApplicationScopeConfiguration;"
|
||||
&& method.name == "setDefaultHTTPUserAgent"
|
||||
}
|
||||
}
|
||||
|
||||
internal val renderSecondLoginScreenFingerprint = fingerprint {
|
||||
strings("authenticationButtonFactory", "intent_login")
|
||||
}
|
||||
|
||||
internal val renderThirdLoginScreenFingerprint = fingerprint {
|
||||
strings("EMAIL_OR_USERNAME", "listener")
|
||||
}
|
||||
|
||||
internal val thirdLoginScreenLoginOnClickFingerprint = fingerprint {
|
||||
strings("login", "listener", "none")
|
||||
internal val extensionFixConstantsFingerprint = fingerprint {
|
||||
custom { _, classDef -> classDef.type == "Lapp/revanced/extension/spotify/misc/fix/Constants;" }
|
||||
}
|
||||
|
||||
internal val runIntegrityVerificationFingerprint = fingerprint {
|
||||
|
||||
@@ -2,18 +2,13 @@ package app.revanced.patches.spotify.misc.fix
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.patch.intOption
|
||||
import app.revanced.patcher.patch.stringOption
|
||||
import app.revanced.patches.shared.misc.hex.HexPatchBuilder
|
||||
import app.revanced.patches.shared.misc.hex.hexPatch
|
||||
import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.util.*
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import app.revanced.util.returnEarly
|
||||
|
||||
internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/spotify/misc/fix/SpoofClientPatch;"
|
||||
|
||||
@@ -25,16 +20,40 @@ val spoofClientPatch = bytecodePatch(
|
||||
val requestListenerPort by intOption(
|
||||
key = "requestListenerPort",
|
||||
default = 4345,
|
||||
title = " Login request listener port",
|
||||
description = "The port to use for the listener that intercepts and handles login requests. " +
|
||||
"Port must be between 0 and 65535.",
|
||||
required = true,
|
||||
title = "Request listener port",
|
||||
description = "The port to use for the listener that intercepts and handles spoofed requests. " +
|
||||
"Port must be between 0 and 65535. " +
|
||||
"Do not change this option, if you do not know what you are doing.",
|
||||
validator = {
|
||||
it!!
|
||||
!(it < 0 || it > 65535)
|
||||
}
|
||||
)
|
||||
|
||||
val clientVersion by stringOption(
|
||||
key = "clientVersion",
|
||||
default = "iphone-9.0.58.558.g200011c",
|
||||
title = "Client version",
|
||||
description = "The client version used for spoofing the client token. " +
|
||||
"Do not change this option, if you do not know what you are doing."
|
||||
)
|
||||
|
||||
val hardwareMachine by stringOption(
|
||||
key = "hardwareMachine",
|
||||
default = "iPhone16,1",
|
||||
title = "Hardware machine",
|
||||
description = "The hardware machine used for spoofing the client token. " +
|
||||
"Do not change this option, if you do not know what you are doing."
|
||||
)
|
||||
|
||||
val systemVersion by stringOption(
|
||||
key = "systemVersion",
|
||||
default = "17.7.2",
|
||||
title = "System version",
|
||||
description = "The system version used for spoofing the client token. " +
|
||||
"Do not change this option, if you do not know what you are doing."
|
||||
)
|
||||
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
hexPatch(ignoreMissingTargetFiles = true, block = fun HexPatchBuilder.() {
|
||||
@@ -44,10 +63,8 @@ val spoofClientPatch = bytecodePatch(
|
||||
"x86",
|
||||
"x86_64"
|
||||
).forEach { architecture ->
|
||||
"https://login5.spotify.com/v3/login" to "http://127.0.0.1:$requestListenerPort/v3/login" inFile
|
||||
"lib/$architecture/liborbit-jni-spotify.so"
|
||||
|
||||
"https://login5.spotify.com/v4/login" to "http://127.0.0.1:$requestListenerPort/v4/login" inFile
|
||||
"https://clienttoken.spotify.com/v1/clienttoken" to
|
||||
"http://127.0.0.1:$requestListenerPort/v1/clienttoken" inFile
|
||||
"lib/$architecture/liborbit-jni-spotify.so"
|
||||
}
|
||||
})
|
||||
@@ -56,52 +73,29 @@ val spoofClientPatch = bytecodePatch(
|
||||
compatibleWith("com.spotify.music")
|
||||
|
||||
execute {
|
||||
// region Spoof package info.
|
||||
val clientVersion = clientVersion!!
|
||||
val hardwareMachine = hardwareMachine!!
|
||||
val systemVersion = systemVersion!!
|
||||
|
||||
getPackageInfoFingerprint.method.apply {
|
||||
// region Spoof signature.
|
||||
// region Spoof login request.
|
||||
|
||||
val version = clientVersion
|
||||
.substringAfter('-')
|
||||
.substringBeforeLast('.')
|
||||
.substringBeforeLast('.')
|
||||
|
||||
setUserAgentFingerprint.method.addInstruction(
|
||||
0,
|
||||
"const-string p1, \"Spotify/$version iOS/$systemVersion ($hardwareMachine)\""
|
||||
)
|
||||
|
||||
val failedToGetSignaturesStringIndex =
|
||||
getPackageInfoFingerprint.stringMatches!!.first().index
|
||||
|
||||
val concatSignaturesIndex = indexOfFirstInstructionReversedOrThrow(
|
||||
failedToGetSignaturesStringIndex,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
)
|
||||
|
||||
val signatureRegister = getInstruction<OneRegisterInstruction>(concatSignaturesIndex).registerA
|
||||
val expectedSignature = "d6a6dced4a85f24204bf9505ccc1fce114cadb32"
|
||||
|
||||
replaceInstruction(concatSignaturesIndex, "const-string v$signatureRegister, \"$expectedSignature\"")
|
||||
|
||||
// endregion
|
||||
|
||||
// region Spoof installer name.
|
||||
|
||||
val expectedInstallerName = "com.android.vending"
|
||||
|
||||
findInstructionIndicesReversedOrThrow {
|
||||
val reference = getReference<MethodReference>()
|
||||
reference?.name == "getInstallerPackageName" || reference?.name == "getInstallingPackageName"
|
||||
}.forEach { index ->
|
||||
val returnObjectIndex = index + 1
|
||||
|
||||
val installerPackageNameRegister = getInstruction<OneRegisterInstruction>(
|
||||
returnObjectIndex
|
||||
).registerA
|
||||
|
||||
addInstruction(
|
||||
returnObjectIndex + 1,
|
||||
"const-string v$installerPackageNameRegister, \"$expectedInstallerName\""
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
setClientIdFingerprint.method.addInstruction(
|
||||
0, "const-string p1, \"58bd3c95768941ea9eb4350aaa033eb3\""
|
||||
)
|
||||
|
||||
// endregion
|
||||
|
||||
// region Spoof client.
|
||||
// region Spoof client-token request.
|
||||
|
||||
loadOrbitLibraryFingerprint.method.addInstructions(
|
||||
0,
|
||||
@@ -111,72 +105,12 @@ val spoofClientPatch = bytecodePatch(
|
||||
"""
|
||||
)
|
||||
|
||||
startupPageLayoutInflateFingerprint.method.apply {
|
||||
val openLoginWebViewDescriptor =
|
||||
"$EXTENSION_CLASS_DESCRIPTOR->launchLogin(Landroid/view/LayoutInflater;)V"
|
||||
|
||||
addInstructions(
|
||||
0,
|
||||
"invoke-static/range { p1 .. p1 }, $openLoginWebViewDescriptor"
|
||||
)
|
||||
}
|
||||
|
||||
renderStartLoginScreenFingerprint.method.apply {
|
||||
val onEventIndex = indexOfFirstInstructionOrThrow {
|
||||
opcode == Opcode.INVOKE_INTERFACE && getReference<MethodReference>()?.name == "getView"
|
||||
}
|
||||
|
||||
val buttonRegister = getInstruction<OneRegisterInstruction>(onEventIndex + 1).registerA
|
||||
|
||||
addInstruction(
|
||||
onEventIndex + 2,
|
||||
"invoke-static { v$buttonRegister }, $EXTENSION_CLASS_DESCRIPTOR->setNativeLoginHandler(Landroid/view/View;)V"
|
||||
)
|
||||
}
|
||||
|
||||
renderSecondLoginScreenFingerprint.method.apply {
|
||||
val getViewIndex = indexOfFirstInstructionOrThrow {
|
||||
opcode == Opcode.INVOKE_INTERFACE && getReference<MethodReference>()?.name == "getView"
|
||||
}
|
||||
|
||||
val buttonRegister = getInstruction<OneRegisterInstruction>(getViewIndex + 1).registerA
|
||||
|
||||
// Early return the render for loop since the first item of the loop is the login button.
|
||||
addInstructions(
|
||||
getViewIndex + 2,
|
||||
"""
|
||||
invoke-virtual { v$buttonRegister }, Landroid/view/View;->performClick()Z
|
||||
return-void
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
renderThirdLoginScreenFingerprint.method.apply {
|
||||
val invokeSetListenerIndex = indexOfFirstInstructionOrThrow {
|
||||
val reference = getReference<MethodReference>()
|
||||
reference?.definingClass == "Landroid/view/View;" && reference.name == "setOnClickListener"
|
||||
}
|
||||
|
||||
val buttonRegister = getInstruction<FiveRegisterInstruction>(invokeSetListenerIndex).registerC
|
||||
|
||||
addInstruction(
|
||||
invokeSetListenerIndex + 1,
|
||||
"invoke-virtual { v$buttonRegister }, Landroid/view/View;->performClick()Z"
|
||||
)
|
||||
}
|
||||
|
||||
thirdLoginScreenLoginOnClickFingerprint.method.apply {
|
||||
// Use placeholder credentials to pass the login screen.
|
||||
val loginActionIndex = indexOfFirstInstructionOrThrow(Opcode.RETURN_VOID) - 1
|
||||
val loginActionInstruction = getInstruction<FiveRegisterInstruction>(loginActionIndex)
|
||||
|
||||
addInstructions(
|
||||
loginActionIndex,
|
||||
"""
|
||||
const-string v${loginActionInstruction.registerD}, "placeholder"
|
||||
const-string v${loginActionInstruction.registerE}, "placeholder"
|
||||
"""
|
||||
)
|
||||
mapOf(
|
||||
"getClientVersion" to clientVersion,
|
||||
"getSystemVersion" to systemVersion,
|
||||
"getHardwareMachine" to hardwareMachine
|
||||
).forEach { (methodName, value) ->
|
||||
extensionFixConstantsFingerprint.classDef.methods.single { it.name == methodName }.returnEarly(value)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
@@ -6,7 +6,6 @@ import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.patch.stringOption
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
||||
@@ -57,16 +56,10 @@ val changeLyricsProviderPatch = bytecodePatch(
|
||||
}
|
||||
|
||||
execute {
|
||||
if (IS_SPOTIFY_LEGACY_APP_TARGET) {
|
||||
Logger.getLogger(this::class.java.name).severe(
|
||||
"Change lyrics provider patch is not supported for this target version."
|
||||
)
|
||||
return@execute
|
||||
}
|
||||
|
||||
val httpClientBuilderMethod = httpClientBuilderFingerprint.originalMethod
|
||||
|
||||
// region Create a modified copy of the HTTP client builder method with the custom lyrics provider host.
|
||||
|
||||
val patchedHttpClientBuilderMethod = with(httpClientBuilderMethod) {
|
||||
val invokeBuildUrlIndex = indexOfFirstInstructionOrThrow {
|
||||
getReference<MethodReference>()?.returnType == "Lokhttp3/HttpUrl;"
|
||||
@@ -89,9 +82,11 @@ val changeLyricsProviderPatch = bytecodePatch(
|
||||
httpClientBuilderFingerprint.classDef.methods.add(this)
|
||||
}
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
// region Replace the call to the HTTP client builder method used exclusively for lyrics by the modified one.
|
||||
|
||||
getLyricsHttpClientFingerprint(httpClientBuilderMethod).method.apply {
|
||||
val getLyricsHttpClientIndex = indexOfFirstInstructionOrThrow {
|
||||
getReference<MethodReference>() == httpClientBuilderMethod
|
||||
@@ -118,6 +113,7 @@ val changeLyricsProviderPatch = bytecodePatch(
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
//endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ internal val shareCopyUrlFingerprint = fingerprint {
|
||||
}
|
||||
}
|
||||
|
||||
internal val shareCopyUrlLegacyFingerprint = fingerprint {
|
||||
internal val oldShareCopyUrlFingerprint = fingerprint {
|
||||
returns("Ljava/lang/Object;")
|
||||
parameters("Ljava/lang/Object;")
|
||||
strings("clipboard", "createNewSession failed")
|
||||
@@ -38,7 +38,7 @@ internal val formatAndroidShareSheetUrlFingerprint = fingerprint {
|
||||
}
|
||||
}
|
||||
|
||||
internal val formatAndroidShareSheetUrlLegacyFingerprint = fingerprint {
|
||||
internal val oldFormatAndroidShareSheetUrlFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC)
|
||||
returns("Ljava/lang/String;")
|
||||
parameters("Lcom/spotify/share/social/sharedata/ShareData;", "Ljava/lang/String;")
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package app.revanced.patches.spotify.misc.privacy
|
||||
|
||||
import app.revanced.patcher.Fingerprint
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
@@ -28,10 +26,10 @@ val sanitizeSharingLinksPatch = bytecodePatch(
|
||||
val extensionMethodDescriptor = "$EXTENSION_CLASS_DESCRIPTOR->" +
|
||||
"sanitizeUrl(Ljava/lang/String;)Ljava/lang/String;"
|
||||
|
||||
val copyFingerprint = if (IS_SPOTIFY_LEGACY_APP_TARGET) {
|
||||
shareCopyUrlLegacyFingerprint
|
||||
} else {
|
||||
val copyFingerprint = if (shareCopyUrlFingerprint.originalMethodOrNull != null) {
|
||||
shareCopyUrlFingerprint
|
||||
} else {
|
||||
oldShareCopyUrlFingerprint
|
||||
}
|
||||
|
||||
copyFingerprint.method.apply {
|
||||
@@ -50,15 +48,10 @@ val sanitizeSharingLinksPatch = bytecodePatch(
|
||||
}
|
||||
|
||||
// Android native share sheet is used for all other quick share types (X, WhatsApp, etc).
|
||||
val shareUrlParameter : String
|
||||
val shareSheetFingerprint : Fingerprint
|
||||
if (IS_SPOTIFY_LEGACY_APP_TARGET) {
|
||||
shareSheetFingerprint = formatAndroidShareSheetUrlLegacyFingerprint
|
||||
shareUrlParameter = "p2"
|
||||
} else {
|
||||
shareSheetFingerprint = formatAndroidShareSheetUrlFingerprint
|
||||
val methodAccessFlags = formatAndroidShareSheetUrlFingerprint.originalMethod.accessFlags
|
||||
shareUrlParameter = if (AccessFlags.STATIC.isSet(methodAccessFlags)) {
|
||||
val shareUrlParameter: String
|
||||
val shareSheetFingerprint = if (formatAndroidShareSheetUrlFingerprint.originalMethodOrNull != null) {
|
||||
val methodAccessFlags = formatAndroidShareSheetUrlFingerprint.originalMethod
|
||||
shareUrlParameter = if (AccessFlags.STATIC.isSet(methodAccessFlags.accessFlags)) {
|
||||
// In newer implementations the method is static, so p0 is not `this`.
|
||||
"p1"
|
||||
} else {
|
||||
@@ -66,6 +59,11 @@ val sanitizeSharingLinksPatch = bytecodePatch(
|
||||
// For that reason, add one to the parameter register.
|
||||
"p2"
|
||||
}
|
||||
|
||||
formatAndroidShareSheetUrlFingerprint
|
||||
} else {
|
||||
shareUrlParameter = "p2"
|
||||
oldFormatAndroidShareSheetUrlFingerprint
|
||||
}
|
||||
|
||||
shareSheetFingerprint.method.addInstructions(
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package app.revanced.patches.spotify.misc.widgets
|
||||
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
|
||||
import app.revanced.util.returnEarly
|
||||
import java.util.logging.Logger
|
||||
|
||||
@Suppress("unused")
|
||||
val fixThirdPartyLaunchersWidgets = bytecodePatch(
|
||||
@@ -13,14 +11,6 @@ val fixThirdPartyLaunchersWidgets = bytecodePatch(
|
||||
compatibleWith("com.spotify.music")
|
||||
|
||||
execute {
|
||||
if (IS_SPOTIFY_LEGACY_APP_TARGET) {
|
||||
// The permission check does not exist in legacy versions.
|
||||
Logger.getLogger(this::class.java.name).warning(
|
||||
"Legacy app target does not have any third party launcher restrictions. No changes applied."
|
||||
)
|
||||
return@execute
|
||||
}
|
||||
|
||||
// Only system app launchers are granted the BIND_APPWIDGET permission.
|
||||
// Override the method that checks for it to always return true, as this permission is not actually required
|
||||
// for the widgets to work.
|
||||
|
||||
@@ -1,38 +1,15 @@
|
||||
package app.revanced.patches.spotify.shared
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
private const val SPOTIFY_MAIN_ACTIVITY = "Lcom/spotify/music/SpotifyMainActivity;"
|
||||
|
||||
/**
|
||||
* Main activity of target 8.6.98.900.
|
||||
*/
|
||||
internal const val SPOTIFY_MAIN_ACTIVITY_LEGACY = "Lcom/spotify/music/MainActivity;"
|
||||
|
||||
internal val mainActivityOnCreateFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("V")
|
||||
parameters("Landroid/os/Bundle;")
|
||||
custom { method, classDef ->
|
||||
method.name == "onCreate" && (classDef.type == SPOTIFY_MAIN_ACTIVITY
|
||||
|| classDef.type == SPOTIFY_MAIN_ACTIVITY_LEGACY)
|
||||
method.name == "onCreate" && classDef.type == SPOTIFY_MAIN_ACTIVITY
|
||||
}
|
||||
}
|
||||
|
||||
private var isLegacyAppTarget: Boolean? = null
|
||||
|
||||
/**
|
||||
* If patching a legacy 8.x target. This may also be set if patching slightly older/newer app targets,
|
||||
* but the only legacy target of interest is 8.6.98.900 as it's the last version that
|
||||
* supports Spotify integration on Kenwood/Pioneer car stereos.
|
||||
*/
|
||||
context(BytecodePatchContext)
|
||||
internal val IS_SPOTIFY_LEGACY_APP_TARGET
|
||||
get(): Boolean {
|
||||
if (isLegacyAppTarget == null) {
|
||||
isLegacyAppTarget = mainActivityOnCreateFingerprint.originalClassDef.type == SPOTIFY_MAIN_ACTIVITY_LEGACY
|
||||
}
|
||||
return isLegacyAppTarget!!
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package app.revanced.patches.youtube.interaction.doubletap
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.fingerprint
|
||||
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.SwitchPreference
|
||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/youtube/patches/DisableDoubleTapActionsPatch;"
|
||||
|
||||
@Suppress("unused")
|
||||
val disableDoubleTapActionsPatch = bytecodePatch(
|
||||
name = "Disable double tap actions",
|
||||
description = "Adds an option to disable player double tap gestures.",
|
||||
) {
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
settingsPatch,
|
||||
addResourcesPatch,
|
||||
)
|
||||
|
||||
compatibleWith(
|
||||
"com.google.android.youtube"(
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
)
|
||||
)
|
||||
|
||||
execute {
|
||||
addResources("youtube", "interaction.doubletap.disableDoubleTapActionsPatch")
|
||||
|
||||
PreferenceScreen.PLAYER.addPreferences(
|
||||
SwitchPreference("revanced_disable_chapter_skip_double_tap"),
|
||||
)
|
||||
|
||||
val doubleTapInfoGetSeekSourceFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
parameters("Z")
|
||||
returns(seekTypeEnumFingerprint.originalClassDef.type)
|
||||
opcodes(
|
||||
Opcode.IF_EQZ,
|
||||
Opcode.SGET_OBJECT,
|
||||
Opcode.RETURN_OBJECT,
|
||||
Opcode.SGET_OBJECT,
|
||||
Opcode.RETURN_OBJECT,
|
||||
)
|
||||
custom { _, classDef ->
|
||||
classDef.fields.count() == 4
|
||||
}
|
||||
}
|
||||
|
||||
// Force isChapterSeek flag to false.
|
||||
doubleTapInfoGetSeekSourceFingerprint.method.addInstructions(
|
||||
0,
|
||||
"""
|
||||
invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->disableDoubleTapChapters(Z)Z
|
||||
move-result p1
|
||||
"""
|
||||
)
|
||||
|
||||
doubleTapInfoCtorFingerprint.match(
|
||||
doubleTapInfoGetSeekSourceFingerprint.classDef
|
||||
).method.addInstructions(
|
||||
0,
|
||||
"""
|
||||
invoke-static { p3 }, $EXTENSION_CLASS_DESCRIPTOR->disableDoubleTapChapters(Z)Z
|
||||
move-result p3
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Patch was renamed", ReplaceWith("disableDoubleTapActionsPatch"))
|
||||
val disableChapterSkipDoubleTapPatch = bytecodePatch {
|
||||
dependsOn(disableDoubleTapActionsPatch)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package app.revanced.patches.youtube.interaction.doubletap
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal val seekTypeEnumFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR)
|
||||
strings(
|
||||
"SEEK_SOURCE_SEEK_TO_NEXT_CHAPTER",
|
||||
"SEEK_SOURCE_SEEK_TO_PREVIOUS_CHAPTER"
|
||||
)
|
||||
}
|
||||
|
||||
internal val doubleTapInfoCtorFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
|
||||
parameters(
|
||||
"Landroid/view/MotionEvent;",
|
||||
"I",
|
||||
"Z",
|
||||
"Lj\$/time/Duration;"
|
||||
)
|
||||
}
|
||||
@@ -6,7 +6,6 @@ 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.settings.preference.InputType
|
||||
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
|
||||
@@ -40,7 +39,10 @@ private val downloadsResourcePatch = resourcePatch {
|
||||
preferences = setOf(
|
||||
SwitchPreference("revanced_external_downloader"),
|
||||
SwitchPreference("revanced_external_downloader_action_button"),
|
||||
TextPreference("revanced_external_downloader_name", inputType = InputType.TEXT),
|
||||
TextPreference(
|
||||
"revanced_external_downloader_name",
|
||||
tag = "app.revanced.extension.youtube.settings.preference.ExternalDownloaderPreference",
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -73,12 +73,9 @@ val enableSlideToSeekPatch = bytecodePatch(
|
||||
|
||||
// Disable the double speed seek gesture.
|
||||
if (is_19_17_or_greater) {
|
||||
arrayOf(
|
||||
disableFastForwardGestureFingerprint,
|
||||
disableFastForwardNoticeFingerprint,
|
||||
).forEach { fingerprint ->
|
||||
fingerprint.method.apply {
|
||||
val targetIndex = fingerprint.patternMatch!!.endIndex
|
||||
disableFastForwardGestureFingerprint.let {
|
||||
it.method.apply {
|
||||
val targetIndex = it.patternMatch!!.endIndex
|
||||
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
|
||||
@@ -3,14 +3,12 @@ package app.revanced.patches.youtube.interaction.seekbar
|
||||
import app.revanced.patcher.fingerprint
|
||||
import app.revanced.util.containsLiteralInstruction
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstruction
|
||||
import app.revanced.util.indexOfFirstInstructionReversed
|
||||
import app.revanced.util.literal
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||
|
||||
internal val swipingUpGestureParentFingerprint = fingerprint {
|
||||
returns("Z")
|
||||
@@ -59,25 +57,6 @@ internal val disableFastForwardGestureFingerprint = fingerprint {
|
||||
}
|
||||
}
|
||||
|
||||
internal val disableFastForwardNoticeFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("V")
|
||||
parameters()
|
||||
opcodes(
|
||||
Opcode.CHECK_CAST,
|
||||
Opcode.IGET_OBJECT,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.MOVE_RESULT,
|
||||
)
|
||||
custom { method, _ ->
|
||||
method.name == "run" && method.indexOfFirstInstruction {
|
||||
// In later targets the code is found in different methods with different strings.
|
||||
val string = getReference<StringReference>()?.string
|
||||
string == "Failed to easy seek haptics vibrate." || string == "search_landing_cache_key"
|
||||
} >= 0
|
||||
}
|
||||
}
|
||||
|
||||
internal val onTouchEventHandlerFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.PUBLIC)
|
||||
returns("Z")
|
||||
|
||||
@@ -22,6 +22,8 @@ import app.revanced.util.forEachLiteralValueInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import java.io.File
|
||||
|
||||
private val variants = arrayOf("light", "dark")
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/youtube/patches/ChangeHeaderPatch;"
|
||||
|
||||
@@ -29,6 +31,17 @@ private val changeHeaderBytecodePatch = bytecodePatch {
|
||||
dependsOn(resourceMappingPatch)
|
||||
|
||||
execute {
|
||||
// Resources are not used during patching, but extension code uses these
|
||||
// images so verify they exist.
|
||||
arrayOf(
|
||||
"yt_ringo2_wordmark_header",
|
||||
"yt_ringo2_premium_wordmark_header"
|
||||
).forEach { resource ->
|
||||
variants.forEach { theme ->
|
||||
resourceMappings["drawable", resource + "_" + theme]
|
||||
}
|
||||
}
|
||||
|
||||
arrayOf(
|
||||
"ytWordmarkHeader",
|
||||
"ytPremiumWordmarkHeader"
|
||||
@@ -57,7 +70,6 @@ private val targetResourceDirectoryNames = mapOf(
|
||||
"mdpi" to "129px x 48px"
|
||||
).mapKeys { (dpi, _) -> "drawable-$dpi" }
|
||||
|
||||
private val variants = arrayOf("light", "dark")
|
||||
|
||||
/**
|
||||
* Header logos built into this patch.
|
||||
|
||||
@@ -8,6 +8,7 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWith
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.instructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patcher.util.smali.ExternalLabel
|
||||
@@ -200,9 +201,9 @@ val hideLayoutComponentsPatch = bytecodePatch(
|
||||
key = "revanced_hide_filter_bar_screen",
|
||||
preferences = setOf(
|
||||
SwitchPreference("revanced_hide_filter_bar_feed_in_feed"),
|
||||
SwitchPreference("revanced_hide_filter_bar_feed_in_history"),
|
||||
SwitchPreference("revanced_hide_filter_bar_feed_in_search"),
|
||||
SwitchPreference("revanced_hide_filter_bar_feed_in_related_videos"),
|
||||
SwitchPreference("revanced_hide_filter_bar_feed_in_search"),
|
||||
SwitchPreference("revanced_hide_filter_bar_feed_in_history"),
|
||||
),
|
||||
),
|
||||
PreferenceScreenPreference(
|
||||
@@ -222,7 +223,6 @@ val hideLayoutComponentsPatch = bytecodePatch(
|
||||
SwitchPreference("revanced_hide_crowdfunding_box"),
|
||||
SwitchPreference("revanced_hide_chips_shelf"),
|
||||
SwitchPreference("revanced_hide_expandable_card"),
|
||||
SwitchPreference("revanced_hide_feed_survey"),
|
||||
SwitchPreference("revanced_hide_floating_microphone_button"),
|
||||
SwitchPreference("revanced_hide_horizontal_shelves"),
|
||||
SwitchPreference("revanced_hide_image_shelf"),
|
||||
@@ -232,6 +232,7 @@ val hideLayoutComponentsPatch = bytecodePatch(
|
||||
SwitchPreference("revanced_hide_notify_me_button"),
|
||||
SwitchPreference("revanced_hide_playables"),
|
||||
SwitchPreference("revanced_hide_show_more_button"),
|
||||
SwitchPreference("revanced_hide_surveys"),
|
||||
SwitchPreference("revanced_hide_ticket_shelf"),
|
||||
SwitchPreference("revanced_hide_video_recommendation_labels"),
|
||||
SwitchPreference("revanced_hide_doodles"),
|
||||
@@ -379,16 +380,13 @@ val hideLayoutComponentsPatch = bytecodePatch(
|
||||
findInstructionIndicesReversedOrThrow {
|
||||
getReference<MethodReference>()?.name == "setImageDrawable"
|
||||
}.forEach { insertIndex ->
|
||||
val register = getInstruction<FiveRegisterInstruction>(insertIndex).registerD
|
||||
val drawableRegister = getInstruction<FiveRegisterInstruction>(insertIndex).registerD
|
||||
val imageViewRegister = getInstruction<FiveRegisterInstruction>(insertIndex).registerC
|
||||
|
||||
addInstructionsWithLabels(
|
||||
replaceInstruction(
|
||||
insertIndex,
|
||||
"""
|
||||
invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideYoodles(Landroid/graphics/drawable/Drawable;)Landroid/graphics/drawable/Drawable;
|
||||
move-result-object v$register
|
||||
if-eqz v$register, :hide
|
||||
""",
|
||||
ExternalLabel("hide", getInstruction(insertIndex + 1)),
|
||||
"invoke-static { v$imageViewRegister, v$drawableRegister }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->" +
|
||||
"setDoodleDrawable(Landroid/widget/ImageView;Landroid/graphics/drawable/Drawable;)V"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,8 +66,8 @@ private val hideShortsComponentsResourcePatch = resourcePatch {
|
||||
|
||||
PreferenceScreen.SHORTS.addPreferences(
|
||||
SwitchPreference("revanced_hide_shorts_home"),
|
||||
SwitchPreference("revanced_hide_shorts_subscriptions"),
|
||||
SwitchPreference("revanced_hide_shorts_search"),
|
||||
SwitchPreference("revanced_hide_shorts_subscriptions"),
|
||||
SwitchPreference("revanced_hide_shorts_history"),
|
||||
|
||||
PreferenceScreenPreference(
|
||||
|
||||
@@ -12,31 +12,15 @@ 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.overrideThemeColors
|
||||
import app.revanced.patches.shared.misc.settings.preference.BasePreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.BasePreferenceScreen
|
||||
import app.revanced.patches.shared.misc.settings.preference.InputType
|
||||
import app.revanced.patches.shared.misc.settings.preference.IntentPreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.ListPreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceCategory
|
||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.*
|
||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.TextPreference
|
||||
import app.revanced.patches.shared.misc.settings.settingsPatch
|
||||
import app.revanced.patches.youtube.misc.check.checkEnvironmentPatch
|
||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.youtube.misc.fix.playbackspeed.fixPlaybackSpeedWhilePlayingPatch
|
||||
import app.revanced.patches.youtube.misc.playservice.is_19_34_or_greater
|
||||
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
||||
import app.revanced.util.ResourceGroup
|
||||
import app.revanced.util.addInstructionsAtControlFlowLabel
|
||||
import app.revanced.util.copyResources
|
||||
import app.revanced.util.copyXmlNode
|
||||
import app.revanced.util.findElementByAttributeValueOrThrow
|
||||
import app.revanced.util.findInstructionIndicesReversedOrThrow
|
||||
import app.revanced.util.inputStreamFromBundledResource
|
||||
import app.revanced.util.insertLiteralOverride
|
||||
import app.revanced.util.*
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||
@@ -152,15 +136,24 @@ private val settingsResourcePatch = resourcePatch {
|
||||
}
|
||||
}
|
||||
|
||||
// Modify the manifest and add a data intent filter to the LicenseActivity.
|
||||
// Some devices freak out if undeclared data is passed to an intent,
|
||||
// and this change appears to fix the issue.
|
||||
// Modify the manifest to enhance LicenseActivity behavior:
|
||||
// 1. Add a data intent filter with MIME type "text/plain".
|
||||
// Some devices crash if undeclared data is passed to an intent,
|
||||
// and this change appears to fix the issue.
|
||||
// 2. Add android:configChanges="orientation|screenSize|keyboardHidden".
|
||||
// This prevents the activity from being recreated on configuration changes
|
||||
// (e.g., screen rotation), preserving its current state and fragment.
|
||||
document("AndroidManifest.xml").use { document ->
|
||||
val licenseElement = document.childNodes.findElementByAttributeValueOrThrow(
|
||||
"android:name",
|
||||
"com.google.android.libraries.social.licenses.LicenseActivity",
|
||||
)
|
||||
|
||||
licenseElement.setAttribute(
|
||||
"android:configChanges",
|
||||
"orientation|screenSize|keyboardHidden"
|
||||
)
|
||||
|
||||
val mimeType = document.createElement("data")
|
||||
mimeType.setAttribute("android:mimeType", "text/plain")
|
||||
|
||||
@@ -242,9 +235,9 @@ val settingsPatch = bytecodePatch(
|
||||
methods.removeIf { it.name != "onCreate" && !MethodUtil.isConstructor(it) }
|
||||
}
|
||||
|
||||
// Add context override to force a specific settings language.
|
||||
licenseActivityOnCreateFingerprint.classDef.apply {
|
||||
val attachBaseContext = ImmutableMethod(
|
||||
// Add attachBaseContext method to override the context for setting a specific language.
|
||||
ImmutableMethod(
|
||||
type,
|
||||
"attachBaseContext",
|
||||
listOf(ImmutableMethodParameter("Landroid/content/Context;", null, null)),
|
||||
@@ -262,9 +255,50 @@ val settingsPatch = bytecodePatch(
|
||||
return-void
|
||||
"""
|
||||
)
|
||||
}
|
||||
}.let(methods::add)
|
||||
|
||||
methods.add(attachBaseContext)
|
||||
// Add onBackPressed method to handle back button presses, delegating to SearchViewController.
|
||||
ImmutableMethod(
|
||||
type,
|
||||
"onBackPressed",
|
||||
emptyList(),
|
||||
"V",
|
||||
AccessFlags.PUBLIC.value,
|
||||
null,
|
||||
null,
|
||||
MutableMethodImplementation(3),
|
||||
).toMutable().apply {
|
||||
addInstructions(
|
||||
"""
|
||||
invoke-static {}, Lapp/revanced/extension/youtube/settings/SearchViewController;->handleBackPress()Z
|
||||
move-result v0
|
||||
if-nez v0, :search_handled
|
||||
invoke-virtual { p0 }, Landroid/app/Activity;->finish()V
|
||||
:search_handled
|
||||
return-void
|
||||
"""
|
||||
)
|
||||
}.let(methods::add)
|
||||
|
||||
// Add onConfigurationChanged method to handle configuration changes (e.g., screen orientation).
|
||||
ImmutableMethod(
|
||||
type,
|
||||
"onConfigurationChanged",
|
||||
listOf(ImmutableMethodParameter("Landroid/content/res/Configuration;", null, null)),
|
||||
"V",
|
||||
AccessFlags.PUBLIC.value,
|
||||
null,
|
||||
null,
|
||||
MutableMethodImplementation(3)
|
||||
).toMutable().apply {
|
||||
addInstructions(
|
||||
"""
|
||||
invoke-super { p0, p1 }, Landroid/app/Activity;->onConfigurationChanged(Landroid/content/res/Configuration;)V
|
||||
invoke-static { p0, p1 }, $EXTENSION_CLASS_DESCRIPTOR->handleConfigurationChanged(Landroid/app/Activity;Landroid/content/res/Configuration;)V
|
||||
return-void
|
||||
"""
|
||||
)
|
||||
}.let(methods::add)
|
||||
}
|
||||
|
||||
// Update shared dark mode status based on YT theme.
|
||||
@@ -338,20 +372,18 @@ object PreferenceScreen : BasePreferenceScreen() {
|
||||
icon = "@drawable/revanced_settings_screen_05_player",
|
||||
layout = "@layout/preference_with_icon",
|
||||
)
|
||||
|
||||
val SHORTS = Screen(
|
||||
key = "revanced_settings_screen_06_shorts",
|
||||
summaryKey = null,
|
||||
icon = "@drawable/revanced_settings_screen_06_shorts",
|
||||
layout = "@layout/preference_with_icon",
|
||||
)
|
||||
|
||||
val SEEKBAR = Screen(
|
||||
key = "revanced_settings_screen_07_seekbar",
|
||||
summaryKey = null,
|
||||
icon = "@drawable/revanced_settings_screen_07_seekbar",
|
||||
layout = "@layout/preference_with_icon",
|
||||
)
|
||||
)
|
||||
val SWIPE_CONTROLS = Screen(
|
||||
key = "revanced_settings_screen_08_swipe_controls",
|
||||
summaryKey = null,
|
||||
|
||||
@@ -9,7 +9,6 @@ import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||
import app.revanced.patches.shared.misc.settings.preference.InputType
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.TextPreference
|
||||
import app.revanced.patches.youtube.interaction.seekbar.disableFastForwardNoticeFingerprint
|
||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter
|
||||
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package app.revanced.patches.youtube.video.speed.custom
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstruction
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||
|
||||
internal val speedLimiterFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
@@ -19,3 +22,16 @@ internal val speedLimiterFingerprint = fingerprint {
|
||||
Opcode.INVOKE_STATIC,
|
||||
)
|
||||
}
|
||||
|
||||
internal val disableFastForwardNoticeFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("V")
|
||||
parameters()
|
||||
custom { method, _ ->
|
||||
method.name == "run" && method.indexOfFirstInstruction {
|
||||
// In later targets the code is found in different methods with different strings.
|
||||
val string = getReference<StringReference>()?.string
|
||||
string == "Failed to easy seek haptics vibrate." || string == "search_landing_cache_key"
|
||||
} >= 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ internal fun Node.addResource(
|
||||
appendChild(resource.serialize(ownerDocument, resourceCallback))
|
||||
}
|
||||
|
||||
internal fun org.w3c.dom.Document.getNode(tagName: String) = this.getElementsByTagName(tagName).item(0)
|
||||
internal fun Document.getNode(tagName: String) = getElementsByTagName(tagName).item(0)
|
||||
|
||||
internal fun NodeList.findElementByAttributeValue(attributeName: String, value: String): Element? {
|
||||
for (i in 0 until length) {
|
||||
|
||||
@@ -40,7 +40,7 @@ Second \"item\" text"</string>
|
||||
</patch>
|
||||
<patch id="layout.hide.general.hideLayoutComponentsPatch">
|
||||
<!-- 'Notify me' should be translated using the same localized wording YouTube displays.
|
||||
This item appear in the subscription feed for future livestreams or unreleased videos. -->
|
||||
This item appear in the Subscriptions feed for future livestreams or unreleased videos. -->
|
||||
<!-- 'Show more' should be translated with the same localized wording that YouTube displays.
|
||||
This button usually appears when searching for a YT creator. -->
|
||||
<!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. -->
|
||||
@@ -58,7 +58,6 @@ Second \"item\" text"</string>
|
||||
</patch>
|
||||
<patch id="ad.general.hideAdsResourcePatch">
|
||||
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
|
||||
<!-- 'View products' should be translated with the same localized wording that YouTube displays. -->
|
||||
</patch>
|
||||
<patch id="ad.getpremium.hideGetPremiumPatch">
|
||||
</patch>
|
||||
@@ -68,6 +67,8 @@ Second \"item\" text"</string>
|
||||
</patch>
|
||||
<patch id="interaction.dialog.removeViewerDiscretionDialogPatch">
|
||||
</patch>
|
||||
<patch id="interaction.doubletap.disableDoubleTapActionsPatch">
|
||||
</patch>
|
||||
<patch id="interaction.downloads.downloadsResourcePatch">
|
||||
<!-- 'Download action button' should be translated using the same wording as the translation of 'revanced_hide_download_button_title'. -->
|
||||
</patch>
|
||||
@@ -126,6 +127,7 @@ Second \"item\" text"</string>
|
||||
<patch id="layout.hide.rollingnumber.disableRollingNumberAnimationPatch">
|
||||
</patch>
|
||||
<patch id="layout.hide.seekbar.hideSeekbarPatch">
|
||||
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
|
||||
</patch>
|
||||
<patch id="layout.hide.shorts.hideShortsComponentsResourcePatch">
|
||||
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
|
||||
@@ -168,8 +170,6 @@ Second \"item\" text"</string>
|
||||
<patch id="layout.formfactor.changeFormFactorPatch">
|
||||
</patch>
|
||||
<patch id="layout.spoofappversion.spoofAppVersionPatch">
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||
</patch>
|
||||
<patch id="layout.startpage.changeStartPagePatch">
|
||||
</patch>
|
||||
|
||||
@@ -40,7 +40,7 @@ Second \"item\" text"</string>
|
||||
</patch>
|
||||
<patch id="layout.hide.general.hideLayoutComponentsPatch">
|
||||
<!-- 'Notify me' should be translated using the same localized wording YouTube displays.
|
||||
This item appear in the subscription feed for future livestreams or unreleased videos. -->
|
||||
This item appear in the Subscriptions feed for future livestreams or unreleased videos. -->
|
||||
<!-- 'Show more' should be translated with the same localized wording that YouTube displays.
|
||||
This button usually appears when searching for a YT creator. -->
|
||||
<!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. -->
|
||||
@@ -58,7 +58,6 @@ Second \"item\" text"</string>
|
||||
</patch>
|
||||
<patch id="ad.general.hideAdsResourcePatch">
|
||||
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
|
||||
<!-- 'View products' should be translated with the same localized wording that YouTube displays. -->
|
||||
</patch>
|
||||
<patch id="ad.getpremium.hideGetPremiumPatch">
|
||||
</patch>
|
||||
@@ -68,6 +67,8 @@ Second \"item\" text"</string>
|
||||
</patch>
|
||||
<patch id="interaction.dialog.removeViewerDiscretionDialogPatch">
|
||||
</patch>
|
||||
<patch id="interaction.doubletap.disableDoubleTapActionsPatch">
|
||||
</patch>
|
||||
<patch id="interaction.downloads.downloadsResourcePatch">
|
||||
<!-- 'Download action button' should be translated using the same wording as the translation of 'revanced_hide_download_button_title'. -->
|
||||
</patch>
|
||||
@@ -126,6 +127,7 @@ Second \"item\" text"</string>
|
||||
<patch id="layout.hide.rollingnumber.disableRollingNumberAnimationPatch">
|
||||
</patch>
|
||||
<patch id="layout.hide.seekbar.hideSeekbarPatch">
|
||||
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
|
||||
</patch>
|
||||
<patch id="layout.hide.shorts.hideShortsComponentsResourcePatch">
|
||||
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
|
||||
@@ -168,8 +170,6 @@ Second \"item\" text"</string>
|
||||
<patch id="layout.formfactor.changeFormFactorPatch">
|
||||
</patch>
|
||||
<patch id="layout.spoofappversion.spoofAppVersionPatch">
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||
</patch>
|
||||
<patch id="layout.startpage.changeStartPagePatch">
|
||||
</patch>
|
||||
|
||||
@@ -137,7 +137,7 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_debug_logs_disabled">تم تعطيل تسجيلات تصحيح الأخطاء</string>
|
||||
<string name="revanced_debug_logs_none_found">لم يتم العثور على سجلات</string>
|
||||
<string name="revanced_debug_logs_copied_to_clipboard">تم نسخ السجلات</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">فشل تصدير السجلات: $s</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">فشل تصدير السجلات: %s</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_title">مسح سجلات تصحيح الأخطاء</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_summary">يمسح جميع سجلات تصحيح أخطاء ReVanced المخزنة</string>
|
||||
<string name="revanced_debug_logs_clear_toast">تم مسح السجلات</string>
|
||||
@@ -164,9 +164,6 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_expandable_card_title">إخفاء البطاقة القابلة للتوسيع</string>
|
||||
<string name="revanced_hide_expandable_card_summary_on">البطاقة القابلة للتوسيع أسفل مقاطع الفيديو مخفية</string>
|
||||
<string name="revanced_hide_expandable_card_summary_off">البطاقة القابلة للتوسيع أسفل مقاطع الفيديو معروضة</string>
|
||||
<string name="revanced_hide_feed_survey_title">إخفاء الاستبيانات</string>
|
||||
<string name="revanced_hide_feed_survey_summary_on">تم إخفاء الاستبيانات</string>
|
||||
<string name="revanced_hide_feed_survey_summary_off">يتم عرض الاستبيانات</string>
|
||||
<string name="revanced_hide_floating_microphone_button_title">إخفاء زر الميكروفون العائم</string>
|
||||
<string name="revanced_hide_floating_microphone_button_summary_on">زر الميكروفون العائم في البحث مخفي</string>
|
||||
<string name="revanced_hide_floating_microphone_button_summary_off">يظهر زر الميكروفون العائم في البحث</string>
|
||||
@@ -192,7 +189,7 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_movies_section_summary_on">تم إخفاء قسم الأفلام</string>
|
||||
<string name="revanced_hide_movies_section_summary_off">يتم عرض قسم الأفلام</string>
|
||||
<!-- 'Notify me' should be translated using the same localized wording YouTube displays.
|
||||
This item appear in the subscription feed for future livestreams or unreleased videos. -->
|
||||
This item appear in the Subscriptions feed for future livestreams or unreleased videos. -->
|
||||
<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>
|
||||
@@ -204,6 +201,9 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_show_more_button_title">إخفاء زر \'عرض المزيد\'</string>
|
||||
<string name="revanced_hide_show_more_button_summary_on">زر إظهار المزيد في نتائج البحث مخفي</string>
|
||||
<string name="revanced_hide_show_more_button_summary_off">زر إظهار المزيد في نتائج البحث معروض</string>
|
||||
<string name="revanced_hide_surveys_title">إخفاء الاستبيانات</string>
|
||||
<string name="revanced_hide_surveys_summary_on">الاستطلاعات مخفية</string>
|
||||
<string name="revanced_hide_surveys_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>
|
||||
@@ -251,8 +251,8 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_timed_reactions_summary_on">تم إخفاء ردود الفعل المؤقتة</string>
|
||||
<string name="revanced_hide_timed_reactions_summary_off">يتم عرض ردود الفعل المؤقتة</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_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>
|
||||
@@ -280,19 +280,19 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_description_components_screen_title">وصف الفيديو</string>
|
||||
<string name="revanced_hide_description_components_screen_summary">إخفاء أو عرض مكونات وصف الفيديو</string>
|
||||
<string name="revanced_hide_filter_bar_screen_title">شريط التصفية</string>
|
||||
<string name="revanced_hide_filter_bar_screen_summary">إخفاء أو إظهار شريط الفلترة في الخلاصات، السجل، نتائج البحث، والفيديوهات ذات الصلة</string>
|
||||
<string name="revanced_hide_filter_bar_screen_summary">إخفاء أو إظهار شريط الفلترة في الموجزات ومقاطع الفيديو ذات الصلة ونتائج البحث وسجل المشاهدة</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_feed_title">إخفاء في الموجز</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_feed_summary_on">مخفي في الموجز</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_feed_summary_off">معروض في الموجز</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_title">إخفاء في السجل</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_on">مخفي في السجل</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_off">معروض في السجل</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_title">إخفاء في نتائج البحث</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_on">مخفي في نتائج البحث</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_off">يُعرض في نتائج البحث</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_title">إخفاء في الفيديوهات ذات الصلة</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_summary_on">مخفي في الفيديوهات ذات الصلة</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_summary_off">يُعرض في الفيديوهات ذات الصلة</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_title">إخفاء في نتائج البحث</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_on">مخفي في نتائج البحث</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_off">يُعرض في نتائج البحث</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_title">إخفاء في سجل المشاهدة</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_on">مخفي في سجل المشاهدة</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_off">معروض في سجل المشاهدة</string>
|
||||
<string name="revanced_channel_screen_title">صفحة القناة</string>
|
||||
<string name="revanced_channel_screen_summary">إخفاء أو إظهار مكونات صفحة القناة</string>
|
||||
<!-- 'For You' should be translated using the same localized wording YouTube displays. -->
|
||||
@@ -315,12 +315,12 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_visit_store_button_summary_off">زر زيارة المتجر معروض</string>
|
||||
<string name="revanced_comments_screen_title">التعليقات</string>
|
||||
<string name="revanced_comments_screen_summary">إخفاء أو عرض مكونات قسم التعليقات</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_title">إخفاء ملخص محادثات الذكاء الاصطناعي</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_on">تم إخفاء ملخص المحادثات</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_off">يتم عرض ملخص المحادثات</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_title">إخفاء ملخص الدردشة بالذكاء الاصطناعي</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_on">ملخص الدردشة بالذكاء الاصطناعي مخفي</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_off">ملخص الدردشة بالذكاء الاصطناعي معروض</string>
|
||||
<string name="revanced_hide_comments_ai_summary_title">إخفاء ملخص تعليقات الذكاء الاصطناعي</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_on">تم إخفاء ملخص التعليقات</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_off">يتم عرض ملخص التعليقات</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_on">ملخص تعليقات الذكاء الاصطناعي مخفي</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_off">ملخص تعليقات الذكاء الاصطناعي معروض</string>
|
||||
<string name="revanced_hide_comments_channel_guidelines_title">إخفاء إرشادات القناة</string>
|
||||
<string name="revanced_hide_comments_channel_guidelines_summary_on">إرشادات القناة مخفية</string>
|
||||
<string name="revanced_hide_comments_channel_guidelines_summary_off">إرشادات القناة معروضة</string>
|
||||
@@ -419,7 +419,6 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_shopping_links_title">إخفاء روابط التسوق</string>
|
||||
<string name="revanced_hide_shopping_links_summary_on">روابط التسوق في وصف الفيديو مخفية</string>
|
||||
<string name="revanced_hide_shopping_links_summary_off">يتم عرض روابط التسوق في وصف الفيديو</string>
|
||||
<!-- 'View products' should be translated with the same localized wording that YouTube displays. -->
|
||||
<string name="revanced_hide_view_products_banner_title">إخفاء لافتة \'عرض المنتجات\'</string>
|
||||
<string name="revanced_hide_view_products_banner_summary_on">لافتة عرض المنتجات في تراكب الفيديو مخفية</string>
|
||||
<string name="revanced_hide_view_products_banner_summary_off">لافتة عرض المنتجات في تراكب الفيديو معروضة</string>
|
||||
@@ -453,6 +452,11 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_remove_viewer_discretion_dialog_summary_off">سيتم عرض مربع الحوار</string>
|
||||
<string name="revanced_remove_viewer_discretion_dialog_user_dialog_message">وهذا لا يتجاوز قيود السن. بل يقبلها تلقائيًا.</string>
|
||||
</patch>
|
||||
<patch id="interaction.doubletap.disableDoubleTapActionsPatch">
|
||||
<string name="revanced_disable_chapter_skip_double_tap_title">تعطيل تخطي الفصل بالنقر المزدوج</string>
|
||||
<string name="revanced_disable_chapter_skip_double_tap_summary_on">لا يمكن للنقر المزدوج مطلقًا أن يؤدي إلى تخطي الفصل التالي/السابق</string>
|
||||
<string name="revanced_disable_chapter_skip_double_tap_summary_off">يمكن للنقر المزدوج أن يؤدي أحيانًا إلى تخطي الفصل التالي/السابق</string>
|
||||
</patch>
|
||||
<patch id="interaction.downloads.downloadsResourcePatch">
|
||||
<string name="revanced_external_downloader_screen_title">التنزيلات الخارجية</string>
|
||||
<string name="revanced_external_downloader_screen_summary">إعدادات لاستخدام أداة التنزيل الخارجية</string>
|
||||
@@ -464,8 +468,15 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_on">يفتح زر التنزيل أداة التنزيل الخارجية</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_off">يفتح زر التنزيل أداة التنزيل الأصلية داخل التطبيق</string>
|
||||
<string name="revanced_external_downloader_name_title">اسم حزمة أداة التنزيل</string>
|
||||
<string name="revanced_external_downloader_name_summary">اسم الحزمة لتطبيق التنزيل الخارجي المثبت لديك، مثل NewPipe أو Seal</string>
|
||||
<string name="revanced_external_downloader_name_summary">اسم حزمة تطبيق التنزيل الخارجي المثبت لديك</string>
|
||||
<string name="revanced_external_downloader_other_item_hint">أدخل اسم الحزمة</string>
|
||||
<string name="revanced_external_downloader_other_item">أخرى</string>
|
||||
<string name="revanced_external_downloader_not_found_title">التطبيق غير مثبت</string>
|
||||
<string name="revanced_external_downloader_not_installed_warning">لم يتم تثبيت %s . الرجاء تثبيته.</string>
|
||||
<string name="revanced_external_downloader_package_not_found_warning">"تعذر العثور على التطبيق المثبت باسم الحزمة: %s
|
||||
|
||||
تأكد من أن اسم الحزمة صحيح وأن التطبيق مثبت"</string>
|
||||
<string name="revanced_external_downloader_empty_warning">لا يمكن أن يكون اسم الحزمة فارغًا</string>
|
||||
</patch>
|
||||
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
|
||||
<string name="revanced_disable_precise_seeking_gesture_title">تعطيل إيماءة التمرير الدقيقة</string>
|
||||
@@ -697,9 +708,9 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_cast_button_title">إخفاء زر البث</string>
|
||||
<string name="revanced_hide_cast_button_summary_on">تم إخفاء زر البث</string>
|
||||
<string name="revanced_hide_cast_button_summary_off">يتم عرض زر البث</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_title">إخفاء خلفية أزرار التحكم في المشغل</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_summary_on">تم إخفاء خلفية أزرار التحكم في المشغل</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_summary_off">تم إظهار خلفية أزرار التحكم في المشغل</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_title">إخفاء خلفية عناصر التحكم بالمشغل</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_summary_on">خلفية عناصر تحكم المشغل مخفية</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_summary_off">يتم عرض خلفية عناصر التحكم بالمشغل</string>
|
||||
<string name="revanced_hide_player_previous_next_buttons_title">إخفاء زري \"السابق\" و \"التالي\"</string>
|
||||
<string name="revanced_hide_player_previous_next_buttons_summary_on">تم إخفاء الأزرار</string>
|
||||
<string name="revanced_hide_player_previous_next_buttons_summary_off">يتم عرض الأزرار</string>
|
||||
@@ -725,27 +736,28 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_disable_rolling_number_animations_summary_off">عدد مرات المشاهدة والإعجابات متحركة</string>
|
||||
</patch>
|
||||
<patch id="layout.hide.seekbar.hideSeekbarPatch">
|
||||
<string name="revanced_hide_seekbar_title">إخفاء شريط التقدم في مشغل الفيديو</string>
|
||||
<string name="revanced_hide_seekbar_title">إخفاء شريط تقدم مشغل الفيديو</string>
|
||||
<string name="revanced_hide_seekbar_summary_on">تم إخفاء شريط تقدم الفيديو</string>
|
||||
<string name="revanced_hide_seekbar_summary_off">يتم عرض شريط تقدم الفيديو</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_title">إخفاء شريط التقدم في مُصَّغَرات الفيديو</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_on">تم إخفاء مصغرة شريط التقدم</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_off">يتم عرض مصغرة شريط التقدم</string>
|
||||
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
|
||||
<string name="revanced_hide_seekbar_thumbnail_title">إخفاء شريط تقدم صور مصغرة للفيديو</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_on">شريط تقدم صور مصغرة للفيديو مخفي</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_off">شريط تقدم صور مصغرة للفيديو معروض</string>
|
||||
</patch>
|
||||
<patch id="layout.hide.shorts.hideShortsComponentsResourcePatch">
|
||||
<string name="revanced_shorts_player_screen_title">مشغل Shorts</string>
|
||||
<string name="revanced_shorts_player_screen_summary">إخفاء أو عرض المكونات في مشغل Shorts</string>
|
||||
<string name="revanced_shorts_player_screen_summary">إخفاء أو إظهار مكونات مشغل Shorts</string>
|
||||
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
|
||||
<string name="revanced_hide_shorts_home_title">إخفاء Shorts في الصفحة الرئيسية</string>
|
||||
<string name="revanced_hide_shorts_home_summary_on">مخفية في الصفحة الرئيسية ومقاطع الفيديو ذات الصلة</string>
|
||||
<string name="revanced_hide_shorts_home_summary_off">معروضة في الصفحة الرئيسية ومقاطع الفيديو ذات الصلة</string>
|
||||
<string name="revanced_hide_shorts_search_title">إخفاء Shorts في نتائج البحث</string>
|
||||
<string name="revanced_hide_shorts_search_summary_on">مخفية في نتائج البحث</string>
|
||||
<string name="revanced_hide_shorts_search_summary_off">تُعرض في نتائج البحث</string>
|
||||
<!-- 'Subscriptions' should be translated using the same localized wording YouTube displays for the Subscriptions tab. -->
|
||||
<string name="revanced_hide_shorts_subscriptions_title">إخفاء Shorts في خلاصة الاشتراكات</string>
|
||||
<string name="revanced_hide_shorts_subscriptions_summary_on">مخفية في خلاصة الاشتراكات</string>
|
||||
<string name="revanced_hide_shorts_subscriptions_summary_off">معروضة في خلاصة الاشتراكات</string>
|
||||
<string name="revanced_hide_shorts_search_title">إخفاء Shorts في نتائج البحث</string>
|
||||
<string name="revanced_hide_shorts_search_summary_on">مخفية في نتائج البحث</string>
|
||||
<string name="revanced_hide_shorts_search_summary_off">تُعرض في نتائج البحث</string>
|
||||
<string name="revanced_hide_shorts_history_title">إخفاء Shorts في سجل المشاهدة</string>
|
||||
<string name="revanced_hide_shorts_history_summary_on">مخفية في سجل المشاهدة</string>
|
||||
<string name="revanced_hide_shorts_history_summary_off">تُعرض في سجل المشاهدة</string>
|
||||
@@ -1185,8 +1197,6 @@ Second \"item\" text"</string>
|
||||
سيؤدي هذا إلى تغيير مظهر ومميزات التطبيق، ولكن قد تحدث تأثيرات جانبية غير معروفة.
|
||||
|
||||
إذا تم إيقاف تشغيله لاحقًا، من المستحسن مسح بيانات التطبيق لمنع حدوث أخطاء في واجهة المستخدم."</string>
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||
<string name="revanced_spoof_app_version_target_title">الهدف من تغيير إصدار التطبيق</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 - استعادة أيقونات مشغل Shorts القديمة</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 - استعادة أيقونات التنقل القديمة</string>
|
||||
|
||||
@@ -40,7 +40,7 @@ Second \"item\" text"</string>
|
||||
</patch>
|
||||
<patch id="layout.hide.general.hideLayoutComponentsPatch">
|
||||
<!-- 'Notify me' should be translated using the same localized wording YouTube displays.
|
||||
This item appear in the subscription feed for future livestreams or unreleased videos. -->
|
||||
This item appear in the Subscriptions feed for future livestreams or unreleased videos. -->
|
||||
<!-- 'Show more' should be translated with the same localized wording that YouTube displays.
|
||||
This button usually appears when searching for a YT creator. -->
|
||||
<!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. -->
|
||||
@@ -58,7 +58,6 @@ Second \"item\" text"</string>
|
||||
</patch>
|
||||
<patch id="ad.general.hideAdsResourcePatch">
|
||||
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
|
||||
<!-- 'View products' should be translated with the same localized wording that YouTube displays. -->
|
||||
</patch>
|
||||
<patch id="ad.getpremium.hideGetPremiumPatch">
|
||||
</patch>
|
||||
@@ -68,6 +67,8 @@ Second \"item\" text"</string>
|
||||
</patch>
|
||||
<patch id="interaction.dialog.removeViewerDiscretionDialogPatch">
|
||||
</patch>
|
||||
<patch id="interaction.doubletap.disableDoubleTapActionsPatch">
|
||||
</patch>
|
||||
<patch id="interaction.downloads.downloadsResourcePatch">
|
||||
<!-- 'Download action button' should be translated using the same wording as the translation of 'revanced_hide_download_button_title'. -->
|
||||
</patch>
|
||||
@@ -126,6 +127,7 @@ Second \"item\" text"</string>
|
||||
<patch id="layout.hide.rollingnumber.disableRollingNumberAnimationPatch">
|
||||
</patch>
|
||||
<patch id="layout.hide.seekbar.hideSeekbarPatch">
|
||||
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
|
||||
</patch>
|
||||
<patch id="layout.hide.shorts.hideShortsComponentsResourcePatch">
|
||||
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
|
||||
@@ -168,8 +170,6 @@ Second \"item\" text"</string>
|
||||
<patch id="layout.formfactor.changeFormFactorPatch">
|
||||
</patch>
|
||||
<patch id="layout.spoofappversion.spoofAppVersionPatch">
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||
</patch>
|
||||
<patch id="layout.startpage.changeStartPagePatch">
|
||||
</patch>
|
||||
|
||||
@@ -137,7 +137,6 @@ Gözlənilməz hallardan xəbərdar olmayacaqsınız."</string>
|
||||
<string name="revanced_debug_logs_disabled">Sazlama qeydi qapalıdır</string>
|
||||
<string name="revanced_debug_logs_none_found">Qeydlər tapılmadı</string>
|
||||
<string name="revanced_debug_logs_copied_to_clipboard">Qeydlər köçürüldü</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">Qeydləri ixrac etmək alınmadı: $s</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_title">Sazlama qeydlərini təmizlə</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_summary">Saxlanılan bütün ReVanced sazlama qeydlərini təmizləyir</string>
|
||||
<string name="revanced_debug_logs_clear_toast">Qeydlər silindi</string>
|
||||
@@ -164,11 +163,18 @@ Gözlənilməz hallardan xəbərdar olmayacaqsınız."</string>
|
||||
<string name="revanced_hide_expandable_card_title">Genişlənən kartı gizlət</string>
|
||||
<string name="revanced_hide_expandable_card_summary_on">Videoların aşağısında genişlənən kart gizlidir</string>
|
||||
<string name="revanced_hide_expandable_card_summary_off">Videoların altında genişlənən kart görünür</string>
|
||||
<string name="revanced_hide_feed_survey_title">Axın sorğuların gizlət</string>
|
||||
<string name="revanced_hide_feed_survey_summary_on">Axın sorğuları gizlidir</string>
|
||||
<string name="revanced_hide_feed_survey_summary_off">Axın sorğuları göstərilir</string>
|
||||
<string name="revanced_hide_floating_microphone_button_title">Üzən mikrofon düyməsini gizlət</string>
|
||||
<string name="revanced_hide_floating_microphone_button_summary_on">Axtarışda üzən mikrofon düyməsi gizlidir</string>
|
||||
<string name="revanced_hide_floating_microphone_button_summary_off">Üzən mikrofon düyməsi axtarışda göstərilir</string>
|
||||
<string name="revanced_hide_horizontal_shelves_title">Üfüqi hissələri gizlət</string>
|
||||
<string name="revanced_hide_horizontal_shelves_summary_on">"Üfüqi cərgələr gizlidir, məsələn:
|
||||
• Son xəbərlər
|
||||
• İzləməyə davam et
|
||||
• Daha çox kanal kəşf et
|
||||
• Ən uyğun
|
||||
• Alış-veriş
|
||||
• Yenidən izləyin"</string>
|
||||
<string name="revanced_hide_horizontal_shelves_summary_off">Üfüqi cərgələr görünür</string>
|
||||
<string name="revanced_hide_image_shelf_title">Şəkil cərgəsin gizlət</string>
|
||||
<string name="revanced_hide_image_shelf_summary_on">Şəkil cərgəsi axtarış nəticələrində gizlidir</string>
|
||||
<string name="revanced_hide_image_shelf_summary_off">Şəkil cərgəsi axtarış nəticələrində görünür</string>
|
||||
@@ -182,20 +188,32 @@ Gözlənilməz hallardan xəbərdar olmayacaqsınız."</string>
|
||||
<string name="revanced_hide_movies_section_summary_on">Filmlər bölməsi gizlidir</string>
|
||||
<string name="revanced_hide_movies_section_summary_off">Filmlər bölməsi göstərilir</string>
|
||||
<!-- 'Notify me' should be translated using the same localized wording YouTube displays.
|
||||
This item appear in the subscription feed for future livestreams or unreleased videos. -->
|
||||
This item appear in the Subscriptions feed for future livestreams or unreleased videos. -->
|
||||
<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">Mənə bildir düyməsi gizlidir</string>
|
||||
<string name="revanced_hide_notify_me_button_summary_off">Mənə bildir düyməsi görünür</string>
|
||||
<string name="revanced_hide_playables_title">Oynadılan elementləri gizlət</string>
|
||||
<string name="revanced_hide_playables_summary_on">Oynadılanlar gizlidir</string>
|
||||
<string name="revanced_hide_playables_summary_off">Oynadılanlar göstərilir</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">Daha çox göstər düyməsi axtarış nəticələrində gizlidir</string>
|
||||
<string name="revanced_hide_show_more_button_summary_off">Daha çox göstər düyməsi axtarış nəticələrində görünür</string>
|
||||
<string name="revanced_hide_surveys_title">Sorğuları gizlət</string>
|
||||
<string name="revanced_hide_surveys_summary_on">Sorğular gizlədilib</string>
|
||||
<string name="revanced_hide_surveys_summary_off">Sorğular görünür</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>
|
||||
<!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. -->
|
||||
<string name="revanced_hide_video_recommendation_labels_title">Video tövsiyə etiketlərini gizlət</string>
|
||||
<string name="revanced_hide_video_recommendation_labels_summary_on">\'İnsanlar həmçinin izləyiblər\' və \'Bunu da bəyənə bilərsiniz\' etiketləri axtarış nəticələrində gizlədilib</string>
|
||||
<string name="revanced_hide_video_recommendation_labels_summary_off">\'İnsanlar həmçinin izləyiblər\' və \'Bunu da bəyənə bilərsiniz\' etiketləri axtarış nəticələrində görünür</string>
|
||||
<!-- https://logos.fandom.com/wiki/YouTube/Yoodles -->
|
||||
<string name="revanced_hide_doodles_title">YouTube Doodle-ları gizlət</string>
|
||||
<string name="revanced_hide_doodles_summary_on">YouTube Doodles animasiyası simvolda gizlidir</string>
|
||||
<string name="revanced_hide_doodles_summary_off">YouTube Doodles animasiyası simvolda görünür</string>
|
||||
<string name="revanced_hide_doodles_user_dialog_message">"YouTube Doodle-ları hər il bir neçə gün görünür.
|
||||
|
||||
Əgər hazırda bölgənizdə Doodle göstərilirsə və bu gizlətmə seçimi aktivdirsə, axtarış cizgisi aşağısındakı filtr sahəsi də gizlədiləcək."</string>
|
||||
@@ -214,6 +232,8 @@ Gözlənilməz hallardan xəbərdar olmayacaqsınız."</string>
|
||||
<!-- 'Join' should be translated using the same localized wording YouTube displays.
|
||||
This appears in the video player for certain videos. -->
|
||||
<string name="revanced_hide_join_membership_button_title">Qoşul düyməsin gizlət</string>
|
||||
<string name="revanced_hide_join_membership_button_summary_on">Qoşul düyməsi gizlidir</string>
|
||||
<string name="revanced_hide_join_membership_button_summary_off">Qoşul düyməsi görünür</string>
|
||||
<string name="revanced_hide_medical_panels_title">Tibbi lövhələri gizlət</string>
|
||||
<string name="revanced_hide_medical_panels_summary_on">Tibbi lövhələr gizlidir</string>
|
||||
<string name="revanced_hide_medical_panels_summary_off">Tibbi lövhələr göstərilir</string>
|
||||
@@ -230,8 +250,8 @@ Gözlənilməz hallardan xəbərdar olmayacaqsınız."</string>
|
||||
<string name="revanced_hide_timed_reactions_summary_on">Zamanlanmış reaksiyalar gizlədilir</string>
|
||||
<string name="revanced_hide_timed_reactions_summary_off">Zamanlanmış reaksiyalar göstərilir</string>
|
||||
<string name="revanced_hide_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_ai_generated_video_summary_section_summary_on">Sİ ilə yaradılan video xülasə bölməsi gizlədilib</string>
|
||||
<string name="revanced_hide_ai_generated_video_summary_section_summary_off">Sİ ilə yaradılan video xülasə bölməsi görünür</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>
|
||||
@@ -259,19 +279,19 @@ Gözlənilməz hallardan xəbərdar olmayacaqsınız."</string>
|
||||
<string name="revanced_hide_description_components_screen_title">Video təsviri</string>
|
||||
<string name="revanced_hide_description_components_screen_summary">Video təsviri elementlərini gizlət və ya göstər</string>
|
||||
<string name="revanced_hide_filter_bar_screen_title">Filtr çubuğu</string>
|
||||
<string name="revanced_hide_filter_bar_screen_summary">Axınlar, tarixçə, axtarış nəticələri və əlaqəli videolarda filtr panelini gizlət və ya göstər</string>
|
||||
<string name="revanced_hide_filter_bar_screen_summary">Axınlar, əlaqəli videolar, axtarış nəticələri və baxış tarixçəsində filtr cərgəsin gizlət və ya göstər</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_feed_title">Axınlarda gizlət</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_feed_summary_on">Axınlarda gizlidir</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_feed_summary_off">Axınlarda göstər</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_title">Tarixçədə gizlət</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_on">Tarixçədə gizlədilib</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_off">Tarixçədə göstərilib</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_title">Axtarış nəticələrində gizlət</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_on">Axtarış nəticələrində gizlədilib</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_off">Axtarış nəticələrində göstərilir</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_title">Əlaqəli videolarda gizlət</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_summary_on">Əlaqəli videolarda gizlidir</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_summary_off">Əlaqəli videolarda görünür</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_title">Axtarış nəticələrində gizlət</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_on">Axtarış nəticələrində gizlədilib</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_off">Axtarış nəticələrində göstərilir</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_title">Baxış tarixçəsində gizlət</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_on">Baxış tarixçəsində gizlədilib</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_off">Baxış tarixçəsində görünür</string>
|
||||
<string name="revanced_channel_screen_title">Kanal səhifəsi</string>
|
||||
<string name="revanced_channel_screen_summary">Kanal səhifə elementlərini gizlət və ya göstər</string>
|
||||
<!-- 'For You' should be translated using the same localized wording YouTube displays. -->
|
||||
@@ -294,12 +314,12 @@ Gözlənilməz hallardan xəbərdar olmayacaqsınız."</string>
|
||||
<string name="revanced_hide_visit_store_button_summary_off">Mağazaya baxın düyməsi görünür</string>
|
||||
<string name="revanced_comments_screen_title">Şərhlər</string>
|
||||
<string name="revanced_comments_screen_summary">Şərhlər bölməsi elementlərin gizlət və ya göstər</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_title">AI Söhbət Xülasəsini Gizlət</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_on">Söhbət yekunu gizlidir </string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_off">Söhbət yekunu görünür</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_title">Sİ söhbət xülasəsini gizlət</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_on">Sİ söhbət xülasəsi gizlidir</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_off">Sİ söhbət xülasəsi görünür</string>
|
||||
<string name="revanced_hide_comments_ai_summary_title">AI Ṣərhlər Xülasəsini Gizlət</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_on">Ṣərh yekunu gizlidir </string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_off">Şərh yekunu görünür</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_on">Sİ şərhlər xülasəsi gizlidir</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_off">Sİ şərhlər xülasəsi görünür</string>
|
||||
<string name="revanced_hide_comments_channel_guidelines_title">Kanal təlimatlarını gizlət</string>
|
||||
<string name="revanced_hide_comments_channel_guidelines_summary_on">Kanal təlimatları gizlidir</string>
|
||||
<string name="revanced_hide_comments_channel_guidelines_summary_off">Kanal təlimatları görünür</string>
|
||||
@@ -371,7 +391,11 @@ Məhdudiyyətlər
|
||||
</patch>
|
||||
<patch id="ad.general.hideAdsResourcePatch">
|
||||
<string name="revanced_hide_creator_store_shelf_title">Yaradıcı mağaza bölümün gizlət</string>
|
||||
<string name="revanced_hide_creator_store_shelf_summary_on">Yaradıcı alış-veriş cərgəsi video oynadıcı altında gizlidir</string>
|
||||
<string name="revanced_hide_creator_store_shelf_summary_off">Yaradıcı alış-veriş cərgəsi video oynadıcı altında görünür</string>
|
||||
<string name="revanced_hide_end_screen_store_banner_title">Son ekran mağaza etiketini gizlət</string>
|
||||
<string name="revanced_hide_end_screen_store_banner_summary_on">Son ekran alış-veriş etiketi gizlədilib</string>
|
||||
<string name="revanced_hide_end_screen_store_banner_summary_off">Son ekran alış-veriş etiketi görünür</string>
|
||||
<string name="revanced_hide_fullscreen_ads_title">Tam ekran reklamlarını gizlət</string>
|
||||
<string name="revanced_hide_fullscreen_ads_summary_on">"Tam ekran reklamları gizlidir
|
||||
|
||||
@@ -392,8 +416,11 @@ Bu xüsusiyyət yalnız köhnə cihazlar üçün mövcuddur"</string>
|
||||
<string name="revanced_hide_self_sponsor_ads_summary_on">Özünə sponsorluq edilən kartlar gizlidir</string>
|
||||
<string name="revanced_hide_self_sponsor_ads_summary_off">Özünə sponsorluq edilən kartlar göstərilir</string>
|
||||
<string name="revanced_hide_shopping_links_title">Alış-veriş linklərini gizlət</string>
|
||||
<!-- 'View products' should be translated with the same localized wording that YouTube displays. -->
|
||||
<string name="revanced_hide_shopping_links_summary_on">Alış-veriş linkləri video təsvirdə gizlidir</string>
|
||||
<string name="revanced_hide_shopping_links_summary_off">Alış-veriş linkləri video təsvirdə görünür</string>
|
||||
<string name="revanced_hide_view_products_banner_title">“Məhsullara baxın” panelin gizlət</string>
|
||||
<string name="revanced_hide_view_products_banner_summary_on">Məhsullara baxış etiketi video örtüyündə gizlidir</string>
|
||||
<string name="revanced_hide_view_products_banner_summary_off">Məhsullara baxış etiketi video örtüyündə görünür</string>
|
||||
<string name="revanced_hide_web_search_results_title">Veb axtarış nəticələrini gizlət</string>
|
||||
<string name="revanced_hide_web_search_results_summary_on">Veb axtarış nəticələri gizlədilir</string>
|
||||
<string name="revanced_hide_web_search_results_summary_off">Veb axtarış nəticələri göstərilir</string>
|
||||
@@ -424,6 +451,11 @@ Bu xüsusiyyət yalnız köhnə cihazlar üçün mövcuddur"</string>
|
||||
<string name="revanced_remove_viewer_discretion_dialog_summary_off">Dialoq göstərilir</string>
|
||||
<string name="revanced_remove_viewer_discretion_dialog_user_dialog_message">Bu, yaş məhdudiyyətini ötürmür. Sadəcə birbaşa qəbul edir.</string>
|
||||
</patch>
|
||||
<patch id="interaction.doubletap.disableDoubleTapActionsPatch">
|
||||
<string name="revanced_disable_chapter_skip_double_tap_title">Cüt toxunuşla fəsil ötürməsini qapat</string>
|
||||
<string name="revanced_disable_chapter_skip_double_tap_summary_on">Cüt toxunma heç vaxt növbəti/əvvəlki fəsilə keçidi zorlaya bilməz</string>
|
||||
<string name="revanced_disable_chapter_skip_double_tap_summary_off">Cüt toxunma bəzən növbəti/əvvəlki fəsilə keçidi zorlaya bilər</string>
|
||||
</patch>
|
||||
<patch id="interaction.downloads.downloadsResourcePatch">
|
||||
<string name="revanced_external_downloader_screen_title">Xarici yükləmələr</string>
|
||||
<string name="revanced_external_downloader_screen_summary">Xarici yükləyici istifadəsi üçün tənzimləmələr</string>
|
||||
@@ -435,7 +467,6 @@ Bu xüsusiyyət yalnız köhnə cihazlar üçün mövcuddur"</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_on">Yükləmə düyməsi, xarici yükləyicinizi açır</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_off">Yükləmə düyməsi tətbiqə xas yükləyicini açır</string>
|
||||
<string name="revanced_external_downloader_name_title">Yükləyici paketi adı</string>
|
||||
<string name="revanced_external_downloader_name_summary">NewPipe və ya Seal kimi quraşdırılan xarici yükləmə tətbiqinizin paket adı</string>
|
||||
<string name="revanced_external_downloader_not_installed_warning">%s quraşdırılmayıb. Lütfən, bunu quraşdır.</string>
|
||||
</patch>
|
||||
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
|
||||
@@ -668,9 +699,9 @@ Audio trek seçimin göstərmək üçün \"Video axınları saxtalaşdır\"ı iO
|
||||
<string name="revanced_hide_cast_button_title">Yayımla düyməsini gizlət</string>
|
||||
<string name="revanced_hide_cast_button_summary_on">Yayım düyməsi gizlidir</string>
|
||||
<string name="revanced_hide_cast_button_summary_off">Yayım düyməsi göstərilir</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_title">Oynadıcı idarəetmə düymələri fonunu gizlət</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_summary_on">Oynadıcı idarəetmə düymələri fonu gizlidir</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_summary_off">Oynadıcı idarəetmə düymələri fonu görünür</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_title">Oynadıcı idarəetmələri fonunu gizlət</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_summary_on">Oynadıcı idarəetmə fonu gizlədilib</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_summary_off">Oynadıcı idarəetmə fonu görünür</string>
|
||||
<string name="revanced_hide_player_previous_next_buttons_title">Əvvəlki və Növbəti düymələrin gizlət</string>
|
||||
<string name="revanced_hide_player_previous_next_buttons_summary_on">Düymələr gizlidir</string>
|
||||
<string name="revanced_hide_player_previous_next_buttons_summary_off">Düymələr göstərilir</string>
|
||||
@@ -696,25 +727,34 @@ Audio trek seçimin göstərmək üçün \"Video axınları saxtalaşdır\"ı iO
|
||||
<string name="revanced_disable_rolling_number_animations_summary_off">Sürüşən say animasiyası açıqdır</string>
|
||||
</patch>
|
||||
<patch id="layout.hide.seekbar.hideSeekbarPatch">
|
||||
<string name="revanced_hide_seekbar_title">Video oynadıcıda irəliləyiş cizgisin gizlə</string>
|
||||
<string name="revanced_hide_seekbar_title">Video oynadıcı irəliləyiş cizgisin gizlət</string>
|
||||
<string name="revanced_hide_seekbar_summary_on">Video oynadıcı irəliləyiş cizgisi gizlidir</string>
|
||||
<string name="revanced_hide_seekbar_summary_off">Video oynadıcı irəliləyiş cizgisi göstərilir</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_title">Video miniatürdə irəliləmə cizgisin gizlə</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_on">Miniatür irəliləmə cizgisi gizlədilib</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_off">Miniatür irəliləmə cizgisi göstərilir</string>
|
||||
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
|
||||
<string name="revanced_hide_seekbar_thumbnail_title">Video miniatür irəliləyiş cizgisin gizlət</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_on">Video miniatür irəliləyiş cizgisi gizlidir</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_off">Video miniatür irəliləyiş cizgisi görünür</string>
|
||||
</patch>
|
||||
<patch id="layout.hide.shorts.hideShortsComponentsResourcePatch">
|
||||
<string name="revanced_shorts_player_screen_title">Shorts oynadıcı</string>
|
||||
<string name="revanced_shorts_player_screen_summary">Shorts oynadıcıda hissəcikləri gizlət və ya göstər</string>
|
||||
<string name="revanced_shorts_player_screen_summary">Shorts oynadıcı elementlərini gizlət və ya göstər</string>
|
||||
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
|
||||
<!-- 'Subscriptions' should be translated using the same localized wording YouTube displays for the Subscriptions tab. -->
|
||||
<string name="revanced_hide_shorts_home_title">Shorts-u Ev axınında gizlət</string>
|
||||
<string name="revanced_hide_shorts_home_summary_on">Ev axını və əlaqəli videolarda gizlidir</string>
|
||||
<string name="revanced_hide_shorts_home_summary_off">Ev axını və əlaqəli videolarda görünür</string>
|
||||
<string name="revanced_hide_shorts_search_title">Axtarış nəticələrindəki \"Shorts\"u gizlət</string>
|
||||
<string name="revanced_hide_shorts_search_summary_on">Axtarış nəticələrində gizlidir</string>
|
||||
<string name="revanced_hide_shorts_search_summary_off">Axtarış nəticələrində görünür</string>
|
||||
<!-- 'Subscriptions' should be translated using the same localized wording YouTube displays for the Subscriptions tab. -->
|
||||
<string name="revanced_hide_shorts_subscriptions_title">Shorts-u Abunəliklər axınında gizlət</string>
|
||||
<string name="revanced_hide_shorts_subscriptions_summary_on">Abunəliklər axınında gizlidir</string>
|
||||
<string name="revanced_hide_shorts_subscriptions_summary_off">Abunəliklər axınında görünür</string>
|
||||
<string name="revanced_hide_shorts_history_title">Baxış tarixçəsində Shorts-u gizlət</string>
|
||||
<string name="revanced_hide_shorts_history_summary_on">Baxış tarixçəsində gizlidir</string>
|
||||
<string name="revanced_hide_shorts_history_summary_off">Baxış tarixçəsində göstərilib</string>
|
||||
<string name="revanced_hide_shorts_super_thanks_button_title">Super Təşəkkür Al düyməsini gizlət</string>
|
||||
<string name="revanced_hide_shorts_super_thanks_button_summary_on">Super Təşəkkürlər Al düyməsi gizlidir</string>
|
||||
<string name="revanced_hide_shorts_super_thanks_button_summary_off">Super Təşəkkürlər Al düyməsi görünür</string>
|
||||
<string name="revanced_hide_shorts_effect_button_title">Effekt düyməsini gizlət</string>
|
||||
<string name="revanced_hide_shorts_effect_button_summary_on">Effekt düyməsi gizlidir</string>
|
||||
<string name="revanced_hide_shorts_effect_button_summary_off">Effekt düyməsi görünür</string>
|
||||
@@ -797,7 +837,11 @@ Audio trek seçimin göstərmək üçün \"Video axınları saxtalaşdır\"ı iO
|
||||
<string name="revanced_hide_shorts_channel_bar_summary_on">Kanal çubuğu gizlidir</string>
|
||||
<string name="revanced_hide_shorts_channel_bar_summary_off">Kanal çubuğu göstərilir</string>
|
||||
<string name="revanced_hide_shorts_video_title_title">Video başlığını gizlət</string>
|
||||
<string name="revanced_hide_shorts_video_title_summary_on">Video başlığı gizlidir</string>
|
||||
<string name="revanced_hide_shorts_video_title_summary_off">Video başlığı görünür</string>
|
||||
<string name="revanced_hide_shorts_sound_metadata_label_title">Səs üst məlumat etiketini gizlət</string>
|
||||
<string name="revanced_hide_shorts_sound_metadata_label_summary_on">Səs üst məlumat etiketi gizlədilib</string>
|
||||
<string name="revanced_hide_shorts_sound_metadata_label_summary_off">Səs üst məlumat etiketi görünür</string>
|
||||
<string name="revanced_hide_shorts_full_video_link_label_title">Video keçidi etiketini gizlət</string>
|
||||
<string name="revanced_hide_shorts_full_video_link_label_summary_on">Video linki etiketi gizlidir</string>
|
||||
<string name="revanced_hide_shorts_full_video_link_label_summary_off">Video link etiketi göstərilir</string>
|
||||
@@ -813,6 +857,9 @@ Avtomatik oynatma YouTube ayarlarında dəyişdirilə bilər: Ayarlar → Oxunu
|
||||
<string name="revanced_end_screen_suggested_video_summary_off">Son ekranda bildirilən video göstərilir</string>
|
||||
</patch>
|
||||
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
|
||||
<string name="revanced_hide_related_videos_overlay_title">Əlaqəli videolar örtüyünü gizlət</string>
|
||||
<string name="revanced_hide_related_videos_overlay_summary_on">Əlaqəli videolar yerləşməsi tam ekranda gizlidir</string>
|
||||
<string name="revanced_hide_related_videos_overlay_summary_off">Əlaqəli videolar yerləşməsi tam ekranda görünür</string>
|
||||
</patch>
|
||||
<patch id="layout.hide.time.hideTimestampPatch">
|
||||
<string name="revanced_hide_timestamp_title">Video vaxt möhürünü gizlət</string>
|
||||
@@ -1140,8 +1187,6 @@ Avtomobil tərtibatı
|
||||
Bu tətbiqin görünüşün və xüsusiyyətlərin dəyişdirəcək, lakin bilinməyən yan təsirlər ola bilər.
|
||||
|
||||
Sonradan qapadılarsa, UI səhvlərin önləmək üçün tətbiq məlumatların silmək tövsiyə olunur."</string>
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||
<string name="revanced_spoof_app_version_target_title">Saxta tətbiq versiyası hədəfi</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 - Köhnə Shorts oynadıcı işarələrin bərpa et</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 - Köhnə fəaliyyət simvolların bərpa et</string>
|
||||
@@ -1279,8 +1324,10 @@ Bunu aktivləşdirmə, bəzi regionlarda əngəllənib silinən şəkilləri dü
|
||||
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
|
||||
<string name="revanced_alt_thumbnail_home_title">Ev paneli</string>
|
||||
<!-- 'Subscriptions' should be translated using the same localized wording YouTube displays for the Subscriptions tab. -->
|
||||
<string name="revanced_alt_thumbnail_subscription_title">Abunəliklər bölməsi</string>
|
||||
<!-- 'You' should be translated using the same localized wording YouTube displays for the You (Library) tab. -->
|
||||
<string name="revanced_alt_thumbnail_library_title">\"Siz\" paneli</string>
|
||||
<string name="revanced_alt_thumbnail_player_title">Oynadıcı pleylistləri & tövsiyələri</string>
|
||||
<string name="revanced_alt_thumbnail_search_title">Axtarış nəticələri</string>
|
||||
<string name="revanced_alt_thumbnail_options_entry_1">Orijinal miniatürlər</string>
|
||||
<string name="revanced_alt_thumbnail_options_entry_2">DeArrow & Orijinal miniatürlər</string>
|
||||
@@ -1496,6 +1543,7 @@ AVC maksimum 1080p görüntü imkanına malikdir, Opus audio kodlama olmur və v
|
||||
<string name="revanced_block_video_ads_summary_off">Video reklamlar bloklanmır</string>
|
||||
</patch>
|
||||
<patch id="chat.antidelete.showDeletedMessagesPatch">
|
||||
<string name="revanced_deleted_msg">Məlumat silindi</string>
|
||||
<string name="revanced_show_deleted_messages_title">Silinən mesajları göstər</string>
|
||||
<string name="revanced_show_deleted_messages_entry_1">Silinən mesajlar göstərilməsin</string>
|
||||
<string name="revanced_show_deleted_messages_entry_2">Silinmiş mesajları boz panel arxasında gizlət</string>
|
||||
@@ -1516,8 +1564,11 @@ AVC maksimum 1080p görüntü imkanına malikdir, Opus audio kodlama olmur və v
|
||||
<string name="revanced_settings">ReVanced Tənzimləmələri</string>
|
||||
<string name="revanced_about_title">Haqqında</string>
|
||||
<string name="revanced_about_summary">ReVanced Haqqında</string>
|
||||
<string name="revanced_ads_screen_title">Reklam Əngəlləmə</string>
|
||||
<string name="revanced_ads_screen_summary">Reklam Əngəlləmə tənzimləmələri</string>
|
||||
<string name="revanced_chat_screen_title">Söhbət</string>
|
||||
<string name="revanced_chat_screen_summary">Söhbət tənzimləmələri</string>
|
||||
<string name="revanced_misc_screen_title">Çoxvariantlı</string>
|
||||
<string name="revanced_misc_screen_summary">Müxtəlif tənzimləmələr</string>
|
||||
<string name="revanced_general_category_title">Ümumi tənzimləmələr</string>
|
||||
<string name="revanced_other_category_title">Digər tənzimləmələr</string>
|
||||
|
||||
@@ -137,7 +137,7 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_debug_logs_disabled">Адладачнае лагаванне адключана</string>
|
||||
<string name="revanced_debug_logs_none_found">Лагі не знойдзены</string>
|
||||
<string name="revanced_debug_logs_copied_to_clipboard">Лагі скапіяваны</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">Не атрымалася экспартаваць лагі: $s</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">Не ўдалося экспартаваць журналы: %s</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_title">Ачысціць адладачныя лагі</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_summary">Ачышчае ўсе захаваныя адладачныя лагі ReVanced</string>
|
||||
<string name="revanced_debug_logs_clear_toast">Лагі ачышчаны</string>
|
||||
@@ -164,9 +164,6 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_expandable_card_title">Схаваць раскладвальную картку</string>
|
||||
<string name="revanced_hide_expandable_card_summary_on">Раскладвальная картка пад відэа схаваная</string>
|
||||
<string name="revanced_hide_expandable_card_summary_off">Раскладвальная картка пад відэа паказаная</string>
|
||||
<string name="revanced_hide_feed_survey_title">Схаваць апытанні стужкі</string>
|
||||
<string name="revanced_hide_feed_survey_summary_on">Апытанні каналаў схаваныя</string>
|
||||
<string name="revanced_hide_feed_survey_summary_off">Паказваюцца даследаванні кармоў</string>
|
||||
<string name="revanced_hide_floating_microphone_button_title">Схаваць плаваючую кнопку мікрафона</string>
|
||||
<string name="revanced_hide_floating_microphone_button_summary_on">Плаваючая кнопка мікрафона ў пошуку схаваная</string>
|
||||
<string name="revanced_hide_floating_microphone_button_summary_off">Плаваючая кнопка мікрафона ў пошуку паказана</string>
|
||||
@@ -192,7 +189,7 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_movies_section_summary_on">Раздзел фільмаў схаваны</string>
|
||||
<string name="revanced_hide_movies_section_summary_off">Паказваецца раздзел фільмаў</string>
|
||||
<!-- 'Notify me' should be translated using the same localized wording YouTube displays.
|
||||
This item appear in the subscription feed for future livestreams or unreleased videos. -->
|
||||
This item appear in the Subscriptions feed for future livestreams or unreleased videos. -->
|
||||
<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>
|
||||
@@ -204,6 +201,9 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_show_more_button_title">Схаваць кнопку \"Паказаць больш\"</string>
|
||||
<string name="revanced_hide_show_more_button_summary_on">Кнопка «Паказаць больш» у выніках пошуку схаваная</string>
|
||||
<string name="revanced_hide_show_more_button_summary_off">Кнопка «Паказаць больш» у выніках пошуку паказаная</string>
|
||||
<string name="revanced_hide_surveys_title">Схаваць апытанні</string>
|
||||
<string name="revanced_hide_surveys_summary_on">Апытанні схаваныя</string>
|
||||
<string name="revanced_hide_surveys_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>
|
||||
@@ -251,8 +251,8 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_timed_reactions_summary_on">Часовыя рэакцыі схаваныя</string>
|
||||
<string name="revanced_hide_timed_reactions_summary_off">Паказваюцца рэакцыі па часе</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_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>
|
||||
@@ -280,19 +280,19 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_description_components_screen_title">Апісанне відэа</string>
|
||||
<string name="revanced_hide_description_components_screen_summary">Схаваць або паказаць кампаненты апісання відэа</string>
|
||||
<string name="revanced_hide_filter_bar_screen_title">Панэль фільтраў</string>
|
||||
<string name="revanced_hide_filter_bar_screen_summary">Схаваць ці паказаць панэль фільтраў у стужках, гісторыі, выніках пошуку і звязаных відэа</string>
|
||||
<string name="revanced_hide_filter_bar_screen_summary">Схаваць або паказаць панэль фільтраў у стужках, звязаных відэа, выніках пошуку і гісторыі праглядаў</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_feed_title">Схаваць у стужках</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_feed_summary_on">Схавана ў стужках</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_feed_summary_off">Паказана ў стужках</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_title">Схаваць у гісторыі</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_on">Схавана ў гісторыі</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_off">Паказана ў гісторыі</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_title">Схаваць у выніках пошуку</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_on">Схавана ў выніках пошуку</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_off">Паказана ў выніках пошуку</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_title">Схаваць у звязаных відэа</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_summary_on">Схавана ў звязаных відэа</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_summary_off">Паказана ў звязаных відэа</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_title">Схаваць у выніках пошуку</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_on">Схавана ў выніках пошуку</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_off">Паказана ў выніках пошуку</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_title">Схаваць у гісторыі праглядаў</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_on">Схавана ў гісторыі праглядаў</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_off">Паказаны ў гісторыі праглядаў</string>
|
||||
<string name="revanced_channel_screen_title">Старонка канала</string>
|
||||
<string name="revanced_channel_screen_summary">Схаваць або паказаць кампаненты старонкі канала</string>
|
||||
<!-- 'For You' should be translated using the same localized wording YouTube displays. -->
|
||||
@@ -316,11 +316,11 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_comments_screen_title">Каментарыі</string>
|
||||
<string name="revanced_comments_screen_summary">Схаваць або паказаць кампаненты раздзела каментарыяў</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_title">Схаваць зводку чата са штучным інтэлектам</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_on">Зводка чата схаваная</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_off">Зводка чата паказаная</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_on">Зводка чата са штучным інтэлектам схавана</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_off">Зводка чата са штучным інтэлектам паказана</string>
|
||||
<string name="revanced_hide_comments_ai_summary_title">Схаваць зводку каментарыяў са штучным інтэлектам</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_on">Зводка каментарыяў схаваная</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_off">Зводка каментарыяў паказаная</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_on">Зводка каментарыяў штучнага інтэлекту схавана</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_off">Зводка каментарыяў штучнага інтэлекту паказана</string>
|
||||
<string name="revanced_hide_comments_channel_guidelines_title">Схаваць правілы канала</string>
|
||||
<string name="revanced_hide_comments_channel_guidelines_summary_on">Рэкамендацыі канала схаваны</string>
|
||||
<string name="revanced_hide_comments_channel_guidelines_summary_off">Рэкамендацыі канала паказаны</string>
|
||||
@@ -419,7 +419,6 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_shopping_links_title">Схаваць спасылкі для пакупак</string>
|
||||
<string name="revanced_hide_shopping_links_summary_on">Спасылкі на пакупкі ў апісанні відэа схаваныя</string>
|
||||
<string name="revanced_hide_shopping_links_summary_off">Спасылкі на пакупкі ў апісанні відэа паказаны</string>
|
||||
<!-- 'View products' should be translated with the same localized wording that YouTube displays. -->
|
||||
<string name="revanced_hide_view_products_banner_title">Схаваць банэр \"Прагледзець тавары\"</string>
|
||||
<string name="revanced_hide_view_products_banner_summary_on">Банер «Паглядзець тавары» ў накладцы відэа схаваны</string>
|
||||
<string name="revanced_hide_view_products_banner_summary_off">Банер «Паглядзець тавары» ў накладцы відэа паказаны</string>
|
||||
@@ -453,6 +452,11 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_remove_viewer_discretion_dialog_summary_off">Будзе паказана дыялогавае акно</string>
|
||||
<string name="revanced_remove_viewer_discretion_dialog_user_dialog_message">Гэта не абыходзіць узроставае абмежаванне. Ён проста прымае гэта аўтаматычна.</string>
|
||||
</patch>
|
||||
<patch id="interaction.doubletap.disableDoubleTapActionsPatch">
|
||||
<string name="revanced_disable_chapter_skip_double_tap_title">Адключыць прапуск раздзела па двайным націску</string>
|
||||
<string name="revanced_disable_chapter_skip_double_tap_summary_on">Двайны націск ніколі не можа выклікаць прапуск да наступнага/папярэдняга раздзела</string>
|
||||
<string name="revanced_disable_chapter_skip_double_tap_summary_off">Двайны націск можа час ад часу выклікаць прапуск да наступнага/папярэдняга раздзела</string>
|
||||
</patch>
|
||||
<patch id="interaction.downloads.downloadsResourcePatch">
|
||||
<string name="revanced_external_downloader_screen_title">Знешнія загрузкі</string>
|
||||
<string name="revanced_external_downloader_screen_summary">Налады для выкарыстання вонкавага загрузніка</string>
|
||||
@@ -464,8 +468,15 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_on">Кнопка \"Спампаваць\" адкрывае ваш знешні загрузнік</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_off">Кнопка \"Спампаваць\" адкрывае ўласную праграму загрузкі ў праграме</string>
|
||||
<string name="revanced_external_downloader_name_title">Назва пакета загрузніка</string>
|
||||
<string name="revanced_external_downloader_name_summary">Імя пакета ўсталяванай знешняй праграмы загрузкі, напрыклад NewPipe або Seal</string>
|
||||
<string name="revanced_external_downloader_name_summary">Назва пакета вашага ўсталяванага знешняга спампоўшчыка праграмы</string>
|
||||
<string name="revanced_external_downloader_other_item_hint">Увядзіце назву пакета</string>
|
||||
<string name="revanced_external_downloader_other_item">Іншае</string>
|
||||
<string name="revanced_external_downloader_not_found_title">Праграма не ўстаноўлена</string>
|
||||
<string name="revanced_external_downloader_not_installed_warning">%s не ўсталяваны. Калі ласка, усталюйце яго.</string>
|
||||
<string name="revanced_external_downloader_package_not_found_warning">"Не ўдалося знайсці ўстаноўленую праграму з назвай пакета: %s
|
||||
|
||||
Праверце, ці правільная назва пакета і ці ўстаноўлена праграма"</string>
|
||||
<string name="revanced_external_downloader_empty_warning">Назва пакета не можа быць пустой</string>
|
||||
</patch>
|
||||
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
|
||||
<string name="revanced_disable_precise_seeking_gesture_title">Адключыць жэст дакладнага пошуку</string>
|
||||
@@ -697,9 +708,9 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_cast_button_title">Схаваць кнопку «Трансляцыя»</string>
|
||||
<string name="revanced_hide_cast_button_summary_on">Кнопка Cast схавана</string>
|
||||
<string name="revanced_hide_cast_button_summary_off">Паказана кнопка Cast</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_title">Схаваць фон кнопак кіравання прайгравальнікам</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_summary_on">Фон кнопак кіравання прайгравальнікам схаваны</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_summary_off">Фон кнопак кіравання прайгравальнікам паказаны</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_title">Схаваць фон элементаў кіравання прайгравальнікам</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_summary_on">Фон элементаў кіравання плэерам схаваны</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_summary_off">Фон элементаў кіравання паказаны</string>
|
||||
<string name="revanced_hide_player_previous_next_buttons_title">Схаваць папярэднія & кнопкі «Далей»</string>
|
||||
<string name="revanced_hide_player_previous_next_buttons_summary_on">Кнопкі схаваныя</string>
|
||||
<string name="revanced_hide_player_previous_next_buttons_summary_off">Паказваюцца кнопкі</string>
|
||||
@@ -725,27 +736,28 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_disable_rolling_number_animations_summary_off">Пракатныя лічбы аніміраваныя</string>
|
||||
</patch>
|
||||
<patch id="layout.hide.seekbar.hideSeekbarPatch">
|
||||
<string name="revanced_hide_seekbar_title">Схаваць панэль пошуку ў відэаплэеры</string>
|
||||
<string name="revanced_hide_seekbar_title">Схаваць панэль прагрэсу відэапрайгравальніка</string>
|
||||
<string name="revanced_hide_seekbar_summary_on">Панэль пошуку відэаплэера схавана</string>
|
||||
<string name="revanced_hide_seekbar_summary_off">Адлюстроўваецца панэль пошуку відэаплэера</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_title">Схаваць панэль пошуку ў мініяцюрах відэа</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_on">Панэль пошуку эскізаў схавана</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_off">Адлюстроўваецца панэль пошуку эскізаў</string>
|
||||
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
|
||||
<string name="revanced_hide_seekbar_thumbnail_title">Схаваць панэль прагрэсу з эскізамі відэа</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_on">Панэль прагрэсу з эскізамі відэа схавана</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_off">Панэль прагрэсу з эскізамі відэа паказана</string>
|
||||
</patch>
|
||||
<patch id="layout.hide.shorts.hideShortsComponentsResourcePatch">
|
||||
<string name="revanced_shorts_player_screen_title">Прайгравальнік Shorts</string>
|
||||
<string name="revanced_shorts_player_screen_summary">Схаваць або паказаць кампаненты ў прайгравальніку Shorts</string>
|
||||
<string name="revanced_shorts_player_screen_summary">Схаваць або паказаць кампаненты прайгравальніка Shorts</string>
|
||||
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
|
||||
<string name="revanced_hide_shorts_home_title">Схаваць Shorts у стужцы «Галоўная»</string>
|
||||
<string name="revanced_hide_shorts_home_summary_on">Схавана ў стужцы «Галоўная» і звязаных відэа</string>
|
||||
<string name="revanced_hide_shorts_home_summary_off">Паказана ў стужцы «Галоўная» і звязаных відэа</string>
|
||||
<string name="revanced_hide_shorts_search_title">Схаваць Shorts у выніках пошуку</string>
|
||||
<string name="revanced_hide_shorts_search_summary_on">Схаваны ў выніках пошуку</string>
|
||||
<string name="revanced_hide_shorts_search_summary_off">Паказана ў выніках пошуку</string>
|
||||
<!-- 'Subscriptions' should be translated using the same localized wording YouTube displays for the Subscriptions tab. -->
|
||||
<string name="revanced_hide_shorts_subscriptions_title">Схаваць Shorts у стужцы «Падпіскі»</string>
|
||||
<string name="revanced_hide_shorts_subscriptions_summary_on">Схавана ў стужцы «Падпіскі»</string>
|
||||
<string name="revanced_hide_shorts_subscriptions_summary_off">Паказана ў стужцы «Падпіскі»</string>
|
||||
<string name="revanced_hide_shorts_search_title">Схаваць Shorts у выніках пошуку</string>
|
||||
<string name="revanced_hide_shorts_search_summary_on">Схаваны ў выніках пошуку</string>
|
||||
<string name="revanced_hide_shorts_search_summary_off">Паказана ў выніках пошуку</string>
|
||||
<string name="revanced_hide_shorts_history_title">Схаваць Shorts з гісторыі праглядаў</string>
|
||||
<string name="revanced_hide_shorts_history_summary_on">Схавана ў гісторыі праглядаў</string>
|
||||
<string name="revanced_hide_shorts_history_summary_off">Паказаны ў гісторыі праглядаў</string>
|
||||
@@ -1186,8 +1198,6 @@ Second \"item\" text"</string>
|
||||
Гэта зменіць знешні выгляд і функцыі прыкладання, але могуць узнікнуць невядомыя пабочныя эфекты.
|
||||
|
||||
Калі пазней будзе адключана, рэкамендуецца ачысціць даныя прыкладання, каб пазбегнуць памылак у інтэрфейсе."</string>
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||
<string name="revanced_spoof_app_version_target_title">Падробка мэтавай версіі праграмы</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 — Восстановить старые значки плеера Shorts</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 - Аднаўленне старых значкоў навігацыі</string>
|
||||
|
||||
@@ -137,7 +137,7 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_debug_logs_disabled">Отстраняването на грешки е деактивирано</string>
|
||||
<string name="revanced_debug_logs_none_found">Не са намерени логове</string>
|
||||
<string name="revanced_debug_logs_copied_to_clipboard">Логовете са копирани</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">Неуспешно експортиране на логове: $s</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">Неуспешно експортиране на логове: %s</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_title">Изчистване на логовете за отстраняване на грешки</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_summary">Изчиства всички съхранени логове за отстраняване на грешки на ReVanced</string>
|
||||
<string name="revanced_debug_logs_clear_toast">Логовете са изчистени</string>
|
||||
@@ -164,14 +164,17 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_expandable_card_title">Скриване на разгъваемата карта</string>
|
||||
<string name="revanced_hide_expandable_card_summary_on">Разгъваемата карта под видеоклиповете е скрита</string>
|
||||
<string name="revanced_hide_expandable_card_summary_off">Разгъваемата карта под видеоклиповете е показана</string>
|
||||
<string name="revanced_hide_feed_survey_title">Скриване на анкети в емисиите</string>
|
||||
<string name="revanced_hide_feed_survey_summary_on">Анкетите за емисии са скрити</string>
|
||||
<string name="revanced_hide_feed_survey_summary_off">Анкетите за емисии се показват</string>
|
||||
<string name="revanced_hide_floating_microphone_button_title">Плаващ бутон за микрофона</string>
|
||||
<string name="revanced_hide_floating_microphone_button_summary_on">Плаващ бутон за микрофон при търсене е скрит</string>
|
||||
<string name="revanced_hide_floating_microphone_button_summary_off">Плаващият бутон за микрофон в търсенето е показан</string>
|
||||
<string name="revanced_hide_horizontal_shelves_title">Хоризонтални секции</string>
|
||||
<string name="revanced_hide_horizontal_shelves_summary_on">"Хоризонтални рафтове са скрити, като:\n• Извънредни новини\n• Продължи гледането\n• Разгледайте още канали\n• Най-подходящи\n• Пазаруване\n• Гледайте отново"</string>
|
||||
<string name="revanced_hide_horizontal_shelves_summary_on">"Хоризонтални рафтове са скрити, като:
|
||||
• Извънредни новини
|
||||
• Продължи гледането
|
||||
• Разгледайте още канали
|
||||
• Най-подходящи
|
||||
• Пазаруване
|
||||
• Гледайте отново"</string>
|
||||
<string name="revanced_hide_horizontal_shelves_summary_off">Хоризонталните рафтове са показани</string>
|
||||
<string name="revanced_hide_image_shelf_title">Скриване на рафта с изображения</string>
|
||||
<string name="revanced_hide_image_shelf_summary_on">Рафтът с изображения в резултатите от търсенето е скрит</string>
|
||||
@@ -186,7 +189,7 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_movies_section_summary_on">Секцията с коментари е скрита</string>
|
||||
<string name="revanced_hide_movies_section_summary_off">Разделът за филми е показан</string>
|
||||
<!-- 'Notify me' should be translated using the same localized wording YouTube displays.
|
||||
This item appear in the subscription feed for future livestreams or unreleased videos. -->
|
||||
This item appear in the Subscriptions feed for future livestreams or unreleased videos. -->
|
||||
<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>
|
||||
@@ -198,6 +201,9 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_show_more_button_title">Скриване на бутона \"Покажи още\"</string>
|
||||
<string name="revanced_hide_show_more_button_summary_on">Бутонът за показване на още в резултатите от търсене е скрит</string>
|
||||
<string name="revanced_hide_show_more_button_summary_off">Бутонът за показване на още в резултатите от търсене е показан</string>
|
||||
<string name="revanced_hide_surveys_title">Скриване на проучвания</string>
|
||||
<string name="revanced_hide_surveys_summary_on">Анкетите са скрити</string>
|
||||
<string name="revanced_hide_surveys_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>
|
||||
@@ -245,8 +251,8 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_timed_reactions_summary_on">Времевите реакции са скрити</string>
|
||||
<string name="revanced_hide_timed_reactions_summary_off">Времевите реакции се показват</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_ai_generated_video_summary_section_summary_on">Разделът за резюме на видеоклипове, генерирани от AI, е скрит</string>
|
||||
<string name="revanced_hide_ai_generated_video_summary_section_summary_off">Разделът с генерирани от AI резюмета на видеоклипове е показан</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>
|
||||
@@ -274,19 +280,19 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_description_components_screen_title">Описание на видеото</string>
|
||||
<string name="revanced_hide_description_components_screen_summary">Скриване или показване на компонентите за описание на видеоклиповете</string>
|
||||
<string name="revanced_hide_filter_bar_screen_title">Лента с филтри</string>
|
||||
<string name="revanced_hide_filter_bar_screen_summary">Скриване или показване на лентата с филтри в емисиите, историята, резултатите от търсенето и свързаните видеоклипове</string>
|
||||
<string name="revanced_hide_filter_bar_screen_summary">Скриване или показване на лентата за филтри в емисиите, свързаните видеоклипове, резултатите от търсенето и историята на гледане</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_feed_title">Скриване в емисии</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_feed_summary_on">Скрити в емисии</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_feed_summary_off">Показани в емисии</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_title">Скриване в историята</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_on">Скрити в историята</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_off">Показани в историята</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_title">Скриване в резултатите от търсенето</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_on">Скрито в резултатите от търсенето</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_off">Показано в резултатите от търсенето</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_title">Скриване в сродни видеоклипове</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_summary_on">Скриване в сродни видеоклипове</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_summary_off">Показано в сродни видеоклипове</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_title">Скриване в резултатите от търсенето</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_on">Скрито в резултатите от търсенето</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_off">Показано в резултатите от търсенето</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_title">Скриване в историята на гледанията</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_on">Скрито в историята на гледане</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_off">Показано в историята на гледанията</string>
|
||||
<string name="revanced_channel_screen_title">Страница на канала</string>
|
||||
<string name="revanced_channel_screen_summary">Скриване или показване на компоненти на страницата на канала</string>
|
||||
<!-- 'For You' should be translated using the same localized wording YouTube displays. -->
|
||||
@@ -309,12 +315,12 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_visit_store_button_summary_off">Бутонът „Посетете магазина“ е показан</string>
|
||||
<string name="revanced_comments_screen_title">Коментари</string>
|
||||
<string name="revanced_comments_screen_summary">Скриване или показване на секцията за коментари</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_title">Скриване на резюмето на AI Chat</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_on">Резюмето на чата е скрито</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_off">Резюмето на чата е показано</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_title">Скриване на резюмето на AI чата</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_on">Резюмето на AI чата е скрито</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_off">Резюмето на AI чата е показано</string>
|
||||
<string name="revanced_hide_comments_ai_summary_title">Скриване на резюмето на AI коментарите</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_on">Резюмето на коментарите е скрито</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_off">Резюмето на коментарите е показано</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_on">Резюмето на AI коментари е скрито</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_off">Резюмето на AI коментари е показано</string>
|
||||
<string name="revanced_hide_comments_channel_guidelines_title">Скриване на насоките на канала</string>
|
||||
<string name="revanced_hide_comments_channel_guidelines_summary_on">Указанията за канала са скрити</string>
|
||||
<string name="revanced_hide_comments_channel_guidelines_summary_off">Указанията за канала са показани</string>
|
||||
@@ -413,7 +419,6 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_shopping_links_title">Скриване на връзки за пазаруване</string>
|
||||
<string name="revanced_hide_shopping_links_summary_on">Линковете за пазаруване във видео описанието са скрити</string>
|
||||
<string name="revanced_hide_shopping_links_summary_off">Линковете за пазаруване в описанието на видеоклипа са показани</string>
|
||||
<!-- 'View products' should be translated with the same localized wording that YouTube displays. -->
|
||||
<string name="revanced_hide_view_products_banner_title">Скриване на банера „Преглед на продукти“</string>
|
||||
<string name="revanced_hide_view_products_banner_summary_on">Банерът за преглед на продукти във видео наслагването е скрит</string>
|
||||
<string name="revanced_hide_view_products_banner_summary_off">Банерът за преглед на продукти във видео наслагването е показан</string>
|
||||
@@ -447,6 +452,11 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_remove_viewer_discretion_dialog_summary_off">Диалоговият прозорец ще бъде показан</string>
|
||||
<string name="revanced_remove_viewer_discretion_dialog_user_dialog_message">Тази функция не заобикаля възрастовото ограничение. Тя просто приема възрастовата граница автоматично.</string>
|
||||
</patch>
|
||||
<patch id="interaction.doubletap.disableDoubleTapActionsPatch">
|
||||
<string name="revanced_disable_chapter_skip_double_tap_title">Деактивиране на пропускане на глава с двойно докосване</string>
|
||||
<string name="revanced_disable_chapter_skip_double_tap_summary_on">Двойното докосване никога не може да предизвика пропускане до следваща/предишна глава</string>
|
||||
<string name="revanced_disable_chapter_skip_double_tap_summary_off">Двойното докосване може понякога да предизвика пропускане до следваща/предишна глава</string>
|
||||
</patch>
|
||||
<patch id="interaction.downloads.downloadsResourcePatch">
|
||||
<string name="revanced_external_downloader_screen_title">Външни изтегляния</string>
|
||||
<string name="revanced_external_downloader_screen_summary">Настройки за използване на външно приложение за изтегляне</string>
|
||||
@@ -458,8 +468,15 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_on">Бутонът за изтегляне отваря избраното от Вас външно приложение за изтегляне</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_off">Бутонът за изтегляне отваря вграденото приложение за изтегляне</string>
|
||||
<string name="revanced_external_downloader_name_title">Име на пакета на изтеглящото приложение</string>
|
||||
<string name="revanced_external_downloader_name_summary">Име на пакета на приложението за изтегляне, като NewPipe или Seal</string>
|
||||
<string name="revanced_external_downloader_name_summary">Име на пакета на вашето инсталирано външно приложение за изтегляне</string>
|
||||
<string name="revanced_external_downloader_other_item_hint">Въведете името на пакета</string>
|
||||
<string name="revanced_external_downloader_other_item">Други</string>
|
||||
<string name="revanced_external_downloader_not_found_title">Приложението не е инсталирано</string>
|
||||
<string name="revanced_external_downloader_not_installed_warning">%s не е инсталиран. Инсталирайте го.</string>
|
||||
<string name="revanced_external_downloader_package_not_found_warning">"Не може да бъде намерено инсталирано приложение с име на пакет: %s
|
||||
|
||||
Проверете дали името на пакета е правилно и приложението е инсталирано"</string>
|
||||
<string name="revanced_external_downloader_empty_warning">Името на пакета не може да бъде празно</string>
|
||||
</patch>
|
||||
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
|
||||
<string name="revanced_disable_precise_seeking_gesture_title">Деактивиране на жеста за точно търсене</string>
|
||||
@@ -691,9 +708,9 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_cast_button_title">Скриване на бутона Cast</string>
|
||||
<string name="revanced_hide_cast_button_summary_on">Бутонът за предаване е скрит</string>
|
||||
<string name="revanced_hide_cast_button_summary_off">Бутонът за предаване се показва</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_title">Скриване на фона на бутоните за управление на плейъра</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_summary_on">Фонът на бутоните за управление на плейъра е скрит</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_summary_off">Фонът на бутоните за управление на плейъра е показан</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_title">Скриване на фона на контролите на плейъра</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_summary_on">Фонът на контролите на плейъра е скрит</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_summary_off">Фонът на контролите на плейъра е показан</string>
|
||||
<string name="revanced_hide_player_previous_next_buttons_title">Скриване на бутоните \"Предишен и Следващ\"</string>
|
||||
<string name="revanced_hide_player_previous_next_buttons_summary_on">Бутоните са скрити</string>
|
||||
<string name="revanced_hide_player_previous_next_buttons_summary_off">Бутоните се показват</string>
|
||||
@@ -719,27 +736,28 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_disable_rolling_number_animations_summary_off">Анимацията е активирана</string>
|
||||
</patch>
|
||||
<patch id="layout.hide.seekbar.hideSeekbarPatch">
|
||||
<string name="revanced_hide_seekbar_title">Скриване на лента за време на плейъра</string>
|
||||
<string name="revanced_hide_seekbar_title">Скриване на лентата за търсене на видео плейър</string>
|
||||
<string name="revanced_hide_seekbar_summary_on">Лентата за време на плейъра е скрита</string>
|
||||
<string name="revanced_hide_seekbar_summary_off">Лентата за време на плейъра се показва</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_title">Скр. лента за време при миниатюрите</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_on">Лентата за време при миниатюрите е скрита</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_off">Лентата за време при миниатюрите се показва</string>
|
||||
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
|
||||
<string name="revanced_hide_seekbar_thumbnail_title">Скриване на лентата за търсене на миниатюри на видеоклипове</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_on">Лентата за търсене на миниатюри на видеоклипове е скрита</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_off">Лентата за търсене на миниатюри на видеоклипове е показана</string>
|
||||
</patch>
|
||||
<patch id="layout.hide.shorts.hideShortsComponentsResourcePatch">
|
||||
<string name="revanced_shorts_player_screen_title">Играч на Shorts</string>
|
||||
<string name="revanced_shorts_player_screen_summary">Скриване или показване на компоненти в Shorts плейъра</string>
|
||||
<string name="revanced_shorts_player_screen_summary">Скриване или показване на компонентите на плейъра за Shorts</string>
|
||||
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
|
||||
<string name="revanced_hide_shorts_home_title">Скриване на Shorts в емисията Начало</string>
|
||||
<string name="revanced_hide_shorts_home_summary_on">Скрито в емисията Начало и свързани видеоклипове</string>
|
||||
<string name="revanced_hide_shorts_home_summary_off">Показано в емисията Начало и свързани видеоклипове</string>
|
||||
<string name="revanced_hide_shorts_search_title">Shorts в резултатите от търсенето</string>
|
||||
<string name="revanced_hide_shorts_search_summary_on">Скрити в резултатите от търсенето</string>
|
||||
<string name="revanced_hide_shorts_search_summary_off">Показано в резултатите от търсенето</string>
|
||||
<!-- 'Subscriptions' should be translated using the same localized wording YouTube displays for the Subscriptions tab. -->
|
||||
<string name="revanced_hide_shorts_subscriptions_title">Скриване на Shorts в емисията Абонаменти</string>
|
||||
<string name="revanced_hide_shorts_subscriptions_summary_on">Скрито в емисията Абонаменти</string>
|
||||
<string name="revanced_hide_shorts_subscriptions_summary_off">Показано в емисията Абонаменти</string>
|
||||
<string name="revanced_hide_shorts_search_title">Shorts в резултатите от търсенето</string>
|
||||
<string name="revanced_hide_shorts_search_summary_on">Скрити в резултатите от търсенето</string>
|
||||
<string name="revanced_hide_shorts_search_summary_off">Показано в резултатите от търсенето</string>
|
||||
<string name="revanced_hide_shorts_history_title">Скриване на шортите в историята на гледане</string>
|
||||
<string name="revanced_hide_shorts_history_summary_on">Скрито в историята на гледане</string>
|
||||
<string name="revanced_hide_shorts_history_summary_off">Показва се в историята на гледане</string>
|
||||
@@ -1179,8 +1197,6 @@ Second \"item\" text"</string>
|
||||
Това ще промени външния вид и функциите на приложението, но може да възникнат неизвестни странични ефекти.
|
||||
|
||||
Ако по-късно бъде изключено, препоръчително е да изчистите данните на приложението, за да предотвратите грешки в потребителския интерфейс."</string>
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||
<string name="revanced_spoof_app_version_target_title">Подлъгване за версията на</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 - Възстановете старите икони на Shorts в плейъра</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 - Възстановяване на стари икони за навигация</string>
|
||||
|
||||
@@ -133,7 +133,7 @@ MicroG-এর জন্য ব্যাটারি অপ্টিমাইজ
|
||||
<string name="revanced_debug_logs_disabled">ডিবাগ লগিং নিষ্ক্রিয় করা হয়েছে</string>
|
||||
<string name="revanced_debug_logs_none_found">কোনো লগ পাওয়া যায়নি</string>
|
||||
<string name="revanced_debug_logs_copied_to_clipboard">লগ অনুলিপি করা হয়েছে</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">লগ রপ্তানি করতে ব্যর্থ: $s</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">লগ এক্সপোর্ট করা যায়নি: %s</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_title">ডিবাগ লগগুলি সাফ করুন</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_summary">সমস্ত সঞ্চিত ReVanced ডিবাগ লগ সাফ করে</string>
|
||||
<string name="revanced_debug_logs_clear_toast">লগ সাফ করা হয়েছে</string>
|
||||
@@ -160,9 +160,6 @@ MicroG-এর জন্য ব্যাটারি অপ্টিমাইজ
|
||||
<string name="revanced_hide_expandable_card_title">প্রসারিত কার্ড লুকান</string>
|
||||
<string name="revanced_hide_expandable_card_summary_on">ভিডিওর নিচে প্রসারিত কার্ড লুকানো আছে</string>
|
||||
<string name="revanced_hide_expandable_card_summary_off">ভিডিওর নিচে প্রসারিত কার্ড দেখানো আছে</string>
|
||||
<string name="revanced_hide_feed_survey_title">ফিড জরিপ লুকান</string>
|
||||
<string name="revanced_hide_feed_survey_summary_on">ফিড জরিপ লুকিয়ে রয়েছে</string>
|
||||
<string name="revanced_hide_feed_survey_summary_off">ফিড জরিপ প্রদর্শিত হয়েছে</string>
|
||||
<string name="revanced_hide_floating_microphone_button_title">ভাসমান মাইক্রোফোন বোতাম লুকান</string>
|
||||
<string name="revanced_hide_floating_microphone_button_summary_on">অনুসন্ধানে ফ্লোটিং মাইক্রোফোন বোতাম লুকানো আছে</string>
|
||||
<string name="revanced_hide_floating_microphone_button_summary_off">অনুসন্ধানে ভাসমান মাইক্রোফোন বোতাম দেখানো হয়</string>
|
||||
@@ -188,7 +185,7 @@ MicroG-এর জন্য ব্যাটারি অপ্টিমাইজ
|
||||
<string name="revanced_hide_movies_section_summary_on">চলচ্চিত্র বিভাগ লুকিয়ে রয়েছে</string>
|
||||
<string name="revanced_hide_movies_section_summary_off">চলচ্চিত্র বিভাগ প্রদর্শিত হয়েছে</string>
|
||||
<!-- 'Notify me' should be translated using the same localized wording YouTube displays.
|
||||
This item appear in the subscription feed for future livestreams or unreleased videos. -->
|
||||
This item appear in the Subscriptions feed for future livestreams or unreleased videos. -->
|
||||
<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>
|
||||
@@ -200,6 +197,9 @@ MicroG-এর জন্য ব্যাটারি অপ্টিমাইজ
|
||||
<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_surveys_title">সার্ভে লুকান</string>
|
||||
<string name="revanced_hide_surveys_summary_on">জরিপগুলি লুকানো হয়েছে</string>
|
||||
<string name="revanced_hide_surveys_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>
|
||||
@@ -247,8 +247,8 @@ MicroG-এর জন্য ব্যাটারি অপ্টিমাইজ
|
||||
<string name="revanced_hide_timed_reactions_summary_on">সময় অনুযায়ী প্রতিক্রিয়া লুকিয়ে রয়েছে</string>
|
||||
<string name="revanced_hide_timed_reactions_summary_off">সময় অনুযায়ী প্রতিক্রিয়া প্রদর্শিত হয়েছে</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_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>
|
||||
@@ -276,19 +276,19 @@ MicroG-এর জন্য ব্যাটারি অপ্টিমাইজ
|
||||
<string name="revanced_hide_description_components_screen_title">ভিডিওর বিবরণ</string>
|
||||
<string name="revanced_hide_description_components_screen_summary">ভিডিও বিবরণ এর উপাদান লুকান বা প্রদর্শন করুন</string>
|
||||
<string name="revanced_hide_filter_bar_screen_title">ফিল্টার বার</string>
|
||||
<string name="revanced_hide_filter_bar_screen_summary">ফিড, ইতিহাস, অনুসন্ধান ফলাফল এবং সম্পর্কিত ভিডিওগুলিতে ফিল্টার বার লুকান বা দেখান</string>
|
||||
<string name="revanced_hide_filter_bar_screen_summary">ফিড, সম্পর্কিত ভিডিও, অনুসন্ধান ফলাফল এবং দেখার ইতিহাসে ফিল্টার বার লুকান বা দেখান</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_feed_title">ফিডে লুকান</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_feed_summary_on">ফিডে লুকানো</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_feed_summary_off">ফিডে দেখানো হয়েছে</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_title">ইতিহাসে লুকান</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_on">ইতিহাসে লুকানো</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_off">ইতিহাসে দেখানো হয়েছে</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_title">অনুসন্ধান ফলাফলে লুকান</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_on">অনুসন্ধান ফলাফলে লুকানো আছে</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_off">অনুসন্ধান ফলাফলে দেখানো হয়েছে</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_title">সম্পর্কিত ভিডিওতে লুকান</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_summary_on">সম্পর্কিত ভিডিওতে লুকিয়ে রয়েছে</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_summary_off">সম্পর্কিত ভিডিওতে প্রদর্শিত হয়েছে</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_title">অনুসন্ধান ফলাফলে লুকান</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_on">অনুসন্ধান ফলাফলে লুকানো আছে</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_off">অনুসন্ধান ফলাফলে দেখানো হয়েছে</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_title">দেখার ইতিহাসে লুকান</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_on">দেখার ইতিহাসে লুকানো</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_off">দেখার ইতিহাসে দেখানো হয়েছে</string>
|
||||
<string name="revanced_channel_screen_title">চ্যানেল পৃষ্ঠা</string>
|
||||
<string name="revanced_channel_screen_summary">চ্যানেল পৃষ্ঠার উপাদানগুলি লুকান বা দেখান</string>
|
||||
<!-- 'For You' should be translated using the same localized wording YouTube displays. -->
|
||||
@@ -311,12 +311,12 @@ MicroG-এর জন্য ব্যাটারি অপ্টিমাইজ
|
||||
<string name="revanced_hide_visit_store_button_summary_off">স্টোর ভিজিট বাটন দেখানো আছে</string>
|
||||
<string name="revanced_comments_screen_title">মন্তব্য</string>
|
||||
<string name="revanced_comments_screen_summary">মন্তব্য বিভাগের উপাদানগুলি লুকান বা দেখান৷</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_title">এআই চ্যাট সংক্ষিপ্তসার লুকান</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_on">চ্যাট সংক্ষিপ্তসার লুকানো আছে</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_off">চ্যাট সংক্ষিপ্তসার দেখানো হয়েছে</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_title">এআই চ্যাট সারাংশ লুকান</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_on">এআই চ্যাট সারাংশ লুকানো আছে</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_off">এআই চ্যাট সারাংশ দেখানো হয়েছে</string>
|
||||
<string name="revanced_hide_comments_ai_summary_title">এআই মন্তব্য সংক্ষিপ্তসার লুকান</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_on">মন্তব্য সংক্ষিপ্তসার লুকানো আছে</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_off">মন্তব্য সংক্ষিপ্তসার দেখানো হয়েছে</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_on">এআই মন্তব্য সারাংশ লুকানো আছে</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_off">এআই মন্তব্য সারাংশ দেখানো হয়েছে</string>
|
||||
<string name="revanced_hide_comments_channel_guidelines_title">চ্যানেল নির্দেশিকা লুকান</string>
|
||||
<string name="revanced_hide_comments_channel_guidelines_summary_on">চ্যানেল নির্দেশিকা লুকানো হয়েছে</string>
|
||||
<string name="revanced_hide_comments_channel_guidelines_summary_off">চ্যানেল নির্দেশিকা দেখানো হয়েছে</string>
|
||||
@@ -415,7 +415,6 @@ MicroG-এর জন্য ব্যাটারি অপ্টিমাইজ
|
||||
<string name="revanced_hide_shopping_links_title">কেনাকাটার লিঙ্ক লুকান</string>
|
||||
<string name="revanced_hide_shopping_links_summary_on">ভিডিও বিবরণে কেনাকাটার লিঙ্ক লুকানো আছে</string>
|
||||
<string name="revanced_hide_shopping_links_summary_off">ভিডিও বর্ণনায় কেনাকাটার লিঙ্ক দেখানো হয়</string>
|
||||
<!-- 'View products' should be translated with the same localized wording that YouTube displays. -->
|
||||
<string name="revanced_hide_view_products_banner_title">\'পণ্য দেখুন\' ব্যানার লুকান</string>
|
||||
<string name="revanced_hide_view_products_banner_summary_on">ভিডিও ওভারলেতে \"পণ্য দেখুন\" ব্যানার লুকানো আছে</string>
|
||||
<string name="revanced_hide_view_products_banner_summary_off">ভিডিও ওভারলেতে \"পণ্য দেখুন\" ব্যানার দেখানো হয়েছে</string>
|
||||
@@ -449,6 +448,11 @@ MicroG-এর জন্য ব্যাটারি অপ্টিমাইজ
|
||||
<string name="revanced_remove_viewer_discretion_dialog_summary_off">ডায়ালগ প্রদর্শিত হবে</string>
|
||||
<string name="revanced_remove_viewer_discretion_dialog_user_dialog_message">এটি বয়সের সীমাবদ্ধতাকে বাইপাস করে না। এটা শুধু স্বয়ংক্রিয়ভাবে গ্রহণ করে।</string>
|
||||
</patch>
|
||||
<patch id="interaction.doubletap.disableDoubleTapActionsPatch">
|
||||
<string name="revanced_disable_chapter_skip_double_tap_title">ডাবল ট্যাপ অধ্যায় স্কিপ অক্ষম করুন</string>
|
||||
<string name="revanced_disable_chapter_skip_double_tap_summary_on">ডাবল ট্যাপ কখনও পরবর্তী/পূর্ববর্তী অধ্যায়ে স্কিপ ট্রিগার করতে পারে না</string>
|
||||
<string name="revanced_disable_chapter_skip_double_tap_summary_off">ডাবল ট্যাপ মাঝে মাঝে পরবর্তী/পূর্ববর্তী অধ্যায়ে স্কিপ ট্রিগার করতে পারে</string>
|
||||
</patch>
|
||||
<patch id="interaction.downloads.downloadsResourcePatch">
|
||||
<string name="revanced_external_downloader_screen_title">বাহিরে ডাউনলোড</string>
|
||||
<string name="revanced_external_downloader_screen_summary">বাহিরের ডাউনলোডার ব্যবহার করার সেটিং</string>
|
||||
@@ -460,8 +464,15 @@ MicroG-এর জন্য ব্যাটারি অপ্টিমাইজ
|
||||
<string name="revanced_external_downloader_action_button_summary_on">ডাউনলোড বোতামটি আপনার বাহিরের ডাউনলোডার খুলবে</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_off">ডাউনলোড বোতামটি নেটিভ ইন-অ্যাপ ডাউনলোডার খুলবে</string>
|
||||
<string name="revanced_external_downloader_name_title">ডাউনলোডারের প্যাকেজ নাম</string>
|
||||
<string name="revanced_external_downloader_name_summary">আপনার ইনস্টল করা বাইরের ডাউনলোডার অ্যাপের প্যাকেজ নাম, যেমন NewPipe বা Seal</string>
|
||||
<string name="revanced_external_downloader_name_summary">আপনার ইনস্টল করা বাহ্যিক ডাউনলোডার অ্যাপের প্যাকেজের নাম</string>
|
||||
<string name="revanced_external_downloader_other_item_hint">প্যাকেজের নাম লিখুন</string>
|
||||
<string name="revanced_external_downloader_other_item">অন্যান্য</string>
|
||||
<string name="revanced_external_downloader_not_found_title">অ্যাপ ইনস্টল করা নেই</string>
|
||||
<string name="revanced_external_downloader_not_installed_warning">%s ইনস্টল করা নেই, ইনস্টল করুন।</string>
|
||||
<string name="revanced_external_downloader_package_not_found_warning">"প্যাকেজের নাম: %s সহ ইনস্টল করা অ্যাপটি খুঁজে পাওয়া যায়নি
|
||||
|
||||
প্যাকেজের নামটি সঠিক এবং অ্যাপটি ইনস্টল করা আছে কিনা তা যাচাই করুন"</string>
|
||||
<string name="revanced_external_downloader_empty_warning">প্যাকেজের নাম খালি রাখা যাবে না</string>
|
||||
</patch>
|
||||
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
|
||||
<string name="revanced_disable_precise_seeking_gesture_title">ভিডিওর নির্দিষ্ট অংশে যাওয়ার অঙ্গভঙ্গি নিষ্ক্রিয় করুন</string>
|
||||
@@ -693,9 +704,9 @@ MicroG-এর জন্য ব্যাটারি অপ্টিমাইজ
|
||||
<string name="revanced_hide_cast_button_title">কাস্ট বোতামটি লুকান</string>
|
||||
<string name="revanced_hide_cast_button_summary_on">কাস্ট বাটন লুকিয়ে রয়েছে</string>
|
||||
<string name="revanced_hide_cast_button_summary_off">কাস্ট বাটন প্রদর্শিত হয়েছে</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_title">প্লেয়ার কন্ট্রোল বোতামগুলির পটভূমি লুকান</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_summary_on">প্লেয়ার কন্ট্রোল বোতামগুলির পটভূমি লুকানো আছে</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_summary_off">প্লেয়ার কন্ট্রোল বোতামগুলির পটভূমি দেখানো হয়েছে</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_title">প্লেয়ার কন্ট্রোল ব্যাকগ্রাউন্ড লুকান</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_summary_on">প্লেয়ার নিয়ন্ত্রণের পটভূমি লুকানো আছে</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_summary_off">প্লেয়ার কন্ট্রোল ব্যাকগ্রাউন্ড দেখানো হয়েছে</string>
|
||||
<string name="revanced_hide_player_previous_next_buttons_title">পূর্ববর্তী লুকান & পরবর্তী বোতাম</string>
|
||||
<string name="revanced_hide_player_previous_next_buttons_summary_on">বোতাম লুকানো হয়</string>
|
||||
<string name="revanced_hide_player_previous_next_buttons_summary_off">বোতাম দেখানো হয়</string>
|
||||
@@ -721,27 +732,28 @@ MicroG-এর জন্য ব্যাটারি অপ্টিমাইজ
|
||||
<string name="revanced_disable_rolling_number_animations_summary_off">রোলিং নাম্বার অ্যানিমেটেড</string>
|
||||
</patch>
|
||||
<patch id="layout.hide.seekbar.hideSeekbarPatch">
|
||||
<string name="revanced_hide_seekbar_title">ভিডিও প্লেয়ারে সিকবার লুকান</string>
|
||||
<string name="revanced_hide_seekbar_title">ভিডিও প্লেয়ারের সিকবার লুকান</string>
|
||||
<string name="revanced_hide_seekbar_summary_on">ভিডিও প্লেয়ারে সিকবার লুকিয়ে রয়েছে</string>
|
||||
<string name="revanced_hide_seekbar_summary_off">ভিডিও প্লেয়ারে সিকবার প্রদর্শিত হয়েছে</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_title">ভিডিও থাম্বনেইলে সিকবার লুকান</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_on">থাম্বনেইলে সিকবার লুকিয়ে রয়েছে</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_off">থাম্বনেইলে সিকবার প্রদর্শিত হয়েছে</string>
|
||||
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
|
||||
<string name="revanced_hide_seekbar_thumbnail_title">ভিডিও থাম্বনেইল সিকবার লুকান</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_on">ভিডিও থাম্বনেইল সিকবার লুকানো আছে</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_off">ভিডিও থাম্বনেইল সিকবার দেখানো হয়েছে</string>
|
||||
</patch>
|
||||
<patch id="layout.hide.shorts.hideShortsComponentsResourcePatch">
|
||||
<string name="revanced_shorts_player_screen_title">Shorts প্লেয়ার</string>
|
||||
<string name="revanced_shorts_player_screen_summary">Shorts প্লেয়ারে উপাদান লুকানো বা দেখানো</string>
|
||||
<string name="revanced_shorts_player_screen_summary">Shorts প্লেয়ারের উপাদান লুকান বা দেখান</string>
|
||||
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
|
||||
<string name="revanced_hide_shorts_home_title">হোম ফিডে Shorts লুকান</string>
|
||||
<string name="revanced_hide_shorts_home_summary_on">হোম ফিড এবং সম্পর্কিত ভিডিওতে লুকানো আছে</string>
|
||||
<string name="revanced_hide_shorts_home_summary_off">হোম ফিড এবং সম্পর্কিত ভিডিওতে দেখানো হয়েছে</string>
|
||||
<string name="revanced_hide_shorts_search_title">অনুসন্ধান ফলাফলে Shorts লুকান</string>
|
||||
<string name="revanced_hide_shorts_search_summary_on">অনুসন্ধান ফলাফলে লুকানো</string>
|
||||
<string name="revanced_hide_shorts_search_summary_off">সার্চ রেজাল্টে দেখানো হয়েছে</string>
|
||||
<!-- 'Subscriptions' should be translated using the same localized wording YouTube displays for the Subscriptions tab. -->
|
||||
<string name="revanced_hide_shorts_subscriptions_title">সাবস্ক্রিপশনস ফিডে Shorts লুকান</string>
|
||||
<string name="revanced_hide_shorts_subscriptions_summary_on">সাবস্ক্রিপশনস ফিডে লুকানো আছে</string>
|
||||
<string name="revanced_hide_shorts_subscriptions_summary_off">সাবস্ক্রিপশনস ফিডে দেখানো হয়েছে</string>
|
||||
<string name="revanced_hide_shorts_search_title">অনুসন্ধান ফলাফলে Shorts লুকান</string>
|
||||
<string name="revanced_hide_shorts_search_summary_on">অনুসন্ধান ফলাফলে লুকানো</string>
|
||||
<string name="revanced_hide_shorts_search_summary_off">সার্চ রেজাল্টে দেখানো হয়েছে</string>
|
||||
<string name="revanced_hide_shorts_history_title">Shortsগুলিকে ওয়াচ ইতিহাসে লুকান</string>
|
||||
<string name="revanced_hide_shorts_history_summary_on">ওয়াচ হিস্ট্রিতে লুকানো</string>
|
||||
<string name="revanced_hide_shorts_history_summary_off">ওয়াচ ইতিহাসে দেখানো</string>
|
||||
@@ -1181,8 +1193,6 @@ YouTube সেটিংসে অটো প্লে পরিবর্তন
|
||||
এটি অ্যাপ্লিকেশনটির চেহারা এবং বৈশিষ্ট্য পরিবর্তন করবে, তবে অজানা পার্শ্ব প্রতিক্রিয়া হতে পারে।
|
||||
|
||||
পরে যদি বন্ধ করা হয়, UI বাগ এড়াতে অ্যাপ্লিকেশন ডেটা পরিষ্কার করার পরামর্শ দেওয়া হয়।"</string>
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||
<string name="revanced_spoof_app_version_target_title">স্পুফ অ্যাপ সংস্করণ লক্ষ্য</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 - পুরনো Shorts প্লেয়ার আইকন পুনরুদ্ধার করুন</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 - পুরনো নেভিগেশন আইকন পুনরুদ্ধার করুন</string>
|
||||
|
||||
@@ -40,7 +40,7 @@ Second \"item\" text"</string>
|
||||
</patch>
|
||||
<patch id="layout.hide.general.hideLayoutComponentsPatch">
|
||||
<!-- 'Notify me' should be translated using the same localized wording YouTube displays.
|
||||
This item appear in the subscription feed for future livestreams or unreleased videos. -->
|
||||
This item appear in the Subscriptions feed for future livestreams or unreleased videos. -->
|
||||
<!-- 'Show more' should be translated with the same localized wording that YouTube displays.
|
||||
This button usually appears when searching for a YT creator. -->
|
||||
<!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. -->
|
||||
@@ -58,7 +58,6 @@ Second \"item\" text"</string>
|
||||
</patch>
|
||||
<patch id="ad.general.hideAdsResourcePatch">
|
||||
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
|
||||
<!-- 'View products' should be translated with the same localized wording that YouTube displays. -->
|
||||
</patch>
|
||||
<patch id="ad.getpremium.hideGetPremiumPatch">
|
||||
</patch>
|
||||
@@ -68,6 +67,8 @@ Second \"item\" text"</string>
|
||||
</patch>
|
||||
<patch id="interaction.dialog.removeViewerDiscretionDialogPatch">
|
||||
</patch>
|
||||
<patch id="interaction.doubletap.disableDoubleTapActionsPatch">
|
||||
</patch>
|
||||
<patch id="interaction.downloads.downloadsResourcePatch">
|
||||
<!-- 'Download action button' should be translated using the same wording as the translation of 'revanced_hide_download_button_title'. -->
|
||||
</patch>
|
||||
@@ -126,6 +127,7 @@ Second \"item\" text"</string>
|
||||
<patch id="layout.hide.rollingnumber.disableRollingNumberAnimationPatch">
|
||||
</patch>
|
||||
<patch id="layout.hide.seekbar.hideSeekbarPatch">
|
||||
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
|
||||
</patch>
|
||||
<patch id="layout.hide.shorts.hideShortsComponentsResourcePatch">
|
||||
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
|
||||
@@ -168,8 +170,6 @@ Second \"item\" text"</string>
|
||||
<patch id="layout.formfactor.changeFormFactorPatch">
|
||||
</patch>
|
||||
<patch id="layout.spoofappversion.spoofAppVersionPatch">
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||
</patch>
|
||||
<patch id="layout.startpage.changeStartPagePatch">
|
||||
</patch>
|
||||
|
||||
@@ -40,7 +40,7 @@ Second \"item\" text"</string>
|
||||
</patch>
|
||||
<patch id="layout.hide.general.hideLayoutComponentsPatch">
|
||||
<!-- 'Notify me' should be translated using the same localized wording YouTube displays.
|
||||
This item appear in the subscription feed for future livestreams or unreleased videos. -->
|
||||
This item appear in the Subscriptions feed for future livestreams or unreleased videos. -->
|
||||
<!-- 'Show more' should be translated with the same localized wording that YouTube displays.
|
||||
This button usually appears when searching for a YT creator. -->
|
||||
<!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. -->
|
||||
@@ -58,7 +58,6 @@ Second \"item\" text"</string>
|
||||
</patch>
|
||||
<patch id="ad.general.hideAdsResourcePatch">
|
||||
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
|
||||
<!-- 'View products' should be translated with the same localized wording that YouTube displays. -->
|
||||
</patch>
|
||||
<patch id="ad.getpremium.hideGetPremiumPatch">
|
||||
</patch>
|
||||
@@ -68,6 +67,8 @@ Second \"item\" text"</string>
|
||||
</patch>
|
||||
<patch id="interaction.dialog.removeViewerDiscretionDialogPatch">
|
||||
</patch>
|
||||
<patch id="interaction.doubletap.disableDoubleTapActionsPatch">
|
||||
</patch>
|
||||
<patch id="interaction.downloads.downloadsResourcePatch">
|
||||
<!-- 'Download action button' should be translated using the same wording as the translation of 'revanced_hide_download_button_title'. -->
|
||||
</patch>
|
||||
@@ -126,6 +127,7 @@ Second \"item\" text"</string>
|
||||
<patch id="layout.hide.rollingnumber.disableRollingNumberAnimationPatch">
|
||||
</patch>
|
||||
<patch id="layout.hide.seekbar.hideSeekbarPatch">
|
||||
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
|
||||
</patch>
|
||||
<patch id="layout.hide.shorts.hideShortsComponentsResourcePatch">
|
||||
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
|
||||
@@ -168,8 +170,6 @@ Second \"item\" text"</string>
|
||||
<patch id="layout.formfactor.changeFormFactorPatch">
|
||||
</patch>
|
||||
<patch id="layout.spoofappversion.spoofAppVersionPatch">
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||
</patch>
|
||||
<patch id="layout.startpage.changeStartPagePatch">
|
||||
</patch>
|
||||
|
||||
@@ -137,7 +137,7 @@ Nebudete informováni o žádné neočekávané události."</string>
|
||||
<string name="revanced_debug_logs_disabled">Ladění je vypnuto</string>
|
||||
<string name="revanced_debug_logs_none_found">Nebyly nalezeny žádné protokoly</string>
|
||||
<string name="revanced_debug_logs_copied_to_clipboard">Protokoly zkopírovány</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">Nepodařilo se exportovat protokoly: $s</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">Nepodařilo se exportovat protokoly: %s</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_title">Vymazat ladicí protokoly</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_summary">Vymaže všechny uložené ladicí protokoly ReVanced</string>
|
||||
<string name="revanced_debug_logs_clear_toast">Protokoly vymazány</string>
|
||||
@@ -164,14 +164,17 @@ Nebudete informováni o žádné neočekávané události."</string>
|
||||
<string name="revanced_hide_expandable_card_title">Skrýt rozbalitelnou kartu</string>
|
||||
<string name="revanced_hide_expandable_card_summary_on">Rozbalitelná karta pod videi je skryta</string>
|
||||
<string name="revanced_hide_expandable_card_summary_off">Rozbalitelná karta pod videi je zobrazena</string>
|
||||
<string name="revanced_hide_feed_survey_title">Skrýt průzkumy feedu</string>
|
||||
<string name="revanced_hide_feed_survey_summary_on">Průzkumy feedu jsou skryty</string>
|
||||
<string name="revanced_hide_feed_survey_summary_off">Průzkumy feedu jsou zobrazeny</string>
|
||||
<string name="revanced_hide_floating_microphone_button_title">Skrýt plovoucí tlačítko mikrofonu</string>
|
||||
<string name="revanced_hide_floating_microphone_button_summary_on">Plovoucí tlačítko mikrofonu ve vyhledávání je skryto</string>
|
||||
<string name="revanced_hide_floating_microphone_button_summary_off">Plovoucí tlačítko mikrofonu ve vyhledávání je zobrazeno</string>
|
||||
<string name="revanced_hide_horizontal_shelves_title">Skrýt horizontální police</string>
|
||||
<string name="revanced_hide_horizontal_shelves_summary_on">"Horizontální poličky jsou skryty, například:• Nejnovější zprávy• Pokračovat ve sledování• Prozkoumat další kanály• Nejdůležitější• Nakupování• Sledovat znovu"</string>
|
||||
<string name="revanced_hide_horizontal_shelves_summary_on">"Vodorovné police jsou skryté, například:
|
||||
• Nejnovější zprávy
|
||||
• Pokračovat ve sledování
|
||||
• Prozkoumat další kanály
|
||||
• Nejdůležitější
|
||||
• Nakupování
|
||||
• Znovu zhlédnout"</string>
|
||||
<string name="revanced_hide_horizontal_shelves_summary_off">Horizontální police jsou zobrazeny</string>
|
||||
<string name="revanced_hide_image_shelf_title">Skrýt obrázkovou poličku</string>
|
||||
<string name="revanced_hide_image_shelf_summary_on">Obrázková polička ve výsledcích vyhledávání je skryta</string>
|
||||
@@ -186,7 +189,7 @@ Nebudete informováni o žádné neočekávané události."</string>
|
||||
<string name="revanced_hide_movies_section_summary_on">Sekce Filmy je skrytá</string>
|
||||
<string name="revanced_hide_movies_section_summary_off">Sekce Filmy je viditelná</string>
|
||||
<!-- 'Notify me' should be translated using the same localized wording YouTube displays.
|
||||
This item appear in the subscription feed for future livestreams or unreleased videos. -->
|
||||
This item appear in the Subscriptions feed for future livestreams or unreleased videos. -->
|
||||
<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 \"Upozornit mě\" je skryto</string>
|
||||
<string name="revanced_hide_notify_me_button_summary_off">Tlačítko \"Upozornit mě\" je zobrazeno</string>
|
||||
@@ -198,6 +201,9 @@ Nebudete informováni o žádné neočekávané události."</string>
|
||||
<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 \"Zobrazit více\" ve výsledcích vyhledávání je skryto</string>
|
||||
<string name="revanced_hide_show_more_button_summary_off">Tlačítko \"Zobrazit více\" ve výsledcích vyhledávání je zobrazeno</string>
|
||||
<string name="revanced_hide_surveys_title">Skrýt průzkumy</string>
|
||||
<string name="revanced_hide_surveys_summary_on">Průzkumy jsou skryté</string>
|
||||
<string name="revanced_hide_surveys_summary_off">Průzkumy jsou zobrazené</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>
|
||||
@@ -245,8 +251,8 @@ Pokud se Doodle v současné době zobrazuje ve vaší oblasti a toto nastavení
|
||||
<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>
|
||||
<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_ai_generated_video_summary_section_summary_on">Sekce souhrnu videa generovaná AI je skryta</string>
|
||||
<string name="revanced_hide_ai_generated_video_summary_section_summary_off">Sekce se souhrnem videa generovaným umělou inteligencí 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>
|
||||
@@ -274,19 +280,19 @@ Pokud se Doodle v současné době zobrazuje ve vaší oblasti a toto nastavení
|
||||
<string name="revanced_hide_description_components_screen_title">Popis videa</string>
|
||||
<string name="revanced_hide_description_components_screen_summary">Skrýt nebo zobrazit komponenty popisu videa</string>
|
||||
<string name="revanced_hide_filter_bar_screen_title">Lišta filtrů</string>
|
||||
<string name="revanced_hide_filter_bar_screen_summary">Skrýt nebo zobrazit panel filtru v kanálech, historii, výsledcích vyhledávání a souvisejících videích</string>
|
||||
<string name="revanced_hide_filter_bar_screen_summary">Skrýt nebo zobrazit lištu filtru ve zdrojích, souvisejících videích, výsledcích vyhledávání a historii sledování</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_feed_title">Skrýt v kanálech</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_feed_summary_on">Skryto v kanálech</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_feed_summary_off">Zobrazeno v kanálech</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_title">Skrýt v historii</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_on">Skryto v historii</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_off">Zobrazeno v historii</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_title">Skrýt ve výsledcích vyhledávání</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_on">Skryto ve výsledcích vyhledávání</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_off">Zobrazeno ve výsledcích vyhledávání</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_title">Skrýt v souvisejících videích</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_summary_on">V souvisejících videích skryto</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_summary_off">V souvisejících videích zobrazeno</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_title">Skrýt ve výsledcích vyhledávání</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_on">Skryto ve výsledcích vyhledávání</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_off">Zobrazeno ve výsledcích vyhledávání</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_title">Skrýt v historii sledování</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_on">Skryto v historii sledování</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_off">Zobrazeno v historii sledování</string>
|
||||
<string name="revanced_channel_screen_title">Stránka kanálu</string>
|
||||
<string name="revanced_channel_screen_summary">Skrýt nebo zobrazit komponenty stránky kanálu</string>
|
||||
<!-- 'For You' should be translated using the same localized wording YouTube displays. -->
|
||||
@@ -309,12 +315,12 @@ Pokud se Doodle v současné době zobrazuje ve vaší oblasti a toto nastavení
|
||||
<string name="revanced_hide_visit_store_button_summary_off">Tlačítko Navštívit obchod je zobrazeno</string>
|
||||
<string name="revanced_comments_screen_title">Komentáře</string>
|
||||
<string name="revanced_comments_screen_summary">Skrýt nebo zobrazit komponenty sekce komentářů</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_title">Skrýt souhrn chatu AI</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_on">Souhrn chatu je skrytý</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_off">Souhrn chatu je zobrazen</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_title">Skrýt souhrn chatu s AI</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_on">Souhrn chatu s AI je skryt</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_off">Souhrn chatu s AI je zobrazen</string>
|
||||
<string name="revanced_hide_comments_ai_summary_title">Skrýt souhrn komentářů AI</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_on">Souhrn komentářů je skrytý</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_off">Souhrn komentářů je zobrazen</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_on">Souhrn komentářů AI je skryt</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_off">Souhrn komentářů AI je zobrazen</string>
|
||||
<string name="revanced_hide_comments_channel_guidelines_title">Skrýt pravidla kanálu</string>
|
||||
<string name="revanced_hide_comments_channel_guidelines_summary_on">Pokyny ke kanálu jsou skryty</string>
|
||||
<string name="revanced_hide_comments_channel_guidelines_summary_off">Pokyny ke kanálu jsou zobrazeny</string>
|
||||
@@ -413,7 +419,6 @@ Tato funkce je dostupná pouze pro starší zařízení"</string>
|
||||
<string name="revanced_hide_shopping_links_title">Skrýt nákupní odkazy</string>
|
||||
<string name="revanced_hide_shopping_links_summary_on">Nákupní odkazy v popisu videa jsou skryty</string>
|
||||
<string name="revanced_hide_shopping_links_summary_off">Nákupní odkazy v popisu videa jsou zobrazeny</string>
|
||||
<!-- 'View products' should be translated with the same localized wording that YouTube displays. -->
|
||||
<string name="revanced_hide_view_products_banner_title">Skrýt banner „Zobrazit produkty“</string>
|
||||
<string name="revanced_hide_view_products_banner_summary_on">Banner \"Zobrazit produkty\" v překrytí videa je skryt</string>
|
||||
<string name="revanced_hide_view_products_banner_summary_off">Banner \"Zobrazit produkty\" v překrytí videa je zobrazen</string>
|
||||
@@ -447,6 +452,11 @@ Tato funkce je dostupná pouze pro starší zařízení"</string>
|
||||
<string name="revanced_remove_viewer_discretion_dialog_summary_off">Dialog bude zobrazen</string>
|
||||
<string name="revanced_remove_viewer_discretion_dialog_user_dialog_message">Tímto krokem neobcházíte věkové omezení. Pouze jej automaticky akceptujete.</string>
|
||||
</patch>
|
||||
<patch id="interaction.doubletap.disableDoubleTapActionsPatch">
|
||||
<string name="revanced_disable_chapter_skip_double_tap_title">Zakázat přeskočení kapitoly dvojitým klepnutím</string>
|
||||
<string name="revanced_disable_chapter_skip_double_tap_summary_on">Dvojité klepnutí nikdy nespustí přeskočení na další/předchozí kapitolu</string>
|
||||
<string name="revanced_disable_chapter_skip_double_tap_summary_off">Dvojité klepnutí může občas spustit přeskočení na další/předchozí kapitolu</string>
|
||||
</patch>
|
||||
<patch id="interaction.downloads.downloadsResourcePatch">
|
||||
<string name="revanced_external_downloader_screen_title">Externí stahování</string>
|
||||
<string name="revanced_external_downloader_screen_summary">Nastavení pro použití externího stahování</string>
|
||||
@@ -458,8 +468,15 @@ Tato funkce je dostupná pouze pro starší zařízení"</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_on">Tlačítko pro stahování otevře váš externí stahovač</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_off">Tlačítko pro stahování otevře nativní stahovač v aplikaci</string>
|
||||
<string name="revanced_external_downloader_name_title">Název balíčku stahovače</string>
|
||||
<string name="revanced_external_downloader_name_summary">Název balíčku vaší nainstalované externí aplikace pro stahování, například NewPipe nebo Seal</string>
|
||||
<string name="revanced_external_downloader_name_summary">Název balíčku vaší nainstalované externí aplikace pro stahování</string>
|
||||
<string name="revanced_external_downloader_other_item_hint">Zadejte název balíčku</string>
|
||||
<string name="revanced_external_downloader_other_item">Jiné</string>
|
||||
<string name="revanced_external_downloader_not_found_title">Aplikace není nainstalována</string>
|
||||
<string name="revanced_external_downloader_not_installed_warning">%s není nainstalováno. Prosím, nainstalujte.</string>
|
||||
<string name="revanced_external_downloader_package_not_found_warning">"Nepodařilo se najít nainstalovanou aplikaci s názvem balíčku: %s
|
||||
|
||||
Zkontrolujte, zda je název balíčku správný a aplikace je nainstalována"</string>
|
||||
<string name="revanced_external_downloader_empty_warning">Název balíčku nemůže být prázdný</string>
|
||||
</patch>
|
||||
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
|
||||
<string name="revanced_disable_precise_seeking_gesture_title">Zakázat gesto pro přesné hledání</string>
|
||||
@@ -691,9 +708,9 @@ Chcete-li zobrazit nabídku zvukové stopy, změňte možnost „Zfalšovat stre
|
||||
<string name="revanced_hide_cast_button_title">Skrýt tlačítko Odeslat</string>
|
||||
<string name="revanced_hide_cast_button_summary_on">Tlačítko pro odesílání je skryto</string>
|
||||
<string name="revanced_hide_cast_button_summary_off">Tlačítko vysílání je viditelné</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_title">Skrýt pozadí ovládacích tlačítek přehrávače</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_summary_on">Pozadí ovládacích tlačítek přehrávače je skryté</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_summary_off">Zobrazuje se pozadí ovládacích tlačítek přehrávače</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_title">Skrýt pozadí ovládacích prvků přehrávače</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_summary_on">Pozadí ovládacích prvků přehrávače je skryto</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_summary_off">Pozadí ovládacích prvků přehrávače je zobrazeno</string>
|
||||
<string name="revanced_hide_player_previous_next_buttons_title">Skrýt tlačítka Předchozí a Další</string>
|
||||
<string name="revanced_hide_player_previous_next_buttons_summary_on">Tlačítka jsou skryta</string>
|
||||
<string name="revanced_hide_player_previous_next_buttons_summary_off">Tlačítka jsou zobrazena</string>
|
||||
@@ -719,27 +736,28 @@ Chcete-li zobrazit nabídku zvukové stopy, změňte možnost „Zfalšovat stre
|
||||
<string name="revanced_disable_rolling_number_animations_summary_off">Čísla posuvníku se animují</string>
|
||||
</patch>
|
||||
<patch id="layout.hide.seekbar.hideSeekbarPatch">
|
||||
<string name="revanced_hide_seekbar_title">Skrýt posuvník ve video přehrávači</string>
|
||||
<string name="revanced_hide_seekbar_title">Skrýt posuvník přehrávače videa</string>
|
||||
<string name="revanced_hide_seekbar_summary_on">Posuvník ve video přehrávači je skryt</string>
|
||||
<string name="revanced_hide_seekbar_summary_off">Posuvník ve video přehrávači je zobrazen</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_title">Skrýt posuvník v miniaturách videa</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_on">Posuvník miniatur je skryt</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_off">Posuvník miniatur je zobrazen</string>
|
||||
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
|
||||
<string name="revanced_hide_seekbar_thumbnail_title">Skrýt posuvník miniatur videí</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_on">Posuvník miniatur videí je skryt</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_off">Posuvník miniatur videí je zobrazen</string>
|
||||
</patch>
|
||||
<patch id="layout.hide.shorts.hideShortsComponentsResourcePatch">
|
||||
<string name="revanced_shorts_player_screen_title">Přehrávač Shorts</string>
|
||||
<string name="revanced_shorts_player_screen_summary">Skrýt nebo zobrazit komponenty v přehrávači Shorts</string>
|
||||
<string name="revanced_shorts_player_screen_summary">Skrýt nebo zobrazit komponenty přehrávače Shorts</string>
|
||||
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
|
||||
<string name="revanced_hide_shorts_home_title">Skrýt Shorts v kanálu Domů</string>
|
||||
<string name="revanced_hide_shorts_home_summary_on">Skryto v kanálu Domů a souvisejících videích</string>
|
||||
<string name="revanced_hide_shorts_home_summary_off">Zobrazeno v kanálu Domů a souvisejících videích</string>
|
||||
<string name="revanced_hide_shorts_search_title">Skrýt Shorts ve výsledcích vyhledávání</string>
|
||||
<string name="revanced_hide_shorts_search_summary_on">Skryté ve výsledcích vyhledávání</string>
|
||||
<string name="revanced_hide_shorts_search_summary_off">Zobrazeno ve výsledcích vyhledávání</string>
|
||||
<!-- 'Subscriptions' should be translated using the same localized wording YouTube displays for the Subscriptions tab. -->
|
||||
<string name="revanced_hide_shorts_subscriptions_title">Skrýt Shorts v kanálu Odběry</string>
|
||||
<string name="revanced_hide_shorts_subscriptions_summary_on">Skryto v kanálu Odběry</string>
|
||||
<string name="revanced_hide_shorts_subscriptions_summary_off">Zobrazeno v kanálu Odběry</string>
|
||||
<string name="revanced_hide_shorts_search_title">Skrýt Shorts ve výsledcích vyhledávání</string>
|
||||
<string name="revanced_hide_shorts_search_summary_on">Skryté ve výsledcích vyhledávání</string>
|
||||
<string name="revanced_hide_shorts_search_summary_off">Zobrazeno ve výsledcích vyhledávání</string>
|
||||
<string name="revanced_hide_shorts_history_title">Skrýt Shorts z historie sledování</string>
|
||||
<string name="revanced_hide_shorts_history_summary_on">Skryté v historii sledování</string>
|
||||
<string name="revanced_hide_shorts_history_summary_off">Zobrazené v historii sledování</string>
|
||||
@@ -1179,8 +1197,6 @@ Rozložení automobilu
|
||||
To změní vzhled a funkce aplikace, ale mohou se objevit neznámé vedlejší efekty.
|
||||
|
||||
Pokud bude později vypnuta, doporučujeme vymazat data aplikace, aby se zabránilo chybám uživatelského rozhraní."</string>
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||
<string name="revanced_spoof_app_version_target_title">Cíl napodobení verze aplikace</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 - Obnovuje staré ikony Shorts přehrávače</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 – Obnovit staré ikony navigace</string>
|
||||
|
||||
@@ -137,7 +137,7 @@ Du modtager ikke notifikationer om uventede hændelser."</string>
|
||||
<string name="revanced_debug_logs_disabled">Fejlsøgningslogning er deaktiveret</string>
|
||||
<string name="revanced_debug_logs_none_found">Ingen logfiler fundet</string>
|
||||
<string name="revanced_debug_logs_copied_to_clipboard">Logfiler kopieret</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">Kunne ikke eksportere logfiler: $s</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">Kunne ikke eksportere logfiler: %s</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_title">Ryd fejlsøgningslogfiler</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_summary">Rydder alle gemte ReVanced-fejlsøgningslogfiler</string>
|
||||
<string name="revanced_debug_logs_clear_toast">Logfiler ryddet</string>
|
||||
@@ -164,9 +164,6 @@ Du modtager ikke notifikationer om uventede hændelser."</string>
|
||||
<string name="revanced_hide_expandable_card_title">Skjul udvideligt kort</string>
|
||||
<string name="revanced_hide_expandable_card_summary_on">Udvideligt kort under videoer er skjult</string>
|
||||
<string name="revanced_hide_expandable_card_summary_off">Udvideligt kort under videoer er vist</string>
|
||||
<string name="revanced_hide_feed_survey_title">Skjul feed-undersøgelser</string>
|
||||
<string name="revanced_hide_feed_survey_summary_on">Foderundersøgelser er skjult</string>
|
||||
<string name="revanced_hide_feed_survey_summary_off">Foderundersøgelser er vist</string>
|
||||
<string name="revanced_hide_floating_microphone_button_title">Skjul flydende mikrofonknap</string>
|
||||
<string name="revanced_hide_floating_microphone_button_summary_on">Svævende mikrofonknap i søgning er skjult</string>
|
||||
<string name="revanced_hide_floating_microphone_button_summary_off">Flydende mikrofonknap i søgning vises</string>
|
||||
@@ -192,7 +189,7 @@ Du modtager ikke notifikationer om uventede hændelser."</string>
|
||||
<string name="revanced_hide_movies_section_summary_on">Afsnittet Film er skjult</string>
|
||||
<string name="revanced_hide_movies_section_summary_off">Afsnittet Film vises</string>
|
||||
<!-- 'Notify me' should be translated using the same localized wording YouTube displays.
|
||||
This item appear in the subscription feed for future livestreams or unreleased videos. -->
|
||||
This item appear in the Subscriptions feed for future livestreams or unreleased videos. -->
|
||||
<string name="revanced_hide_notify_me_button_title">Skjul knappen \'Underret mig\'</string>
|
||||
<string name="revanced_hide_notify_me_button_summary_on">Giv mig besked-knap er skjult</string>
|
||||
<string name="revanced_hide_notify_me_button_summary_off">Giv mig besked-knap vises</string>
|
||||
@@ -204,6 +201,9 @@ Du modtager ikke notifikationer om uventede hændelser."</string>
|
||||
<string name="revanced_hide_show_more_button_title">Skjul knappen \'Vis mere\'</string>
|
||||
<string name="revanced_hide_show_more_button_summary_on">Vis mere-knap i søgeresultater er skjult</string>
|
||||
<string name="revanced_hide_show_more_button_summary_off">Vis mere-knap i søgeresultater vises</string>
|
||||
<string name="revanced_hide_surveys_title">Skjul undersøgelser</string>
|
||||
<string name="revanced_hide_surveys_summary_on">Spørgeundersøgelser er skjult</string>
|
||||
<string name="revanced_hide_surveys_summary_off">Spørgeundersøgelser vises</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>
|
||||
@@ -251,8 +251,8 @@ Hvis et Doodle vises i øjeblikket i din region, og denne skjuleindstilling er a
|
||||
<string name="revanced_hide_timed_reactions_summary_on">Tidsreaktioner skjules</string>
|
||||
<string name="revanced_hide_timed_reactions_summary_off">Tidsreaktioner vises</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_ai_generated_video_summary_section_summary_on">AI-genereret videooversigt er skjult</string>
|
||||
<string name="revanced_hide_ai_generated_video_summary_section_summary_off">AI-genereret videooversigtssektion 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>
|
||||
@@ -280,19 +280,19 @@ Hvis et Doodle vises i øjeblikket i din region, og denne skjuleindstilling er a
|
||||
<string name="revanced_hide_description_components_screen_title">Video beskrivelse</string>
|
||||
<string name="revanced_hide_description_components_screen_summary">Skjul eller vis komponenter til videobeskrivelse</string>
|
||||
<string name="revanced_hide_filter_bar_screen_title">Filtrer bjælke</string>
|
||||
<string name="revanced_hide_filter_bar_screen_summary">Skjul eller vis filterlinjen i feeds, historik, søgeresultater og relaterede videoer</string>
|
||||
<string name="revanced_hide_filter_bar_screen_summary">Skjul eller vis filterlinjen i feeds, relaterede videoer, søgeresultater og seerhistorik</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_feed_title">Skjul i feeds</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_feed_summary_on">Skjult i feeds</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_feed_summary_off">Vist i feeds</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_title">Skjul i historik</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_on">Skjult i historik</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_off">Vist i historik</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_title">Skjul i søgeresultater</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_on">Skjult i søgeresultater</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_off">Vises i søgeresultater</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_title">Skjul i relaterede videoer</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_summary_on">Skjult i relaterede videoer</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_summary_off">Vist i relaterede videoer</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_title">Skjul i søgeresultater</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_on">Skjult i søgeresultater</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_off">Vises i søgeresultater</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_title">Skjul i seerhistorik</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_on">Skjult i seerhistorik</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_off">Vist i seerhistorik</string>
|
||||
<string name="revanced_channel_screen_title">Kanalside</string>
|
||||
<string name="revanced_channel_screen_summary">Skjul eller vis kanalsidekomponenter</string>
|
||||
<!-- 'For You' should be translated using the same localized wording YouTube displays. -->
|
||||
@@ -315,12 +315,12 @@ Hvis et Doodle vises i øjeblikket i din region, og denne skjuleindstilling er a
|
||||
<string name="revanced_hide_visit_store_button_summary_off">Besøg butik-knap er vist</string>
|
||||
<string name="revanced_comments_screen_title">Kommentarer</string>
|
||||
<string name="revanced_comments_screen_summary">Skjul eller vis kommentarer sektion komponenter</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_title">Skjul AI Chat-oversigt</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_on">Chat-oversigt er skjult</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_off">Chat-oversigt er vist</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_title">Skjul AI-chatsammendrag</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_on">AI-chatsammendrag er skjult</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_off">AI-chatsammendrag vises</string>
|
||||
<string name="revanced_hide_comments_ai_summary_title">Skjul AI Kommentar-oversigt</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_on">Kommentar-oversigt er skjult</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_off">Kommentar-oversigt er vist</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_on">AI-kommentarsammendrag er skjult</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_off">AI-kommentarsammendrag vises</string>
|
||||
<string name="revanced_hide_comments_channel_guidelines_title">Skjul kanalretningslinjer</string>
|
||||
<string name="revanced_hide_comments_channel_guidelines_summary_on">Kanalretningslinjer er skjult</string>
|
||||
<string name="revanced_hide_comments_channel_guidelines_summary_off">Kanalretningslinjer er vist</string>
|
||||
@@ -419,7 +419,6 @@ Denne funktion er kun tilgængelig for ældre enheder"</string>
|
||||
<string name="revanced_hide_shopping_links_title">Skjul shopping-links</string>
|
||||
<string name="revanced_hide_shopping_links_summary_on">Shoppinglinks i videobeskrivelse er skjult</string>
|
||||
<string name="revanced_hide_shopping_links_summary_off">Shoppinglinks i videobeskrivelsen vises</string>
|
||||
<!-- 'View products' should be translated with the same localized wording that YouTube displays. -->
|
||||
<string name="revanced_hide_view_products_banner_title">Skjul banneret \"Se produkter\"</string>
|
||||
<string name="revanced_hide_view_products_banner_summary_on">Se produkter-banner i videooverlay er skjult</string>
|
||||
<string name="revanced_hide_view_products_banner_summary_off">Se produkter-banner i videooverlay vises</string>
|
||||
@@ -453,6 +452,11 @@ Denne funktion er kun tilgængelig for ældre enheder"</string>
|
||||
<string name="revanced_remove_viewer_discretion_dialog_summary_off">Dialog vil blive vist</string>
|
||||
<string name="revanced_remove_viewer_discretion_dialog_user_dialog_message">Dette går ikke uden om aldersbegrænsningen. Det accepterer bare det automatisk.</string>
|
||||
</patch>
|
||||
<patch id="interaction.doubletap.disableDoubleTapActionsPatch">
|
||||
<string name="revanced_disable_chapter_skip_double_tap_title">Deaktiver dobbeltklik kapitelspring</string>
|
||||
<string name="revanced_disable_chapter_skip_double_tap_summary_on">Dobbeltklik kan aldrig udløse et spring til næste/forrige kapitel</string>
|
||||
<string name="revanced_disable_chapter_skip_double_tap_summary_off">Dobbeltklik kan lejlighedsvis udløse et spring til næste/forrige kapitel</string>
|
||||
</patch>
|
||||
<patch id="interaction.downloads.downloadsResourcePatch">
|
||||
<string name="revanced_external_downloader_screen_title">Eksterne downloads</string>
|
||||
<string name="revanced_external_downloader_screen_summary">Indstillinger for brug af en ekstern downloader</string>
|
||||
@@ -464,8 +468,15 @@ Denne funktion er kun tilgængelig for ældre enheder"</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_on">Download-knappen åbner din eksterne downloader</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_off">Download-knappen åbner den indfødte in-app downloader</string>
|
||||
<string name="revanced_external_downloader_name_title">Downloader pakkenavn</string>
|
||||
<string name="revanced_external_downloader_name_summary">Pakkenavn på din installerede eksterne downloader-app, såsom NewPipe eller Seal</string>
|
||||
<string name="revanced_external_downloader_name_summary">Pakkenavn på din installerede eksterne download-app</string>
|
||||
<string name="revanced_external_downloader_other_item_hint">Indtast pakkenavnet</string>
|
||||
<string name="revanced_external_downloader_other_item">Andet</string>
|
||||
<string name="revanced_external_downloader_not_found_title">Appen er ikke installeret</string>
|
||||
<string name="revanced_external_downloader_not_installed_warning">%s er ikke installeret. Installér den.</string>
|
||||
<string name="revanced_external_downloader_package_not_found_warning">"Kunne ikke finde installeret app med pakkenavn: %s
|
||||
|
||||
Kontrollér, at pakkenavnet er korrekt, og at appen er installeret"</string>
|
||||
<string name="revanced_external_downloader_empty_warning">Pakkenavnet må ikke være tomt</string>
|
||||
</patch>
|
||||
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
|
||||
<string name="revanced_disable_precise_seeking_gesture_title">Deaktivér præcis søgemåde</string>
|
||||
@@ -697,9 +708,9 @@ For at vise lydspormenuen skal du ændre \"Spoof videostream\" til iOS TV"</stri
|
||||
<string name="revanced_hide_cast_button_title">Skjul Cast-knappen</string>
|
||||
<string name="revanced_hide_cast_button_summary_on">Cast-knappen er skjult</string>
|
||||
<string name="revanced_hide_cast_button_summary_off">Cast knap er vist</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_title">Skjul baggrunden for afspillerens kontrolknapper</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_summary_on">Baggrunden for afspillerens kontrolknapper er skjult</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_summary_off">Baggrunden for afspillerens kontrolknapper vises</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_title">Skjul baggrund for afspillerkontroller</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_summary_on">Baggrund for afspilningskontroller er skjult</string>
|
||||
<string name="revanced_hide_player_control_buttons_background_summary_off">Baggrund for afspillerkontroller vises</string>
|
||||
<string name="revanced_hide_player_previous_next_buttons_title">Skjul Forrige & Næste knapper</string>
|
||||
<string name="revanced_hide_player_previous_next_buttons_summary_on">Knapper er skjult</string>
|
||||
<string name="revanced_hide_player_previous_next_buttons_summary_off">Knapper vises</string>
|
||||
@@ -725,27 +736,28 @@ For at vise lydspormenuen skal du ændre \"Spoof videostream\" til iOS TV"</stri
|
||||
<string name="revanced_disable_rolling_number_animations_summary_off">Rullende numre er animeret</string>
|
||||
</patch>
|
||||
<patch id="layout.hide.seekbar.hideSeekbarPatch">
|
||||
<string name="revanced_hide_seekbar_title">Skjul søgelinje i videoafspiller</string>
|
||||
<string name="revanced_hide_seekbar_title">Skjul videoafspillerens søgelinje</string>
|
||||
<string name="revanced_hide_seekbar_summary_on">Videoafspillerens søgelinje er skjult</string>
|
||||
<string name="revanced_hide_seekbar_summary_off">Videoafspillerens søgelinje vises</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_title">Skjul søgelinje i videominiaturer</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_on">Miniaturesøgelinjen er skjult</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_off">Miniaturesøgelinjen vises</string>
|
||||
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
|
||||
<string name="revanced_hide_seekbar_thumbnail_title">Skjul video-miniaturebilledernes søgelinje</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_on">Video-miniaturebilledernes søgelinje er skjult</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_off">Video-miniaturebilledernes søgelinje vises</string>
|
||||
</patch>
|
||||
<patch id="layout.hide.shorts.hideShortsComponentsResourcePatch">
|
||||
<string name="revanced_shorts_player_screen_title">Shorts-afspiller</string>
|
||||
<string name="revanced_shorts_player_screen_summary">Skjul eller vis komponenter i Shorts-afspilleren</string>
|
||||
<string name="revanced_shorts_player_screen_summary">Skjul eller vis Shorts-afspillerkomponenter</string>
|
||||
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
|
||||
<string name="revanced_hide_shorts_home_title">Skjul Shorts i \'Hjem\'-feed</string>
|
||||
<string name="revanced_hide_shorts_home_summary_on">Skjult i \'Hjem\'-feed og relaterede videoer</string>
|
||||
<string name="revanced_hide_shorts_home_summary_off">Vises i \'Hjem\'-feed og relaterede videoer</string>
|
||||
<string name="revanced_hide_shorts_search_title">Skjul Shorts i søgeresultater</string>
|
||||
<string name="revanced_hide_shorts_search_summary_on">Skjult i søgeresultater</string>
|
||||
<string name="revanced_hide_shorts_search_summary_off">Vises i søgeresultater</string>
|
||||
<!-- 'Subscriptions' should be translated using the same localized wording YouTube displays for the Subscriptions tab. -->
|
||||
<string name="revanced_hide_shorts_subscriptions_title">Skjul Shorts i \'Abonnementer\'-feed</string>
|
||||
<string name="revanced_hide_shorts_subscriptions_summary_on">Skjult i \'Abonnementer\'-feed</string>
|
||||
<string name="revanced_hide_shorts_subscriptions_summary_off">Vises i \'Abonnementer\'-feed</string>
|
||||
<string name="revanced_hide_shorts_search_title">Skjul Shorts i søgeresultater</string>
|
||||
<string name="revanced_hide_shorts_search_summary_on">Skjult i søgeresultater</string>
|
||||
<string name="revanced_hide_shorts_search_summary_off">Vises i søgeresultater</string>
|
||||
<string name="revanced_hide_shorts_history_title">Skjul shorts i historik</string>
|
||||
<string name="revanced_hide_shorts_history_summary_on">Skjult i historikken</string>
|
||||
<string name="revanced_hide_shorts_history_summary_off">Vises i historik</string>
|
||||
@@ -1187,8 +1199,6 @@ Automotive-layout
|
||||
Dette ændrer appens udseende og funktioner, men ukendte bivirkninger kan forekomme.
|
||||
|
||||
Hvis det senere slås fra, anbefales det at rydde app-dataene for at forhindre UI-fejl."</string>
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||
<string name="revanced_spoof_app_version_target_title">Spoof app version mål</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 - Gendan gamle Shorts player ikoner</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 - Gendan gamle navigationsikoner</string>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user