mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-12-07 01:51:27 +01:00
Compare commits
108 Commits
v5.43.0-de
...
a16db13b93
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a16db13b93 | ||
|
|
300b12f948 | ||
|
|
ac583d40d0 | ||
|
|
c400188c38 | ||
|
|
0af0ee92c4 | ||
|
|
fff29544b9 | ||
|
|
9495cf49ef | ||
|
|
15675b5164 | ||
|
|
654d091e65 | ||
|
|
98371be33c | ||
|
|
2f0de15e67 | ||
|
|
df160370e2 | ||
|
|
bb745b555b | ||
|
|
8df9a46721 | ||
|
|
94ae84ad0f | ||
|
|
4febb2e2e9 | ||
|
|
b9bc7e3e58 | ||
|
|
9f3bb26cb9 | ||
|
|
d64dfc2884 | ||
|
|
a39ef1e0a4 | ||
|
|
1d8e977a43 | ||
|
|
da4cf94091 | ||
|
|
d23fa5e3b7 | ||
|
|
34d29abdfa | ||
|
|
ab808aeb77 | ||
|
|
a6b07cceb1 | ||
|
|
d291881215 | ||
|
|
7a18ebc7ab | ||
|
|
475197af45 | ||
|
|
d390b54dab | ||
|
|
4d1eaa6b14 | ||
|
|
c6364f5b49 | ||
|
|
f177eae385 | ||
|
|
2e9d6959c9 | ||
|
|
81f83690d6 | ||
|
|
f1bd6848c9 | ||
|
|
59d85b28a7 | ||
|
|
f238ae9895 | ||
|
|
e030e9c07a | ||
|
|
5029e979be | ||
|
|
55e1a6784b | ||
|
|
0cad5e73f0 | ||
|
|
ce503d5b58 | ||
|
|
57263538c7 | ||
|
|
70f4955e89 | ||
|
|
582144026d | ||
|
|
d80892cc0e | ||
|
|
6c4b931b8a | ||
|
|
76dcfaefd8 | ||
|
|
e4f52343c0 | ||
|
|
1196b1a147 | ||
|
|
858edbf3e7 | ||
|
|
a52c0153b1 | ||
|
|
cd9ef81354 | ||
|
|
1b2cd64a86 | ||
|
|
0c03599f07 | ||
|
|
5f23bfe833 | ||
|
|
2cf8f0e636 | ||
|
|
c17cf98c7e | ||
|
|
3e4990afff | ||
|
|
292fae440c | ||
|
|
12e7c0943a | ||
|
|
a0c5604951 | ||
|
|
38d9299dfe | ||
|
|
dfdbbfa047 | ||
|
|
e9f45ce926 | ||
|
|
1b38b1a3c8 | ||
|
|
13cf1724bf | ||
|
|
6d01863ec7 | ||
|
|
a32ed30b4c | ||
|
|
ef44eaa119 | ||
|
|
c73a03c9e1 | ||
|
|
c1681f982a | ||
|
|
1502bf7524 | ||
|
|
dffb1e6525 | ||
|
|
b8c2ede2bf | ||
|
|
591e106098 | ||
|
|
e7336d2ef3 | ||
|
|
7a53f8f62d | ||
|
|
3466d9d210 | ||
|
|
876d7a6b03 | ||
|
|
a2aa9cac27 | ||
|
|
08baa19b4a | ||
|
|
7283b93cea | ||
|
|
754b71959a | ||
|
|
c64e29ec57 | ||
|
|
b2dd008aee | ||
|
|
de97562c5d | ||
|
|
6373829fd6 | ||
|
|
cfd244b408 | ||
|
|
e8e28e2b6a | ||
|
|
2e4c6fdcad | ||
|
|
644d6dcb51 | ||
|
|
14dd7346a8 | ||
|
|
0af8c8a766 | ||
|
|
96454c843b | ||
|
|
476ef0fae1 | ||
|
|
bbec724afb | ||
|
|
7a1dcbd4ee | ||
|
|
2a1e31860f | ||
|
|
949d6bdd19 | ||
|
|
7563990750 | ||
|
|
4b605eb270 | ||
|
|
c2d7a7fb8b | ||
|
|
a55560dc25 | ||
|
|
e8522d703e | ||
|
|
068d029a03 | ||
|
|
0c19dbaf30 |
2
.github/workflows/build_pull_request.yml
vendored
2
.github/workflows/build_pull_request.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
run: ./gradlew :patches:buildAndroid --no-daemon
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: revanced-patches
|
||||
path: patches/build/libs
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
run: ./gradlew :patches:buildAndroid clean
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 'lts/*'
|
||||
cache: 'npm'
|
||||
|
||||
327
CHANGELOG.md
327
CHANGELOG.md
@@ -1,3 +1,330 @@
|
||||
# [5.47.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.6...v5.47.0-dev.7) (2025-12-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Spoof SIM provider:** Spoof additional TelephonyManager methods ([#6293](https://github.com/ReVanced/revanced-patches/issues/6293)) ([ac583d4](https://github.com/ReVanced/revanced-patches/commit/ac583d40d0f4c0e6544e3661ff3e82a25912f2b0))
|
||||
|
||||
# [5.47.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.5...v5.47.0-dev.6) (2025-11-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Letterboxd:** Add `Hide ads` patch ([#6309](https://github.com/ReVanced/revanced-patches/issues/6309)) ([0af0ee9](https://github.com/ReVanced/revanced-patches/commit/0af0ee92c48bb2ffc332197e05439e20c5c05d83))
|
||||
|
||||
# [5.47.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.4...v5.47.0-dev.5) (2025-11-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Hide player flyout menu items:** Allow hiding audio menu with 'Android No SDK' client type ([9495cf4](https://github.com/ReVanced/revanced-patches/commit/9495cf49ef8a872be64de6c971c1919b4b9a8720))
|
||||
|
||||
# [5.47.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.3...v5.47.0-dev.4) (2025-11-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Sanitize sharing links:** Handle non hierarchical urls ([654d091](https://github.com/ReVanced/revanced-patches/commit/654d091e650cda37650b57cbf3ba6f1cdd6d47d3))
|
||||
|
||||
# [5.47.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.2...v5.47.0-dev.3) (2025-11-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Instagram:** Add `Disable auto story flipping` patch ([#6262](https://github.com/ReVanced/revanced-patches/issues/6262)) ([2f0de15](https://github.com/ReVanced/revanced-patches/commit/2f0de15e67e4f99ed6ecdc136d04cceb23b0d069))
|
||||
|
||||
# [5.47.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.1...v5.47.0-dev.2) (2025-11-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Instagram - Disable signature check:** Change patch to default excluded ([#6283](https://github.com/ReVanced/revanced-patches/issues/6283)) ([bb745b5](https://github.com/ReVanced/revanced-patches/commit/bb745b555b3808b7679c5995319aa365630fbd76))
|
||||
|
||||
# [5.47.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.46.0...v5.47.0-dev.1) (2025-11-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Instagram:** Add `Anonymous story viewing` patch ([#6263](https://github.com/ReVanced/revanced-patches/issues/6263)) ([94ae84a](https://github.com/ReVanced/revanced-patches/commit/94ae84ad0fc3a9197c82d5356301d464730c3b17))
|
||||
|
||||
# [5.46.0](https://github.com/ReVanced/revanced-patches/compare/v5.45.0...v5.46.0) (2025-11-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Duolingo - Disable ads:** Constrain patch to last working app target ([f238ae9](https://github.com/ReVanced/revanced-patches/commit/f238ae9895000f01d1dccb800cc8efde0d5362bd))
|
||||
* **Instagram - Hide navigation buttons:** Constrain patch to last working app target ([e030e9c](https://github.com/ReVanced/revanced-patches/commit/e030e9c07a7748e117ac44f6776a9f6317b20623))
|
||||
* **Spotify - Hide Create button:** Remove obsolete patch that is no longer needed ([#6252](https://github.com/ReVanced/revanced-patches/issues/6252)) ([59d85b2](https://github.com/ReVanced/revanced-patches/commit/59d85b28a7fcb285ff5f2bb6ae654020d76b2019))
|
||||
* **YouTube - Check watch history domain name resolution:** Fix false positive warning message if the internet connection fails halfway into the DNS check ([5726353](https://github.com/ReVanced/revanced-patches/commit/57263538c79f5a561c449229ac8e068c641285d3))
|
||||
* **YouTube - Hide layout components:** Fix "Hide Hype points" ([#6247](https://github.com/ReVanced/revanced-patches/issues/6247)) ([5821440](https://github.com/ReVanced/revanced-patches/commit/582144026d28e57bb7adcbba39244f3c7cdbc0f3))
|
||||
* **YouTube - Settings:** Add additional languages to ReVanced language preference ([d390b54](https://github.com/ReVanced/revanced-patches/commit/d390b54dab92d75b4e0d3e38344eae489dd69d98))
|
||||
* **YouTube - Settings:** Resolve settings search crash when searching for specific words ([#6231](https://github.com/ReVanced/revanced-patches/issues/6231)) ([76dcfae](https://github.com/ReVanced/revanced-patches/commit/76dcfaefd8679e45a70f265b0239436e60c055cf))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Debugging:** Add setting to block experimental client flags ([#6196](https://github.com/ReVanced/revanced-patches/issues/6196)) ([2e9d695](https://github.com/ReVanced/revanced-patches/commit/2e9d6959c94df7588b9e34b18770e9f437e91926))
|
||||
* **YouTube - Hide layout components:** Add "Hide Hype points" ([#6230](https://github.com/ReVanced/revanced-patches/issues/6230)) ([a52c015](https://github.com/ReVanced/revanced-patches/commit/a52c0153b12c3f6f0ad260e03d2e9850c0466392))
|
||||
* **YouTube - Hide layout components:** Add video description "Hide Featured content" and "Hide Subscribe button" ([#6253](https://github.com/ReVanced/revanced-patches/issues/6253)) ([da4cf94](https://github.com/ReVanced/revanced-patches/commit/da4cf940911a4406e2c9dd558b60305385a80c61))
|
||||
* **YouTube - Hide player flyout menu items:** Add "Hide Listen with YouTube Music" ([#6232](https://github.com/ReVanced/revanced-patches/issues/6232)) ([858edbf](https://github.com/ReVanced/revanced-patches/commit/858edbf3e7f394fcc766d767c8dc54cf5ba24370))
|
||||
* **YouTube Music:** Add `Change miniplayer color` patch ([#6259](https://github.com/ReVanced/revanced-patches/issues/6259)) ([ab808ae](https://github.com/ReVanced/revanced-patches/commit/ab808aeb773592cb26c848d8456478a346ec3bad))
|
||||
* **YouTube Music:** Add `Hide buttons` patch ([#6255](https://github.com/ReVanced/revanced-patches/issues/6255)) ([7a18ebc](https://github.com/ReVanced/revanced-patches/commit/7a18ebc7ab74ba30c5d5284a4856c55cdfc31097))
|
||||
|
||||
# [5.46.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.9...v5.46.0-dev.10) (2025-11-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Hide layout components:** Add video description "Hide Featured content" and "Hide Subscribe button" ([#6253](https://github.com/ReVanced/revanced-patches/issues/6253)) ([da4cf94](https://github.com/ReVanced/revanced-patches/commit/da4cf940911a4406e2c9dd558b60305385a80c61))
|
||||
|
||||
# [5.46.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.8...v5.46.0-dev.9) (2025-11-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube Music:** Add `Change miniplayer color` patch ([#6259](https://github.com/ReVanced/revanced-patches/issues/6259)) ([ab808ae](https://github.com/ReVanced/revanced-patches/commit/ab808aeb773592cb26c848d8456478a346ec3bad))
|
||||
|
||||
# [5.46.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.7...v5.46.0-dev.8) (2025-11-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube Music:** Add `Hide buttons` patch ([#6255](https://github.com/ReVanced/revanced-patches/issues/6255)) ([7a18ebc](https://github.com/ReVanced/revanced-patches/commit/7a18ebc7ab74ba30c5d5284a4856c55cdfc31097))
|
||||
|
||||
# [5.46.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.6...v5.46.0-dev.7) (2025-11-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Settings:** Add additional languages to ReVanced language preference ([d390b54](https://github.com/ReVanced/revanced-patches/commit/d390b54dab92d75b4e0d3e38344eae489dd69d98))
|
||||
|
||||
# [5.46.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.5...v5.46.0-dev.6) (2025-11-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Debugging:** Add setting to block experimental client flags ([#6196](https://github.com/ReVanced/revanced-patches/issues/6196)) ([2e9d695](https://github.com/ReVanced/revanced-patches/commit/2e9d6959c94df7588b9e34b18770e9f437e91926))
|
||||
|
||||
# [5.46.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.4...v5.46.0-dev.5) (2025-11-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Duolingo - Disable ads:** Constrain patch to last working app target ([f238ae9](https://github.com/ReVanced/revanced-patches/commit/f238ae9895000f01d1dccb800cc8efde0d5362bd))
|
||||
* **Instagram - Hide navigation buttons:** Constrain patch to last working app target ([e030e9c](https://github.com/ReVanced/revanced-patches/commit/e030e9c07a7748e117ac44f6776a9f6317b20623))
|
||||
* **Spotify - Hide Create button:** Remove obsolete patch that is no longer needed ([#6252](https://github.com/ReVanced/revanced-patches/issues/6252)) ([59d85b2](https://github.com/ReVanced/revanced-patches/commit/59d85b28a7fcb285ff5f2bb6ae654020d76b2019))
|
||||
|
||||
# [5.46.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.3...v5.46.0-dev.4) (2025-11-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Check watch history domain name resolution:** Fix false positive warning message if the internet connection fails halfway into the DNS check ([5726353](https://github.com/ReVanced/revanced-patches/commit/57263538c79f5a561c449229ac8e068c641285d3))
|
||||
|
||||
# [5.46.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.2...v5.46.0-dev.3) (2025-11-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Hide layout components:** Fix "Hide Hype points" ([#6247](https://github.com/ReVanced/revanced-patches/issues/6247)) ([5821440](https://github.com/ReVanced/revanced-patches/commit/582144026d28e57bb7adcbba39244f3c7cdbc0f3))
|
||||
|
||||
# [5.46.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.1...v5.46.0-dev.2) (2025-11-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Settings:** Resolve settings search crash when searching for specific words ([#6231](https://github.com/ReVanced/revanced-patches/issues/6231)) ([76dcfae](https://github.com/ReVanced/revanced-patches/commit/76dcfaefd8679e45a70f265b0239436e60c055cf))
|
||||
|
||||
# [5.46.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.45.0...v5.46.0-dev.1) (2025-11-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Hide layout components:** Add "Hide Hype points" ([#6230](https://github.com/ReVanced/revanced-patches/issues/6230)) ([a52c015](https://github.com/ReVanced/revanced-patches/commit/a52c0153b12c3f6f0ad260e03d2e9850c0466392))
|
||||
* **YouTube - Hide player flyout menu items:** Add "Hide Listen with YouTube Music" ([#6232](https://github.com/ReVanced/revanced-patches/issues/6232)) ([858edbf](https://github.com/ReVanced/revanced-patches/commit/858edbf3e7f394fcc766d767c8dc54cf5ba24370))
|
||||
|
||||
# [5.45.0](https://github.com/ReVanced/revanced-patches/compare/v5.44.0...v5.45.0) (2025-11-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Instagram:** Update failing fingerprints on newer versions ([#6181](https://github.com/ReVanced/revanced-patches/issues/6181)) ([c73a03c](https://github.com/ReVanced/revanced-patches/commit/c73a03c9e18a12262939c974cdf16221221d1487))
|
||||
* **TikTok - Downloads:** Fix download path setting ([#6191](https://github.com/ReVanced/revanced-patches/issues/6191)) ([3e4990a](https://github.com/ReVanced/revanced-patches/commit/3e4990afff4c86b93970b153db713ad0f813124d))
|
||||
* **YouTube - Change header:** Do not mirror header graphic with RTL languages ([a0c5604](https://github.com/ReVanced/revanced-patches/commit/a0c56049510ce040e1ccd49257864672c343344d))
|
||||
* **YouTube - Force original audio:** Fall back to visionOS and not Android Studio if Android VR is not available ([6d01863](https://github.com/ReVanced/revanced-patches/commit/6d01863ec70617d9abc864ce6686ed9764dd151d))
|
||||
* **YouTube - Spoof video streams:** Remove spoof stream audio selector that no longer works ([292fae4](https://github.com/ReVanced/revanced-patches/commit/292fae440c6d5694c5e84407becec2d91f1fd156))
|
||||
* **YouTube Music - Hide category bar:** Correctly hide the category bar in newer app targets ([#6175](https://github.com/ReVanced/revanced-patches/issues/6175)) ([13cf172](https://github.com/ReVanced/revanced-patches/commit/13cf1724bf2f946c7129cab0db96721c90f9fe89))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Spoof video streams:** Add experimental "Android No SDK" client type ([5f23bfe](https://github.com/ReVanced/revanced-patches/commit/5f23bfe833c6e01617a7dbc5325b4a3fb931e53e))
|
||||
* **TikTok:** Add `Sanitize sharing links` patch ([#6176](https://github.com/ReVanced/revanced-patches/issues/6176)) ([ef44eaa](https://github.com/ReVanced/revanced-patches/commit/ef44eaa119b9d6c5faec051e22d20f883d0da4f1))
|
||||
* **YouTube - Change Header:** Use SVG for header logo ([#6178](https://github.com/ReVanced/revanced-patches/issues/6178)) ([e9f45ce](https://github.com/ReVanced/revanced-patches/commit/e9f45ce92695d5857473ff71c14b190bded28a73))
|
||||
|
||||
# [5.45.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.45.0-dev.5...v5.45.0-dev.6) (2025-11-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Spoof video streams:** Add experimental "Android No SDK" client type ([5f23bfe](https://github.com/ReVanced/revanced-patches/commit/5f23bfe833c6e01617a7dbc5325b4a3fb931e53e))
|
||||
|
||||
# [5.45.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.45.0-dev.4...v5.45.0-dev.5) (2025-11-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **TikTok - Downloads:** Fix download path setting ([#6191](https://github.com/ReVanced/revanced-patches/issues/6191)) ([3e4990a](https://github.com/ReVanced/revanced-patches/commit/3e4990afff4c86b93970b153db713ad0f813124d))
|
||||
* **YouTube - Spoof video streams:** Remove spoof stream audio selector that no longer works ([292fae4](https://github.com/ReVanced/revanced-patches/commit/292fae440c6d5694c5e84407becec2d91f1fd156))
|
||||
|
||||
# [5.45.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.45.0-dev.3...v5.45.0-dev.4) (2025-10-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Change header:** Do not mirror header graphic with RTL languages ([a0c5604](https://github.com/ReVanced/revanced-patches/commit/a0c56049510ce040e1ccd49257864672c343344d))
|
||||
|
||||
# [5.45.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.45.0-dev.2...v5.45.0-dev.3) (2025-10-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Change Header:** Use SVG for header logo ([#6178](https://github.com/ReVanced/revanced-patches/issues/6178)) ([e9f45ce](https://github.com/ReVanced/revanced-patches/commit/e9f45ce92695d5857473ff71c14b190bded28a73))
|
||||
|
||||
# [5.45.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.45.0-dev.1...v5.45.0-dev.2) (2025-10-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Force original audio:** Fall back to visionOS and not Android Studio if Android VR is not available ([6d01863](https://github.com/ReVanced/revanced-patches/commit/6d01863ec70617d9abc864ce6686ed9764dd151d))
|
||||
* **YouTube Music - Hide category bar:** Correctly hide the category bar in newer app targets ([#6175](https://github.com/ReVanced/revanced-patches/issues/6175)) ([13cf172](https://github.com/ReVanced/revanced-patches/commit/13cf1724bf2f946c7129cab0db96721c90f9fe89))
|
||||
|
||||
# [5.45.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.44.0...v5.45.0-dev.1) (2025-10-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Instagram:** Update failing fingerprints on newer versions ([#6181](https://github.com/ReVanced/revanced-patches/issues/6181)) ([c73a03c](https://github.com/ReVanced/revanced-patches/commit/c73a03c9e18a12262939c974cdf16221221d1487))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **TikTok:** Add `Sanitize sharing links` patch ([#6176](https://github.com/ReVanced/revanced-patches/issues/6176)) ([ef44eaa](https://github.com/ReVanced/revanced-patches/commit/ef44eaa119b9d6c5faec051e22d20f883d0da4f1))
|
||||
|
||||
# [5.44.0](https://github.com/ReVanced/revanced-patches/compare/v5.43.1...v5.44.0) (2025-10-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Google Photos - Spoof features:** Add support for Pixel 10 devices ([#6161](https://github.com/ReVanced/revanced-patches/issues/6161)) ([754b719](https://github.com/ReVanced/revanced-patches/commit/754b71959a0155413eb33cf1bdc2c8976eaca634))
|
||||
* **X / Twitter - Change link sharing domain:** Use bytecode patching to resolve patching with Manager ([#6125](https://github.com/ReVanced/revanced-patches/issues/6125)) ([0af8c8a](https://github.com/ReVanced/revanced-patches/commit/0af8c8a766ae4ba6926404d59da2f14d649f91f7))
|
||||
* **YouTube - Hide layout components:** Hide new kind of community post ([#6146](https://github.com/ReVanced/revanced-patches/issues/6146)) ([cfd244b](https://github.com/ReVanced/revanced-patches/commit/cfd244b4088daacd2788ec38357ac521e4b296d5))
|
||||
* **YouTube Music:** Resolve patching 7.29 target ([2e4c6fd](https://github.com/ReVanced/revanced-patches/commit/2e4c6fdcadeef45a80733e374421d52e5e8af910))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add `Custom network security` patch ([#6151](https://github.com/ReVanced/revanced-patches/issues/6151)) ([e7336d2](https://github.com/ReVanced/revanced-patches/commit/e7336d2ef361cc5d6fe6e8442b36d9cf1f542931))
|
||||
* **Duolingo - Enable debug menu:** Support latest app target ([#6163](https://github.com/ReVanced/revanced-patches/issues/6163)) ([08baa19](https://github.com/ReVanced/revanced-patches/commit/08baa19b4a62e62bd103d177c3f4454de199cf16))
|
||||
* **Duolingo:** Add `Skip energy recharge ads` patch ([#6167](https://github.com/ReVanced/revanced-patches/issues/6167)) ([591e106](https://github.com/ReVanced/revanced-patches/commit/591e106098c6eff431b8b7ac7d985ce7373d701e))
|
||||
* **Samsung Radio:** Add `Disable device checks` patch ([#6145](https://github.com/ReVanced/revanced-patches/issues/6145)) ([de97562](https://github.com/ReVanced/revanced-patches/commit/de97562c5ddc8ec707761c1e04e74c4e18f9c158))
|
||||
|
||||
# [5.44.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.44.0-dev.3...v5.44.0-dev.4) (2025-10-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add `Custom network security` patch ([#6151](https://github.com/ReVanced/revanced-patches/issues/6151)) ([e7336d2](https://github.com/ReVanced/revanced-patches/commit/e7336d2ef361cc5d6fe6e8442b36d9cf1f542931))
|
||||
* **Duolingo:** Add `Skip energy recharge ads` patch ([#6167](https://github.com/ReVanced/revanced-patches/issues/6167)) ([591e106](https://github.com/ReVanced/revanced-patches/commit/591e106098c6eff431b8b7ac7d985ce7373d701e))
|
||||
|
||||
# [5.44.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.44.0-dev.2...v5.44.0-dev.3) (2025-10-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Duolingo - Enable debug menu:** Support latest app target ([#6163](https://github.com/ReVanced/revanced-patches/issues/6163)) ([08baa19](https://github.com/ReVanced/revanced-patches/commit/08baa19b4a62e62bd103d177c3f4454de199cf16))
|
||||
|
||||
# [5.44.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.44.0-dev.1...v5.44.0-dev.2) (2025-10-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Google Photos - Spoof features:** Add support for Pixel 10 devices ([#6161](https://github.com/ReVanced/revanced-patches/issues/6161)) ([754b719](https://github.com/ReVanced/revanced-patches/commit/754b71959a0155413eb33cf1bdc2c8976eaca634))
|
||||
|
||||
# [5.44.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.43.2-dev.3...v5.44.0-dev.1) (2025-10-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Samsung Radio:** Add `Disable device checks` patch ([#6145](https://github.com/ReVanced/revanced-patches/issues/6145)) ([de97562](https://github.com/ReVanced/revanced-patches/commit/de97562c5ddc8ec707761c1e04e74c4e18f9c158))
|
||||
|
||||
## [5.43.2-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.43.2-dev.2...v5.43.2-dev.3) (2025-10-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Hide layout components:** Hide new kind of community post ([#6146](https://github.com/ReVanced/revanced-patches/issues/6146)) ([cfd244b](https://github.com/ReVanced/revanced-patches/commit/cfd244b4088daacd2788ec38357ac521e4b296d5))
|
||||
|
||||
## [5.43.2-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.43.2-dev.1...v5.43.2-dev.2) (2025-10-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube Music:** Resolve patching 7.29 target ([2e4c6fd](https://github.com/ReVanced/revanced-patches/commit/2e4c6fdcadeef45a80733e374421d52e5e8af910))
|
||||
|
||||
## [5.43.2-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.43.1...v5.43.2-dev.1) (2025-10-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **X / Twitter - Change link sharing domain:** Use bytecode patching to resolve patching with Manager ([#6125](https://github.com/ReVanced/revanced-patches/issues/6125)) ([0af8c8a](https://github.com/ReVanced/revanced-patches/commit/0af8c8a766ae4ba6926404d59da2f14d649f91f7))
|
||||
|
||||
## [5.43.1](https://github.com/ReVanced/revanced-patches/compare/v5.43.0...v5.43.1) (2025-10-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **X / Twitter - Change link sharing domain:** Resolve duplicate patch option ([#6119](https://github.com/ReVanced/revanced-patches/issues/6119)) ([7563990](https://github.com/ReVanced/revanced-patches/commit/75639907502382f63fa127a886362d4a4573e6e3))
|
||||
* **X / Twitter:** Do not crash Manager when clicking on domain patch option ([2a1e318](https://github.com/ReVanced/revanced-patches/commit/2a1e31860f22f537d51b40a5b71d9ad9d538789e))
|
||||
|
||||
## [5.43.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.43.1-dev.1...v5.43.1-dev.2) (2025-10-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **X / Twitter:** Do not crash Manager when clicking on domain patch option ([2a1e318](https://github.com/ReVanced/revanced-patches/commit/2a1e31860f22f537d51b40a5b71d9ad9d538789e))
|
||||
|
||||
## [5.43.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.43.0...v5.43.1-dev.1) (2025-10-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **X / Twitter - Change link sharing domain:** Resolve duplicate patch option ([#6119](https://github.com/ReVanced/revanced-patches/issues/6119)) ([7563990](https://github.com/ReVanced/revanced-patches/commit/75639907502382f63fa127a886362d4a4573e6e3))
|
||||
|
||||
# [5.43.0](https://github.com/ReVanced/revanced-patches/compare/v5.42.1...v5.43.0) (2025-10-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Custom branding:** Use white notification icon for expanded status bar panel ([95eee59](https://github.com/ReVanced/revanced-patches/commit/95eee59a87a680e212a3ba06e1afefee8d91ee9d))
|
||||
* **Instagram - Change sharing domain:** Display patch option ([#6089](https://github.com/ReVanced/revanced-patches/issues/6089)) ([be2b144](https://github.com/ReVanced/revanced-patches/commit/be2b144cc9c4108ec37e16f3dd20573d88ffaa2b))
|
||||
* **X / Twitter - Change Link Sharing Domain:** Change link domain of share copy action ([#6091](https://github.com/ReVanced/revanced-patches/issues/6091)) ([5484625](https://github.com/ReVanced/revanced-patches/commit/54846253d748f4e7e30b2bba427c7d2fb9c341e2))
|
||||
* **YouTube - Custom branding:** Do not add a broken custom icon if the user provides an invalid custom icon path ([6555f6e](https://github.com/ReVanced/revanced-patches/commit/6555f6e6f8b52c2f1ddab1f52c6704cd2d8cfc12))
|
||||
* **YouTube - Custom branding:** Use ReVanced icon for status bar notification icon ([#6108](https://github.com/ReVanced/revanced-patches/issues/6108)) ([10ea250](https://github.com/ReVanced/revanced-patches/commit/10ea250d4a91f8ab3b7f865612a403fc93a857b5))
|
||||
* **YouTube - Force original audio:** Do not use translated audio if stream spoofing is off and force audio is on ([0c19dba](https://github.com/ReVanced/revanced-patches/commit/0c19dbaf30bcb95a29448d98b028ebeea54cc7d3))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Instagram:** Add `Hide suggested content` patch ([#6075](https://github.com/ReVanced/revanced-patches/issues/6075)) ([50f0b9c](https://github.com/ReVanced/revanced-patches/commit/50f0b9c5eee95ff5f9974e344802e1d2a4aab47b))
|
||||
|
||||
# [5.43.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.43.0-dev.3...v5.43.0-dev.4) (2025-10-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Force original audio:** Do not use translated audio if stream spoofing is off and force audio is on ([0c19dba](https://github.com/ReVanced/revanced-patches/commit/0c19dbaf30bcb95a29448d98b028ebeea54cc7d3))
|
||||
|
||||
# [5.43.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.43.0-dev.2...v5.43.0-dev.3) (2025-10-14)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package app.revanced.extension.music.patches;
|
||||
|
||||
import app.revanced.extension.music.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ChangeMiniplayerColorPatch {
|
||||
|
||||
/**
|
||||
* Injection point
|
||||
*/
|
||||
public static boolean changeMiniplayerColor() {
|
||||
return Settings.CHANGE_MINIPLAYER_COLOR.get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package app.revanced.extension.music.patches;
|
||||
|
||||
import static app.revanced.extension.shared.Utils.hideViewBy0dpUnderCondition;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import app.revanced.extension.music.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class HideButtonsPatch {
|
||||
|
||||
/**
|
||||
* Injection point
|
||||
*/
|
||||
public static int hideCastButton(int original) {
|
||||
return Settings.HIDE_CAST_BUTTON.get() ? View.GONE : original;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point
|
||||
*/
|
||||
public static void hideCastButton(View view) {
|
||||
hideViewBy0dpUnderCondition(Settings.HIDE_CAST_BUTTON, view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point
|
||||
*/
|
||||
public static boolean hideHistoryButton(boolean original) {
|
||||
return original && !Settings.HIDE_HISTORY_BUTTON.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point
|
||||
*/
|
||||
public static void hideNotificationButton(View view) {
|
||||
if (view.getParent() instanceof ViewGroup viewGroup) {
|
||||
hideViewBy0dpUnderCondition(Settings.HIDE_NOTIFICATION_BUTTON, viewGroup);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point
|
||||
*/
|
||||
public static void hideSearchButton(View view) {
|
||||
hideViewBy0dpUnderCondition(Settings.HIDE_SEARCH_BUTTON, view);
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package app.revanced.extension.music.patches;
|
||||
|
||||
import static app.revanced.extension.shared.Utils.hideViewBy0dpUnderCondition;
|
||||
|
||||
import android.view.View;
|
||||
import app.revanced.extension.music.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class HideCastButtonPatch {
|
||||
|
||||
/**
|
||||
* Injection point
|
||||
*/
|
||||
public static int hideCastButton(int original) {
|
||||
return Settings.HIDE_CAST_BUTTON.get() ? View.GONE : original;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point
|
||||
*/
|
||||
public static void hideCastButton(View view) {
|
||||
hideViewBy0dpUnderCondition(Settings.HIDE_CAST_BUTTON.get(), view);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
package app.revanced.extension.music.patches;
|
||||
|
||||
import static app.revanced.extension.shared.Utils.hideViewBy0dpUnderCondition;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import app.revanced.extension.music.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@@ -8,7 +12,7 @@ public class HideCategoryBarPatch {
|
||||
/**
|
||||
* Injection point
|
||||
*/
|
||||
public static boolean hideCategoryBar() {
|
||||
return Settings.HIDE_CATEGORY_BAR.get();
|
||||
public static void hideCategoryBar(View view) {
|
||||
hideViewBy0dpUnderCondition(Settings.HIDE_CATEGORY_BAR, view);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package app.revanced.extension.music.patches.spoof;
|
||||
|
||||
import static app.revanced.extension.music.settings.Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE;
|
||||
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_NO_SDK;
|
||||
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_43_32;
|
||||
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_61_48;
|
||||
import static app.revanced.extension.shared.spoof.ClientType.VISIONOS;
|
||||
@@ -18,8 +19,9 @@ public class SpoofVideoStreamsPatch {
|
||||
public static void setClientOrderToUse() {
|
||||
List<ClientType> availableClients = List.of(
|
||||
ANDROID_VR_1_43_32,
|
||||
ANDROID_VR_1_61_48,
|
||||
VISIONOS
|
||||
ANDROID_NO_SDK,
|
||||
VISIONOS,
|
||||
ANDROID_VR_1_61_48
|
||||
);
|
||||
|
||||
app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.setClientsToUse(
|
||||
|
||||
@@ -16,8 +16,11 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting HIDE_GET_PREMIUM_LABEL = new BooleanSetting("revanced_music_hide_get_premium_label", TRUE, true);
|
||||
|
||||
// General
|
||||
public static final BooleanSetting HIDE_CAST_BUTTON = new BooleanSetting("revanced_music_hide_cast_button", TRUE, false);
|
||||
public static final BooleanSetting HIDE_CAST_BUTTON = new BooleanSetting("revanced_music_hide_cast_button", TRUE, true);
|
||||
public static final BooleanSetting HIDE_CATEGORY_BAR = new BooleanSetting("revanced_music_hide_category_bar", FALSE, true);
|
||||
public static final BooleanSetting HIDE_HISTORY_BUTTON = new BooleanSetting("revanced_music_hide_history_button", FALSE, true);
|
||||
public static final BooleanSetting HIDE_SEARCH_BUTTON = new BooleanSetting("revanced_music_hide_search_button", FALSE, true);
|
||||
public static final BooleanSetting HIDE_NOTIFICATION_BUTTON = new BooleanSetting("revanced_music_hide_notification_button", FALSE, true);
|
||||
public static final BooleanSetting HIDE_NAVIGATION_BAR_HOME_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_home_button", FALSE, true);
|
||||
public static final BooleanSetting HIDE_NAVIGATION_BAR_SAMPLES_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_samples_button", FALSE, true);
|
||||
public static final BooleanSetting HIDE_NAVIGATION_BAR_EXPLORE_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_explore_button", FALSE, true);
|
||||
@@ -27,6 +30,7 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting HIDE_NAVIGATION_BAR_LABEL = new BooleanSetting("revanced_music_hide_navigation_bar_labels", FALSE, true);
|
||||
|
||||
// Player
|
||||
public static final BooleanSetting CHANGE_MINIPLAYER_COLOR = new BooleanSetting("revanced_music_change_miniplayer_color", FALSE, true);
|
||||
public static final BooleanSetting PERMANENT_REPEAT = new BooleanSetting("revanced_music_play_permanent_repeat", FALSE, true);
|
||||
|
||||
// Miscellaneous
|
||||
|
||||
@@ -16,6 +16,7 @@ import java.util.Arrays;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.ui.Dim;
|
||||
|
||||
import com.amazon.video.sdk.player.Player;
|
||||
|
||||
@@ -64,9 +65,8 @@ public class PlaybackSpeedPatch {
|
||||
SpeedIconDrawable speedIcon = new SpeedIconDrawable();
|
||||
speedButton.setImageDrawable(speedIcon);
|
||||
|
||||
int buttonSize = Utils.dipToPixels(48);
|
||||
speedButton.setMinimumWidth(buttonSize);
|
||||
speedButton.setMinimumHeight(buttonSize);
|
||||
speedButton.setMinimumWidth(Dim.dp48);
|
||||
speedButton.setMinimumHeight(Dim.dp48);
|
||||
|
||||
return speedButton;
|
||||
}
|
||||
@@ -197,11 +197,11 @@ class SpeedIconDrawable extends Drawable {
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return Utils.dipToPixels(32);
|
||||
return Dim.dp32;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return Utils.dipToPixels(32);
|
||||
return Dim.dp32;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4
extensions/samsung/radio/build.gradle.kts
Normal file
4
extensions/samsung/radio/build.gradle.kts
Normal file
@@ -0,0 +1,4 @@
|
||||
dependencies {
|
||||
compileOnly(project(":extensions:shared:library"))
|
||||
compileOnly(project(":extensions:samsung:radio:stub"))
|
||||
}
|
||||
1
extensions/samsung/radio/src/main/AndroidManifest.xml
Normal file
1
extensions/samsung/radio/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest/>
|
||||
@@ -0,0 +1,24 @@
|
||||
package app.revanced.extension.samsung.radio.misc.fix.crash;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class FixCrashPatch {
|
||||
/**
|
||||
* Injection point.
|
||||
* <p>
|
||||
* Add the required permissions to the request list to avoid crashes on API 34+.
|
||||
**/
|
||||
public static final String[] fixPermissionRequestList(String[] perms) {
|
||||
List<String> permsList = new ArrayList<>(Arrays.asList(perms));
|
||||
if (permsList.contains("android.permission.POST_NOTIFICATIONS")) {
|
||||
permsList.addAll(Arrays.asList("android.permission.RECORD_AUDIO", "android.permission.READ_PHONE_STATE", "android.permission.FOREGROUND_SERVICE_MICROPHONE"));
|
||||
}
|
||||
if (permsList.contains("android.permission.RECORD_AUDIO")) {
|
||||
permsList.add("android.permission.FOREGROUND_SERVICE_MICROPHONE");
|
||||
}
|
||||
return permsList.toArray(new String[0]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package app.revanced.extension.samsung.radio.restrictions.device;
|
||||
|
||||
import android.os.SemSystemProperties;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class BypassDeviceChecksPatch {
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* <p>
|
||||
* Check if the device has the required hardware
|
||||
**/
|
||||
public static final boolean checkIfDeviceIsIncompatible(String[] deviceList) {
|
||||
String currentDevice = SemSystemProperties.getSalesCode();
|
||||
return Arrays.asList(deviceList).contains(currentDevice);
|
||||
}
|
||||
}
|
||||
17
extensions/samsung/radio/stub/build.gradle.kts
Normal file
17
extensions/samsung/radio/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 = 24
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<manifest/>
|
||||
@@ -0,0 +1,7 @@
|
||||
package android.os;
|
||||
|
||||
public class SemSystemProperties {
|
||||
public static String getSalesCode() {
|
||||
throw new UnsupportedOperationException("Stub");
|
||||
}
|
||||
}
|
||||
@@ -23,9 +23,7 @@ import android.os.Looper;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceGroup;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Pair;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -34,17 +32,15 @@ import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.Toast;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.text.Bidi;
|
||||
import java.text.Collator;
|
||||
import java.text.Normalizer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -61,6 +57,7 @@ import app.revanced.extension.shared.settings.AppLanguage;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.shared.settings.BooleanSetting;
|
||||
import app.revanced.extension.shared.settings.preference.ReVancedAboutPreference;
|
||||
import app.revanced.extension.shared.ui.Dim;
|
||||
|
||||
@SuppressWarnings("NewApi")
|
||||
public class Utils {
|
||||
@@ -79,6 +76,15 @@ public class Utils {
|
||||
@Nullable
|
||||
private static Boolean isDarkModeEnabled;
|
||||
|
||||
// Cached Collator instance with its locale.
|
||||
@Nullable
|
||||
private static Locale cachedCollatorLocale;
|
||||
@Nullable
|
||||
private static Collator cachedCollator;
|
||||
|
||||
private static final Pattern PUNCTUATION_PATTERN = Pattern.compile("\\p{P}+");
|
||||
private static final Pattern DIACRITICS_PATTERN = Pattern.compile("\\p{M}");
|
||||
|
||||
private Utils() {
|
||||
} // utility class
|
||||
|
||||
@@ -749,31 +755,25 @@ public class Utils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide a view by setting its layout params to 0x0
|
||||
* @param view The view to hide.
|
||||
* Hides a view by setting its layout width and height to 0dp.
|
||||
* Handles null layout params safely.
|
||||
*
|
||||
* @param view The view to hide. If null, does nothing.
|
||||
*/
|
||||
public static void hideViewByLayoutParams(View view) {
|
||||
if (view instanceof LinearLayout) {
|
||||
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(0, 0);
|
||||
view.setLayoutParams(layoutParams);
|
||||
} else if (view instanceof FrameLayout) {
|
||||
FrameLayout.LayoutParams layoutParams2 = new FrameLayout.LayoutParams(0, 0);
|
||||
view.setLayoutParams(layoutParams2);
|
||||
} else if (view instanceof RelativeLayout) {
|
||||
RelativeLayout.LayoutParams layoutParams3 = new RelativeLayout.LayoutParams(0, 0);
|
||||
view.setLayoutParams(layoutParams3);
|
||||
} else if (view instanceof Toolbar) {
|
||||
Toolbar.LayoutParams layoutParams4 = new Toolbar.LayoutParams(0, 0);
|
||||
view.setLayoutParams(layoutParams4);
|
||||
} else if (view instanceof ViewGroup) {
|
||||
ViewGroup.LayoutParams layoutParams5 = new ViewGroup.LayoutParams(0, 0);
|
||||
view.setLayoutParams(layoutParams5);
|
||||
public static void hideViewByLayoutParams(@Nullable View view) {
|
||||
if (view == null) return;
|
||||
|
||||
ViewGroup.LayoutParams params = view.getLayoutParams();
|
||||
|
||||
if (params == null) {
|
||||
// Create generic 0x0 layout params accepted by all ViewGroups.
|
||||
params = new ViewGroup.LayoutParams(0, 0);
|
||||
} else {
|
||||
ViewGroup.LayoutParams params = view.getLayoutParams();
|
||||
params.width = 0;
|
||||
params.height = 0;
|
||||
view.setLayoutParams(params);
|
||||
}
|
||||
|
||||
view.setLayoutParams(params);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -790,13 +790,10 @@ public class Utils {
|
||||
public static void setDialogWindowParameters(Window window, int gravity, int yOffsetDip, int widthPercentage, boolean dimAmount) {
|
||||
WindowManager.LayoutParams params = window.getAttributes();
|
||||
|
||||
DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
|
||||
int portraitWidth = Math.min(displayMetrics.widthPixels, displayMetrics.heightPixels);
|
||||
|
||||
params.width = (int) (portraitWidth * (widthPercentage / 100.0f)); // Set width based on parameters.
|
||||
params.width = Dim.pctPortraitWidth(widthPercentage);
|
||||
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||
params.gravity = gravity;
|
||||
params.y = yOffsetDip > 0 ? dipToPixels(yOffsetDip) : 0;
|
||||
params.y = yOffsetDip > 0 ? Dim.dp(yOffsetDip) : 0;
|
||||
if (dimAmount) {
|
||||
params.dimAmount = 0f;
|
||||
}
|
||||
@@ -805,18 +802,6 @@ public class Utils {
|
||||
window.setBackgroundDrawable(null); // Remove default dialog background
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an array of corner radii for a rounded rectangle shape.
|
||||
*
|
||||
* @param dp Radius in density-independent pixels (dip) to apply to all corners.
|
||||
* @return An array of eight float values representing the corner radii
|
||||
* (top-left, top-right, bottom-right, bottom-left).
|
||||
*/
|
||||
public static float[] createCornerRadii(float dp) {
|
||||
final float radius = dipToPixels(dp);
|
||||
return new float[]{radius, radius, radius, radius, radius, radius, radius, radius};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the theme light color used by the app.
|
||||
*/
|
||||
@@ -976,30 +961,60 @@ public class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
private static final Pattern punctuationPattern = Pattern.compile("\\p{P}+");
|
||||
|
||||
/**
|
||||
* Strips all punctuation and converts to lower case. A null parameter returns an empty string.
|
||||
* Removes punctuation and converts text to lowercase. Returns an empty string if input is null.
|
||||
*/
|
||||
public static String removePunctuationToLowercase(@Nullable CharSequence original) {
|
||||
if (original == null) return "";
|
||||
return punctuationPattern.matcher(original).replaceAll("")
|
||||
return PUNCTUATION_PATTERN.matcher(original).replaceAll("")
|
||||
.toLowerCase(BaseSettings.REVANCED_LANGUAGE.get().getLocale());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort a PreferenceGroup and all it's sub groups by title or key.
|
||||
* Normalizes text for search: applies NFD, removes diacritics, and lowercases (locale-neutral).
|
||||
* Returns an empty string if input is null.
|
||||
*/
|
||||
public static String normalizeTextToLowercase(@Nullable CharSequence original) {
|
||||
if (original == null) return "";
|
||||
return DIACRITICS_PATTERN.matcher(Normalizer.normalize(original, Normalizer.Form.NFD))
|
||||
.replaceAll("").toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a cached Collator for the current locale, or creates a new one if locale changed.
|
||||
*/
|
||||
private static Collator getCollator() {
|
||||
Locale currentLocale = BaseSettings.REVANCED_LANGUAGE.get().getLocale();
|
||||
|
||||
if (cachedCollator == null || !currentLocale.equals(cachedCollatorLocale)) {
|
||||
cachedCollatorLocale = currentLocale;
|
||||
cachedCollator = Collator.getInstance(currentLocale);
|
||||
cachedCollator.setStrength(Collator.SECONDARY); // Case-insensitive, diacritic-insensitive.
|
||||
}
|
||||
|
||||
return cachedCollator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts a {@link PreferenceGroup} and all nested subgroups by title or key.
|
||||
* <p>
|
||||
* Sort order is determined by the preferences key {@link Sort} suffix.
|
||||
* The sort order is controlled by the {@link Sort} suffix present in the preference key.
|
||||
* Preferences without a key or without a {@link Sort} suffix remain in their original order.
|
||||
* <p>
|
||||
* If a preference has no key or no {@link Sort} suffix,
|
||||
* then the preferences are left unsorted.
|
||||
* Sorting is performed using {@link Collator} with the current user locale,
|
||||
* ensuring correct alphabetical ordering for all supported languages
|
||||
* (e.g., Ukrainian "і", German "ß", French accented characters, etc.).
|
||||
*
|
||||
* @param group the {@link PreferenceGroup} to sort
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void sortPreferenceGroups(PreferenceGroup group) {
|
||||
Sort groupSort = Sort.fromKey(group.getKey(), Sort.UNSORTED);
|
||||
List<Pair<String, Preference>> preferences = new ArrayList<>();
|
||||
|
||||
// Get cached Collator for locale-aware string comparison.
|
||||
Collator collator = getCollator();
|
||||
|
||||
for (int i = 0, prefCount = group.getPreferenceCount(); i < prefCount; i++) {
|
||||
Preference preference = group.getPreference(i);
|
||||
|
||||
@@ -1030,10 +1045,11 @@ public class Utils {
|
||||
preferences.add(new Pair<>(sortValue, preference));
|
||||
}
|
||||
|
||||
//noinspection ComparatorCombinators
|
||||
// Sort the list using locale-specific collation rules.
|
||||
Collections.sort(preferences, (pair1, pair2)
|
||||
-> pair1.first.compareTo(pair2.first));
|
||||
-> collator.compare(pair1.first, pair2.first));
|
||||
|
||||
// Reassign order values to reflect the new sorted sequence
|
||||
int index = 0;
|
||||
for (Pair<String, Preference> pair : preferences) {
|
||||
int order = index++;
|
||||
@@ -1090,42 +1106,6 @@ public class Utils {
|
||||
return getResourceColor(colorString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts dip value to actual device pixels.
|
||||
*
|
||||
* @param dip The density-independent pixels value.
|
||||
* @return The device pixel value.
|
||||
*/
|
||||
public static int dipToPixels(float dip) {
|
||||
return (int) TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
dip,
|
||||
Resources.getSystem().getDisplayMetrics()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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));
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses {@link #adjustColorBrightness(int, float)} depending if light or dark mode is active.
|
||||
*/
|
||||
|
||||
@@ -61,7 +61,11 @@ public class CheckWatchHistoryDomainNameResolutionPatch {
|
||||
// Prevent this false positive by verify youtube.com resolves.
|
||||
// If youtube.com does not resolve, then it's not a watch history domain resolving error
|
||||
// because the entire app will not work since no domains are resolving.
|
||||
if (!domainResolvesToValidIP("youtube.com")
|
||||
String domainYouTube = "youtube.com";
|
||||
if (!domainResolvesToValidIP(domainYouTube)
|
||||
|| domainResolvesToValidIP(HISTORY_TRACKING_ENDPOINT)
|
||||
// Check multiple times, so a false positive from a flaky connection is almost impossible.
|
||||
|| !domainResolvesToValidIP(domainYouTube)
|
||||
|| domainResolvesToValidIP(HISTORY_TRACKING_ENDPOINT)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Color;
|
||||
import android.view.View;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -71,6 +72,17 @@ public class CustomBrandingPatch {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static View getLottieViewOrNull(View lottieStartupView) {
|
||||
if (BaseSettings.CUSTOM_BRANDING_ICON.get() == BrandingTheme.ORIGINAL) {
|
||||
return lottieStartupView;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
@@ -150,14 +162,14 @@ public class CustomBrandingPatch {
|
||||
}
|
||||
|
||||
for (ComponentName disable : componentsToDisable) {
|
||||
// Use info logging because if the alias status become corrupt the app cannot launch.
|
||||
Logger.printInfo(() -> "Disabling: " + disable.getClassName());
|
||||
pm.setComponentEnabledSetting(disable,
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
|
||||
}
|
||||
|
||||
// Use info logging because if the alias status become corrupt the app cannot launch.
|
||||
ComponentName componentToEnableFinal = componentToEnable;
|
||||
Logger.printInfo(() -> "Enabling: " + componentToEnableFinal.getClassName());
|
||||
|
||||
pm.setComponentEnabledSetting(componentToEnable,
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
|
||||
} catch (Exception ex) {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package app.revanced.extension.shared.patches;
|
||||
|
||||
import static java.lang.Boolean.TRUE;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
@@ -21,12 +25,28 @@ public final class EnableDebuggingPatch {
|
||||
? new ConcurrentHashMap<>(800, 0.5f, 1)
|
||||
: null;
|
||||
|
||||
private static final Set<Long> DISABLED_FEATURE_FLAGS = parseFlags(BaseSettings.DISABLED_FEATURE_FLAGS.get());
|
||||
|
||||
// Log all disabled flags on app startup.
|
||||
static {
|
||||
if (LOG_FEATURE_FLAGS && !DISABLED_FEATURE_FLAGS.isEmpty()) {
|
||||
StringBuilder sb = new StringBuilder("Disabled feature flags:\n");
|
||||
for (Long flag : DISABLED_FEATURE_FLAGS) {
|
||||
sb.append(" ").append(flag).append('\n');
|
||||
}
|
||||
Logger.printDebug(sb::toString);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static boolean isBooleanFeatureFlagEnabled(boolean value, Long flag) {
|
||||
if (LOG_FEATURE_FLAGS && value) {
|
||||
if (featureFlags.putIfAbsent(flag, true) == null) {
|
||||
if (DISABLED_FEATURE_FLAGS.contains(flag)) {
|
||||
return false;
|
||||
}
|
||||
if (featureFlags.putIfAbsent(flag, TRUE) == null) {
|
||||
Logger.printDebug(() -> "boolean feature is enabled: " + flag);
|
||||
}
|
||||
}
|
||||
@@ -70,10 +90,44 @@ public final class EnableDebuggingPatch {
|
||||
if (LOG_FEATURE_FLAGS && !defaultValue.equals(value)) {
|
||||
if (featureFlags.putIfAbsent(flag, true) == null) {
|
||||
Logger.printDebug(() -> " string feature is enabled: " + flag
|
||||
+ " value: " + value + (defaultValue.isEmpty() ? "" : " default: " + defaultValue));
|
||||
+ " value: " + value + (defaultValue.isEmpty() ? "" : " default: " + defaultValue));
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all logged feature flags.
|
||||
* @return Set of all known flags
|
||||
*/
|
||||
public static Set<Long> getAllLoggedFlags() {
|
||||
if (featureFlags != null) {
|
||||
return new HashSet<>(featureFlags.keySet());
|
||||
}
|
||||
|
||||
return new HashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Public method for parsing flags.
|
||||
* @param flags String containing newline-separated flag IDs
|
||||
* @return Set of parsed flag IDs
|
||||
*/
|
||||
public static Set<Long> parseFlags(String flags) {
|
||||
Set<Long> parsedFlags = new HashSet<>();
|
||||
if (!flags.isBlank()) {
|
||||
for (String flag : flags.split("\n")) {
|
||||
String trimmedFlag = flag.trim();
|
||||
if (trimmedFlag.isEmpty()) continue; // Skip empty lines.
|
||||
try {
|
||||
parsedFlags.add(Long.parseLong(trimmedFlag));
|
||||
} catch (NumberFormatException e) {
|
||||
Logger.printException(() -> "Invalid flag ID: " + flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parsedFlags;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,6 @@ public class LinkSanitizer {
|
||||
|
||||
public LinkSanitizer(String ... parametersToRemove) {
|
||||
final int parameterCount = parametersToRemove.length;
|
||||
if (parameterCount == 0) {
|
||||
throw new IllegalArgumentException("No parameters specified");
|
||||
}
|
||||
|
||||
// List is faster if only checking a few parameters.
|
||||
this.parametersToRemove = parameterCount > 4
|
||||
@@ -38,12 +35,23 @@ public class LinkSanitizer {
|
||||
|
||||
public Uri sanitizeUri(Uri uri) {
|
||||
try {
|
||||
String scheme = uri.getScheme();
|
||||
if (scheme == null || !(scheme.equals("http") || scheme.equals("https"))) {
|
||||
// Opening YouTube share sheet 'other' option passes the video title as a URI.
|
||||
// Checking !uri.isHierarchical() works for all cases, except if the
|
||||
// video title starts with / and then it's hierarchical but still an invalid URI.
|
||||
Logger.printDebug(() -> "Ignoring uri: " + uri);
|
||||
return uri;
|
||||
}
|
||||
|
||||
Uri.Builder builder = uri.buildUpon().clearQuery();
|
||||
|
||||
for (String paramName : uri.getQueryParameterNames()) {
|
||||
if (!parametersToRemove.contains(paramName)) {
|
||||
for (String value : uri.getQueryParameters(paramName)) {
|
||||
builder.appendQueryParameter(paramName, value);
|
||||
if (!parametersToRemove.isEmpty()) {
|
||||
for (String paramName : uri.getQueryParameterNames()) {
|
||||
if (!parametersToRemove.contains(paramName)) {
|
||||
for (String value : uri.getQueryParameters(paramName)) {
|
||||
builder.appendQueryParameter(paramName, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
@@ -16,6 +15,7 @@ import android.widget.Toolbar;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.preference.ToolbarPreferenceFragment;
|
||||
import app.revanced.extension.shared.ui.Dim;
|
||||
|
||||
/**
|
||||
* Base class for hooking activities to inject a custom PreferenceFragment with a toolbar.
|
||||
@@ -109,13 +109,12 @@ public abstract class BaseActivityHook extends Activity {
|
||||
toolbar.setNavigationOnClickListener(getNavigationClickListener(activity));
|
||||
toolbar.setTitle(STRING_REVANCED_SETTINGS_TITLE);
|
||||
|
||||
final int margin = Utils.dipToPixels(16);
|
||||
toolbar.setTitleMarginStart(margin);
|
||||
toolbar.setTitleMarginEnd(margin);
|
||||
toolbar.setTitleMarginStart(Dim.dp16);
|
||||
toolbar.setTitleMarginEnd(Dim.dp16);
|
||||
TextView toolbarTextView = Utils.getChildView(toolbar, false, view -> view instanceof TextView);
|
||||
if (toolbarTextView != null) {
|
||||
toolbarTextView.setTextColor(Utils.getAppForegroundColor());
|
||||
toolbarTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
|
||||
toolbarTextView.setTextSize(20);
|
||||
}
|
||||
setToolbarLayoutParams(toolbar);
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import static java.lang.Boolean.FALSE;
|
||||
import static java.lang.Boolean.TRUE;
|
||||
import static app.revanced.extension.shared.patches.CustomBrandingPatch.BrandingTheme;
|
||||
import static app.revanced.extension.shared.settings.Setting.parent;
|
||||
import static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.AudioStreamLanguageOverrideAvailability;
|
||||
|
||||
/**
|
||||
* Settings shared across multiple apps.
|
||||
@@ -34,7 +33,6 @@ public class BaseSettings {
|
||||
//
|
||||
|
||||
public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("revanced_spoof_video_streams", TRUE, true, "revanced_spoof_video_streams_user_dialog_message");
|
||||
public static final EnumSetting<AppLanguage> SPOOF_VIDEO_STREAMS_LANGUAGE = new EnumSetting<>("revanced_spoof_video_streams_language", AppLanguage.DEFAULT, new AudioStreamLanguageOverrideAvailability());
|
||||
public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE, parent(SPOOF_VIDEO_STREAMS));
|
||||
|
||||
public static final BooleanSetting SANITIZE_SHARED_LINKS = new BooleanSetting("revanced_sanitize_sharing_links", TRUE);
|
||||
@@ -44,4 +42,6 @@ public class BaseSettings {
|
||||
|
||||
public static final EnumSetting<BrandingTheme> CUSTOM_BRANDING_ICON = new EnumSetting<>("revanced_custom_branding_icon", BrandingTheme.ORIGINAL, true);
|
||||
public static final IntegerSetting CUSTOM_BRANDING_NAME = new IntegerSetting("revanced_custom_branding_name", 1, true);
|
||||
|
||||
public static final StringSetting DISABLED_FEATURE_FLAGS = new StringSetting("revanced_disabled_feature_flags", "", true, parent(DEBUG));
|
||||
}
|
||||
|
||||
@@ -392,10 +392,13 @@ public abstract class Setting<T> {
|
||||
|
||||
/**
|
||||
* Get the parent Settings that this setting depends on.
|
||||
* @return List of parent Settings (e.g., BooleanSetting or EnumSetting), or empty list if no dependencies exist.
|
||||
* @return List of parent Settings, or empty list if no dependencies exist.
|
||||
* Defensive: handles null availability or missing getParentSettings() override.
|
||||
*/
|
||||
public List<Setting<?>> getParentSettings() {
|
||||
return availability == null ? Collections.emptyList() : availability.getParentSettings();
|
||||
return availability == null
|
||||
? Collections.emptyList()
|
||||
: Objects.requireNonNullElse(availability.getParentSettings(), Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package app.revanced.extension.shared.settings.preference;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||
import static app.revanced.extension.shared.Utils.getResourceIdentifierOrThrow;
|
||||
|
||||
import android.app.Dialog;
|
||||
@@ -37,6 +36,7 @@ import app.revanced.extension.shared.settings.Setting;
|
||||
import app.revanced.extension.shared.settings.StringSetting;
|
||||
import app.revanced.extension.shared.ui.ColorDot;
|
||||
import app.revanced.extension.shared.ui.CustomDialog;
|
||||
import app.revanced.extension.shared.ui.Dim;
|
||||
|
||||
/**
|
||||
* A custom preference for selecting a color via a hexadecimal code or a color picker dialog.
|
||||
@@ -310,11 +310,8 @@ public class ColorPickerPreference extends EditTextPreference {
|
||||
inputLayout.setGravity(Gravity.CENTER_VERTICAL);
|
||||
|
||||
dialogColorDot = new View(context);
|
||||
LinearLayout.LayoutParams previewParams = new LinearLayout.LayoutParams(
|
||||
dipToPixels(20),
|
||||
dipToPixels(20)
|
||||
);
|
||||
previewParams.setMargins(dipToPixels(16), 0, dipToPixels(10), 0);
|
||||
LinearLayout.LayoutParams previewParams = new LinearLayout.LayoutParams(Dim.dp20,Dim.dp20);
|
||||
previewParams.setMargins(Dim.dp16, 0, Dim.dp10, 0);
|
||||
dialogColorDot.setLayoutParams(previewParams);
|
||||
inputLayout.addView(dialogColorDot);
|
||||
updateDialogColorDot();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package app.revanced.extension.shared.settings.preference;
|
||||
|
||||
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||
import static app.revanced.extension.shared.settings.preference.ColorPickerPreference.getColorString;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
@@ -21,6 +20,7 @@ import androidx.annotation.ColorInt;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.ui.Dim;
|
||||
|
||||
/**
|
||||
* A custom color picker view that allows the user to select a color using a hue slider, a saturation-value selector
|
||||
@@ -54,28 +54,28 @@ public class ColorPickerView extends View {
|
||||
}
|
||||
|
||||
/** Expanded touch area for the hue and opacity bars to increase the touch-sensitive area. */
|
||||
public static final float TOUCH_EXPANSION = dipToPixels(20f);
|
||||
public static final float TOUCH_EXPANSION = Dim.dp20;
|
||||
|
||||
/** Margin between different areas of the view (saturation-value selector, hue bar, and opacity slider). */
|
||||
private static final float MARGIN_BETWEEN_AREAS = dipToPixels(24);
|
||||
private static final float MARGIN_BETWEEN_AREAS = Dim.dp24;
|
||||
|
||||
/** Padding around the view. */
|
||||
private static final float VIEW_PADDING = dipToPixels(16);
|
||||
private static final float VIEW_PADDING = Dim.dp16;
|
||||
|
||||
/** Height of the hue bar. */
|
||||
private static final float HUE_BAR_HEIGHT = dipToPixels(12);
|
||||
private static final float HUE_BAR_HEIGHT = Dim.dp12;
|
||||
|
||||
/** Height of the opacity slider. */
|
||||
private static final float OPACITY_BAR_HEIGHT = dipToPixels(12);
|
||||
private static final float OPACITY_BAR_HEIGHT = Dim.dp12;
|
||||
|
||||
/** Corner radius for the hue bar. */
|
||||
private static final float HUE_CORNER_RADIUS = dipToPixels(6);
|
||||
private static final float HUE_CORNER_RADIUS = Dim.dp6;
|
||||
|
||||
/** Corner radius for the opacity slider. */
|
||||
private static final float OPACITY_CORNER_RADIUS = dipToPixels(6);
|
||||
private static final float OPACITY_CORNER_RADIUS = Dim.dp6;
|
||||
|
||||
/** Radius of the selector handles. */
|
||||
private static final float SELECTOR_RADIUS = dipToPixels(12);
|
||||
private static final float SELECTOR_RADIUS = Dim.dp12;
|
||||
|
||||
/** Stroke width for the selector handle outlines. */
|
||||
private static final float SELECTOR_STROKE_WIDTH = 8;
|
||||
@@ -202,7 +202,7 @@ public class ColorPickerView extends View {
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
final float DESIRED_ASPECT_RATIO = 0.8f; // height = width * 0.8
|
||||
|
||||
final int minWidth = dipToPixels(250);
|
||||
final int minWidth = Dim.dp(250);
|
||||
final int minHeight = (int) (minWidth * DESIRED_ASPECT_RATIO) + (int) (HUE_BAR_HEIGHT + MARGIN_BETWEEN_AREAS)
|
||||
+ (opacitySliderEnabled ? (int) (OPACITY_BAR_HEIGHT + MARGIN_BETWEEN_AREAS) : 0);
|
||||
|
||||
|
||||
@@ -0,0 +1,623 @@
|
||||
package app.revanced.extension.shared.settings.preference;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.Utils.getResourceIdentifierOrThrow;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.ShapeDrawable;
|
||||
import android.graphics.drawable.shapes.RoundRectShape;
|
||||
import android.preference.Preference;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Pair;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.view.Gravity;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Space;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.patches.EnableDebuggingPatch;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.shared.ui.CustomDialog;
|
||||
import app.revanced.extension.shared.ui.Dim;
|
||||
|
||||
/**
|
||||
* A custom preference that opens a dialog for managing feature flags.
|
||||
* Allows moving boolean flags between active and blocked states with advanced selection.
|
||||
*/
|
||||
@SuppressWarnings({"deprecation", "unused"})
|
||||
public class FeatureFlagsManagerPreference extends Preference {
|
||||
|
||||
private static final int DRAWABLE_REVANCED_SETTINGS_SELECT_ALL =
|
||||
getResourceIdentifierOrThrow("revanced_settings_select_all", "drawable");
|
||||
private static final int DRAWABLE_REVANCED_SETTINGS_DESELECT_ALL =
|
||||
getResourceIdentifierOrThrow("revanced_settings_deselect_all", "drawable");
|
||||
private static final int DRAWABLE_REVANCED_SETTINGS_COPY_ALL =
|
||||
getResourceIdentifierOrThrow("revanced_settings_copy_all", "drawable");
|
||||
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_RIGHT_ONE =
|
||||
getResourceIdentifierOrThrow("revanced_settings_arrow_right_one", "drawable");
|
||||
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_RIGHT_DOUBLE =
|
||||
getResourceIdentifierOrThrow("revanced_settings_arrow_right_double", "drawable");
|
||||
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_LEFT_ONE =
|
||||
getResourceIdentifierOrThrow("revanced_settings_arrow_left_one", "drawable");
|
||||
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_LEFT_DOUBLE =
|
||||
getResourceIdentifierOrThrow("revanced_settings_arrow_left_double", "drawable");
|
||||
|
||||
/**
|
||||
* Flags to hide from the UI.
|
||||
*/
|
||||
private static final Set<Long> FLAGS_TO_IGNORE = Set.of(
|
||||
45386834L // 'You' tab settings icon.
|
||||
);
|
||||
|
||||
/**
|
||||
* Tracks state for range selection in ListView.
|
||||
*/
|
||||
private static class ListViewSelectionState {
|
||||
int lastClickedPosition = -1; // Position of the last clicked item.
|
||||
boolean isRangeSelecting = false; // True while a range is being selected.
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to pass ListView and Adapter together.
|
||||
*/
|
||||
private record ColumnViews(ListView listView, FlagAdapter adapter) {}
|
||||
|
||||
{
|
||||
setOnPreferenceClickListener(pref -> {
|
||||
showFlagsManagerDialog();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public FeatureFlagsManagerPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
public FeatureFlagsManagerPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public FeatureFlagsManagerPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public FeatureFlagsManagerPreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the main dialog for managing feature flags.
|
||||
*/
|
||||
private void showFlagsManagerDialog() {
|
||||
if (!BaseSettings.DEBUG.get()) {
|
||||
Utils.showToastShort(str("revanced_debug_logs_disabled"));
|
||||
return;
|
||||
}
|
||||
|
||||
Context context = getContext();
|
||||
|
||||
// Load all known and disabled flags.
|
||||
TreeSet<Long> allKnownFlags = new TreeSet<>(EnableDebuggingPatch.getAllLoggedFlags());
|
||||
allKnownFlags.removeAll(FLAGS_TO_IGNORE);
|
||||
|
||||
TreeSet<Long> disabledFlags = new TreeSet<>(EnableDebuggingPatch.parseFlags(
|
||||
BaseSettings.DISABLED_FEATURE_FLAGS.get()));
|
||||
disabledFlags.removeAll(FLAGS_TO_IGNORE);
|
||||
|
||||
if (allKnownFlags.isEmpty() && disabledFlags.isEmpty()) {
|
||||
// String does not need to be localized because it's basically impossible
|
||||
// to reach the settings menu without encountering at least 1 flag.
|
||||
Utils.showToastShort("No feature flags logged yet");
|
||||
return;
|
||||
}
|
||||
|
||||
TreeSet<Long> availableFlags = new TreeSet<>(allKnownFlags);
|
||||
availableFlags.removeAll(disabledFlags);
|
||||
TreeSet<Long> blockedFlags = new TreeSet<>(disabledFlags);
|
||||
|
||||
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
|
||||
context,
|
||||
getTitle() != null ? getTitle().toString() : "",
|
||||
null,
|
||||
null,
|
||||
str("revanced_settings_save"),
|
||||
() -> saveFlags(blockedFlags),
|
||||
() -> {},
|
||||
str("revanced_settings_reset"),
|
||||
this::resetFlags,
|
||||
true
|
||||
);
|
||||
|
||||
LinearLayout mainLayout = dialogPair.second;
|
||||
LinearLayout.LayoutParams contentParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT, 0, 1.0f);
|
||||
|
||||
// Insert content before the dialog button row.
|
||||
View contentView = createContentView(context, availableFlags, blockedFlags);
|
||||
mainLayout.addView(contentView, mainLayout.getChildCount() - 1, contentParams);
|
||||
|
||||
Dialog dialog = dialogPair.first;
|
||||
dialog.show();
|
||||
|
||||
Window window = dialog.getWindow();
|
||||
if (window != null) {
|
||||
Utils.setDialogWindowParameters(window, Gravity.CENTER, 0, 100, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the main content view with two columns.
|
||||
*/
|
||||
private View createContentView(Context context, TreeSet<Long> availableFlags, TreeSet<Long> blockedFlags) {
|
||||
LinearLayout contentLayout = new LinearLayout(context);
|
||||
contentLayout.setOrientation(LinearLayout.VERTICAL);
|
||||
|
||||
// Headers.
|
||||
TextView availableHeader = createHeader(context, "revanced_debug_feature_flags_manager_active_header");
|
||||
TextView blockedHeader = createHeader(context, "revanced_debug_feature_flags_manager_blocked_header");
|
||||
|
||||
LinearLayout headersLayout = new LinearLayout(context);
|
||||
headersLayout.setOrientation(LinearLayout.HORIZONTAL);
|
||||
headersLayout.addView(availableHeader, new LinearLayout.LayoutParams(
|
||||
0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f));
|
||||
headersLayout.addView(blockedHeader, new LinearLayout.LayoutParams(
|
||||
0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f));
|
||||
|
||||
// Columns.
|
||||
View leftColumn = createColumn(context, availableFlags, availableHeader);
|
||||
View rightColumn = createColumn(context, blockedFlags, blockedHeader);
|
||||
|
||||
ColumnViews leftViews = (ColumnViews) leftColumn.getTag();
|
||||
ColumnViews rightViews = (ColumnViews) rightColumn.getTag();
|
||||
|
||||
updateHeaderCount(availableHeader, leftViews.adapter);
|
||||
updateHeaderCount(blockedHeader, rightViews.adapter);
|
||||
|
||||
// Main columns layout.
|
||||
LinearLayout columnsLayout = new LinearLayout(context);
|
||||
columnsLayout.setOrientation(LinearLayout.HORIZONTAL);
|
||||
columnsLayout.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f));
|
||||
columnsLayout.addView(leftColumn, new LinearLayout.LayoutParams(
|
||||
0, ViewGroup.LayoutParams.MATCH_PARENT, 1f));
|
||||
|
||||
Space spaceBetweenColumns = new Space(context);
|
||||
spaceBetweenColumns.setLayoutParams(new LinearLayout.LayoutParams(Dim.dp8, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
columnsLayout.addView(spaceBetweenColumns);
|
||||
|
||||
columnsLayout.addView(rightColumn, new LinearLayout.LayoutParams(
|
||||
0, ViewGroup.LayoutParams.MATCH_PARENT, 1f));
|
||||
|
||||
// Move buttons below columns.
|
||||
Pair<LinearLayout, LinearLayout> moveButtons = createMoveButtons(context,
|
||||
leftViews.listView, rightViews.listView,
|
||||
availableFlags, blockedFlags, availableHeader, blockedHeader);
|
||||
|
||||
// Layout for buttons row.
|
||||
LinearLayout buttonsRow = new LinearLayout(context);
|
||||
buttonsRow.setOrientation(LinearLayout.HORIZONTAL);
|
||||
buttonsRow.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
buttonsRow.addView(moveButtons.first, new LinearLayout.LayoutParams(
|
||||
0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f));
|
||||
|
||||
Space spaceBetweenButtons = new Space(context);
|
||||
spaceBetweenButtons.setLayoutParams(new LinearLayout.LayoutParams(Dim.dp8, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
buttonsRow.addView(spaceBetweenButtons);
|
||||
|
||||
buttonsRow.addView(moveButtons.second, new LinearLayout.LayoutParams(
|
||||
0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f));
|
||||
|
||||
contentLayout.addView(headersLayout);
|
||||
contentLayout.addView(columnsLayout);
|
||||
contentLayout.addView(buttonsRow);
|
||||
|
||||
return contentLayout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a header TextView.
|
||||
*/
|
||||
private TextView createHeader(Context context, String tag) {
|
||||
TextView textview = new TextView(context);
|
||||
textview.setTag(tag);
|
||||
textview.setTextSize(16);
|
||||
textview.setTextColor(Utils.getAppForegroundColor());
|
||||
textview.setGravity(Gravity.CENTER);
|
||||
|
||||
return textview;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a single column (search + buttons + list).
|
||||
*/
|
||||
private View createColumn(Context context, TreeSet<Long> flags, TextView countText) {
|
||||
LinearLayout wrapper = new LinearLayout(context);
|
||||
wrapper.setOrientation(LinearLayout.VERTICAL);
|
||||
|
||||
Pair<ListView, FlagAdapter> pair = createListView(context, flags, countText);
|
||||
ListView listView = pair.first;
|
||||
FlagAdapter adapter = pair.second;
|
||||
|
||||
EditText search = createSearchBox(context, adapter, listView, countText);
|
||||
LinearLayout buttons = createActionButtons(context, listView, adapter);
|
||||
|
||||
listView.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f));
|
||||
ShapeDrawable background = new ShapeDrawable(new RoundRectShape(
|
||||
Dim.roundedCorners(10), null, null));
|
||||
background.getPaint().setColor(Utils.getEditTextBackground());
|
||||
listView.setPadding(0, Dim.dp4, 0, Dim.dp4);
|
||||
listView.setBackground(background);
|
||||
listView.setOverScrollMode(View.OVER_SCROLL_NEVER);
|
||||
|
||||
wrapper.addView(search);
|
||||
wrapper.addView(buttons);
|
||||
wrapper.addView(listView);
|
||||
|
||||
// Save references for move buttons.
|
||||
wrapper.setTag(new ColumnViews(listView, adapter));
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the header text with the current count.
|
||||
*/
|
||||
private void updateHeaderCount(TextView header, FlagAdapter adapter) {
|
||||
header.setText(str((String) header.getTag(), adapter.getCount()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a search box that filters the list.
|
||||
*/
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private EditText createSearchBox(Context context, FlagAdapter adapter, ListView listView, TextView countText) {
|
||||
EditText search = new EditText(context);
|
||||
search.setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||
search.setTextSize(16);
|
||||
search.setHint(str("revanced_debug_feature_flags_manager_search_hint"));
|
||||
search.setHapticFeedbackEnabled(false);
|
||||
search.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
search.addTextChangedListener(new TextWatcher() {
|
||||
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
adapter.setSearchQuery(s.toString());
|
||||
listView.clearChoices();
|
||||
updateHeaderCount(countText, adapter);
|
||||
Drawable clearIcon = context.getResources().getDrawable(android.R.drawable.ic_menu_close_clear_cancel);
|
||||
clearIcon.setBounds(0, 0, Dim.dp20, Dim.dp20);
|
||||
search.setCompoundDrawables(null, null, TextUtils.isEmpty(s) ? null : clearIcon, null);
|
||||
}
|
||||
@Override public void afterTextChanged(Editable s) {}
|
||||
});
|
||||
|
||||
search.setOnTouchListener((v, event) -> {
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
Drawable[] compoundDrawables = search.getCompoundDrawables();
|
||||
if (compoundDrawables[2] != null &&
|
||||
event.getRawX() >= (search.getRight() - compoundDrawables[2].getBounds().width())) {
|
||||
search.setText("");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
return search;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates action buttons.
|
||||
*/
|
||||
private LinearLayout createActionButtons(Context context, ListView listView, FlagAdapter adapter) {
|
||||
LinearLayout row = new LinearLayout(context);
|
||||
row.setOrientation(LinearLayout.HORIZONTAL);
|
||||
row.setGravity(Gravity.CENTER);
|
||||
row.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
ImageButton selectAll = createButton(context, DRAWABLE_REVANCED_SETTINGS_SELECT_ALL,
|
||||
() -> {
|
||||
for (int i = 0, count = adapter.getCount(); i < count; i++) {
|
||||
listView.setItemChecked(i, true);
|
||||
}
|
||||
});
|
||||
|
||||
ImageButton clearAll = createButton(context, DRAWABLE_REVANCED_SETTINGS_DESELECT_ALL,
|
||||
() -> {
|
||||
listView.clearChoices();
|
||||
adapter.notifyDataSetChanged();
|
||||
});
|
||||
|
||||
ImageButton copy = createButton(context, DRAWABLE_REVANCED_SETTINGS_COPY_ALL,
|
||||
() -> {
|
||||
List<String> items = new ArrayList<>();
|
||||
SparseBooleanArray checked = listView.getCheckedItemPositions();
|
||||
|
||||
if (checked.size() > 0) {
|
||||
for (int i = 0, count = adapter.getCount(); i < count; i++) {
|
||||
if (checked.get(i)) {
|
||||
items.add(adapter.getItem(i));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (Long flag : adapter.getFullFlags()) {
|
||||
items.add(String.valueOf(flag));
|
||||
}
|
||||
}
|
||||
|
||||
Utils.setClipboard(TextUtils.join("\n", items));
|
||||
|
||||
Utils.showToastShort(str("revanced_debug_feature_flags_manager_toast_copied"));
|
||||
});
|
||||
|
||||
row.addView(selectAll);
|
||||
row.addView(clearAll);
|
||||
row.addView(copy);
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the move buttons (left and right groups).
|
||||
*/
|
||||
private Pair<LinearLayout, LinearLayout> createMoveButtons(Context context,
|
||||
ListView availableListView, ListView blockedListView,
|
||||
TreeSet<Long> availableFlags, TreeSet<Long> blockedFlags,
|
||||
TextView availableCountText, TextView blockedCountText) {
|
||||
// Left group: >> >
|
||||
LinearLayout leftButtons = new LinearLayout(context);
|
||||
leftButtons.setOrientation(LinearLayout.HORIZONTAL);
|
||||
leftButtons.setGravity(Gravity.CENTER);
|
||||
|
||||
ImageButton moveAllRight = createButton(context, DRAWABLE_REVANCED_SETTINGS_ARROW_RIGHT_DOUBLE,
|
||||
() -> moveFlags(availableListView, blockedListView, availableFlags, blockedFlags,
|
||||
availableCountText, blockedCountText, true));
|
||||
|
||||
ImageButton moveOneRight = createButton(context, DRAWABLE_REVANCED_SETTINGS_ARROW_RIGHT_ONE,
|
||||
() -> moveFlags(availableListView, blockedListView, availableFlags, blockedFlags,
|
||||
availableCountText, blockedCountText, false));
|
||||
|
||||
leftButtons.addView(moveAllRight);
|
||||
leftButtons.addView(moveOneRight);
|
||||
|
||||
// Right group: < <<
|
||||
LinearLayout rightButtons = new LinearLayout(context);
|
||||
rightButtons.setOrientation(LinearLayout.HORIZONTAL);
|
||||
rightButtons.setGravity(Gravity.CENTER);
|
||||
|
||||
ImageButton moveOneLeft = createButton(context, DRAWABLE_REVANCED_SETTINGS_ARROW_LEFT_ONE,
|
||||
() -> moveFlags(blockedListView, availableListView, blockedFlags, availableFlags,
|
||||
blockedCountText, availableCountText, false));
|
||||
|
||||
ImageButton moveAllLeft = createButton(context, DRAWABLE_REVANCED_SETTINGS_ARROW_LEFT_DOUBLE,
|
||||
() -> moveFlags(blockedListView, availableListView, blockedFlags, availableFlags,
|
||||
blockedCountText, availableCountText, true));
|
||||
|
||||
rightButtons.addView(moveOneLeft);
|
||||
rightButtons.addView(moveAllLeft);
|
||||
|
||||
return new Pair<>(leftButtons, rightButtons);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a styled ImageButton.
|
||||
*/
|
||||
@SuppressLint("ResourceType")
|
||||
private ImageButton createButton(Context context, int drawableResId, Runnable action) {
|
||||
ImageButton button = new ImageButton(context);
|
||||
|
||||
button.setImageResource(drawableResId);
|
||||
button.setScaleType(ImageView.ScaleType.CENTER);
|
||||
int[] attrs = {android.R.attr.selectableItemBackgroundBorderless};
|
||||
//noinspection Recycle
|
||||
TypedArray ripple = context.obtainStyledAttributes(attrs);
|
||||
button.setBackgroundDrawable(ripple.getDrawable(0));
|
||||
ripple.close();
|
||||
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(Dim.dp32, Dim.dp32);
|
||||
params.setMargins(Dim.dp8, Dim.dp8, Dim.dp8, Dim.dp8);
|
||||
button.setLayoutParams(params);
|
||||
|
||||
button.setOnClickListener(v -> action.run());
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom adapter with search filtering.
|
||||
*/
|
||||
private static class FlagAdapter extends ArrayAdapter<String> {
|
||||
private final TreeSet<Long> fullFlags;
|
||||
private String searchQuery = "";
|
||||
|
||||
public FlagAdapter(Context context, TreeSet<Long> fullFlags) {
|
||||
super(context, android.R.layout.simple_list_item_multiple_choice, new ArrayList<>());
|
||||
this.fullFlags = fullFlags;
|
||||
updateFiltered();
|
||||
}
|
||||
|
||||
public void setSearchQuery(String query) {
|
||||
searchQuery = query == null ? "" : query.trim();
|
||||
updateFiltered();
|
||||
}
|
||||
|
||||
private void updateFiltered() {
|
||||
clear();
|
||||
for (Long flag : fullFlags) {
|
||||
String flagString = String.valueOf(flag);
|
||||
if (searchQuery.isEmpty() || flagString.contains(searchQuery)) {
|
||||
add(flagString);
|
||||
}
|
||||
}
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
updateFiltered();
|
||||
}
|
||||
|
||||
public List<Long> getFullFlags() {
|
||||
return new ArrayList<>(fullFlags);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ListView with filtering, multi-select, and range selection.
|
||||
*/
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private Pair<ListView, FlagAdapter> createListView(Context context,
|
||||
TreeSet<Long> flags, TextView countText) {
|
||||
ListView listView = new ListView(context);
|
||||
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
|
||||
listView.setDividerHeight(0);
|
||||
|
||||
FlagAdapter adapter = new FlagAdapter(context, flags);
|
||||
listView.setAdapter(adapter);
|
||||
|
||||
final ListViewSelectionState state = new ListViewSelectionState();
|
||||
|
||||
listView.setOnItemClickListener((parent, view, position, id) -> {
|
||||
if (!state.isRangeSelecting) {
|
||||
state.lastClickedPosition = position;
|
||||
} else {
|
||||
state.isRangeSelecting = false;
|
||||
}
|
||||
});
|
||||
|
||||
listView.setOnItemLongClickListener((parent, view, position, id) -> {
|
||||
if (state.lastClickedPosition == -1) {
|
||||
listView.setItemChecked(position, true);
|
||||
state.lastClickedPosition = position;
|
||||
} else {
|
||||
int start = Math.min(state.lastClickedPosition, position);
|
||||
int end = Math.max(state.lastClickedPosition, position);
|
||||
for (int i = start; i <= end; i++) {
|
||||
listView.setItemChecked(i, true);
|
||||
}
|
||||
state.isRangeSelecting = true;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
listView.setOnTouchListener((view, event) -> {
|
||||
if (event.getAction() == MotionEvent.ACTION_UP && state.isRangeSelecting) {
|
||||
state.isRangeSelecting = false;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
return new Pair<>(listView, adapter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves selected or all flags from one list to another.
|
||||
*
|
||||
* @param fromListView Source ListView.
|
||||
* @param toListView Destination ListView.
|
||||
* @param fromFlags Source flag set.
|
||||
* @param toFlags Destination flag set.
|
||||
* @param fromCountText Header showing count of source items.
|
||||
* @param toCountText Header showing count of destination items.
|
||||
* @param moveAll If true, move all items; if false, move only selected.
|
||||
*/
|
||||
private void moveFlags(ListView fromListView, ListView toListView,
|
||||
TreeSet<Long> fromFlags, TreeSet<Long> toFlags,
|
||||
TextView fromCountText, TextView toCountText,
|
||||
boolean moveAll) {
|
||||
if (fromListView == null || toListView == null) return;
|
||||
|
||||
List<Long> flagsToMove = new ArrayList<>();
|
||||
FlagAdapter fromAdapter = (FlagAdapter) fromListView.getAdapter();
|
||||
|
||||
if (moveAll) {
|
||||
flagsToMove.addAll(fromFlags);
|
||||
} else {
|
||||
SparseBooleanArray checked = fromListView.getCheckedItemPositions();
|
||||
for (int i = 0, count = fromAdapter.getCount(); i < count; i++) {
|
||||
if (checked.get(i)) {
|
||||
String item = fromAdapter.getItem(i);
|
||||
if (item != null) {
|
||||
flagsToMove.add(Long.parseLong(item));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (flagsToMove.isEmpty()) return;
|
||||
|
||||
for (Long flag : flagsToMove) {
|
||||
fromFlags.remove(flag);
|
||||
toFlags.add(flag);
|
||||
}
|
||||
|
||||
// Clear selections before refreshing.
|
||||
fromListView.clearChoices();
|
||||
toListView.clearChoices();
|
||||
|
||||
// Refresh both adapters.
|
||||
fromAdapter.refresh();
|
||||
((FlagAdapter) toListView.getAdapter()).refresh();
|
||||
|
||||
// Update headers.
|
||||
updateHeaderCount(fromCountText, fromAdapter);
|
||||
updateHeaderCount(toCountText, (FlagAdapter) toListView.getAdapter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves blocked flags to settings.
|
||||
*/
|
||||
private void saveFlags(TreeSet<Long> blockedFlags) {
|
||||
StringBuilder flagsString = new StringBuilder();
|
||||
for (Long flag : blockedFlags) {
|
||||
if (flagsString.length() > 0) {
|
||||
flagsString.append("\n");
|
||||
}
|
||||
flagsString.append(flag);
|
||||
}
|
||||
|
||||
BaseSettings.DISABLED_FEATURE_FLAGS.save(flagsString.toString());
|
||||
Utils.showToastShort(str("revanced_debug_feature_flags_manager_toast_saved"));
|
||||
Logger.printDebug(() -> "Feature flags saved. Blocked: " + blockedFlags.size());
|
||||
|
||||
AbstractPreferenceFragment.showRestartDialog(getContext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets all blocked flags.
|
||||
*/
|
||||
private void resetFlags() {
|
||||
BaseSettings.DISABLED_FEATURE_FLAGS.save("");
|
||||
Utils.showToastShort(str("revanced_debug_feature_flags_manager_toast_reset"));
|
||||
|
||||
AbstractPreferenceFragment.showRestartDialog(getContext());
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import android.preference.Preference;
|
||||
import android.text.InputType;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Pair;
|
||||
import android.util.TypedValue;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
@@ -35,7 +34,7 @@ public class ImportExportPreference extends EditTextPreference implements Prefer
|
||||
editText.setAutofillHints((String) null);
|
||||
}
|
||||
editText.setInputType(editText.getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
editText.setTextSize(TypedValue.COMPLEX_UNIT_PT, 7); // Use a smaller font to reduce text wrap.
|
||||
editText.setTextSize(14);
|
||||
|
||||
setOnPreferenceClickListener(this);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package app.revanced.extension.shared.settings.preference;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||
import static app.revanced.extension.shared.requests.Route.Method.GET;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
@@ -41,6 +40,7 @@ import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.requests.Requester;
|
||||
import app.revanced.extension.shared.requests.Route;
|
||||
import app.revanced.extension.shared.ui.Dim;
|
||||
|
||||
/**
|
||||
* Opens a dialog showing official links.
|
||||
@@ -222,11 +222,10 @@ class WebViewDialog extends Dialog {
|
||||
LinearLayout mainLayout = new LinearLayout(getContext());
|
||||
mainLayout.setOrientation(LinearLayout.VERTICAL);
|
||||
|
||||
final int padding = dipToPixels(10);
|
||||
mainLayout.setPadding(padding, padding, padding, padding);
|
||||
mainLayout.setPadding(Dim.dp10, Dim.dp10, Dim.dp10, Dim.dp10);
|
||||
// Set rounded rectangle background.
|
||||
ShapeDrawable mainBackground = new ShapeDrawable(new RoundRectShape(
|
||||
Utils.createCornerRadii(28), null, null));
|
||||
Dim.roundedCorners(28), null, null));
|
||||
mainBackground.getPaint().setColor(Utils.getDialogBackgroundColor());
|
||||
mainLayout.setBackground(mainBackground);
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import android.os.Build;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceGroup;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.util.TypedValue;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowInsets;
|
||||
@@ -20,6 +19,7 @@ import androidx.annotation.Nullable;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.BaseActivityHook;
|
||||
import app.revanced.extension.shared.ui.Dim;
|
||||
|
||||
@SuppressWarnings({"deprecation", "NewApi"})
|
||||
public class ToolbarPreferenceFragment extends AbstractPreferenceFragment {
|
||||
@@ -88,14 +88,13 @@ public class ToolbarPreferenceFragment extends AbstractPreferenceFragment {
|
||||
toolbar.setNavigationIcon(getBackButtonDrawable());
|
||||
toolbar.setNavigationOnClickListener(view -> preferenceScreenDialog.dismiss());
|
||||
|
||||
final int margin = Utils.dipToPixels(16);
|
||||
toolbar.setTitleMargin(margin, 0, margin, 0);
|
||||
toolbar.setTitleMargin(Dim.dp16, 0, Dim.dp16, 0);
|
||||
|
||||
TextView toolbarTextView = Utils.getChildView(toolbar,
|
||||
true, TextView.class::isInstance);
|
||||
if (toolbarTextView != null) {
|
||||
toolbarTextView.setTextColor(Utils.getAppForegroundColor());
|
||||
toolbarTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
|
||||
toolbarTextView.setTextSize(20);
|
||||
}
|
||||
|
||||
// Allow package-specific toolbar customization.
|
||||
|
||||
@@ -75,7 +75,7 @@ public abstract class BaseSearchResultItem {
|
||||
|
||||
// Shared method for highlighting text with search query.
|
||||
protected static CharSequence highlightSearchQuery(CharSequence text, Pattern queryPattern) {
|
||||
if (TextUtils.isEmpty(text)) return text;
|
||||
if (TextUtils.isEmpty(text) || queryPattern == null) return text;
|
||||
|
||||
final int adjustedColor = Utils.adjustColorBrightness(
|
||||
Utils.getAppBackgroundColor(), 0.95f, 1.20f);
|
||||
@@ -84,7 +84,10 @@ public abstract class BaseSearchResultItem {
|
||||
|
||||
Matcher matcher = queryPattern.matcher(text);
|
||||
while (matcher.find()) {
|
||||
spannable.setSpan(highlightSpan, matcher.start(), matcher.end(),
|
||||
int start = matcher.start();
|
||||
int end = matcher.end();
|
||||
if (start == end) continue; // Skip zero matches.
|
||||
spannable.setSpan(highlightSpan, start, end,
|
||||
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
@@ -224,10 +227,14 @@ public abstract class BaseSearchResultItem {
|
||||
return searchBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends normalized searchable text to the builder.
|
||||
* Uses full Unicode normalization for accurate search across all languages.
|
||||
*/
|
||||
private void appendText(StringBuilder builder, CharSequence text) {
|
||||
if (!TextUtils.isEmpty(text)) {
|
||||
if (builder.length() > 0) builder.append(" ");
|
||||
builder.append(Utils.removePunctuationToLowercase(text));
|
||||
builder.append(Utils.normalizeTextToLowercase(text));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,7 +279,7 @@ public abstract class BaseSearchResultItem {
|
||||
*/
|
||||
@Override
|
||||
boolean matchesQuery(String query) {
|
||||
return searchableText.contains(Utils.removePunctuationToLowercase(query));
|
||||
return searchableText.contains(Utils.normalizeTextToLowercase(query));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -484,7 +484,7 @@ public abstract class BaseSearchResultsAdapter extends ArrayAdapter<BaseSearchRe
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < adapter.getCount(); i++) {
|
||||
for (int i = 0, count = adapter.getCount(); i < count; i++) {
|
||||
Object item = adapter.getItem(i);
|
||||
if (item == targetPreference) {
|
||||
return i;
|
||||
@@ -522,8 +522,8 @@ public abstract class BaseSearchResultsAdapter extends ArrayAdapter<BaseSearchRe
|
||||
if (currentAnimator != null && currentAnimator.isRunning()) {
|
||||
currentAnimator.cancel();
|
||||
}
|
||||
int startColor = Utils.getAppBackgroundColor();
|
||||
int highlightColor = Utils.adjustColorBrightness(
|
||||
final int startColor = Utils.getAppBackgroundColor();
|
||||
final int highlightColor = Utils.adjustColorBrightness(
|
||||
startColor,
|
||||
Utils.isDarkModeEnabled() ? 1.25f : 0.8f
|
||||
);
|
||||
@@ -566,7 +566,7 @@ public abstract class BaseSearchResultsAdapter extends ArrayAdapter<BaseSearchRe
|
||||
}
|
||||
|
||||
// First search on current level.
|
||||
for (int i = 0; i < group.getPreferenceCount(); i++) {
|
||||
for (int i = 0, count = group.getPreferenceCount(); i < count; i++) {
|
||||
Preference pref = group.getPreference(i);
|
||||
if (key.equals(pref.getKey())) {
|
||||
return pref;
|
||||
|
||||
@@ -13,7 +13,6 @@ import android.preference.PreferenceCategory;
|
||||
import android.preference.PreferenceGroup;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.text.TextUtils;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
@@ -45,6 +44,7 @@ import app.revanced.extension.shared.settings.Setting;
|
||||
import app.revanced.extension.shared.settings.preference.ColorPickerPreference;
|
||||
import app.revanced.extension.shared.settings.preference.CustomDialogListPreference;
|
||||
import app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory;
|
||||
import app.revanced.extension.shared.ui.Dim;
|
||||
|
||||
/**
|
||||
* Abstract controller for managing the overlay search view in ReVanced settings.
|
||||
@@ -123,7 +123,7 @@ public abstract class BaseSearchViewController {
|
||||
searchView.setQueryHint(str("revanced_settings_search_hint"));
|
||||
|
||||
// Set text size.
|
||||
searchEditText.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
|
||||
searchEditText.setTextSize(16);
|
||||
|
||||
// Set cursor color.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
@@ -149,7 +149,7 @@ public abstract class BaseSearchViewController {
|
||||
// Create cursor drawable.
|
||||
GradientDrawable cursorDrawable = new GradientDrawable();
|
||||
cursorDrawable.setShape(GradientDrawable.RECTANGLE);
|
||||
cursorDrawable.setSize(Utils.dipToPixels(2), -1); // Width: 2dp, Height: match text height.
|
||||
cursorDrawable.setSize(Dim.dp2, -1); // Width: 2dp, Height: match text height.
|
||||
cursorDrawable.setColor(cursorColor);
|
||||
|
||||
// Set cursor drawable.
|
||||
@@ -164,7 +164,7 @@ public abstract class BaseSearchViewController {
|
||||
overlayContainer = new FrameLayout(activity);
|
||||
overlayContainer.setVisibility(View.GONE);
|
||||
overlayContainer.setBackgroundColor(Utils.getAppBackgroundColor());
|
||||
overlayContainer.setElevation(Utils.dipToPixels(8));
|
||||
overlayContainer.setElevation(Dim.dp8);
|
||||
|
||||
// Container for search results.
|
||||
FrameLayout searchResultsContainer = new FrameLayout(activity);
|
||||
@@ -450,7 +450,7 @@ public abstract class BaseSearchViewController {
|
||||
|
||||
filteredSearchItems.clear();
|
||||
|
||||
String queryLower = Utils.removePunctuationToLowercase(query);
|
||||
String queryLower = Utils.normalizeTextToLowercase(query);
|
||||
Pattern queryPattern = Pattern.compile(Pattern.quote(queryLower), Pattern.CASE_INSENSITIVE);
|
||||
|
||||
// Clear highlighting only for items that were previously visible.
|
||||
@@ -669,7 +669,7 @@ public abstract class BaseSearchViewController {
|
||||
protected static GradientDrawable createBackgroundDrawable() {
|
||||
GradientDrawable background = new GradientDrawable();
|
||||
background.setShape(GradientDrawable.RECTANGLE);
|
||||
background.setCornerRadius(Utils.dipToPixels(28));
|
||||
background.setCornerRadius(Dim.dp28);
|
||||
background.setColor(getSearchViewBackground());
|
||||
return background;
|
||||
}
|
||||
|
||||
@@ -54,6 +54,33 @@ public enum ClientType {
|
||||
ANDROID_VR_1_61_48.supportsMultiAudioTracks,
|
||||
"Android VR 1.43"
|
||||
),
|
||||
/**
|
||||
* Video not playable: Paid / Movie / Private / Age-restricted.
|
||||
* Note: The 'Authorization' key must be excluded from the header.
|
||||
*
|
||||
* According to TeamNewPipe in 2022, if the 'androidSdkVersion' field is missing,
|
||||
* the GVS did not return a valid response:
|
||||
* [NewPipe#8713 (comment)](https://github.com/TeamNewPipe/NewPipe/issues/8713#issuecomment-1207443550).
|
||||
*
|
||||
* According to the latest commit in yt-dlp, the GVS returns a valid response
|
||||
* even if the 'androidSdkVersion' field is missing:
|
||||
* [yt-dlp#14693](https://github.com/yt-dlp/yt-dlp/pull/14693).
|
||||
*
|
||||
* For some reason, PoToken is not required.
|
||||
*/
|
||||
ANDROID_NO_SDK(
|
||||
3,
|
||||
"ANDROID",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
Build.VERSION.RELEASE,
|
||||
"20.05.46",
|
||||
"com.google.android.youtube/20.05.46 (Linux; U; Android " + Build.VERSION.RELEASE + ") gzip",
|
||||
false,
|
||||
true,
|
||||
"Android No SDK"
|
||||
),
|
||||
/**
|
||||
* Cannot play livestreams and lacks HDR, but can play videos with music and labeled "for children".
|
||||
* <a href="https://dumps.tadiphone.dev/dumps/google/barbet">Google Pixel 9 Pro Fold</a>
|
||||
|
||||
@@ -14,19 +14,11 @@ import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.AppLanguage;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.shared.settings.Setting;
|
||||
import app.revanced.extension.shared.spoof.requests.StreamingDataRequest;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class SpoofVideoStreamsPatch {
|
||||
|
||||
public static final class AudioStreamLanguageOverrideAvailability implements Setting.Availability {
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return BaseSettings.SPOOF_VIDEO_STREAMS.get() && !preferredClient.useAuth;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Domain used for internet connectivity verification.
|
||||
* It has an empty response body and is only used to check for a 204 response code.
|
||||
@@ -62,8 +54,7 @@ public class SpoofVideoStreamsPatch {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param language Language override for non-authenticated requests. If this is null then
|
||||
* {@link BaseSettings#SPOOF_VIDEO_STREAMS_LANGUAGE} is used.
|
||||
* @param language Language override for non-authenticated requests.
|
||||
*/
|
||||
public static void setLanguageOverride(@Nullable AppLanguage language) {
|
||||
languageOverride = language;
|
||||
@@ -81,7 +72,7 @@ public class SpoofVideoStreamsPatch {
|
||||
public static boolean spoofingToClientWithNoMultiAudioStreams() {
|
||||
return isPatchIncluded()
|
||||
&& SPOOF_STREAMING_DATA
|
||||
&& preferredClient != ClientType.IPADOS;
|
||||
&& !preferredClient.supportsMultiAudioTracks;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package app.revanced.extension.shared.spoof.requests;
|
||||
|
||||
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_43_32;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
@@ -13,7 +11,6 @@ import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.requests.Requester;
|
||||
import app.revanced.extension.shared.requests.Route;
|
||||
import app.revanced.extension.shared.settings.AppLanguage;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.shared.spoof.ClientType;
|
||||
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
|
||||
|
||||
@@ -44,7 +41,7 @@ final class PlayerRoutes {
|
||||
AppLanguage language = SpoofVideoStreamsPatch.getLanguageOverride();
|
||||
if (language == null) {
|
||||
// Force original audio has not overrode the language.
|
||||
language = BaseSettings.SPOOF_VIDEO_STREAMS_LANGUAGE.get();
|
||||
language = AppLanguage.DEFAULT;
|
||||
}
|
||||
//noinspection ExtractMethodRecommender
|
||||
Locale streamLocale = language.getLocale();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package app.revanced.extension.shared.ui;
|
||||
|
||||
import static app.revanced.extension.shared.Utils.adjustColorBrightness;
|
||||
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||
import static app.revanced.extension.shared.Utils.getAppBackgroundColor;
|
||||
import static app.revanced.extension.shared.Utils.isDarkModeEnabled;
|
||||
import static app.revanced.extension.shared.settings.preference.ColorPickerPreference.DISABLED_ALPHA;
|
||||
@@ -13,7 +12,7 @@ import android.view.View;
|
||||
import androidx.annotation.ColorInt;
|
||||
|
||||
public class ColorDot {
|
||||
private static final int STROKE_WIDTH = dipToPixels(1.5f); // Stroke width in dp.
|
||||
private static final int STROKE_WIDTH = Dim.dp(1.5f);
|
||||
|
||||
/**
|
||||
* Creates a circular drawable with a main fill and a stroke.
|
||||
@@ -55,7 +54,7 @@ public class ColorDot {
|
||||
targetView.setAlpha(enabled ? 1.0f : DISABLED_ALPHA);
|
||||
if (!isDarkModeEnabled()) {
|
||||
targetView.setClipToOutline(true);
|
||||
targetView.setElevation(dipToPixels(2));
|
||||
targetView.setElevation(Dim.dp2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package app.revanced.extension.shared.ui;
|
||||
|
||||
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
@@ -37,7 +35,6 @@ public class CustomDialog {
|
||||
private final Context context;
|
||||
private final Dialog dialog;
|
||||
private final LinearLayout mainLayout;
|
||||
private final int dip4, dip8, dip16, dip24, dip36;
|
||||
|
||||
/**
|
||||
* Creates a custom dialog with a styled layout, including a title, message, buttons, and an optional EditText.
|
||||
@@ -93,13 +90,6 @@ public class CustomDialog {
|
||||
this.dialog = new Dialog(context);
|
||||
this.dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); // Remove default title bar.
|
||||
|
||||
// Preset size constants.
|
||||
dip4 = dipToPixels(4);
|
||||
dip8 = dipToPixels(8);
|
||||
dip16 = dipToPixels(16);
|
||||
dip24 = dipToPixels(24);
|
||||
dip36 = dipToPixels(36);
|
||||
|
||||
// Create main layout.
|
||||
mainLayout = createMainLayout();
|
||||
addTitle(title);
|
||||
@@ -122,11 +112,11 @@ public class CustomDialog {
|
||||
private LinearLayout createMainLayout() {
|
||||
LinearLayout layout = new LinearLayout(context);
|
||||
layout.setOrientation(LinearLayout.VERTICAL);
|
||||
layout.setPadding(dip24, dip16, dip24, dip24);
|
||||
layout.setPadding(Dim.dp24, Dim.dp16, Dim.dp24, Dim.dp24);
|
||||
|
||||
// Set rounded rectangle background.
|
||||
ShapeDrawable background = new ShapeDrawable(new RoundRectShape(
|
||||
Utils.createCornerRadii(28), null, null));
|
||||
Dim.roundedCorners(28), null, null));
|
||||
// Dialog background.
|
||||
background.getPaint().setColor(Utils.getDialogBackgroundColor());
|
||||
layout.setBackground(background);
|
||||
@@ -152,7 +142,7 @@ public class CustomDialog {
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
params.setMargins(0, 0, 0, dip16);
|
||||
params.setMargins(0, 0, 0, Dim.dp16);
|
||||
titleView.setLayoutParams(params);
|
||||
|
||||
mainLayout.addView(titleView);
|
||||
@@ -180,9 +170,9 @@ public class CustomDialog {
|
||||
// EditText (if provided).
|
||||
if (editText != null) {
|
||||
ShapeDrawable background = new ShapeDrawable(new RoundRectShape(
|
||||
Utils.createCornerRadii(10), null, null));
|
||||
Dim.roundedCorners(10), null, null));
|
||||
background.getPaint().setColor(Utils.getEditTextBackground());
|
||||
scrollView.setPadding(dip8, dip8, dip8, dip8);
|
||||
scrollView.setPadding(Dim.dp8, Dim.dp8, Dim.dp8, Dim.dp8);
|
||||
scrollView.setBackground(background);
|
||||
scrollView.setClipToOutline(true);
|
||||
|
||||
@@ -241,7 +231,7 @@ public class CustomDialog {
|
||||
LinearLayout.LayoutParams buttonContainerParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
buttonContainerParams.setMargins(0, dip16, 0, 0);
|
||||
buttonContainerParams.setMargins(0, Dim.dp16, 0, 0);
|
||||
buttonContainer.setLayoutParams(buttonContainerParams);
|
||||
|
||||
List<Button> buttons = new ArrayList<>();
|
||||
@@ -289,12 +279,12 @@ public class CustomDialog {
|
||||
button.setEllipsize(TextUtils.TruncateAt.END);
|
||||
button.setGravity(Gravity.CENTER);
|
||||
// Set internal padding.
|
||||
button.setPadding(dip16, 0, dip16, 0);
|
||||
button.setPadding(Dim.dp16, 0, Dim.dp16, 0);
|
||||
|
||||
// Background color for OK button (inversion).
|
||||
// Background color for Cancel or Neutral buttons.
|
||||
ShapeDrawable background = new ShapeDrawable(new RoundRectShape(
|
||||
Utils.createCornerRadii(20), null, null));
|
||||
Dim.roundedCorners(20), null, null));
|
||||
background.getPaint().setColor(isOkButton
|
||||
? Utils.getOkButtonBackgroundColor()
|
||||
: Utils.getCancelOrNeutralButtonBackgroundColor());
|
||||
@@ -331,20 +321,19 @@ public class CustomDialog {
|
||||
if (buttons.isEmpty()) return;
|
||||
|
||||
// Check if buttons fit in one row.
|
||||
int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
|
||||
int totalWidth = 0;
|
||||
for (Integer width : buttonWidths) {
|
||||
totalWidth += width;
|
||||
}
|
||||
if (buttonWidths.size() > 1) {
|
||||
// Add margins for gaps.
|
||||
totalWidth += (buttonWidths.size() - 1) * dip8;
|
||||
totalWidth += (buttonWidths.size() - 1) * Dim.dp8;
|
||||
}
|
||||
|
||||
// Single button: stretch to full width.
|
||||
if (buttons.size() == 1) {
|
||||
layoutSingleButton(buttonContainer, buttons.get(0));
|
||||
} else if (totalWidth <= screenWidth * 0.8) {
|
||||
} else if (totalWidth <= Dim.pctWidth(80)) {
|
||||
// Single row: Neutral, Cancel, OK.
|
||||
layoutButtonsInRow(buttonContainer, buttons, buttonWidths);
|
||||
} else {
|
||||
@@ -369,7 +358,7 @@ public class CustomDialog {
|
||||
|
||||
button.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
dip36));
|
||||
Dim.dp36));
|
||||
singleContainer.addView(button);
|
||||
buttonContainer.addView(singleContainer);
|
||||
}
|
||||
@@ -405,17 +394,17 @@ public class CustomDialog {
|
||||
if (parent != null) parent.removeView(button);
|
||||
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
|
||||
0, dip36, buttonWidths.get(i));
|
||||
0, Dim.dp36, buttonWidths.get(i));
|
||||
|
||||
// Set margins based on button type and combination.
|
||||
if (buttons.size() == 2) {
|
||||
// Neutral + OK or Cancel + OK.
|
||||
params.setMargins(i == 0 ? 0 : dip4, 0, i == 0 ? dip4 : 0, 0);
|
||||
params.setMargins(i == 0 ? 0 : Dim.dp4, 0, i == 0 ? Dim.dp4 : 0, 0);
|
||||
} else if (buttons.size() == 3) {
|
||||
// Neutral.
|
||||
// Cancel.
|
||||
// OK.
|
||||
params.setMargins(i == 0 ? 0 : dip4, 0, i == 2 ? 0 : dip4, 0);
|
||||
params.setMargins(i == 0 ? 0 : Dim.dp4, 0, i == 2 ? 0 : Dim.dp4, 0);
|
||||
}
|
||||
|
||||
button.setLayoutParams(params);
|
||||
@@ -447,14 +436,14 @@ public class CustomDialog {
|
||||
singleContainer.setGravity(Gravity.CENTER);
|
||||
singleContainer.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
dip36));
|
||||
Dim.dp36));
|
||||
|
||||
ViewGroup parent = (ViewGroup) button.getParent();
|
||||
if (parent != null) parent.removeView(button);
|
||||
|
||||
button.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
dip36));
|
||||
Dim.dp36));
|
||||
singleContainer.addView(button);
|
||||
buttonContainer.addView(singleContainer);
|
||||
|
||||
@@ -463,7 +452,7 @@ public class CustomDialog {
|
||||
View spacer = new View(context);
|
||||
LinearLayout.LayoutParams spacerParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
dip8);
|
||||
Dim.dp8);
|
||||
spacer.setLayoutParams(spacerParams);
|
||||
buttonContainer.addView(spacer);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package app.revanced.extension.shared.ui;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.TypedValue;
|
||||
|
||||
/**
|
||||
* Utility class for converting design units (dp) and screen percentages to pixels.
|
||||
*/
|
||||
public final class Dim {
|
||||
private Dim() {} // Prevent instantiation.
|
||||
|
||||
private static final DisplayMetrics METRICS = Resources.getSystem().getDisplayMetrics();
|
||||
public static final int SCREEN_WIDTH = METRICS.widthPixels;
|
||||
public static final int SCREEN_HEIGHT = METRICS.heightPixels;
|
||||
|
||||
// DP constants (density-independent pixels).
|
||||
public static final int dp1 = dp(1);
|
||||
public static final int dp2 = dp(2);
|
||||
public static final int dp4 = dp(4);
|
||||
public static final int dp6 = dp(6);
|
||||
public static final int dp7 = dp(7);
|
||||
public static final int dp8 = dp(8);
|
||||
public static final int dp10 = dp(10);
|
||||
public static final int dp12 = dp(12);
|
||||
public static final int dp16 = dp(16);
|
||||
public static final int dp20 = dp(20);
|
||||
public static final int dp24 = dp(24);
|
||||
public static final int dp28 = dp(28);
|
||||
public static final int dp32 = dp(32);
|
||||
public static final int dp36 = dp(36);
|
||||
public static final int dp40 = dp(40);
|
||||
public static final int dp48 = dp(48);
|
||||
|
||||
/**
|
||||
* Converts dp (density-independent pixels) to actual device pixels.
|
||||
* Uses Android's official TypedValue.applyDimension() for accurate rounding.
|
||||
*
|
||||
* @param dp The dp value to convert (supports float, e.g. 1.2f).
|
||||
* @return The equivalent pixel value as int.
|
||||
*/
|
||||
public static int dp(float dp) {
|
||||
return (int) TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, dp, METRICS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a percentage of the screen height to pixels.
|
||||
*
|
||||
* @param percent The percentage (0–100).
|
||||
* @return The pixel value corresponding to the percentage of screen height.
|
||||
*/
|
||||
public static int pctHeight(int percent) {
|
||||
return (SCREEN_HEIGHT * percent) / 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a percentage of the screen width to pixels.
|
||||
*
|
||||
* @param percent The percentage (0–100).
|
||||
* @return The pixel value corresponding to the percentage of screen width.
|
||||
*/
|
||||
public static int pctWidth(int percent) {
|
||||
return (SCREEN_WIDTH * percent) / 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a percentage of the screen's portrait width (min side) to pixels.
|
||||
*
|
||||
* @param percent The percentage (0–100).
|
||||
* @return The pixel value.
|
||||
*/
|
||||
public static int pctPortraitWidth(int percent) {
|
||||
final int portraitWidth = Math.min(SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||
return (int) (portraitWidth * (percent / 100.0f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an array of corner radii for a rounded rectangle.
|
||||
* All corners use the same radius.
|
||||
*
|
||||
* @param dp radius in density-independent pixels
|
||||
* @return array of 8 floats: [top-left-x, top-left-y, top-right-x, top-right-y, ...]
|
||||
*/
|
||||
public static float[] roundedCorners(float dp) {
|
||||
final float r = dp(dp);
|
||||
return new float[]{r, r, r, r, r, r, r, r};
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package app.revanced.extension.shared.ui;
|
||||
|
||||
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
@@ -63,9 +61,8 @@ public class SheetBottomDialog {
|
||||
|
||||
// Add top spacer.
|
||||
View spacer = new View(context);
|
||||
final int dip40 = dipToPixels(40);
|
||||
LinearLayout.LayoutParams spacerParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT, dip40);
|
||||
LinearLayout.LayoutParams.MATCH_PARENT, Dim.dp40);
|
||||
spacer.setLayoutParams(spacerParams);
|
||||
spacer.setClickable(true);
|
||||
dragContainer.addView(spacer);
|
||||
@@ -105,20 +102,15 @@ public class SheetBottomDialog {
|
||||
* @return A configured {@link DraggableLinearLayout} with a handle bar and styled background.
|
||||
*/
|
||||
public static DraggableLinearLayout createMainLayout(@NonNull Context context, @Nullable Integer backgroundColor) {
|
||||
// Preset size constants.
|
||||
final int dip4 = dipToPixels(4); // Handle bar height.
|
||||
final int dip8 = dipToPixels(8); // Dialog padding.
|
||||
final int dip40 = dipToPixels(40); // Handle bar width.
|
||||
|
||||
DraggableLinearLayout mainLayout = new DraggableLinearLayout(context);
|
||||
mainLayout.setOrientation(LinearLayout.VERTICAL);
|
||||
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
layoutParams.setMargins(dip8, 0, dip8, dip8);
|
||||
layoutParams.setMargins(Dim.dp8, 0, Dim.dp8, Dim.dp8);
|
||||
mainLayout.setLayoutParams(layoutParams);
|
||||
|
||||
ShapeDrawable background = new ShapeDrawable(new RoundRectShape(
|
||||
Utils.createCornerRadii(12), null, null));
|
||||
Dim.roundedCorners(12), null, null));
|
||||
int color = (backgroundColor != null) ? backgroundColor : Utils.getDialogBackgroundColor();
|
||||
background.getPaint().setColor(color);
|
||||
mainLayout.setBackground(background);
|
||||
@@ -127,14 +119,14 @@ public class SheetBottomDialog {
|
||||
LinearLayout handleContainer = new LinearLayout(context);
|
||||
LinearLayout.LayoutParams containerParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
containerParams.setMargins(0, dip8, 0, 0);
|
||||
containerParams.setMargins(0, Dim.dp8, 0, 0);
|
||||
handleContainer.setLayoutParams(containerParams);
|
||||
handleContainer.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
|
||||
View handleBar = new View(context);
|
||||
ShapeDrawable handleBackground = new ShapeDrawable(new RoundRectShape(
|
||||
Utils.createCornerRadii(4), null, null));
|
||||
Dim.roundedCorners(4), null, null));
|
||||
handleBackground.getPaint().setColor(Utils.adjustColorBrightness(color, 0.9f, 1.25f));
|
||||
LinearLayout.LayoutParams handleParams = new LinearLayout.LayoutParams(dip40, dip4);
|
||||
LinearLayout.LayoutParams handleParams = new LinearLayout.LayoutParams(Dim.dp40, Dim.dp4);
|
||||
handleBar.setLayoutParams(handleParams);
|
||||
handleBar.setBackground(handleBackground);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import app.revanced.extension.spotify.shared.ComponentFilters.StringComponentFil
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Deprecated(forRemoval = true)
|
||||
@SuppressWarnings("unused")
|
||||
public final class HideCreateButtonPatch {
|
||||
|
||||
|
||||
@@ -23,6 +23,12 @@ public class ExtensionPreferenceCategory extends ConditionalPreferenceCategory {
|
||||
public void addPreferences(Context context) {
|
||||
addPreference(new ReVancedTikTokAboutPreference(context));
|
||||
|
||||
addPreference(new TogglePreference(context,
|
||||
"Sanitize sharing links",
|
||||
"Remove tracking parameters from shared links.",
|
||||
BaseSettings.SANITIZE_SHARED_LINKS
|
||||
));
|
||||
|
||||
addPreference(new TogglePreference(context,
|
||||
"Enable debug log",
|
||||
"Show extension debug log.",
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package app.revanced.extension.tiktok.share;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.privacy.LinkSanitizer;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class ShareUrlSanitizer {
|
||||
|
||||
private static final LinkSanitizer sanitizer = new LinkSanitizer();
|
||||
|
||||
/**
|
||||
* Injection point for setting check.
|
||||
*/
|
||||
public static boolean shouldSanitize() {
|
||||
return BaseSettings.SANITIZE_SHARED_LINKS.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point for URL sanitization.
|
||||
*/
|
||||
public static String sanitizeShareUrl(final String url) {
|
||||
if (url == null || url.isEmpty()) {
|
||||
return url;
|
||||
}
|
||||
|
||||
return sanitizer.sanitizeUrlString(url);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package app.revanced.twitter.patches.links;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class ChangeLinkSharingDomainPatch {
|
||||
private static final String LINK_FORMAT = "%s/%s/status/%s";
|
||||
private static final String LINK_FORMAT = "https://%s/%s/status/%s";
|
||||
|
||||
/**
|
||||
* Method is modified during patching. Do not change.
|
||||
@@ -11,6 +11,16 @@ public final class ChangeLinkSharingDomainPatch {
|
||||
return "";
|
||||
}
|
||||
|
||||
// TODO remove this once changeLinkSharingDomainResourcePatch is restored
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static String formatResourceLink(Object... formatArgs) {
|
||||
String username = (String) formatArgs[0];
|
||||
String tweetId = (String) formatArgs[1];
|
||||
return String.format(LINK_FORMAT, getShareDomain(), username, tweetId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
|
||||
@@ -17,15 +17,25 @@ public class ChangeHeaderPatch {
|
||||
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");
|
||||
ROUNDED("revanced_header_rounded"),
|
||||
MINIMAL("revanced_header_minimal"),
|
||||
CUSTOM("revanced_header_custom"),
|
||||
|
||||
// Old enum names for data migration. TODO: Eventually delete these.
|
||||
@Deprecated
|
||||
REVANCED(ROUNDED.attributeName),
|
||||
@Deprecated
|
||||
REVANCED_MINIMAL(MINIMAL.attributeName);
|
||||
|
||||
@Nullable
|
||||
private final String attributeName;
|
||||
@Nullable
|
||||
private final String drawableName;
|
||||
|
||||
HeaderLogo(String attributeName) {
|
||||
this(Objects.requireNonNull(attributeName), Objects.requireNonNull(attributeName));
|
||||
}
|
||||
|
||||
HeaderLogo(@Nullable String attributeName, @Nullable String drawableName) {
|
||||
this.attributeName = attributeName;
|
||||
this.drawableName = drawableName;
|
||||
@@ -42,9 +52,8 @@ public class ChangeHeaderPatch {
|
||||
|
||||
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);
|
||||
// Should never happen.
|
||||
Logger.printException(() -> "Could not find attribute: " + drawableName);
|
||||
Settings.HEADER_LOGO.resetToDefault();
|
||||
return null;
|
||||
}
|
||||
@@ -63,12 +72,14 @@ public class ChangeHeaderPatch {
|
||||
: "_light");
|
||||
|
||||
final int identifier = Utils.getResourceIdentifier(drawableFullName, "drawable");
|
||||
if (identifier == 0) {
|
||||
Logger.printDebug(() -> "Could not find drawable: " + drawableFullName);
|
||||
Settings.HEADER_LOGO.resetToDefault();
|
||||
return null;
|
||||
if (identifier != 0) {
|
||||
return Utils.getContext().getDrawable(identifier);
|
||||
}
|
||||
return Utils.getContext().getDrawable(identifier);
|
||||
|
||||
// Should never happen.
|
||||
Logger.printException(() -> "Could not find drawable: " + drawableFullName);
|
||||
Settings.HEADER_LOGO.resetToDefault();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.view.View;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.ui.Dim;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@@ -31,7 +32,7 @@ public final class WideSearchbarPatch {
|
||||
final int paddingRight = searchBarView.getPaddingRight();
|
||||
final int paddingTop = searchBarView.getPaddingTop();
|
||||
final int paddingBottom = searchBarView.getPaddingBottom();
|
||||
final int paddingStart = Utils.dipToPixels(8);
|
||||
final int paddingStart = Dim.dp8;
|
||||
|
||||
if (Utils.isRightToLeftLocale()) {
|
||||
searchBarView.setPadding(paddingLeft, paddingTop, paddingStart, paddingBottom);
|
||||
|
||||
@@ -2,7 +2,6 @@ package app.revanced.extension.youtube.patches.announcements;
|
||||
|
||||
import static android.text.Html.FROM_HTML_MODE_COMPACT;
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||
import static app.revanced.extension.youtube.patches.announcements.requests.AnnouncementsRoutes.GET_LATEST_ANNOUNCEMENTS;
|
||||
import static app.revanced.extension.youtube.patches.announcements.requests.AnnouncementsRoutes.GET_LATEST_ANNOUNCEMENT_IDS;
|
||||
|
||||
@@ -24,6 +23,7 @@ import java.time.LocalDateTime;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.requests.Requester;
|
||||
import app.revanced.extension.shared.ui.Dim;
|
||||
import app.revanced.extension.youtube.patches.announcements.requests.AnnouncementsRoutes;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
@@ -148,7 +148,7 @@ public final class AnnouncementsPatch {
|
||||
if (child instanceof TextView childTextView && finalTitle.equals(childTextView.getText().toString())) {
|
||||
childTextView.setCompoundDrawablesWithIntrinsicBounds(
|
||||
finalLevel.icon, 0, 0, 0);
|
||||
childTextView.setCompoundDrawablePadding(dipToPixels(8));
|
||||
childTextView.setCompoundDrawablePadding(Dim.dp8);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,16 +7,17 @@ import app.revanced.extension.youtube.shared.PlayerType;
|
||||
@SuppressWarnings("unused")
|
||||
final class DescriptionComponentsFilter extends Filter {
|
||||
|
||||
private static final String INFOCARDS_SECTION_PATH = "infocards_section.e";
|
||||
|
||||
private final StringTrieSearch exceptions = new StringTrieSearch();
|
||||
|
||||
private final ByteArrayFilterGroupList macroMarkersCarouselGroupList = new ByteArrayFilterGroupList();
|
||||
|
||||
private final StringFilterGroup macroMarkersCarousel;
|
||||
|
||||
private final ByteArrayFilterGroupList macroMarkersCarouselGroupList = new ByteArrayFilterGroupList();
|
||||
private final StringFilterGroup horizontalShelf;
|
||||
private final ByteArrayFilterGroup cellVideoAttribute;
|
||||
|
||||
private final StringFilterGroup infoCardsSection;
|
||||
private final StringFilterGroup subscribeButton;
|
||||
private final StringFilterGroup aiGeneratedVideoSummarySection;
|
||||
private final StringFilterGroup hypePoints;
|
||||
|
||||
public DescriptionComponentsFilter() {
|
||||
exceptions.addPatterns(
|
||||
@@ -43,9 +44,10 @@ final class DescriptionComponentsFilter extends Filter {
|
||||
"video_attributes_section"
|
||||
);
|
||||
|
||||
final StringFilterGroup infoCardsSection = new StringFilterGroup(
|
||||
Settings.HIDE_INFO_CARDS_SECTION,
|
||||
"infocards_section"
|
||||
final StringFilterGroup featuredSection = new StringFilterGroup(
|
||||
Settings.HIDE_FEATURED_SECTION,
|
||||
// "media_lockup", "structured_description_video_lockup"
|
||||
"compact_infocard"
|
||||
);
|
||||
|
||||
final StringFilterGroup podcastSection = new StringFilterGroup(
|
||||
@@ -63,6 +65,21 @@ final class DescriptionComponentsFilter extends Filter {
|
||||
"how_this_was_made_section"
|
||||
);
|
||||
|
||||
hypePoints = new StringFilterGroup(
|
||||
Settings.HIDE_HYPE_POINTS,
|
||||
"hype_points_factoid"
|
||||
);
|
||||
|
||||
infoCardsSection = new StringFilterGroup(
|
||||
Settings.HIDE_INFO_CARDS_SECTION,
|
||||
INFOCARDS_SECTION_PATH
|
||||
);
|
||||
|
||||
subscribeButton = new StringFilterGroup(
|
||||
Settings.HIDE_DESCRIPTION_SUBSCRIBE_BUTTON,
|
||||
"subscribe_button"
|
||||
);
|
||||
|
||||
macroMarkersCarousel = new StringFilterGroup(
|
||||
null,
|
||||
"macro_markers_carousel.e"
|
||||
@@ -93,11 +110,14 @@ final class DescriptionComponentsFilter extends Filter {
|
||||
aiGeneratedVideoSummarySection,
|
||||
askSection,
|
||||
attributesSection,
|
||||
infoCardsSection,
|
||||
featuredSection,
|
||||
horizontalShelf,
|
||||
howThisWasMadeSection,
|
||||
hypePoints,
|
||||
infoCardsSection,
|
||||
macroMarkersCarousel,
|
||||
podcastSection,
|
||||
subscribeButton,
|
||||
transcriptSection
|
||||
);
|
||||
}
|
||||
@@ -106,11 +126,15 @@ final class DescriptionComponentsFilter extends Filter {
|
||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
|
||||
if (matchedGroup == aiGeneratedVideoSummarySection) {
|
||||
if (matchedGroup == aiGeneratedVideoSummarySection || matchedGroup == hypePoints) {
|
||||
// Only hide if player is open, in case this component is used somewhere else.
|
||||
return PlayerType.getCurrent().isMaximizedOrFullscreen();
|
||||
}
|
||||
|
||||
if (matchedGroup == subscribeButton) {
|
||||
return path.startsWith(INFOCARDS_SECTION_PATH);
|
||||
}
|
||||
|
||||
if (exceptions.matches(path)) return false;
|
||||
|
||||
if (matchedGroup == macroMarkersCarousel) {
|
||||
|
||||
@@ -21,13 +21,14 @@ import app.revanced.extension.youtube.shared.PlayerType;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class LayoutComponentsFilter extends Filter {
|
||||
private static final StringTrieSearch mixPlaylistsExceptions = new StringTrieSearch(
|
||||
private static final StringTrieSearch mixPlaylistsContextExceptions = new StringTrieSearch(
|
||||
"V.ED", // Playlist browse id.
|
||||
"java.lang.ref.WeakReference"
|
||||
);
|
||||
private static final ByteArrayFilterGroup mixPlaylistsExceptions2 = new ByteArrayFilterGroup(
|
||||
private static final ByteArrayFilterGroup mixPlaylistsBufferExceptions = new ByteArrayFilterGroup(
|
||||
null,
|
||||
"cell_description_body"
|
||||
"cell_description_body",
|
||||
"channel_profile"
|
||||
);
|
||||
private static final ByteArrayFilterGroup mixPlaylists = new ByteArrayFilterGroup(
|
||||
null,
|
||||
@@ -87,7 +88,8 @@ public final class LayoutComponentsFilter extends Filter {
|
||||
"post_shelf_slim.e",
|
||||
"videos_post_responsive_root.e",
|
||||
"text_post_responsive_root.e",
|
||||
"poll_post_responsive_root.e"
|
||||
"poll_post_responsive_root.e",
|
||||
"shared_post_root.e"
|
||||
);
|
||||
|
||||
final var subscribersCommunityGuidelines = new StringFilterGroup(
|
||||
@@ -379,17 +381,15 @@ public final class LayoutComponentsFilter extends Filter {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prevent playlist items being hidden, if a mix playlist is present in it.
|
||||
if (mixPlaylistsExceptions.matches(conversionContext.toString())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prevent hiding the description of some videos accidentally.
|
||||
if (mixPlaylistsExceptions2.check(bytes).isFiltered()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mixPlaylists.check(bytes).isFiltered()) {
|
||||
if (mixPlaylists.check(bytes).isFiltered()
|
||||
// Prevent hiding the description of some videos accidentally.
|
||||
&& !mixPlaylistsBufferExceptions.check(bytes).isFiltered()
|
||||
// Prevent playlist items being hidden, if a mix playlist is present in it.
|
||||
// Check last since it requires creating a context string.
|
||||
//
|
||||
// FIXME: The conversion context passed in does not always generate a valid toString.
|
||||
// This string check may no longer be needed, or the patch may be broken.
|
||||
&& !mixPlaylistsContextExceptions.matches(conversionContext.toString())) {
|
||||
Logger.printDebug(() -> "Filtered mix playlist");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -12,13 +12,9 @@ import java.util.List;
|
||||
public class PlayerFlyoutMenuItemsFilter extends Filter {
|
||||
|
||||
public static final class HideAudioFlyoutMenuAvailability implements Setting.Availability {
|
||||
private static final boolean AVAILABLE_ON_LAUNCH = !SpoofVideoStreamsPatch.spoofingToClientWithNoMultiAudioStreams();
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
// Check conditions of launch and now. Otherwise if spoofing is changed
|
||||
// without a restart the setting will show as available when it's not.
|
||||
return AVAILABLE_ON_LAUNCH && !SpoofVideoStreamsPatch.spoofingToClientWithNoMultiAudioStreams();
|
||||
return !SpoofVideoStreamsPatch.spoofingToClientWithNoMultiAudioStreams();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -63,12 +59,12 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
|
||||
"volume_stable_"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_PLAYER_FLYOUT_HELP,
|
||||
"yt_outline_question_circle_"
|
||||
Settings.HIDE_PLAYER_FLYOUT_LISTEN_WITH_YOUTUBE_MUSIC,
|
||||
"yt_outline_youtube_music_"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_PLAYER_FLYOUT_MORE_INFO,
|
||||
"yt_outline_info_circle_"
|
||||
Settings.HIDE_PLAYER_FLYOUT_HELP,
|
||||
"yt_outline_question_circle_"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_PLAYER_FLYOUT_LOCK_SCREEN,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package app.revanced.extension.youtube.patches.playback.speed;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||
import static app.revanced.extension.youtube.videoplayer.PlayerControlButton.fadeInDuration;
|
||||
import static app.revanced.extension.youtube.videoplayer.PlayerControlButton.getDialogBackgroundColor;
|
||||
|
||||
@@ -30,6 +29,7 @@ import java.util.function.Function;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.ui.Dim;
|
||||
import app.revanced.extension.shared.ui.SheetBottomDialog;
|
||||
import app.revanced.extension.youtube.patches.VideoInformation;
|
||||
import app.revanced.extension.youtube.patches.components.PlaybackSpeedMenuFilter;
|
||||
@@ -264,14 +264,6 @@ public class CustomPlaybackSpeedPatch {
|
||||
SheetBottomDialog.DraggableLinearLayout mainLayout =
|
||||
SheetBottomDialog.createMainLayout(context, getDialogBackgroundColor());
|
||||
|
||||
// Preset size constants.
|
||||
final int dip4 = dipToPixels(4);
|
||||
final int dip8 = dipToPixels(8);
|
||||
final int dip12 = dipToPixels(12);
|
||||
final int dip20 = dipToPixels(20);
|
||||
final int dip32 = dipToPixels(32);
|
||||
final int dip60 = dipToPixels(60);
|
||||
|
||||
// Display current playback speed.
|
||||
TextView currentSpeedText = new TextView(context);
|
||||
float currentSpeed = VideoInformation.getPlaybackSpeed();
|
||||
@@ -283,7 +275,7 @@ public class CustomPlaybackSpeedPatch {
|
||||
currentSpeedText.setGravity(Gravity.CENTER);
|
||||
LinearLayout.LayoutParams textParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
textParams.setMargins(0, dip20, 0, 0);
|
||||
textParams.setMargins(0, Dim.dp20, 0, 0);
|
||||
currentSpeedText.setLayoutParams(textParams);
|
||||
// Add current speed text view to main layout.
|
||||
mainLayout.addView(currentSpeedText);
|
||||
@@ -294,8 +286,8 @@ public class CustomPlaybackSpeedPatch {
|
||||
sliderLayout.setGravity(Gravity.CENTER_VERTICAL);
|
||||
|
||||
// Create +/- buttons.
|
||||
Button minusButton = createStyledButton(context, false, dip8, dip8);
|
||||
Button plusButton = createStyledButton(context, true, dip8, dip8);
|
||||
Button minusButton = createStyledButton(context, false);
|
||||
Button plusButton = createStyledButton(context, true);
|
||||
|
||||
// Create slider for speed adjustment.
|
||||
SeekBar speedSlider = new SeekBar(context);
|
||||
@@ -363,7 +355,7 @@ public class CustomPlaybackSpeedPatch {
|
||||
gridLayout.setRowCount((int) Math.ceil(customPlaybackSpeeds.length / 5.0));
|
||||
LinearLayout.LayoutParams gridParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
gridParams.setMargins(dip4, dip12, dip4, dip12); // Speed buttons container.
|
||||
gridParams.setMargins(Dim.dp4, Dim.dp12, Dim.dp4, Dim.dp12); // Speed buttons container.
|
||||
gridLayout.setLayoutParams(gridParams);
|
||||
|
||||
// For button use 1 digit minimum.
|
||||
@@ -378,8 +370,8 @@ public class CustomPlaybackSpeedPatch {
|
||||
GridLayout.LayoutParams containerParams = new GridLayout.LayoutParams();
|
||||
containerParams.width = 0; // Equal width for columns.
|
||||
containerParams.columnSpec = GridLayout.spec(GridLayout.UNDEFINED, 1, 1f);
|
||||
containerParams.setMargins(dip4, 0, dip4, 0); // Button margins.
|
||||
containerParams.height = dip60; // Fixed height for button and label.
|
||||
containerParams.setMargins(Dim.dp4, 0, Dim.dp4, 0); // Button margins.
|
||||
containerParams.height = Dim.dp(60); // Fixed height for button and label.
|
||||
buttonContainer.setLayoutParams(containerParams);
|
||||
|
||||
// Create speed button.
|
||||
@@ -391,14 +383,14 @@ public class CustomPlaybackSpeedPatch {
|
||||
speedButton.setGravity(Gravity.CENTER);
|
||||
|
||||
ShapeDrawable buttonBackground = new ShapeDrawable(new RoundRectShape(
|
||||
Utils.createCornerRadii(20), null, null));
|
||||
Dim.roundedCorners(20), null, null));
|
||||
buttonBackground.getPaint().setColor(getAdjustedBackgroundColor(false));
|
||||
speedButton.setBackground(buttonBackground);
|
||||
speedButton.setPadding(dip4, dip4, dip4, dip4);
|
||||
speedButton.setPadding(Dim.dp4, Dim.dp4, Dim.dp4, Dim.dp4);
|
||||
|
||||
// Center button vertically and stretch horizontally in container.
|
||||
FrameLayout.LayoutParams buttonParams = new FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT, dip32, Gravity.CENTER);
|
||||
FrameLayout.LayoutParams.MATCH_PARENT, Dim.dp32, Gravity.CENTER);
|
||||
speedButton.setLayoutParams(buttonParams);
|
||||
|
||||
// Add speed buttons view to buttons container layout.
|
||||
@@ -475,21 +467,18 @@ public class CustomPlaybackSpeedPatch {
|
||||
*
|
||||
* @param context The Android context used to create the button.
|
||||
* @param isPlus True to display a plus symbol, false to display a minus symbol.
|
||||
* @param marginStart The start margin in pixels (left for LTR, right for RTL).
|
||||
* @param marginEnd The end margin in pixels (right for LTR, left for RTL).
|
||||
* @return A configured {@link Button} with the specified styling and layout parameters.
|
||||
*/
|
||||
private static Button createStyledButton(Context context, boolean isPlus, int marginStart, int marginEnd) {
|
||||
private static Button createStyledButton(Context context, boolean isPlus) {
|
||||
Button button = new Button(context, null, 0); // Disable default theme style.
|
||||
button.setText(""); // No text on button.
|
||||
ShapeDrawable background = new ShapeDrawable(new RoundRectShape(
|
||||
Utils.createCornerRadii(20), null, null));
|
||||
Dim.roundedCorners(20), null, null));
|
||||
background.getPaint().setColor(getAdjustedBackgroundColor(false));
|
||||
button.setBackground(background);
|
||||
button.setForeground(new OutlineSymbolDrawable(isPlus)); // Plus or minus symbol.
|
||||
final int dip36 = dipToPixels(36);
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(dip36, dip36);
|
||||
params.setMargins(marginStart, 0, marginEnd, 0); // Set margins.
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(Dim.dp36, Dim.dp36);
|
||||
params.setMargins(Dim.dp8, 0, Dim.dp8, 0); // Set margins.
|
||||
button.setLayoutParams(params);
|
||||
return button;
|
||||
}
|
||||
@@ -554,7 +543,7 @@ class OutlineSymbolDrawable extends Drawable {
|
||||
paint = new Paint(Paint.ANTI_ALIAS_FLAG); // Enable anti-aliasing for smooth rendering.
|
||||
paint.setColor(Utils.getAppForegroundColor());
|
||||
paint.setStyle(Paint.Style.STROKE); // Use stroke style for outline.
|
||||
paint.setStrokeWidth(dipToPixels(1)); // 1dp stroke width.
|
||||
paint.setStrokeWidth(Dim.dp1); // 1dp stroke width.
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package app.revanced.extension.youtube.patches.spoof;
|
||||
|
||||
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_CREATOR;
|
||||
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_NO_SDK;
|
||||
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_43_32;
|
||||
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_61_48;
|
||||
import static app.revanced.extension.shared.spoof.ClientType.IPADOS;
|
||||
@@ -21,6 +22,11 @@ public class SpoofVideoStreamsPatch {
|
||||
return Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.isAvailable()
|
||||
&& Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ANDROID_VR_1_43_32;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Setting<?>> getParentSettings() {
|
||||
return List.of(Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -38,9 +44,10 @@ public class SpoofVideoStreamsPatch {
|
||||
}
|
||||
|
||||
List<ClientType> availableClients = List.of(
|
||||
VISIONOS,
|
||||
ANDROID_CREATOR,
|
||||
ANDROID_VR_1_43_32,
|
||||
VISIONOS,
|
||||
ANDROID_NO_SDK,
|
||||
IPADOS);
|
||||
|
||||
app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.setClientsToUse(
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
package app.revanced.extension.youtube.patches.theme;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import app.revanced.extension.youtube.patches.HideSeekbarPatch;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
/**
|
||||
* Used by {@link SeekbarColorPatch} change the color of the seekbar.
|
||||
* and {@link HideSeekbarPatch} to hide the seekbar of the feed and watch history.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class ProgressBarDrawable extends Drawable {
|
||||
|
||||
private final Paint paint = new Paint();
|
||||
{
|
||||
paint.setColor(SeekbarColorPatch.getSeekbarColor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
if (Settings.HIDE_SEEKBAR_THUMBNAIL.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
canvas.drawRect(getBounds(), paint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
paint.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(@Nullable ColorFilter colorFilter) {
|
||||
paint.setColorFilter(colorFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,9 +4,7 @@ import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.Utils.clamp;
|
||||
import static app.revanced.extension.youtube.patches.theme.ThemePatch.SplashScreenAnimationStyle;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.AnimatedVectorDrawable;
|
||||
|
||||
import com.airbnb.lottie.LottieAnimationView;
|
||||
|
||||
@@ -15,7 +13,6 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.Scanner;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
@@ -104,27 +101,6 @@ public final class SeekbarColorPatch {
|
||||
return customSeekbarColor;
|
||||
}
|
||||
|
||||
private static int colorChannelTo3Bits(int channel8Bits) {
|
||||
final float channel3Bits = channel8Bits * 7 / 255f;
|
||||
|
||||
// If a color channel is near zero, then allow rounding up so values between
|
||||
// 0x12 and 0x23 will show as 0x24. But always round down when the channel is
|
||||
// near full saturation, otherwise rounding to nearest will cause all values
|
||||
// between 0xEC and 0xFE to always show as full saturation (0xFF).
|
||||
return channel3Bits < 6
|
||||
? Math.round(channel3Bits)
|
||||
: (int) channel3Bits;
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private static String get9BitStyleIdentifier(int color24Bit) {
|
||||
final int r3 = colorChannelTo3Bits(Color.red(color24Bit));
|
||||
final int g3 = colorChannelTo3Bits(Color.green(color24Bit));
|
||||
final int b3 = colorChannelTo3Bits(Color.blue(color24Bit));
|
||||
|
||||
return String.format(Locale.US, "splash_seekbar_color_style_%d_%d_%d", r3, g3, b3);
|
||||
}
|
||||
|
||||
/**
|
||||
* injection point.
|
||||
*/
|
||||
@@ -135,36 +111,6 @@ public final class SeekbarColorPatch {
|
||||
return original; // false = drawable style, true = lottie style.
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* Old drawable style launch screen.
|
||||
*/
|
||||
public static void setSplashAnimationDrawableTheme(AnimatedVectorDrawable vectorDrawable) {
|
||||
// Alternatively a ColorMatrixColorFilter can be used to change the color of the drawable
|
||||
// without using any styles, but a color filter cannot selectively change the seekbar
|
||||
// while keeping the red YT logo untouched.
|
||||
// Even if the seekbar color xml value is changed to a completely different color (such as green),
|
||||
// a color filter still cannot be selectively applied when the drawable has more than 1 color.
|
||||
try {
|
||||
// Must set the color even if custom seekbar is off,
|
||||
// because the xml color was replaced with a themed value.
|
||||
String seekbarStyle = get9BitStyleIdentifier(customSeekbarColor);
|
||||
Logger.printDebug(() -> "Using splash seekbar style: " + seekbarStyle);
|
||||
|
||||
final int styleIdentifierDefault = Utils.getResourceIdentifierOrThrow(
|
||||
seekbarStyle,
|
||||
"style"
|
||||
);
|
||||
|
||||
Resources.Theme theme = Utils.getContext().getResources().newTheme();
|
||||
theme.applyStyle(styleIdentifierDefault, true);
|
||||
|
||||
vectorDrawable.applyTheme(theme);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "setSplashAnimationDrawableTheme failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* Modern Lottie style animation.
|
||||
|
||||
@@ -40,6 +40,7 @@ import java.util.concurrent.TimeoutException;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.ui.Dim;
|
||||
import app.revanced.extension.youtube.returnyoutubedislike.requests.RYDVoteData;
|
||||
import app.revanced.extension.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
@@ -124,12 +125,12 @@ public class ReturnYouTubeDislike {
|
||||
|
||||
static {
|
||||
leftSeparatorBounds = new Rect(0, 0,
|
||||
Utils.dipToPixels(1.2f),
|
||||
Utils.dipToPixels(14f));
|
||||
final int middleSeparatorSize = Utils.dipToPixels(3.7f);
|
||||
Dim.dp(1.2f),
|
||||
Dim.dp(14f));
|
||||
final int middleSeparatorSize = Dim.dp(3.7f);
|
||||
middleSeparatorBounds = new Rect(0, 0, middleSeparatorSize, middleSeparatorSize);
|
||||
|
||||
leftSeparatorShapePaddingPixels = Utils.dipToPixels(8.4f);
|
||||
leftSeparatorShapePaddingPixels = Dim.dp(8.4f);
|
||||
|
||||
leftSeparatorShape = new ShapeDrawable(new RectShape());
|
||||
leftSeparatorShape.setBounds(leftSeparatorBounds);
|
||||
|
||||
@@ -2,7 +2,6 @@ package app.revanced.extension.youtube.settings;
|
||||
|
||||
import static java.lang.Boolean.FALSE;
|
||||
import static java.lang.Boolean.TRUE;
|
||||
import static app.revanced.extension.shared.settings.Setting.migrateOldSettingToNew;
|
||||
import static app.revanced.extension.shared.settings.Setting.parent;
|
||||
import static app.revanced.extension.shared.settings.Setting.parentsAll;
|
||||
import static app.revanced.extension.shared.settings.Setting.parentsAny;
|
||||
@@ -17,7 +16,6 @@ import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerH
|
||||
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerHideSubtextsAvailability;
|
||||
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerHorizontalDragAvailability;
|
||||
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType;
|
||||
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType.MINIMAL;
|
||||
import static app.revanced.extension.youtube.patches.OpenShortsInRegularPlayerPatch.ShortsPlayerType;
|
||||
import static app.revanced.extension.youtube.patches.SeekbarThumbnailsPatch.SeekbarThumbnailsHighQualityAvailability;
|
||||
import static app.revanced.extension.youtube.patches.components.PlayerFlyoutMenuItemsFilter.HideAudioFlyoutMenuAvailability;
|
||||
@@ -41,7 +39,6 @@ import app.revanced.extension.shared.settings.IntegerSetting;
|
||||
import app.revanced.extension.shared.settings.LongSetting;
|
||||
import app.revanced.extension.shared.settings.Setting;
|
||||
import app.revanced.extension.shared.settings.StringSetting;
|
||||
import app.revanced.extension.shared.settings.preference.SharedPrefCategory;
|
||||
import app.revanced.extension.shared.spoof.ClientType;
|
||||
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.DeArrowAvailability;
|
||||
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.StillImagesAvailability;
|
||||
@@ -213,10 +210,13 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting HIDE_ASK_SECTION = new BooleanSetting("revanced_hide_ask_section", FALSE);
|
||||
public static final BooleanSetting HIDE_ATTRIBUTES_SECTION = new BooleanSetting("revanced_hide_attributes_section", FALSE);
|
||||
public static final BooleanSetting HIDE_CHAPTERS_SECTION = new BooleanSetting("revanced_hide_chapters_section", TRUE);
|
||||
public static final BooleanSetting HIDE_FEATURED_SECTION = new BooleanSetting("revanced_hide_featured_section", TRUE);
|
||||
public static final BooleanSetting HIDE_HOW_THIS_WAS_MADE_SECTION = new BooleanSetting("revanced_hide_how_this_was_made_section", FALSE);
|
||||
public static final BooleanSetting HIDE_HYPE_POINTS = new BooleanSetting("revanced_hide_hype_points", FALSE);
|
||||
public static final BooleanSetting HIDE_INFO_CARDS_SECTION = new BooleanSetting("revanced_hide_info_cards_section", TRUE);
|
||||
public static final BooleanSetting HIDE_KEY_CONCEPTS_SECTION = new BooleanSetting("revanced_hide_key_concepts_section", FALSE);
|
||||
public static final BooleanSetting HIDE_PODCAST_SECTION = new BooleanSetting("revanced_hide_podcast_section", TRUE);
|
||||
public static final BooleanSetting HIDE_DESCRIPTION_SUBSCRIBE_BUTTON = new BooleanSetting("revanced_hide_description_subscribe_button", TRUE);
|
||||
public static final BooleanSetting HIDE_TRANSCRIPT_SECTION = new BooleanSetting("revanced_hide_transcript_section", TRUE);
|
||||
|
||||
// Action buttons
|
||||
@@ -242,9 +242,9 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting HIDE_PLAYER_FLYOUT_AUDIO_TRACK = new BooleanSetting("revanced_hide_player_flyout_audio_track", FALSE, new HideAudioFlyoutMenuAvailability());
|
||||
public static final BooleanSetting HIDE_PLAYER_FLYOUT_CAPTIONS = new BooleanSetting("revanced_hide_player_flyout_captions", FALSE);
|
||||
public static final BooleanSetting HIDE_PLAYER_FLYOUT_HELP = new BooleanSetting("revanced_hide_player_flyout_help", TRUE);
|
||||
public static final BooleanSetting HIDE_PLAYER_FLYOUT_LISTEN_WITH_YOUTUBE_MUSIC = new BooleanSetting("revanced_hide_player_flyout_listen_with_youtube_music", FALSE);
|
||||
public static final BooleanSetting HIDE_PLAYER_FLYOUT_LOCK_SCREEN = new BooleanSetting("revanced_hide_player_flyout_lock_screen", FALSE);
|
||||
public static final BooleanSetting HIDE_PLAYER_FLYOUT_LOOP_VIDEO = new BooleanSetting("revanced_hide_player_flyout_loop_video", FALSE);
|
||||
public static final BooleanSetting HIDE_PLAYER_FLYOUT_MORE_INFO = new BooleanSetting("revanced_hide_player_flyout_more_info", TRUE);
|
||||
public static final BooleanSetting HIDE_PLAYER_FLYOUT_SLEEP_TIMER = new BooleanSetting("revanced_hide_player_flyout_sleep_timer", FALSE);
|
||||
public static final BooleanSetting HIDE_PLAYER_FLYOUT_SPEED = new BooleanSetting("revanced_hide_player_flyout_speed", FALSE);
|
||||
public static final BooleanSetting HIDE_PLAYER_FLYOUT_STABLE_VOLUME = new BooleanSetting("revanced_hide_player_flyout_stable_volume", FALSE);
|
||||
@@ -452,14 +452,7 @@ public class Settings extends BaseSettings {
|
||||
public static final StringSetting SB_CATEGORY_UNSUBMITTED_COLOR = new StringSetting("sb_unsubmitted_color", "#FFFFFFFF", false, false);
|
||||
|
||||
// Deprecated migrations
|
||||
private static final BooleanSetting DEPRECATED_AUTO_REPEAT = new BooleanSetting("revanced_auto_repeat", FALSE);
|
||||
private static final BooleanSetting DEPRECATED_HIDE_PLAYER_BUTTONS = new BooleanSetting("revanced_hide_player_buttons", FALSE, true);
|
||||
private static final BooleanSetting DEPRECATED_HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER = new BooleanSetting("revanced_hide_video_quality_menu_footer", FALSE);
|
||||
private static final IntegerSetting DEPRECATED_SWIPE_OVERLAY_BACKGROUND_ALPHA = new IntegerSetting("revanced_swipe_overlay_background_alpha", 127);
|
||||
private static final StringSetting DEPRECATED_SEEKBAR_CUSTOM_COLOR_PRIMARY = new StringSetting("revanced_seekbar_custom_color_value", "#FF0033");
|
||||
private static final BooleanSetting DEPRECATED_DISABLE_SUGGESTED_VIDEO_END_SCREEN = new BooleanSetting("revanced_disable_suggested_video_end_screen", FALSE);
|
||||
private static final BooleanSetting DEPRECATED_RESTORE_OLD_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_restore_old_video_quality_menu", TRUE);
|
||||
private static final BooleanSetting DEPRECATED_AUTO_CAPTIONS = new BooleanSetting("revanced_auto_captions", FALSE);
|
||||
|
||||
private static final FloatSetting DEPRECATED_SB_CATEGORY_SPONSOR_OPACITY = new FloatSetting("sb_sponsor_opacity", 0.8f, false, false);
|
||||
private static final FloatSetting DEPRECATED_SB_CATEGORY_SELF_PROMO_OPACITY = new FloatSetting("sb_selfpromo_opacity", 0.8f, false, false);
|
||||
@@ -475,17 +468,12 @@ public class Settings extends BaseSettings {
|
||||
static {
|
||||
// region Migration
|
||||
|
||||
migrateOldSettingToNew(DEPRECATED_AUTO_REPEAT, LOOP_VIDEO);
|
||||
migrateOldSettingToNew(DEPRECATED_HIDE_PLAYER_BUTTONS, HIDE_PLAYER_PREVIOUS_NEXT_BUTTONS);
|
||||
migrateOldSettingToNew(DEPRECATED_HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER, HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER);
|
||||
migrateOldSettingToNew(DEPRECATED_DISABLE_SUGGESTED_VIDEO_END_SCREEN, HIDE_END_SCREEN_SUGGESTED_VIDEO);
|
||||
migrateOldSettingToNew(DEPRECATED_RESTORE_OLD_VIDEO_QUALITY_MENU, ADVANCED_VIDEO_QUALITY_MENU);
|
||||
migrateOldSettingToNew(DEPRECATED_AUTO_CAPTIONS, DISABLE_AUTO_CAPTIONS);
|
||||
|
||||
// Migrate renamed enum.
|
||||
//noinspection deprecation
|
||||
if (MINIPLAYER_TYPE.get() == MiniplayerType.PHONE) {
|
||||
MINIPLAYER_TYPE.save(MINIMAL);
|
||||
// Migrate renamed change header enums.
|
||||
if (HEADER_LOGO.get() == HeaderLogo.REVANCED) {
|
||||
HEADER_LOGO.save(HeaderLogo.ROUNDED);
|
||||
}
|
||||
if (HEADER_LOGO.get() == HeaderLogo.REVANCED_MINIMAL) {
|
||||
HEADER_LOGO.save(HeaderLogo.MINIMAL);
|
||||
}
|
||||
|
||||
// Migrate old single color seekbar with a slightly brighter accent color based on the primary.
|
||||
@@ -512,11 +500,6 @@ public class Settings extends BaseSettings {
|
||||
DEPRECATED_SEEKBAR_CUSTOM_COLOR_PRIMARY.resetToDefault();
|
||||
}
|
||||
|
||||
if (!DEPRECATED_SWIPE_OVERLAY_BACKGROUND_ALPHA.isSetToDefault()) {
|
||||
SWIPE_OVERLAY_OPACITY.save(DEPRECATED_SWIPE_OVERLAY_BACKGROUND_ALPHA.get() / 255);
|
||||
DEPRECATED_SWIPE_OVERLAY_BACKGROUND_ALPHA.resetToDefault();
|
||||
}
|
||||
|
||||
// Old spoof versions that no longer work,
|
||||
// or is spoofing to a version the same or newer than this app.
|
||||
if (!SPOOF_APP_VERSION_TARGET.isSetToDefault() &&
|
||||
@@ -534,13 +517,7 @@ public class Settings extends BaseSettings {
|
||||
|
||||
// RYD requires manually migrating old settings since the lack of
|
||||
// a "revanced_" on the old setting causes duplicate key exceptions during export.
|
||||
SharedPrefCategory revancedPrefs = Setting.preferences;
|
||||
Setting.migrateFromOldPreferences(revancedPrefs, RYD_USER_ID, "ryd_user_id");
|
||||
Setting.migrateFromOldPreferences(revancedPrefs, RYD_ENABLED, "ryd_enabled");
|
||||
Setting.migrateFromOldPreferences(revancedPrefs, RYD_DISLIKE_PERCENTAGE, "ryd_dislike_percentage");
|
||||
Setting.migrateFromOldPreferences(revancedPrefs, RYD_COMPACT_LAYOUT, "ryd_compact_layout");
|
||||
Setting.migrateFromOldPreferences(revancedPrefs, RYD_ESTIMATED_LIKE, "ryd_estimated_like");
|
||||
Setting.migrateFromOldPreferences(revancedPrefs, RYD_TOAST_ON_CONNECTION_ERROR, "ryd_toast_on_connection_error");
|
||||
Setting.migrateFromOldPreferences(Setting.preferences, RYD_USER_ID, "ryd_user_id");
|
||||
|
||||
// Migrate old saved data. Must be done here before the settings can be used by any other code.
|
||||
applyOldSbOpacityToColor(SB_CATEGORY_SPONSOR_COLOR, DEPRECATED_SB_CATEGORY_SPONSOR_OPACITY);
|
||||
|
||||
@@ -2,7 +2,6 @@ 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;
|
||||
@@ -15,13 +14,9 @@ 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;
|
||||
@@ -37,6 +32,7 @@ import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.preference.CustomDialogListPreference;
|
||||
import app.revanced.extension.shared.ui.CustomDialog;
|
||||
import app.revanced.extension.shared.ui.Dim;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
/**
|
||||
@@ -264,42 +260,12 @@ public class ExternalDownloaderPreference extends CustomDialogListPreference {
|
||||
|
||||
// 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);
|
||||
LinearLayout.LayoutParams.MATCH_PARENT, 0, 1.0f);
|
||||
listViewParams.bottomMargin = Dim.dp16;
|
||||
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);
|
||||
editText = createEditText(context, packageName, usingCustomDownloader, updateListViewSelection);
|
||||
contentLayout.addView(editText);
|
||||
|
||||
// Create the custom dialog.
|
||||
@@ -350,50 +316,59 @@ public class ExternalDownloaderPreference extends CustomDialogListPreference {
|
||||
);
|
||||
|
||||
// Add the content layout directly to the dialog's main layout.
|
||||
LinearLayout dialogMainLayout = dialogPair.second;
|
||||
dialogMainLayout.addView(contentLayout, dialogMainLayout.getChildCount() - 1);
|
||||
LinearLayout mainLayout = dialogPair.second;
|
||||
LinearLayout.LayoutParams contentParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT, 0, 1.0f);
|
||||
|
||||
// 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);
|
||||
// Insert content before the dialog button row.
|
||||
mainLayout.addView(contentLayout, mainLayout.getChildCount() - 1, contentParams);
|
||||
|
||||
Dialog dialog = dialogPair.first;
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and configures the EditText for the custom package name.
|
||||
*
|
||||
* @param context Context for creating views.
|
||||
* @param initialPackageName The package name to pre-fill.
|
||||
* @param isCustom Whether the "Other" option is selected.
|
||||
* @param textChangeCallback Callback to run when text changes.
|
||||
* @return A configured EditText.
|
||||
*/
|
||||
private EditText createEditText(Context context,
|
||||
String initialPackageName, boolean isCustom,
|
||||
Function<String, Void> textChangeCallback) {
|
||||
EditText editText = new EditText(context);
|
||||
editText.setText(initialPackageName);
|
||||
editText.setSelection(initialPackageName.length());
|
||||
editText.setHint(str("revanced_external_downloader_other_item_hint"));
|
||||
editText.setSingleLine(true);
|
||||
editText.setTextSize(16);
|
||||
editText.setEnabled(isCustom);
|
||||
|
||||
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();
|
||||
textChangeCallback.apply(updatedPackageName);
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
};
|
||||
ShapeDrawable editTextBackground = new ShapeDrawable(new RoundRectShape(
|
||||
Dim.roundedCorners(10), null, null));
|
||||
editTextBackground.getPaint().setColor(Utils.getEditTextBackground());
|
||||
editText.setPadding(Dim.dp8, Dim.dp8, Dim.dp8, Dim.dp8);
|
||||
editText.setBackground(editTextBackground);
|
||||
editText.setClipToOutline(true);
|
||||
|
||||
// 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 editText;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
package app.revanced.extension.youtube.settings.preference;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import app.revanced.extension.shared.settings.preference.SortedListPreference;
|
||||
import app.revanced.extension.shared.spoof.ClientType;
|
||||
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
@SuppressWarnings({"deprecation", "unused"})
|
||||
public class SpoofAudioSelectorListPreference extends SortedListPreference {
|
||||
|
||||
private final boolean available;
|
||||
|
||||
{
|
||||
final boolean isAndroidStudio = Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.ANDROID_CREATOR;
|
||||
|
||||
if (isAndroidStudio || SpoofVideoStreamsPatch.getLanguageOverride() != null) {
|
||||
available = false;
|
||||
super.setEnabled(false);
|
||||
super.setSummary(str(isAndroidStudio
|
||||
? "revanced_spoof_video_streams_language_android_studio"
|
||||
: "revanced_spoof_video_streams_language_not_available"));
|
||||
} else {
|
||||
available = true;
|
||||
}
|
||||
}
|
||||
|
||||
public SpoofAudioSelectorListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
public SpoofAudioSelectorListPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
public SpoofAudioSelectorListPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
public SpoofAudioSelectorListPreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
if (!available) {
|
||||
return;
|
||||
}
|
||||
|
||||
super.setEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSummary(CharSequence summary) {
|
||||
if (!available) {
|
||||
return;
|
||||
}
|
||||
|
||||
super.setSummary(summary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,29 +80,34 @@ public class SpoofStreamingDataSideEffectsPreference extends Preference {
|
||||
Logger.printDebug(() -> "Updating spoof stream side effects preference");
|
||||
setEnabled(BaseSettings.SPOOF_VIDEO_STREAMS.get());
|
||||
|
||||
String summary = str("revanced_spoof_video_streams_about_no_audio_tracks");
|
||||
String summary = "";
|
||||
|
||||
switch (clientType) {
|
||||
case ANDROID_CREATOR ->
|
||||
summary += '\n' + str("revanced_spoof_video_streams_about_no_stable_volume")
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_no_av1")
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_no_force_original_audio");
|
||||
summary = str("revanced_spoof_video_streams_about_no_audio_tracks")
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_no_stable_volume")
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_no_av1")
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_no_force_original_audio");
|
||||
// VR 1.61 is not exposed in the UI and should never be reached here.
|
||||
case ANDROID_VR_1_43_32, ANDROID_VR_1_61_48 ->
|
||||
summary += '\n' + str("revanced_spoof_video_streams_about_no_stable_volume");
|
||||
summary = str("revanced_spoof_video_streams_about_no_audio_tracks")
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_no_stable_volume");
|
||||
case ANDROID_NO_SDK ->
|
||||
summary = str("revanced_spoof_video_streams_about_playback_failure");
|
||||
case IPADOS ->
|
||||
summary = str("revanced_spoof_video_streams_about_playback_failure")
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_no_av1");
|
||||
case VISIONOS ->
|
||||
summary = str("revanced_spoof_video_streams_about_experimental")
|
||||
+ '\n' + summary
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_no_audio_tracks")
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_no_av1");
|
||||
default -> Logger.printException(() -> "Unknown client: " + clientType);
|
||||
}
|
||||
|
||||
// Only iPadOS can play children videos in incognito, but it commonly fails at 1 minute
|
||||
// or doesn't even start playback at all. List the side effect for other clients
|
||||
// or doesn't start playback at all. List the side effect for other clients
|
||||
// since they will fall over to iPadOS.
|
||||
if (clientType != ClientType.IPADOS) {
|
||||
if (clientType != ClientType.IPADOS && clientType != ClientType.ANDROID_NO_SDK) {
|
||||
summary += '\n' + str("revanced_spoof_video_streams_about_kids_videos");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package app.revanced.extension.youtube.sponsorblock;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
@@ -34,6 +33,7 @@ import java.util.Objects;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.ui.Dim;
|
||||
import app.revanced.extension.youtube.patches.VideoInformation;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.shared.PlayerType;
|
||||
@@ -82,7 +82,7 @@ public class SegmentPlaybackController {
|
||||
* Highlight segments have zero length as they are a point in time.
|
||||
* Draw them on screen using a fixed width bar.
|
||||
*/
|
||||
private static final int HIGHLIGHT_SEGMENT_DRAW_BAR_WIDTH = dipToPixels(7);
|
||||
private static final int HIGHLIGHT_SEGMENT_DRAW_BAR_WIDTH = Dim.dp7;
|
||||
|
||||
@Nullable
|
||||
private static String currentVideoId;
|
||||
@@ -808,14 +808,12 @@ public class SegmentPlaybackController {
|
||||
|
||||
LinearLayout mainLayout = new LinearLayout(currentContext);
|
||||
mainLayout.setOrientation(LinearLayout.VERTICAL);
|
||||
final int dip8 = dipToPixels(8);
|
||||
final int dip16 = dipToPixels(16);
|
||||
mainLayout.setPadding(dip16, dip8, dip16, dip8);
|
||||
mainLayout.setPadding(Dim.dp16, Dim.dp8, Dim.dp16, Dim.dp8);
|
||||
mainLayout.setGravity(Gravity.CENTER);
|
||||
mainLayout.setMinimumHeight(dipToPixels(48));
|
||||
mainLayout.setMinimumHeight(Dim.dp48);
|
||||
|
||||
ShapeDrawable background = new ShapeDrawable(new RoundRectShape(
|
||||
Utils.createCornerRadii(20), null, null));
|
||||
Dim.roundedCorners(20), null, null));
|
||||
background.getPaint().setColor(Utils.getDialogBackgroundColor());
|
||||
mainLayout.setBackground(background);
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package app.revanced.extension.youtube.sponsorblock.objects;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||
import static app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings.migrateOldColorString;
|
||||
|
||||
import android.content.Context;
|
||||
@@ -17,6 +16,7 @@ import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.preference.ColorPickerPreference;
|
||||
import app.revanced.extension.shared.ui.ColorDot;
|
||||
import app.revanced.extension.shared.ui.Dim;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class SegmentCategoryPreference extends ColorPickerPreference {
|
||||
@@ -110,7 +110,7 @@ public class SegmentCategoryPreference extends ColorPickerPreference {
|
||||
}
|
||||
|
||||
radioGroup.setOnCheckedChangeListener((group, checkedId) -> selectedDialogEntryIndex = checkedId);
|
||||
radioGroup.setPadding(dipToPixels(10), 0, dipToPixels(10), dipToPixels(10));
|
||||
radioGroup.setPadding(Dim.dp10, 0, Dim.dp10, Dim.dp10);
|
||||
return radioGroup;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.ui.Dim;
|
||||
import app.revanced.extension.youtube.patches.VideoInformation;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.sponsorblock.SponsorBlockUtils;
|
||||
@@ -132,9 +133,7 @@ public final class NewSegmentLayout extends FrameLayout {
|
||||
|
||||
GradientDrawable backgroundDrawable = new GradientDrawable();
|
||||
backgroundDrawable.setColor(getResourceColor("skip_ad_button_background_color"));
|
||||
final float cornerRadius = squareLayout
|
||||
? 0
|
||||
: 16 * getResources().getDisplayMetrics().density;
|
||||
final float cornerRadius = squareLayout ? 0f : Dim.dp16;
|
||||
backgroundDrawable.setCornerRadius(cornerRadius);
|
||||
setBackground(backgroundDrawable);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import android.preference.SwitchPreference;
|
||||
import android.text.InputType;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Pair;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
@@ -34,6 +33,7 @@ import app.revanced.extension.shared.settings.Setting;
|
||||
import app.revanced.extension.shared.settings.preference.CustomDialogListPreference;
|
||||
import app.revanced.extension.shared.settings.preference.ResettableEditTextPreference;
|
||||
import app.revanced.extension.shared.ui.CustomDialog;
|
||||
import app.revanced.extension.shared.ui.Dim;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController;
|
||||
import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings;
|
||||
@@ -507,7 +507,7 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||
EditText editText = getEditText();
|
||||
|
||||
editText.setInputType(editText.getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
editText.setTextSize(TypedValue.COMPLEX_UNIT_PT, 7); // Use a smaller font to reduce text wrap.
|
||||
editText.setTextSize(14);
|
||||
|
||||
// Create a custom dialog.
|
||||
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
|
||||
@@ -550,7 +550,7 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||
| InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
||||
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
editText.setAutofillHints((String) null);
|
||||
editText.setTextSize(TypedValue.COMPLEX_UNIT_PT, 8);
|
||||
editText.setTextSize(14);
|
||||
|
||||
// Set preference listeners.
|
||||
importExport.setOnPreferenceClickListener(preference1 -> {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package app.revanced.extension.youtube.videoplayer;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||
import static app.revanced.extension.shared.settings.preference.CustomDialogListPreference.*;
|
||||
import static app.revanced.extension.youtube.patches.VideoInformation.AUTOMATIC_VIDEO_QUALITY_VALUE;
|
||||
import static app.revanced.extension.youtube.patches.VideoInformation.VIDEO_QUALITY_PREMIUM_NAME;
|
||||
@@ -21,6 +20,7 @@ import android.widget.*;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import app.revanced.extension.shared.ui.Dim;
|
||||
import app.revanced.extension.shared.ui.SheetBottomDialog;
|
||||
import app.revanced.extension.youtube.shared.PlayerType;
|
||||
import com.google.android.libraries.youtube.innertube.model.media.VideoQuality;
|
||||
@@ -214,11 +214,6 @@ public class VideoQualityDialogButton {
|
||||
}
|
||||
}
|
||||
|
||||
// Preset size constants.
|
||||
final int dip8 = dipToPixels(8);
|
||||
final int dip12 = dipToPixels(12);
|
||||
final int dip16 = dipToPixels(16);
|
||||
|
||||
// Create main layout.
|
||||
SheetBottomDialog.DraggableLinearLayout mainLayout =
|
||||
SheetBottomDialog.createMainLayout(context, getDialogBackgroundColor());
|
||||
@@ -269,7 +264,7 @@ public class VideoQualityDialogButton {
|
||||
LinearLayout.LayoutParams titleParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
titleParams.setMargins(dip12, dip16, 0, dip16);
|
||||
titleParams.setMargins(Dim.dp12, Dim.dp16, 0, Dim.dp16);
|
||||
titleView.setLayoutParams(titleParams);
|
||||
mainLayout.addView(titleView);
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M
|
||||
org.gradle.parallel = true
|
||||
android.useAndroidX = true
|
||||
kotlin.code.style = official
|
||||
version = 5.43.0-dev.3
|
||||
version = 5.47.0-dev.7
|
||||
|
||||
@@ -56,10 +56,18 @@ public final class app/revanced/patches/all/misc/connectivity/telephony/sim/spoo
|
||||
public static final fun getSpoofSimCountryPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/all/misc/connectivity/telephony/sim/spoof/SpoofSimProviderPatchKt {
|
||||
public static final fun getSpoofSimProviderPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/all/misc/connectivity/wifi/spoof/SpoofWifiPatchKt {
|
||||
public static final fun getSpoofWifiPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/all/misc/customcertificates/CustomCertificatesPatchKt {
|
||||
public static final fun getCustomNetworkSecurityPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/all/misc/debugging/EnableAndroidDebuggingPatchKt {
|
||||
public static final fun getEnableAndroidDebuggingPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
|
||||
}
|
||||
@@ -184,6 +192,10 @@ public final class app/revanced/patches/duolingo/debug/EnableDebugMenuPatchKt {
|
||||
public static final fun getEnableDebugMenuPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/duolingo/energy/SkipEnergyRechargeAdsPatchKt {
|
||||
public static final fun getSkipEnergyRechargeAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/facebook/ads/mainfeed/HideSponsoredStoriesPatchKt {
|
||||
public static final fun getHideSponsoredStoriesPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
@@ -268,6 +280,10 @@ public final class app/revanced/patches/instagram/feed/LimitFeedToFollowedProfil
|
||||
public static final fun getLimitFeedToFollowedProfiles ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/instagram/ghost/story/AnonymousStoryViewingPatchKt {
|
||||
public static final fun getAnonymousStoryViewingPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/instagram/hide/explore/HideExploreFeedKt {
|
||||
public static final fun getHideExploreFeedPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
@@ -308,10 +324,18 @@ public final class app/revanced/patches/instagram/misc/signature/SignatureCheckP
|
||||
public static final fun getSignatureCheckPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/instagram/story/flipping/DisableStoryAutoFlippingPatchKt {
|
||||
public static final fun getDisableStoryAutoFlippingPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/irplus/ad/RemoveAdsPatchKt {
|
||||
public static final fun getRemoveAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/letterboxd/ads/HideAdsPatchKt {
|
||||
public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/lightroom/misc/login/DisableMandatoryLoginPatchKt {
|
||||
public static final fun getDisableMandatoryLoginPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
@@ -396,6 +420,10 @@ public final class app/revanced/patches/music/layout/branding/CustomBrandingPatc
|
||||
public static final fun getCustomBrandingPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/music/layout/buttons/HideButtonsKt {
|
||||
public static final fun getHideButtons ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/music/layout/castbutton/HideCastButtonKt {
|
||||
public static final fun getHideCastButton ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
@@ -404,6 +432,10 @@ public final class app/revanced/patches/music/layout/compactheader/HideCategoryB
|
||||
public static final fun getHideCategoryBar ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/music/layout/miniplayercolor/ChangeMiniplayerColorKt {
|
||||
public static final fun getChangeMiniplayerColor ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/music/layout/navigationbar/NavigationBarPatchKt {
|
||||
public static final fun getNavigationBarPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
@@ -481,7 +513,9 @@ public final class app/revanced/patches/music/misc/tracks/ForceOriginalAudioPatc
|
||||
|
||||
public final class app/revanced/patches/music/playservice/VersionCheckPatchKt {
|
||||
public static final fun getVersionCheckPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
|
||||
public static final fun is_7_16_or_greater ()Z
|
||||
public static final fun is_7_33_or_greater ()Z
|
||||
public static final fun is_8_05_or_greater ()Z
|
||||
public static final fun is_8_10_or_greater ()Z
|
||||
public static final fun is_8_11_or_greater ()Z
|
||||
public static final fun is_8_15_or_greater ()Z
|
||||
@@ -762,6 +796,14 @@ public final class app/revanced/patches/reddit/misc/tracking/url/SanitizeUrlQuer
|
||||
public static final fun getSanitizeUrlQueryPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/samsung/radio/misc/fix/crash/FixCrashPatchKt {
|
||||
public static final fun getFixCrashPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/samsung/radio/restrictions/device/BypassDeviceChecksPatchKt {
|
||||
public static final fun getBypassDeviceChecksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/serviceportalbund/detection/root/RootDetectionPatchKt {
|
||||
public static final fun getRootDetectionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
@@ -1170,6 +1212,10 @@ public final class app/revanced/patches/tiktok/misc/settings/SettingsPatchKt {
|
||||
public static final fun getSettingsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/tiktok/misc/share/SanitizeShareUrlsPatchKt {
|
||||
public static final fun getSanitizeShareUrlsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/tiktok/misc/spoof/sim/SpoofSimPatchKt {
|
||||
public static final fun getSpoofSimPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
@@ -1,105 +1,9 @@
|
||||
package app.revanced.patches.all.misc.connectivity.telephony.sim.spoof
|
||||
|
||||
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.stringOption
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableMethodReference
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
import java.util.*
|
||||
|
||||
@Deprecated("Patch was renamed", ReplaceWith("spoofSimProviderPatch"))
|
||||
@Suppress("unused")
|
||||
val spoofSimCountryPatch = bytecodePatch(
|
||||
name = "Spoof SIM country",
|
||||
description = "Spoofs country information returned by the SIM card provider.",
|
||||
use = false,
|
||||
) {
|
||||
val countries = Locale.getISOCountries().associateBy { Locale("", it).displayCountry }
|
||||
|
||||
fun isoCountryPatchOption(
|
||||
key: String,
|
||||
title: String,
|
||||
) = stringOption(
|
||||
key,
|
||||
null,
|
||||
countries,
|
||||
title,
|
||||
"ISO-3166-1 alpha-2 country code equivalent for the SIM provider's country code.",
|
||||
false,
|
||||
validator = { it: String? -> it == null || it.uppercase() in countries.values },
|
||||
)
|
||||
|
||||
val networkCountryIso by isoCountryPatchOption(
|
||||
"networkCountryIso",
|
||||
"Network ISO country code",
|
||||
)
|
||||
|
||||
val simCountryIso by isoCountryPatchOption(
|
||||
"simCountryIso",
|
||||
"SIM ISO country code",
|
||||
)
|
||||
|
||||
dependsOn(
|
||||
transformInstructionsPatch(
|
||||
filterMap = { _, _, instruction, instructionIndex ->
|
||||
if (instruction !is ReferenceInstruction) return@transformInstructionsPatch null
|
||||
|
||||
val reference = instruction.reference as? MethodReference ?: return@transformInstructionsPatch null
|
||||
|
||||
val match = MethodCall.entries.firstOrNull { search ->
|
||||
MethodUtil.methodSignaturesMatch(reference, search.reference)
|
||||
} ?: return@transformInstructionsPatch null
|
||||
|
||||
val iso = when (match) {
|
||||
MethodCall.NetworkCountryIso -> networkCountryIso
|
||||
MethodCall.SimCountryIso -> simCountryIso
|
||||
}?.lowercase()
|
||||
|
||||
iso?.let { instructionIndex to it }
|
||||
},
|
||||
transform = { mutableMethod, entry: Pair<Int, String> ->
|
||||
transformMethodCall(entry, mutableMethod)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun transformMethodCall(
|
||||
entry: Pair<Int, String>,
|
||||
mutableMethod: MutableMethod,
|
||||
) {
|
||||
val (instructionIndex, methodCallValue) = entry
|
||||
|
||||
val register = mutableMethod.getInstruction<OneRegisterInstruction>(instructionIndex + 1).registerA
|
||||
|
||||
mutableMethod.replaceInstruction(
|
||||
instructionIndex + 1,
|
||||
"const-string v$register, \"$methodCallValue\"",
|
||||
)
|
||||
}
|
||||
|
||||
private enum class MethodCall(
|
||||
val reference: MethodReference,
|
||||
) {
|
||||
NetworkCountryIso(
|
||||
ImmutableMethodReference(
|
||||
"Landroid/telephony/TelephonyManager;",
|
||||
"getNetworkCountryIso",
|
||||
emptyList(),
|
||||
"Ljava/lang/String;",
|
||||
),
|
||||
),
|
||||
SimCountryIso(
|
||||
ImmutableMethodReference(
|
||||
"Landroid/telephony/TelephonyManager;",
|
||||
"getSimCountryIso",
|
||||
emptyList(),
|
||||
"Ljava/lang/String;",
|
||||
),
|
||||
),
|
||||
}
|
||||
val spoofSimCountryPatch = bytecodePatch {
|
||||
dependsOn(spoofSimProviderPatch)
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package app.revanced.patches.all.misc.connectivity.telephony.sim.spoof
|
||||
|
||||
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.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableMethodReference
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
import java.util.Locale
|
||||
|
||||
@Suppress("unused")
|
||||
val spoofSimProviderPatch = bytecodePatch(
|
||||
name = "Spoof SIM provider",
|
||||
description = "Spoofs information about the SIM card provider.",
|
||||
use = false,
|
||||
) {
|
||||
val countries = Locale.getISOCountries().associateBy { Locale("", it).displayCountry }
|
||||
|
||||
fun isoCountryPatchOption(
|
||||
key: String,
|
||||
title: String,
|
||||
) = stringOption(
|
||||
key,
|
||||
null,
|
||||
countries,
|
||||
title,
|
||||
"ISO-3166-1 alpha-2 country code equivalent for the SIM provider's country code.",
|
||||
false,
|
||||
validator = { it: String? -> it == null || it.uppercase() in countries.values },
|
||||
)
|
||||
|
||||
fun isMccMncValid(it: Int?): Boolean = it == null || (it >= 10000 && it <= 999999)
|
||||
|
||||
val networkCountryIso by isoCountryPatchOption(
|
||||
"networkCountryIso",
|
||||
"Network ISO country code",
|
||||
)
|
||||
|
||||
val networkOperator by intOption(
|
||||
key = "networkOperator",
|
||||
title = "MCC+MNC network operator code",
|
||||
description = "The 5 or 6 digits MCC+MNC (Mobile Country Code + Mobile Network Code) of the network operator.",
|
||||
validator = { isMccMncValid(it) }
|
||||
)
|
||||
|
||||
val networkOperatorName by stringOption(
|
||||
key = "networkOperatorName",
|
||||
title = "Network operator name",
|
||||
description = "The full name of the network operator.",
|
||||
)
|
||||
|
||||
val simCountryIso by isoCountryPatchOption(
|
||||
"simCountryIso",
|
||||
"SIM ISO country code",
|
||||
)
|
||||
|
||||
val simOperator by intOption(
|
||||
key = "simOperator",
|
||||
title = "MCC+MNC SIM operator code",
|
||||
description = "The 5 or 6 digits MCC+MNC (Mobile Country Code + Mobile Network Code) of the SIM operator.",
|
||||
validator = { isMccMncValid(it) }
|
||||
)
|
||||
|
||||
val simOperatorName by stringOption(
|
||||
key = "simOperatorName",
|
||||
title = "SIM operator name",
|
||||
description = "The full name of the SIM operator.",
|
||||
)
|
||||
|
||||
dependsOn(
|
||||
transformInstructionsPatch(
|
||||
filterMap = { _, _, instruction, instructionIndex ->
|
||||
if (instruction !is ReferenceInstruction) return@transformInstructionsPatch null
|
||||
|
||||
val reference = instruction.reference as? MethodReference ?: return@transformInstructionsPatch null
|
||||
|
||||
val match = MethodCall.entries.firstOrNull { search ->
|
||||
MethodUtil.methodSignaturesMatch(reference, search.reference)
|
||||
} ?: return@transformInstructionsPatch null
|
||||
|
||||
val replacement = when (match) {
|
||||
MethodCall.NetworkCountryIso -> networkCountryIso?.lowercase()
|
||||
MethodCall.NetworkOperator -> networkOperator?.toString()
|
||||
MethodCall.NetworkOperatorName -> networkOperatorName
|
||||
MethodCall.SimCountryIso -> simCountryIso?.lowercase()
|
||||
MethodCall.SimOperator -> simOperator?.toString()
|
||||
MethodCall.SimOperatorName -> simOperatorName
|
||||
}
|
||||
replacement?.let { instructionIndex to it }
|
||||
},
|
||||
transform = ::transformMethodCall,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun transformMethodCall(
|
||||
mutableMethod: MutableMethod,
|
||||
entry: Pair<Int, String>,
|
||||
) {
|
||||
val (instructionIndex, methodCallValue) = entry
|
||||
|
||||
// Get the register which would have contained the return value
|
||||
val register = mutableMethod.getInstruction<OneRegisterInstruction>(instructionIndex + 1).registerA
|
||||
|
||||
// Replace the move-result instruction with our fake value
|
||||
mutableMethod.replaceInstruction(
|
||||
instructionIndex + 1,
|
||||
"const-string v$register, \"$methodCallValue\"",
|
||||
)
|
||||
}
|
||||
|
||||
private enum class MethodCall(
|
||||
val reference: MethodReference,
|
||||
) {
|
||||
NetworkCountryIso(
|
||||
ImmutableMethodReference(
|
||||
"Landroid/telephony/TelephonyManager;",
|
||||
"getNetworkCountryIso",
|
||||
emptyList(),
|
||||
"Ljava/lang/String;",
|
||||
),
|
||||
),
|
||||
NetworkOperator(
|
||||
ImmutableMethodReference(
|
||||
"Landroid/telephony/TelephonyManager;",
|
||||
"getNetworkOperator",
|
||||
emptyList(),
|
||||
"Ljava/lang/String;",
|
||||
),
|
||||
),
|
||||
NetworkOperatorName(
|
||||
ImmutableMethodReference(
|
||||
"Landroid/telephony/TelephonyManager;",
|
||||
"getNetworkOperatorName",
|
||||
emptyList(),
|
||||
"Ljava/lang/String;",
|
||||
),
|
||||
),
|
||||
SimCountryIso(
|
||||
ImmutableMethodReference(
|
||||
"Landroid/telephony/TelephonyManager;",
|
||||
"getSimCountryIso",
|
||||
emptyList(),
|
||||
"Ljava/lang/String;",
|
||||
),
|
||||
),
|
||||
SimOperator(
|
||||
ImmutableMethodReference(
|
||||
"Landroid/telephony/TelephonyManager;",
|
||||
"getSimOperator",
|
||||
emptyList(),
|
||||
"Ljava/lang/String;",
|
||||
),
|
||||
),
|
||||
SimOperatorName(
|
||||
ImmutableMethodReference(
|
||||
"Landroid/telephony/TelephonyManager;",
|
||||
"getSimOperatorName",
|
||||
emptyList(),
|
||||
"Ljava/lang/String;",
|
||||
),
|
||||
),
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package app.revanced.patches.all.misc.customcertificates
|
||||
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.patch.booleanOption
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patcher.patch.stringsOption
|
||||
import app.revanced.util.Utils.trimIndentMultiline
|
||||
import app.revanced.util.getNode
|
||||
import org.w3c.dom.Element
|
||||
import java.io.File
|
||||
|
||||
|
||||
|
||||
|
||||
val customNetworkSecurityPatch = resourcePatch(
|
||||
name = "Custom network security",
|
||||
description = "Allows trusting custom certificate authorities for a specific domain.",
|
||||
use = false
|
||||
) {
|
||||
|
||||
val targetDomains by stringsOption(
|
||||
key = "targetDomains",
|
||||
title = "Target domains",
|
||||
description = "List of domains to which the custom trust configuration will be applied (one domain per entry).",
|
||||
default = listOf("example.com"),
|
||||
required = true
|
||||
)
|
||||
|
||||
val includeSubdomains by booleanOption(
|
||||
key = "includeSubdomains",
|
||||
title = "Include subdomains",
|
||||
description = "Applies the configuration to all subdomains of the target domains.",
|
||||
default = false,
|
||||
required = true
|
||||
)
|
||||
|
||||
val customCAFilePaths by stringsOption(
|
||||
key = "customCAFilePaths",
|
||||
title = "Custom CA file paths",
|
||||
description = """
|
||||
List of paths to files in PEM or DER format (one file path per entry).
|
||||
|
||||
Makes an app trust the provided custom certificate authorities (CAs),
|
||||
for the specified domains, and if the option "Include Subdomains" is enabled then also the subdomains.
|
||||
|
||||
|
||||
CA files will be bundled in res/raw/ of resulting APK
|
||||
""".trimIndentMultiline(),
|
||||
default = null,
|
||||
required = false
|
||||
)
|
||||
|
||||
val allowUserCerts by booleanOption(
|
||||
key = "allowUserCerts",
|
||||
title = "Trust user added CAs",
|
||||
description = "Makes an app trust certificates from the Android user store for the specified domains, and if the option \"Include Subdomains\" is enabled then also the subdomains.",
|
||||
|
||||
default = false,
|
||||
required = true
|
||||
)
|
||||
|
||||
val allowSystemCerts by booleanOption(
|
||||
key = "allowSystemCerts",
|
||||
title = "Trust system CAs",
|
||||
description = "Makes an app trust certificates from the Android system store for the specified domains, and and if the option \"Include Subdomains\" is enabled then also the subdomains.",
|
||||
|
||||
default = true,
|
||||
required = true
|
||||
)
|
||||
|
||||
val allowCleartextTraffic by booleanOption(
|
||||
key = "allowCleartextTraffic",
|
||||
title = "Allow cleartext traffic (HTTP)",
|
||||
description = "Allows unencrypted HTTP traffic for the specified domains, and if \"Include Subdomains\" is enabled then also the subdomains.",
|
||||
|
||||
default = false,
|
||||
required = true
|
||||
)
|
||||
|
||||
val overridePins by booleanOption(
|
||||
key = "overridePins",
|
||||
title = "Override certificate pinning",
|
||||
description = "Overrides certificate pinning for the specified domains and their subdomains if the option \"Include Subdomains\" is enabled to allow inspecting app traffic via a proxy.",
|
||||
|
||||
default = false,
|
||||
required = true
|
||||
)
|
||||
|
||||
fun generateNetworkSecurityConfig(): String {
|
||||
val targetDomains = targetDomains ?: emptyList()
|
||||
val includeSubdomains = includeSubdomains ?: false
|
||||
val customCAFilePaths = customCAFilePaths ?: emptyList()
|
||||
val allowUserCerts = allowUserCerts ?: false
|
||||
val allowSystemCerts = allowSystemCerts ?: true
|
||||
val allowCleartextTraffic = allowCleartextTraffic ?: false
|
||||
val overridePins = overridePins ?: false
|
||||
|
||||
val domainsXML = buildString {
|
||||
targetDomains.forEach {
|
||||
appendLine(""" <domain includeSubdomains="$includeSubdomains">$it</domain>""")
|
||||
}
|
||||
}.trimEnd()
|
||||
|
||||
val trustAnchorsXML = buildString {
|
||||
if (allowSystemCerts) {
|
||||
appendLine(""" <certificates src="system" overridePins="$overridePins" />""")
|
||||
}
|
||||
if (allowUserCerts) {
|
||||
appendLine(""" <certificates src="user" overridePins="$overridePins" />""")
|
||||
}
|
||||
customCAFilePaths.forEach { path ->
|
||||
val fileName = path.substringAfterLast('/').substringBeforeLast('.')
|
||||
appendLine(""" <certificates src="@raw/$fileName" overridePins="$overridePins" />""")
|
||||
}
|
||||
}
|
||||
|
||||
if (trustAnchorsXML.isBlank()) {
|
||||
throw PatchException("At least one trust anchor (System, User, or Custom CA) must be enabled.")
|
||||
}
|
||||
|
||||
return """
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<domain-config cleartextTrafficPermitted="$allowCleartextTraffic">
|
||||
$domainsXML
|
||||
<trust-anchors>
|
||||
${trustAnchorsXML.trimEnd()}
|
||||
</trust-anchors>
|
||||
</domain-config>
|
||||
</network-security-config>
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
|
||||
execute {
|
||||
val nscFileNameBare = "network_security_config"
|
||||
val resXmlDir = "res/xml"
|
||||
val resRawDir = "res/raw"
|
||||
val nscFileNameWithSuffix = "$nscFileNameBare.xml"
|
||||
|
||||
|
||||
document("AndroidManifest.xml").use { document ->
|
||||
val applicationNode = document.getNode("application") as Element
|
||||
applicationNode.setAttribute("android:networkSecurityConfig", "@xml/$nscFileNameBare")
|
||||
}
|
||||
|
||||
|
||||
File(get(resXmlDir), nscFileNameWithSuffix).apply {
|
||||
writeText(generateNetworkSecurityConfig())
|
||||
}
|
||||
|
||||
|
||||
|
||||
for (customCAFilePath in customCAFilePaths ?: emptyList()) {
|
||||
val file = File(customCAFilePath)
|
||||
if (!file.exists()) {
|
||||
throw PatchException(
|
||||
"The custom CA file path cannot be found: " +
|
||||
file.absolutePath
|
||||
)
|
||||
}
|
||||
|
||||
if (!file.isFile) {
|
||||
throw PatchException(
|
||||
"The custom CA file path must be a file: "
|
||||
+ file.absolutePath
|
||||
)
|
||||
}
|
||||
val caFileNameWithoutSuffix = customCAFilePath.substringAfterLast('/').substringBefore('.')
|
||||
val caFile = File(customCAFilePath)
|
||||
File(
|
||||
get(resRawDir),
|
||||
caFileNameWithoutSuffix
|
||||
).writeText(
|
||||
caFile.readText()
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ val removeShareTargetsPatch = resourcePatch(
|
||||
document("res/xml/shortcuts.xml")
|
||||
} catch (_: FileNotFoundException) {
|
||||
return@execute Logger.getLogger(this::class.java.name).warning(
|
||||
"The app has no shortcuts. No changes applied.")
|
||||
"The app has no shortcuts. No changes applied.")
|
||||
}.use { document ->
|
||||
val rootNode = document.getNode("shortcuts") as? Element ?: return@use
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
val disableAdsPatch = bytecodePatch(
|
||||
"Disable ads",
|
||||
) {
|
||||
compatibleWith("com.duolingo")
|
||||
// 6.55.3 and higher can show ads after each exercise.
|
||||
compatibleWith("com.duolingo"("6.54.5"))
|
||||
|
||||
execute {
|
||||
// Couple approaches to remove ads exist:
|
||||
|
||||
@@ -1,26 +1,35 @@
|
||||
package app.revanced.patches.duolingo.debug
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
import app.revanced.util.returnEarly
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
@Suppress("unused")
|
||||
val enableDebugMenuPatch = bytecodePatch(
|
||||
name = "Enable debug menu",
|
||||
use = false,
|
||||
use = false
|
||||
) {
|
||||
compatibleWith("com.duolingo"("5.158.4"))
|
||||
compatibleWith("com.duolingo")
|
||||
|
||||
execute {
|
||||
initializeBuildConfigProviderFingerprint.method.apply {
|
||||
val insertIndex = initializeBuildConfigProviderFingerprint.patternMatch!!.startIndex
|
||||
val register = getInstruction<TwoRegisterInstruction>(insertIndex).registerA
|
||||
// It seems all categories are allowed on release. Force this on anyway.
|
||||
debugCategoryAllowOnReleaseBuildsFingerprint.method.returnEarly(true)
|
||||
|
||||
addInstructions(
|
||||
insertIndex,
|
||||
"const/4 v$register, 0x1",
|
||||
)
|
||||
// Change build config debug build flag.
|
||||
buildConfigProviderConstructorFingerprint.match(
|
||||
buildConfigProviderToStringFingerprint.classDef
|
||||
).let {
|
||||
val index = it.patternMatch!!.startIndex
|
||||
|
||||
it.method.apply {
|
||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||
addInstruction(
|
||||
index + 1,
|
||||
"const/4 v$register, 0x1"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,16 +4,25 @@ import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
/**
|
||||
* The `BuildConfigProvider` class has two booleans:
|
||||
*
|
||||
* - `isChina`: (usually) compares "play" with "china"...except for builds in China
|
||||
* - `isDebug`: compares "release" with "debug" <-- we want to force this to `true`
|
||||
*/
|
||||
|
||||
internal val initializeBuildConfigProviderFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
|
||||
returns("V")
|
||||
opcodes(Opcode.IPUT_BOOLEAN)
|
||||
strings("debug", "release", "china")
|
||||
internal val debugCategoryAllowOnReleaseBuildsFingerprint = fingerprint {
|
||||
returns("Z")
|
||||
parameters()
|
||||
custom { method, classDef ->
|
||||
method.name == "getAllowOnReleaseBuilds" && classDef.type == "Lcom/duolingo/debug/DebugCategory;"
|
||||
}
|
||||
}
|
||||
|
||||
internal val buildConfigProviderConstructorFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
|
||||
parameters()
|
||||
opcodes(Opcode.CONST_4)
|
||||
}
|
||||
|
||||
internal val buildConfigProviderToStringFingerprint = fingerprint {
|
||||
parameters()
|
||||
returns("Ljava/lang/String;")
|
||||
strings("BuildConfigProvider(") // Partial string match.
|
||||
custom { method, _ ->
|
||||
method.name == "toString"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package app.revanced.patches.duolingo.energy
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
/**
|
||||
* Matches the class found in [energyConfigToStringFingerprint].
|
||||
*/
|
||||
internal val initializeEnergyConfigFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
|
||||
opcodes(Opcode.RETURN_VOID)
|
||||
}
|
||||
|
||||
// Class name currently is not obfuscated but it may be in the future.
|
||||
internal val energyConfigToStringFingerprint = fingerprint {
|
||||
parameters()
|
||||
returns("Ljava/lang/String;")
|
||||
strings("EnergyConfig(", "maxEnergy=") // Partial string matches.
|
||||
custom { method, _ -> method.name == "toString" }
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package app.revanced.patches.duolingo.energy
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.util.findFieldFromToString
|
||||
|
||||
@Suppress("unused")
|
||||
val skipEnergyRechargeAdsPatch = bytecodePatch(
|
||||
name = "Skip energy recharge ads",
|
||||
description = "Skips watching ads to recharge energy."
|
||||
) {
|
||||
compatibleWith("com.duolingo")
|
||||
|
||||
execute {
|
||||
initializeEnergyConfigFingerprint
|
||||
.match(energyConfigToStringFingerprint.classDef)
|
||||
.method.apply {
|
||||
val energyField = energyConfigToStringFingerprint.method
|
||||
.findFieldFromToString("energy=")
|
||||
val insertIndex = initializeEnergyConfigFingerprint.patternMatch!!.startIndex
|
||||
|
||||
addInstructions(
|
||||
insertIndex,
|
||||
"""
|
||||
const/16 v0, 99
|
||||
iput v0, p0, $energyField
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,7 @@ val spoofFeaturesPatch = bytecodePatch(
|
||||
"com.google.android.feature.PIXEL_2024_MIDYEAR_EXPERIENCE",
|
||||
"com.google.android.feature.PIXEL_2024_EXPERIENCE",
|
||||
"com.google.android.feature.PIXEL_2025_MIDYEAR_EXPERIENCE",
|
||||
"com.google.android.feature.PIXEL_2025_EXPERIENCE",
|
||||
),
|
||||
title = "Features to disable",
|
||||
description = "Google Pixel exclusive features to disable." +
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package app.revanced.patches.instagram.ghost.story
|
||||
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.util.Utils.trimIndentMultiline
|
||||
import app.revanced.util.returnEarly
|
||||
|
||||
@Suppress("unused")
|
||||
val anonymousStoryViewingPatch = bytecodePatch(
|
||||
name = "Anonymous story viewing",
|
||||
description = """
|
||||
View stories without sending any information to the server.
|
||||
Your view will not appear in the story viewers list.
|
||||
Note: Since no data is sent, a story you have already viewed may appear as new on another device.
|
||||
""".trimIndentMultiline(),
|
||||
use = false
|
||||
) {
|
||||
compatibleWith("com.instagram.android")
|
||||
|
||||
execute {
|
||||
// Prevent the hashmap of the seen media to be filled
|
||||
setMediaSeenHashmapFingerprint.method.returnEarly()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package app.revanced.patches.instagram.ghost.story
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val setMediaSeenHashmapFingerprint = fingerprint {
|
||||
parameters()
|
||||
returns("V")
|
||||
strings("media/seen/")
|
||||
}
|
||||
@@ -3,9 +3,10 @@ package app.revanced.patches.instagram.hide.navigation
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal val initializeNavigationButtonsListFingerprint = fingerprint {
|
||||
strings("Nav3")
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
parameters("Lcom/instagram/common/session/UserSession;", "Z")
|
||||
returns("Ljava/util/List;")
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.booleanOption
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.instagram.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.shared.PATCH_NAME_HIDE_NAVIGATION_BUTTONS
|
||||
import app.revanced.util.addInstructionsAtControlFlowLabel
|
||||
import app.revanced.util.findFreeRegister
|
||||
import app.revanced.util.getReference
|
||||
@@ -19,11 +20,11 @@ private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
|
||||
@Suppress("unused")
|
||||
val hideNavigationButtonsPatch = bytecodePatch(
|
||||
name = "Hide navigation buttons",
|
||||
name = PATCH_NAME_HIDE_NAVIGATION_BUTTONS,
|
||||
description = "Hides navigation bar buttons, such as the Reels and Create button.",
|
||||
use = false
|
||||
) {
|
||||
compatibleWith("com.instagram.android")
|
||||
compatibleWith("com.instagram.android"("401.0.0.48.79"))
|
||||
|
||||
dependsOn(sharedExtensionPatch)
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ val enableDeveloperMenuPatch = bytecodePatch(
|
||||
with(clearNotificationReceiverFingerprint.method) {
|
||||
indexOfFirstInstructionReversedOrThrow(clearNotificationReceiverFingerprint.stringMatches!!.first().index) {
|
||||
val reference = getReference<MethodReference>()
|
||||
Opcode.INVOKE_STATIC == opcode &&
|
||||
opcode in listOf(Opcode.INVOKE_STATIC, Opcode.INVOKE_STATIC_RANGE) &&
|
||||
reference?.parameterTypes?.size == 1 &&
|
||||
reference.parameterTypes.first() == "Lcom/instagram/common/session/UserSession;" &&
|
||||
reference.returnType == "Z"
|
||||
|
||||
@@ -6,7 +6,9 @@ import app.revanced.util.returnEarly
|
||||
@Suppress("unused")
|
||||
val signatureCheckPatch = bytecodePatch(
|
||||
name = "Disable signature check",
|
||||
description = "Disables the signature check that causes the app to crash on startup."
|
||||
description = "Disables the signature check that can cause the app to crash on startup. " +
|
||||
"Including this patch may cause issues with sharing or opening external Instagram links.",
|
||||
use = false
|
||||
) {
|
||||
compatibleWith("com.instagram.android")
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package app.revanced.patches.instagram.story.flipping
|
||||
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.util.returnEarly
|
||||
|
||||
@Suppress("unused")
|
||||
val disableStoryAutoFlippingPatch = bytecodePatch(
|
||||
name = "Disable story auto flipping",
|
||||
description = "Disable stories automatically flipping/skipping after some seconds.",
|
||||
use = false
|
||||
) {
|
||||
compatibleWith("com.instagram.android")
|
||||
|
||||
execute {
|
||||
onStoryTimeoutActionFingerprint.method.returnEarly()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package app.revanced.patches.instagram.story.flipping
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val onStoryTimeoutActionFingerprint = fingerprint {
|
||||
parameters("Ljava/lang/Object;")
|
||||
returns("V")
|
||||
strings("userSession")
|
||||
custom { _, classDef ->
|
||||
classDef.type == "Linstagram/features/stories/fragment/ReelViewerFragment;"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package app.revanced.patches.letterboxd.ads
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal const val admobHelperClassName = "Lcom/letterboxd/letterboxd/helpers/AdmobHelper;"
|
||||
|
||||
internal val admobHelperSetShowAdsFingerprint = fingerprint {
|
||||
custom { method, classDef ->
|
||||
method.name == "setShowAds" && classDef.type == admobHelperClassName
|
||||
}
|
||||
}
|
||||
|
||||
internal val admobHelperShouldShowAdsFingerprint = fingerprint {
|
||||
custom { method, classDef ->
|
||||
method.name == "shouldShowAds" && classDef.type == admobHelperClassName
|
||||
}
|
||||
}
|
||||
|
||||
internal val filmFragmentShowAdsFingerprint = fingerprint {
|
||||
custom { method, classDef ->
|
||||
method.name == "showAds" && classDef.type.endsWith("/FilmFragment;")
|
||||
}
|
||||
}
|
||||
|
||||
internal val memberExtensionShowAdsFingerprint = fingerprint {
|
||||
custom { method, classDef ->
|
||||
method.name == "showAds" && classDef.type.endsWith("/AMemberExtensionKt;")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
|
||||
package app.revanced.patches.letterboxd.ads
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.util.returnEarly
|
||||
|
||||
@Suppress("unused")
|
||||
val hideAdsPatch = bytecodePatch(
|
||||
name = "Hide ads",
|
||||
) {
|
||||
compatibleWith("com.letterboxd.letterboxd")
|
||||
|
||||
execute {
|
||||
admobHelperSetShowAdsFingerprint.method.addInstruction(0, "const p1, 0x0")
|
||||
listOf(admobHelperShouldShowAdsFingerprint, filmFragmentShowAdsFingerprint, memberExtensionShowAdsFingerprint).forEach {
|
||||
it.method.returnEarly(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,24 @@
|
||||
package app.revanced.patches.music.layout.branding
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.util.smali.ExternalLabel
|
||||
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.music.misc.gms.Constants.MUSIC_MAIN_ACTIVITY_NAME
|
||||
import app.revanced.patches.music.misc.gms.Constants.MUSIC_PACKAGE_NAME
|
||||
import app.revanced.patches.music.misc.gms.musicActivityOnCreateFingerprint
|
||||
import app.revanced.patches.music.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.shared.layout.branding.EXTENSION_CLASS_DESCRIPTOR
|
||||
import app.revanced.patches.shared.layout.branding.baseCustomBrandingPatch
|
||||
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.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.indexOfFirstInstructionReversed
|
||||
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
|
||||
|
||||
private val disableSplashAnimationPatch = bytecodePatch {
|
||||
|
||||
@@ -33,23 +33,22 @@ private val disableSplashAnimationPatch = bytecodePatch {
|
||||
// but the animation is not always the same size as the launch screen and it's still
|
||||
// barely shown. Instead turn off the animation entirely (app will also launch a little faster).
|
||||
cairoSplashAnimationConfigFingerprint.method.apply {
|
||||
val mainActivityLaunchAnimation = resourceMappings["layout", "main_activity_launch_animation"]
|
||||
val literalIndex = indexOfFirstLiteralInstructionOrThrow(
|
||||
mainActivityLaunchAnimation
|
||||
resourceMappings["layout", "main_activity_launch_animation"]
|
||||
)
|
||||
val insertIndex = indexOfFirstInstructionReversed(literalIndex) {
|
||||
this.opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
getReference<MethodReference>()?.name == "setContentView"
|
||||
} + 1
|
||||
val jumpIndex = indexOfFirstInstructionOrThrow(insertIndex) {
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
getReference<MethodReference>()?.parameterTypes?.firstOrNull() == "Ljava/lang/Runnable;"
|
||||
} + 1
|
||||
val checkCastIndex = indexOfFirstInstructionOrThrow(literalIndex) {
|
||||
opcode == Opcode.CHECK_CAST &&
|
||||
getReference<TypeReference>()?.type == "Lcom/airbnb/lottie/LottieAnimationView;"
|
||||
}
|
||||
val register = getInstruction<OneRegisterInstruction>(checkCastIndex).registerA
|
||||
|
||||
addInstructionsWithLabels(
|
||||
insertIndex,
|
||||
"goto :skip_animation",
|
||||
ExternalLabel("skip_animation", getInstruction(jumpIndex))
|
||||
// If using a custom icon then set the lottie animation view to null to bypasses the startup animation.
|
||||
addInstructions(
|
||||
checkCastIndex,
|
||||
"""
|
||||
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->getLottieViewOrNull(Landroid/view/View;)Landroid/view/View;
|
||||
move-result-object v$register
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package app.revanced.patches.music.layout.buttons
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import app.revanced.util.containsLiteralInstruction
|
||||
import app.revanced.util.literal
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal val mediaRouteButtonFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PRIVATE, AccessFlags.FINAL)
|
||||
returns("Z")
|
||||
strings("MediaRouteButton")
|
||||
}
|
||||
|
||||
internal val playerOverlayChipFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("L")
|
||||
literal { playerOverlayChip }
|
||||
}
|
||||
|
||||
internal val historyMenuItemFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("V")
|
||||
parameters("Landroid/view/Menu;")
|
||||
opcodes(
|
||||
Opcode.INVOKE_INTERFACE,
|
||||
Opcode.RETURN_VOID
|
||||
)
|
||||
literal { historyMenuItem }
|
||||
custom { _, classDef ->
|
||||
classDef.methods.count() == 5
|
||||
}
|
||||
}
|
||||
|
||||
internal val historyMenuItemOfflineTabFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("V")
|
||||
parameters("Landroid/view/Menu;")
|
||||
opcodes(
|
||||
Opcode.INVOKE_INTERFACE,
|
||||
Opcode.RETURN_VOID
|
||||
)
|
||||
custom { method, _ ->
|
||||
method.containsLiteralInstruction(historyMenuItem) &&
|
||||
method.containsLiteralInstruction(offlineSettingsMenuItem)
|
||||
}
|
||||
}
|
||||
|
||||
internal val searchActionViewFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("Landroid/view/View;")
|
||||
parameters()
|
||||
literal { searchButton }
|
||||
custom { _, classDef ->
|
||||
classDef.type.endsWith("/SearchActionProvider;")
|
||||
}
|
||||
}
|
||||
|
||||
internal val topBarMenuItemImageViewFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("Landroid/view/View;")
|
||||
parameters()
|
||||
literal { topBarMenuItemImageView }
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package app.revanced.patches.music.layout.buttons
|
||||
|
||||
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.patch.bytecodePatch
|
||||
import app.revanced.patches.all.misc.resources.addResources
|
||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.music.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.music.misc.settings.settingsPatch
|
||||
import app.revanced.patches.shared.misc.mapping.get
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappings
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
|
||||
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
|
||||
|
||||
internal var playerOverlayChip = -1L
|
||||
private set
|
||||
internal var historyMenuItem = -1L
|
||||
private set
|
||||
internal var offlineSettingsMenuItem = -1L
|
||||
private set
|
||||
internal var searchButton = -1L
|
||||
private set
|
||||
internal var topBarMenuItemImageView = -1L
|
||||
private set
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/HideButtonsPatch;"
|
||||
|
||||
@Suppress("unused")
|
||||
val hideButtons = bytecodePatch(
|
||||
name = "Hide buttons",
|
||||
description = "Adds options to hide the cast, history, notification, and search buttons."
|
||||
) {
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
settingsPatch,
|
||||
addResourcesPatch,
|
||||
resourceMappingPatch
|
||||
)
|
||||
|
||||
compatibleWith(
|
||||
"com.google.android.apps.youtube.music"(
|
||||
"7.29.52",
|
||||
"8.10.52"
|
||||
)
|
||||
)
|
||||
|
||||
execute {
|
||||
playerOverlayChip = resourceMappings["id", "player_overlay_chip"]
|
||||
historyMenuItem = resourceMappings["id", "history_menu_item"]
|
||||
offlineSettingsMenuItem = resourceMappings["id", "offline_settings_menu_item"]
|
||||
searchButton = resourceMappings["layout", "search_button"]
|
||||
topBarMenuItemImageView = resourceMappings["id", "top_bar_menu_item_image_view"]
|
||||
|
||||
addResources("music", "layout.buttons.hideButtons")
|
||||
|
||||
PreferenceScreen.GENERAL.addPreferences(
|
||||
SwitchPreference("revanced_music_hide_cast_button"),
|
||||
SwitchPreference("revanced_music_hide_history_button"),
|
||||
SwitchPreference("revanced_music_hide_notification_button"),
|
||||
SwitchPreference("revanced_music_hide_search_button")
|
||||
)
|
||||
|
||||
// Region for hide history button in the top bar.
|
||||
arrayOf(
|
||||
historyMenuItemFingerprint,
|
||||
historyMenuItemOfflineTabFingerprint
|
||||
).forEach { fingerprint ->
|
||||
fingerprint.method.apply {
|
||||
val targetIndex = fingerprint.patternMatch!!.startIndex
|
||||
val targetRegister = getInstruction<FiveRegisterInstruction>(targetIndex).registerD
|
||||
|
||||
addInstructions(
|
||||
targetIndex,
|
||||
"""
|
||||
invoke-static { v$targetRegister }, $EXTENSION_CLASS_DESCRIPTOR->hideHistoryButton(Z)Z
|
||||
move-result v$targetRegister
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Region for hide cast, search and notification buttons in the top bar.
|
||||
arrayOf(
|
||||
Triple(playerOverlayChipFingerprint, playerOverlayChip, "hideCastButton"),
|
||||
Triple(searchActionViewFingerprint, searchButton, "hideSearchButton"),
|
||||
Triple(topBarMenuItemImageViewFingerprint, topBarMenuItemImageView, "hideNotificationButton")
|
||||
).forEach { (fingerprint, resourceIdLiteral, methodName) ->
|
||||
fingerprint.method.apply {
|
||||
val resourceIndex = indexOfFirstLiteralInstructionOrThrow(resourceIdLiteral)
|
||||
val targetIndex = indexOfFirstInstructionOrThrow(
|
||||
resourceIndex, Opcode.MOVE_RESULT_OBJECT
|
||||
)
|
||||
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
|
||||
|
||||
addInstruction(
|
||||
targetIndex + 1,
|
||||
"invoke-static { v$targetRegister }, " +
|
||||
"$EXTENSION_CLASS_DESCRIPTOR->$methodName(Landroid/view/View;)V"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Region for hide cast button in the player.
|
||||
mediaRouteButtonFingerprint.classDef.methods.single { method ->
|
||||
method.name == "setVisibility"
|
||||
}.addInstructions(
|
||||
0,
|
||||
"""
|
||||
invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->hideCastButton(I)I
|
||||
move-result p1
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package app.revanced.patches.music.layout.castbutton
|
||||
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import app.revanced.patcher.fingerprint
|
||||
import app.revanced.util.literal
|
||||
|
||||
internal val mediaRouteButtonFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PRIVATE, AccessFlags.FINAL)
|
||||
returns("Z")
|
||||
strings("MediaRouteButton")
|
||||
}
|
||||
|
||||
internal val playerOverlayChipFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("L")
|
||||
literal { playerOverlayChip }
|
||||
}
|
||||
@@ -1,77 +1,10 @@
|
||||
package app.revanced.patches.music.layout.castbutton
|
||||
|
||||
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.patch.bytecodePatch
|
||||
import app.revanced.patches.all.misc.resources.addResources
|
||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.music.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.music.misc.settings.settingsPatch
|
||||
import app.revanced.patches.shared.misc.mapping.get
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappings
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
internal var playerOverlayChip = -1L
|
||||
private set
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/HideCastButtonPatch;"
|
||||
import app.revanced.patches.music.layout.buttons.hideButtons
|
||||
|
||||
@Deprecated("Patch was moved", ReplaceWith("hideButtons"))
|
||||
@Suppress("unused")
|
||||
val hideCastButton = bytecodePatch(
|
||||
name = "Hide cast button",
|
||||
description = "Adds an option to hide the cast button."
|
||||
) {
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
settingsPatch,
|
||||
addResourcesPatch,
|
||||
resourceMappingPatch
|
||||
)
|
||||
|
||||
compatibleWith(
|
||||
"com.google.android.apps.youtube.music"(
|
||||
"7.29.52",
|
||||
"8.10.52"
|
||||
)
|
||||
)
|
||||
|
||||
execute {
|
||||
playerOverlayChip = resourceMappings["id", "player_overlay_chip"]
|
||||
|
||||
addResources("music", "layout.castbutton.hideCastButton")
|
||||
|
||||
PreferenceScreen.GENERAL.addPreferences(
|
||||
SwitchPreference("revanced_music_hide_cast_button"),
|
||||
)
|
||||
|
||||
mediaRouteButtonFingerprint.classDef.apply {
|
||||
val setVisibilityMethod = methods.first { method -> method.name == "setVisibility" }
|
||||
|
||||
setVisibilityMethod.addInstructions(
|
||||
0,
|
||||
"""
|
||||
invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->hideCastButton(I)I
|
||||
move-result p1
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
playerOverlayChipFingerprint.method.apply {
|
||||
val resourceIndex = indexOfFirstLiteralInstructionOrThrow(playerOverlayChip)
|
||||
val targetIndex = indexOfFirstInstructionOrThrow(resourceIndex, Opcode.MOVE_RESULT_OBJECT)
|
||||
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
|
||||
|
||||
addInstruction(
|
||||
targetIndex + 1,
|
||||
"invoke-static { v$targetRegister }, $EXTENSION_CLASS_DESCRIPTOR->hideCastButton(Landroid/view/View;)V"
|
||||
)
|
||||
}
|
||||
}
|
||||
val hideCastButton = bytecodePatch{
|
||||
dependsOn(hideButtons)
|
||||
}
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
package app.revanced.patches.music.layout.compactheader
|
||||
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import app.revanced.patcher.fingerprint
|
||||
import app.revanced.util.literal
|
||||
|
||||
internal val constructCategoryBarFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
|
||||
internal val chipCloudFingerprint = fingerprint {
|
||||
returns("V")
|
||||
parameters("Landroid/content/Context;", "L", "L", "L")
|
||||
opcodes(
|
||||
Opcode.IPUT_OBJECT,
|
||||
Opcode.CONST,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.IPUT_OBJECT,
|
||||
Opcode.CONST,
|
||||
Opcode.INVOKE_VIRTUAL
|
||||
Opcode.CONST_4,
|
||||
Opcode.INVOKE_STATIC,
|
||||
Opcode.MOVE_RESULT_OBJECT
|
||||
)
|
||||
literal { chipCloud }
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package app.revanced.patches.music.layout.compactheader
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.all.misc.resources.addResources
|
||||
@@ -8,10 +8,14 @@ import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.music.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.music.misc.settings.settingsPatch
|
||||
import app.revanced.patches.shared.misc.mapping.get
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappings
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.util.findFreeRegister
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
internal var chipCloud = -1L
|
||||
private set
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/HideCategoryBarPatch;"
|
||||
|
||||
@Suppress("unused")
|
||||
@@ -33,28 +37,21 @@ val hideCategoryBar = bytecodePatch(
|
||||
)
|
||||
|
||||
execute {
|
||||
chipCloud = resourceMappings["layout", "chip_cloud"]
|
||||
|
||||
addResources("music", "layout.compactheader.hideCategoryBar")
|
||||
|
||||
PreferenceScreen.GENERAL.addPreferences(
|
||||
SwitchPreference("revanced_music_hide_category_bar"),
|
||||
)
|
||||
|
||||
constructCategoryBarFingerprint.method.apply {
|
||||
val insertIndex = constructCategoryBarFingerprint.patternMatch!!.startIndex
|
||||
val register = getInstruction<OneRegisterInstruction>(insertIndex - 1).registerA
|
||||
val freeRegister = findFreeRegister(insertIndex, register)
|
||||
chipCloudFingerprint.method.apply {
|
||||
val targetIndex = chipCloudFingerprint.patternMatch!!.endIndex
|
||||
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
|
||||
|
||||
addInstructionsWithLabels(
|
||||
insertIndex,
|
||||
"""
|
||||
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->hideCategoryBar()Z
|
||||
move-result v$freeRegister
|
||||
if-eqz v$freeRegister, :show
|
||||
const/16 v$freeRegister, 0x8
|
||||
invoke-virtual { v$register, v$freeRegister }, Landroid/view/View;->setVisibility(I)V
|
||||
:show
|
||||
nop
|
||||
"""
|
||||
addInstruction(
|
||||
targetIndex + 1,
|
||||
"invoke-static { v$targetRegister }, $EXTENSION_CLASS_DESCRIPTOR->hideCategoryBar(Landroid/view/View;)V"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
@file:Suppress("SpellCheckingInspection")
|
||||
|
||||
package app.revanced.patches.music.layout.miniplayercolor
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.all.misc.resources.addResources
|
||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.music.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.music.misc.settings.settingsPatch
|
||||
import app.revanced.patches.shared.misc.mapping.get
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappings
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.util.addInstructionsAtControlFlowLabel
|
||||
import app.revanced.util.findFreeRegister
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
internal var mpp_player_bottom_sheet = -1L
|
||||
private set
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/ChangeMiniplayerColorPatch;"
|
||||
|
||||
@Suppress("unused")
|
||||
val changeMiniplayerColor = bytecodePatch(
|
||||
name = "Change miniplayer color",
|
||||
description = "Adds an option to change the miniplayer background color to match the fullscreen player."
|
||||
) {
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
settingsPatch,
|
||||
addResourcesPatch,
|
||||
resourceMappingPatch
|
||||
)
|
||||
|
||||
compatibleWith(
|
||||
"com.google.android.apps.youtube.music"(
|
||||
"7.29.52",
|
||||
"8.10.52"
|
||||
)
|
||||
)
|
||||
|
||||
execute {
|
||||
mpp_player_bottom_sheet = resourceMappings["id", "mpp_player_bottom_sheet"]
|
||||
|
||||
addResources("music", "layout.miniplayercolor.changeMiniplayerColor")
|
||||
|
||||
PreferenceScreen.PLAYER.addPreferences(
|
||||
SwitchPreference("revanced_music_change_miniplayer_color"),
|
||||
)
|
||||
|
||||
switchToggleColorFingerprint.match(miniPlayerConstructorFingerprint.classDef).let {
|
||||
val relativeIndex = it.patternMatch!!.endIndex + 1
|
||||
|
||||
val invokeVirtualIndex = it.method.indexOfFirstInstructionOrThrow(
|
||||
relativeIndex, Opcode.INVOKE_VIRTUAL
|
||||
)
|
||||
val colorMathPlayerInvokeVirtualReference = it.method
|
||||
.getInstruction<ReferenceInstruction>(invokeVirtualIndex).reference
|
||||
|
||||
val iGetIndex = it.method.indexOfFirstInstructionOrThrow(
|
||||
relativeIndex, Opcode.IGET
|
||||
)
|
||||
val colorMathPlayerIGetReference = it.method
|
||||
.getInstruction<ReferenceInstruction>(iGetIndex).reference as FieldReference
|
||||
|
||||
val colorGreyIndex = miniPlayerConstructorFingerprint.method.indexOfFirstInstructionReversedOrThrow {
|
||||
getReference<MethodReference>()?.name == "getColor"
|
||||
}
|
||||
val iPutIndex = miniPlayerConstructorFingerprint.method.indexOfFirstInstructionOrThrow(
|
||||
colorGreyIndex, Opcode.IPUT
|
||||
)
|
||||
val colorMathPlayerIPutReference = miniPlayerConstructorFingerprint.method
|
||||
.getInstruction<ReferenceInstruction>(iPutIndex).reference
|
||||
|
||||
miniPlayerConstructorFingerprint.classDef.methods.single { method ->
|
||||
method.accessFlags == AccessFlags.PUBLIC.value or AccessFlags.FINAL.value &&
|
||||
method.returnType == "V" &&
|
||||
method.parameters == it.originalMethod.parameters
|
||||
}.apply {
|
||||
val insertIndex = indexOfFirstInstructionReversedOrThrow(Opcode.INVOKE_DIRECT)
|
||||
val freeRegister = findFreeRegister(insertIndex)
|
||||
|
||||
addInstructionsAtControlFlowLabel(
|
||||
insertIndex,
|
||||
"""
|
||||
invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->changeMiniplayerColor()Z
|
||||
move-result v$freeRegister
|
||||
if-eqz v$freeRegister, :off
|
||||
invoke-virtual { p1 }, $colorMathPlayerInvokeVirtualReference
|
||||
move-result-object v$freeRegister
|
||||
check-cast v$freeRegister, ${colorMathPlayerIGetReference.definingClass}
|
||||
iget v$freeRegister, v$freeRegister, $colorMathPlayerIGetReference
|
||||
iput v$freeRegister, p0, $colorMathPlayerIPutReference
|
||||
:off
|
||||
nop
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package app.revanced.patches.music.layout.miniplayercolor
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import app.revanced.util.literal
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal val miniPlayerConstructorFingerprint = fingerprint {
|
||||
returns("V")
|
||||
strings("sharedToggleMenuItemMutations")
|
||||
literal { mpp_player_bottom_sheet }
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches to the class found in [miniPlayerConstructorFingerprint].
|
||||
*/
|
||||
internal val switchToggleColorFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PRIVATE, AccessFlags.FINAL)
|
||||
returns("V")
|
||||
parameters("L", "J")
|
||||
opcodes(
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.CHECK_CAST,
|
||||
Opcode.IGET
|
||||
)
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.music.misc.gms.musicActivityOnCreateFingerprint
|
||||
import app.revanced.patches.music.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.music.misc.settings.settingsPatch
|
||||
import app.revanced.patches.music.playservice.is_7_16_or_greater
|
||||
import app.revanced.patches.music.playservice.is_7_33_or_greater
|
||||
import app.revanced.patches.music.playservice.is_8_11_or_greater
|
||||
import app.revanced.patches.music.playservice.is_8_15_or_greater
|
||||
@@ -18,7 +19,7 @@ import app.revanced.patches.shared.misc.spoof.spoofVideoStreamsPatch
|
||||
val spoofVideoStreamsPatch = spoofVideoStreamsPatch(
|
||||
extensionClassDescriptor = "Lapp/revanced/extension/music/patches/spoof/SpoofVideoStreamsPatch;",
|
||||
mainActivityOnCreateFingerprint = musicActivityOnCreateFingerprint,
|
||||
fixMediaFetchHotConfig = { true },
|
||||
fixMediaFetchHotConfig = { is_7_16_or_greater },
|
||||
fixMediaFetchHotConfigAlternative = { is_8_11_or_greater && !is_8_15_or_greater },
|
||||
fixParsePlaybackResponseFeatureFlag = { is_7_33_or_greater },
|
||||
|
||||
|
||||
@@ -3,14 +3,11 @@ package app.revanced.patches.music.misc.tracks
|
||||
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.music.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.music.misc.settings.settingsPatch
|
||||
import app.revanced.patches.music.playservice.is_8_10_or_greater
|
||||
import app.revanced.patches.music.playservice.is_8_05_or_greater
|
||||
import app.revanced.patches.music.playservice.versionCheckPatch
|
||||
import app.revanced.patches.music.shared.mainActivityOnCreateFingerprint
|
||||
import app.revanced.patches.shared.misc.audio.forceOriginalAudioPatch
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/music/patches/ForceOriginalAudioPatch;"
|
||||
|
||||
@Suppress("unused")
|
||||
val forceOriginalAudioPatch = forceOriginalAudioPatch(
|
||||
block = {
|
||||
@@ -27,8 +24,8 @@ val forceOriginalAudioPatch = forceOriginalAudioPatch(
|
||||
)
|
||||
)
|
||||
},
|
||||
fixUseLocalizedAudioTrackFlag = is_8_10_or_greater,
|
||||
fixUseLocalizedAudioTrackFlag = { is_8_05_or_greater },
|
||||
mainActivityOnCreateFingerprint = mainActivityOnCreateFingerprint,
|
||||
subclassExtensionClassDescriptor = EXTENSION_CLASS_DESCRIPTOR,
|
||||
subclassExtensionClassDescriptor = "Lapp/revanced/extension/music/patches/ForceOriginalAudioPatch;",
|
||||
preferenceScreen = PreferenceScreen.MISC,
|
||||
)
|
||||
|
||||
@@ -4,14 +4,21 @@ package app.revanced.patches.music.playservice
|
||||
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.util.findPlayStoreServicesVersion
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
var is_7_33_or_greater = false
|
||||
// Use notNull delegate so an exception is thrown if these fields are accessed before they are set.
|
||||
|
||||
var is_7_16_or_greater: Boolean by Delegates.notNull()
|
||||
private set
|
||||
var is_8_10_or_greater = false
|
||||
var is_7_33_or_greater: Boolean by Delegates.notNull()
|
||||
private set
|
||||
var is_8_11_or_greater = false
|
||||
var is_8_05_or_greater: Boolean by Delegates.notNull()
|
||||
private set
|
||||
var is_8_15_or_greater = false
|
||||
var is_8_10_or_greater: Boolean by Delegates.notNull()
|
||||
private set
|
||||
var is_8_11_or_greater: Boolean by Delegates.notNull()
|
||||
private set
|
||||
var is_8_15_or_greater: Boolean by Delegates.notNull()
|
||||
private set
|
||||
|
||||
val versionCheckPatch = resourcePatch(
|
||||
@@ -23,8 +30,10 @@ val versionCheckPatch = resourcePatch(
|
||||
val playStoreServicesVersion = findPlayStoreServicesVersion()
|
||||
|
||||
// All bug fix releases always seem to use the same play store version as the minor version.
|
||||
is_7_16_or_greater = 243499000 <= playStoreServicesVersion
|
||||
is_7_33_or_greater = 245199000 <= playStoreServicesVersion
|
||||
is_8_10_or_greater = 244799000 <= playStoreServicesVersion
|
||||
is_8_05_or_greater = 250599000 <= playStoreServicesVersion
|
||||
is_8_10_or_greater = 251099000 <= playStoreServicesVersion
|
||||
is_8_11_or_greater = 251199000 <= playStoreServicesVersion
|
||||
is_8_15_or_greater = 251530000 <= playStoreServicesVersion
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package app.revanced.patches.samsung.radio.misc.fix.crash
|
||||
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.util.asSequence
|
||||
import org.w3c.dom.Element
|
||||
|
||||
@Suppress("unused")
|
||||
internal val addManifestPermissionsPatch = resourcePatch {
|
||||
|
||||
val requiredPermissions = listOf(
|
||||
"android.permission.READ_PHONE_STATE",
|
||||
"android.permission.FOREGROUND_SERVICE_MICROPHONE",
|
||||
"android.permission.RECORD_AUDIO",
|
||||
)
|
||||
|
||||
execute {
|
||||
document("AndroidManifest.xml").use { document ->
|
||||
document.getElementsByTagName("manifest").item(0).let { manifestEl ->
|
||||
|
||||
// Check which permissions are missing
|
||||
val existingPermissionNames = document.getElementsByTagName("uses-permission").asSequence()
|
||||
.mapNotNull { (it as? Element)?.getAttribute("android:name") }.toSet()
|
||||
val missingPermissions = requiredPermissions.filterNot { it in existingPermissionNames }
|
||||
|
||||
// Then add them
|
||||
for (permission in missingPermissions) {
|
||||
val element = document.createElement("uses-permission")
|
||||
element.setAttribute("android:name", permission)
|
||||
manifestEl.appendChild(element)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user