Compare commits

...

486 Commits

Author SHA1 Message Date
semantic-release-bot
0472ec2830 chore: Release v5.31.0-dev.12 [skip ci]
# [5.31.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.11...v5.31.0-dev.12) (2025-07-09)

### Bug Fixes

* **Sync for Reddit - Spoof client:** Use www instead of ssl API to fix auth related issues ([#5392](https://github.com/ReVanced/revanced-patches/issues/5392)) ([6412a5c](6412a5cb1a))
2025-07-09 18:28:47 +00:00
oSumAtrIX
6412a5cb1a fix(Sync for Reddit - Spoof client): Use www instead of ssl API to fix auth related issues (#5392) 2025-07-09 20:25:48 +02:00
semantic-release-bot
cc548689ac chore: Release v5.31.0-dev.11 [skip ci]
# [5.31.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.10...v5.31.0-dev.11) (2025-07-09)

### Features

* **Cricbuzz - Hide ads:** Hide Cricbuzz11 UI elements ([#5381](https://github.com/ReVanced/revanced-patches/issues/5381)) ([a3d47e7](a3d47e72e3))
2025-07-09 17:50:37 +00:00
hoodles
a3d47e72e3 feat(Cricbuzz - Hide ads): Hide Cricbuzz11 UI elements (#5381) 2025-07-09 21:47:10 +04:00
semantic-release-bot
f37482443a chore: Release v5.31.0-dev.10 [skip ci]
# [5.31.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.9...v5.31.0-dev.10) (2025-07-09)

### Bug Fixes

* **YouTube - Hide layout components:** Do not hide playlist sort button if 'Hide AI comments summary' is on ([cc4aef8](cc4aef89d3))
2025-07-09 14:37:19 +00:00
LisoUseInAIKyrios
cc4aef89d3 fix(YouTube - Hide layout components): Do not hide playlist sort button if 'Hide AI comments summary' is on 2025-07-09 18:33:24 +04:00
github-actions[bot]
1c0a0eb4b5 chore: Sync translations (#5389) 2025-07-09 18:33:07 +04:00
semantic-release-bot
b1d6c46763 chore: Release v5.31.0-dev.9 [skip ci]
# [5.31.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.8...v5.31.0-dev.9) (2025-07-07)

### Bug Fixes

* Fix accidental changes ([42195b9](42195b9f63))
2025-07-07 10:32:07 +00:00
oSumAtrIX
42195b9f63 fix: Fix accidental changes 2025-07-07 12:29:21 +02:00
semantic-release-bot
a4e08ea13d chore: Release v5.31.0-dev.8 [skip ci]
# [5.31.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.7...v5.31.0-dev.8) (2025-07-07)

### Bug Fixes

* Correctly name `Enable ROM signature spoofing` patch ([bd2a939](bd2a939a72))
2025-07-07 07:43:53 +00:00
oSumAtrIX
bd2a939a72 fix: Correctly name Enable ROM signature spoofing patch 2025-07-07 09:40:28 +02:00
semantic-release-bot
a89179ab79 chore: Release v5.31.0-dev.7 [skip ci]
# [5.31.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.6...v5.31.0-dev.7) (2025-07-06)

### Bug Fixes

* Fix refactoring typo ([b0129d3](b0129d383a))
2025-07-06 14:22:39 +00:00
LisoUseInAIKyrios
b0129d383a fix: Fix refactoring typo 2025-07-06 18:19:43 +04:00
semantic-release-bot
23b6c42630 chore: Release v5.31.0-dev.6 [skip ci]
# [5.31.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.5...v5.31.0-dev.6) (2025-07-06)

### Bug Fixes

* **YouTube - Playback speed:** Allow custom speeds with 0.01x precision ([#5360](https://github.com/ReVanced/revanced-patches/issues/5360)) ([10f4464](10f4464735))
2025-07-06 13:16:35 +00:00
LisoUseInAIKyrios
10f4464735 fix(YouTube - Playback speed): Allow custom speeds with 0.01x precision (#5360) 2025-07-06 17:13:31 +04:00
github-actions[bot]
4e5addbba5 chore: Sync translations (#5369) 2025-07-06 17:12:43 +04:00
LisoUseInAIKyrios
8d11ede927 chore: Fix resource compile errors from last refactor 2025-07-06 17:07:19 +04:00
ILoveOpenSourceApplications
83a3f4da00 refactor: Standardize string formatting and apply alphabetical sorting (#5343) 2025-07-06 12:24:25 +04:00
LisoUseInAIKyrios
caf3b69731 refactor(YouTube - Change header): Handle importing bad settings data 2025-07-05 13:03:41 +04:00
LisoUseInAIKyrios
3135203b55 chore: Set untranslatable strings as untranslatable 2025-07-05 12:33:07 +04:00
semantic-release-bot
8d113a7c67 chore: Release v5.31.0-dev.5 [skip ci]
# [5.31.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.4...v5.31.0-dev.5) (2025-07-05)

### Features

* **YouTube - Change header:** Add in-app setting to change the app header ([#5346](https://github.com/ReVanced/revanced-patches/issues/5346)) ([4e74207](4e742075f3))
2025-07-05 08:06:28 +00:00
LisoUseInAIKyrios
4e742075f3 feat(YouTube - Change header): Add in-app setting to change the app header (#5346) 2025-07-05 12:02:58 +04:00
github-actions[bot]
04caa66662 chore: Sync translations (#5350) 2025-07-05 12:02:36 +04:00
semantic-release-bot
dacc85f5e7 chore: Release v5.31.0-dev.4 [skip ci]
# [5.31.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.3...v5.31.0-dev.4) (2025-07-04)

### Bug Fixes

* **YouTube - Hide ads:** Hide new type of general ad ([#5345](https://github.com/ReVanced/revanced-patches/issues/5345)) ([f9abec3](f9abec358a))
2025-07-04 20:09:13 +00:00
ILoveOpenSourceApplications
f9abec358a fix(YouTube - Hide ads): Hide new type of general ad (#5345) 2025-07-05 00:06:30 +04:00
github-actions[bot]
7e11514cc1 chore: Sync translations (#5347) 2025-07-05 00:06:16 +04:00
semantic-release-bot
2e9c8df8f6 chore: Release v5.31.0-dev.3 [skip ci]
# [5.31.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.2...v5.31.0-dev.3) (2025-07-04)

### Bug Fixes

* **Spotify:** Remove other ads type from the browse screen ([#5333](https://github.com/ReVanced/revanced-patches/issues/5333)) ([4c8cfc8](4c8cfc8800))
2025-07-04 08:44:24 +00:00
brosssh
4c8cfc8800 fix(Spotify): Remove other ads type from the browse screen (#5333) 2025-07-04 12:41:30 +04:00
semantic-release-bot
0ba6fad33f chore: Release v5.31.0-dev.2 [skip ci]
# [5.31.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.1...v5.31.0-dev.2) (2025-07-04)

### Features

* **YouTube - Hide layout components:** Add `Hide channel links preview` and `Hide 'Visit Community' button` in channel page ([#5320](https://github.com/ReVanced/revanced-patches/issues/5320)) ([3eac215](3eac215e13))
2025-07-04 08:35:55 +00:00
ILoveOpenSourceApplications
3eac215e13 feat(YouTube - Hide layout components): Add Hide channel links preview and Hide 'Visit Community' button in channel page (#5320) 2025-07-04 12:32:48 +04:00
semantic-release-bot
90a3262f68 chore: Release v5.31.0-dev.1 [skip ci]
# [5.31.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.30.0...v5.31.0-dev.1) (2025-07-04)

### Bug Fixes

* **SoundCloud:** Constrain patches to last working app target ([89ec5d5](89ec5d5bc6))

### Features

* **Lightroom:** Constrain patches to last working version ([#5335](https://github.com/ReVanced/revanced-patches/issues/5335)) ([f7f49b8](f7f49b834e))
2025-07-04 08:32:35 +00:00
LisoUseInAIKyrios
f7f49b834e feat(Lightroom): Constrain patches to last working version (#5335) 2025-07-04 12:29:45 +04:00
LisoUseInAIKyrios
89ec5d5bc6 fix(SoundCloud): Constrain patches to last working app target 2025-07-04 12:28:58 +04:00
LisoUseInAIKyrios
e3bc8be936 chore(YouTube - Video Quality): Fix setting parent typo 2025-07-04 01:25:56 +04:00
semantic-release-bot
6c5c3f5a4d chore: Release v5.30.0 [skip ci]
# [5.30.0](https://github.com/ReVanced/revanced-patches/compare/v5.29.0...v5.30.0) (2025-07-02)

### Bug Fixes

* **Spotify - Spoof client patch:** Block sending bad integrity verdicts to potentially fix account suspensions ([#5274](https://github.com/ReVanced/revanced-patches/issues/5274)) ([69600d0](69600d08a4))
* **Spotify - Spoof client:** Handle remaining edge cases to obtain a session ([#5285](https://github.com/ReVanced/revanced-patches/issues/5285)) ([b2e601f](b2e601f0f0))
* **Spotify - Spoof client:** Skip native login screens ([#5228](https://github.com/ReVanced/revanced-patches/issues/5228)) ([d7ed325](d7ed32571f))
* **Spotify - Unlock Premium:** Fix hiding context menu ads on newest versions ([#5318](https://github.com/ReVanced/revanced-patches/issues/5318)) ([8b9e044](8b9e04475d))
* **Spotify - Unlock Premium:** Fix hiding context menu ads on newest versions by simplifying fingerprint ([#5318](https://github.com/ReVanced/revanced-patches/issues/5318)) ([d1313e3](d1313e3ea1))
* **Spotify:** Add `Spoof client` patch to fix various issues by using a web platform access token ([#5173](https://github.com/ReVanced/revanced-patches/issues/5173)) ([1a8aacd](1a8aacdff6))
* **YouTube - Hide ads:** Fix "Hide shopping links" ([#5267](https://github.com/ReVanced/revanced-patches/issues/5267)) ([e169056](e169056b70))
* **YouTube - Hide layout components:** Fix "Hide AI Comments summary" in Comments ([#5284](https://github.com/ReVanced/revanced-patches/issues/5284)) ([f084743](f08474369b))
* **YouTube - Hide layout components:** Fix "Hide AI-generated video summary" in video description ([#5269](https://github.com/ReVanced/revanced-patches/issues/5269)) ([ca694c7](ca694c78d2))
* **YouTube - Hide layout components:** Fix "Hide ticket shelf" hiding unwanted components ([#5292](https://github.com/ReVanced/revanced-patches/issues/5292)) ([ad6da67](ad6da67281))
* **YouTube - Hide Shorts components:** Fix hiding of untoggled components ([#5266](https://github.com/ReVanced/revanced-patches/issues/5266)) ([b6bf1e0](b6bf1e026c))
* **YouTube - SponsorBlock:** Do not show undo skip if PiP is active ([#5314](https://github.com/ReVanced/revanced-patches/issues/5314)) ([209a3a3](209a3a3626))

### Features

* **Spotify:** Remove ads section from browse ([#5193](https://github.com/ReVanced/revanced-patches/issues/5193)) ([92b588c](92b588c866))
* **YouTube - Hide layout components:** Add `Hide in history` option to filter bar ([#5271](https://github.com/ReVanced/revanced-patches/issues/5271)) ([da20e56](da20e565cd))
* **YouTube - SponsorBlock:** Add "Undo automatic skip toast" ([#5277](https://github.com/ReVanced/revanced-patches/issues/5277)) ([6ee94f8](6ee94f8532))
2025-07-02 14:55:17 +00:00
oSumAtrIX
629bd0644b chore: Merge branch dev to main (#5265) 2025-07-02 16:50:31 +02:00
github-actions[bot]
b4005079e3 chore: Sync translations (#5322) 2025-07-02 18:21:04 +04:00
semantic-release-bot
a354c443ad chore: Release v5.30.0-dev.10 [skip ci]
# [5.30.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.9...v5.30.0-dev.10) (2025-07-02)

### Bug Fixes

* **Spotify - Unlock Premium:** Fix hiding context menu ads on newest versions by simplifying fingerprint ([#5318](https://github.com/ReVanced/revanced-patches/issues/5318)) ([d1313e3](d1313e3ea1))
2025-07-02 14:08:17 +00:00
oSumAtrIX
d1313e3ea1 fix(Spotify - Unlock Premium): Fix hiding context menu ads on newest versions by simplifying fingerprint (#5318) 2025-07-02 16:04:26 +02:00
semantic-release-bot
11338008c6 chore: Release v5.30.0-dev.9 [skip ci]
# [5.30.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.8...v5.30.0-dev.9) (2025-07-02)

### Bug Fixes

* **Spotify - Unlock Premium:** Fix hiding context menu ads on newest versions ([#5318](https://github.com/ReVanced/revanced-patches/issues/5318)) ([8b9e044](8b9e04475d))
2025-07-02 12:12:04 +00:00
Nuckyz
8b9e04475d fix(Spotify - Unlock Premium): Fix hiding context menu ads on newest versions (#5318)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-07-02 14:08:11 +02:00
semantic-release-bot
d3c9dc6ed7 chore: Release v5.30.0-dev.8 [skip ci]
# [5.30.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.7...v5.30.0-dev.8) (2025-07-02)

### Bug Fixes

* **Spotify - Spoof client:** Skip native login screens ([#5228](https://github.com/ReVanced/revanced-patches/issues/5228)) ([d7ed325](d7ed32571f))
2025-07-02 10:23:13 +00:00
brosssh
d7ed32571f fix(Spotify - Spoof client): Skip native login screens (#5228)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Co-authored-by: Dawid Krajcarz <80264606+drobotk@users.noreply.github.com>
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-07-02 12:19:20 +02:00
semantic-release-bot
d3935f03c0 chore: Release v5.30.0-dev.7 [skip ci]
# [5.30.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.6...v5.30.0-dev.7) (2025-07-01)

### Bug Fixes

* **Spotify - Spoof client:** Handle remaining edge cases to obtain a session ([#5285](https://github.com/ReVanced/revanced-patches/issues/5285)) ([b2e601f](b2e601f0f0))
2025-07-01 21:15:22 +00:00
oSumAtrIX
b2e601f0f0 fix(Spotify - Spoof client): Handle remaining edge cases to obtain a session (#5285)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
2025-07-01 23:11:05 +02:00
dependabot[bot]
d3ec219a29 chore(deps-dev): bump semantic-release from 24.2.5 to 24.2.6 (#5317) 2025-07-01 22:54:09 +04:00
semantic-release-bot
5ed07d4aaa chore: Release v5.30.0-dev.6 [skip ci]
# [5.30.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.5...v5.30.0-dev.6) (2025-07-01)

### Bug Fixes

* **YouTube - SponsorBlock:** Do not show undo skip if PiP is active ([#5314](https://github.com/ReVanced/revanced-patches/issues/5314)) ([209a3a3](209a3a3626))
2025-07-01 17:40:13 +00:00
LisoUseInAIKyrios
209a3a3626 fix(YouTube - SponsorBlock): Do not show undo skip if PiP is active (#5314) 2025-07-01 21:36:08 +04:00
github-actions[bot]
2b3419571f chore: Sync translations (#5315) 2025-07-01 21:35:50 +04:00
ILoveOpenSourceApplications
bbe504e616 refactor(YouTube): Match YouTube naming and sort strings (#5309) 2025-07-01 00:12:01 +04:00
semantic-release-bot
6c32591f62 chore: Release v5.30.0-dev.5 [skip ci]
# [5.30.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.4...v5.30.0-dev.5) (2025-06-30)

### Bug Fixes

* **YouTube - Hide layout components:** Fix "Hide ticket shelf" hiding unwanted components ([#5292](https://github.com/ReVanced/revanced-patches/issues/5292)) ([ad6da67](ad6da67281))
2025-06-30 08:01:39 +00:00
ILoveOpenSourceApplications
ad6da67281 fix(YouTube - Hide layout components): Fix "Hide ticket shelf" hiding unwanted components (#5292) 2025-06-30 11:58:01 +04:00
github-actions[bot]
14dc593eba chore: Sync translations (#5294) 2025-06-30 11:57:44 +04:00
semantic-release-bot
e52ee41222 chore: Release v5.30.0-dev.4 [skip ci]
# [5.30.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.3...v5.30.0-dev.4) (2025-06-30)

### Features

* **YouTube - SponsorBlock:** Add "Undo automatic skip toast" ([#5277](https://github.com/ReVanced/revanced-patches/issues/5277)) ([6ee94f8](6ee94f8532))
2025-06-30 06:54:53 +00:00
MarcaD
6ee94f8532 feat(YouTube - SponsorBlock): Add "Undo automatic skip toast" (#5277)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-06-30 10:50:52 +04:00
semantic-release-bot
21688201af chore: Release v5.30.0-dev.3 [skip ci]
# [5.30.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.2...v5.30.0-dev.3) (2025-06-28)

### Bug Fixes

* **YouTube - Hide layout components:** Fix "Hide AI Comments summary" in Comments ([#5284](https://github.com/ReVanced/revanced-patches/issues/5284)) ([f084743](f08474369b))
2025-06-28 18:05:53 +00:00
ILoveOpenSourceApplications
f08474369b fix(YouTube - Hide layout components): Fix "Hide AI Comments summary" in Comments (#5284) 2025-06-28 22:02:03 +04:00
LisoUseInAIKyrios
ed617094ea refactor(YouTube - Litho): Use a simpler hook that does not require using a thread local (#5281) 2025-06-28 11:51:29 +04:00
semantic-release-bot
9131c50f1b chore: Release v5.30.0-dev.2 [skip ci]
# [5.30.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.1...v5.30.0-dev.2) (2025-06-27)

### Bug Fixes

* **Spotify - Spoof client patch:** Block sending bad integrity verdicts to potentially fix account suspensions ([#5274](https://github.com/ReVanced/revanced-patches/issues/5274)) ([69600d0](69600d08a4))
2025-06-27 14:07:00 +00:00
brosssh
69600d08a4 fix(Spotify - Spoof client patch): Block sending bad integrity verdicts to potentially fix account suspensions (#5274)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-06-27 16:03:07 +02:00
semantic-release-bot
5dba77612b chore: Release v5.30.0-dev.1 [skip ci]
# [5.30.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.29.1-dev.1...v5.30.0-dev.1) (2025-06-27)

### Bug Fixes

* **YouTube - Hide ads:** Fix "Hide shopping links" ([#5267](https://github.com/ReVanced/revanced-patches/issues/5267)) ([e169056](e169056b70))
* **YouTube - Hide layout components:** Fix "Hide AI-generated video summary" in video description ([#5269](https://github.com/ReVanced/revanced-patches/issues/5269)) ([ca694c7](ca694c78d2))
* **YouTube - Hide Shorts components:** Fix hiding of untoggled components ([#5266](https://github.com/ReVanced/revanced-patches/issues/5266)) ([b6bf1e0](b6bf1e026c))

### Features

* **Spotify:** Remove ads section from browse ([#5193](https://github.com/ReVanced/revanced-patches/issues/5193)) ([92b588c](92b588c866))
* **YouTube - Hide layout components:** Add `Hide in history` option to filter bar ([#5271](https://github.com/ReVanced/revanced-patches/issues/5271)) ([da20e56](da20e565cd))
2025-06-27 11:38:02 +00:00
brosssh
92b588c866 feat(Spotify): Remove ads section from browse (#5193) 2025-06-27 15:34:13 +04:00
ILoveOpenSourceApplications
da20e565cd feat(YouTube - Hide layout components): Add Hide in history option to filter bar (#5271) 2025-06-27 15:33:53 +04:00
ILoveOpenSourceApplications
ca694c78d2 fix(YouTube - Hide layout components): Fix "Hide AI-generated video summary" in video description (#5269) 2025-06-27 15:33:37 +04:00
ILoveOpenSourceApplications
e169056b70 fix(YouTube - Hide ads): Fix "Hide shopping links" (#5267) 2025-06-27 15:33:21 +04:00
ILoveOpenSourceApplications
b6bf1e026c fix(YouTube - Hide Shorts components): Fix hiding of untoggled components (#5266) 2025-06-27 15:33:02 +04:00
github-actions[bot]
9fa89d48c0 chore: Sync translations (#5272) 2025-06-27 15:32:34 +04:00
semantic-release-bot
5d2c21540c chore: Release v5.29.1-dev.1 [skip ci]
## [5.29.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.29.0...v5.29.1-dev.1) (2025-06-26)

### Bug Fixes

* **Spotify:** Add `Spoof client` patch to fix various issues by using a web platform access token ([#5173](https://github.com/ReVanced/revanced-patches/issues/5173)) ([1a8aacd](1a8aacdff6))
2025-06-26 17:56:10 +00:00
oSumAtrIX
1a8aacdff6 fix(Spotify): Add Spoof client patch to fix various issues by using a web platform access token (#5173)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Co-authored-by: brosssh <tiabroch@gmail.com>
Co-authored-by: Dawid Krajcarz <80264606+drobotk@users.noreply.github.com>
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-06-26 19:51:18 +02:00
semantic-release-bot
1804bd9bfc chore: Release v5.29.0 [skip ci]
# [5.29.0](https://github.com/ReVanced/revanced-patches/compare/v5.28.0...v5.29.0) (2025-06-26)

### Bug Fixes

* Add scrollable content to modern style settings dialogs ([#5211](https://github.com/ReVanced/revanced-patches/issues/5211)) ([2b62fc2](2b62fc2224))
* **Google Photos:** Resolve startup crash for Android 5.0 devices ([7be3741](7be374100b))
* **YouTube - Hide ads:** Hide new type of product ad in video description ([#5225](https://github.com/ReVanced/revanced-patches/issues/5225)) ([b656976](b65697603d))
* **YouTube - Hide layout components:** Fix "Hide video description attributes" ([#5250](https://github.com/ReVanced/revanced-patches/issues/5250)) ([978c244](978c24458b))
* **YouTube - Hide Shorts components:** Fix "Hide Use this sound button" ([#5233](https://github.com/ReVanced/revanced-patches/issues/5233)) ([a678f17](a678f178e1))
* **YouTube - Hide Shorts components:** Fix "Hide Use this template button" ([#5249](https://github.com/ReVanced/revanced-patches/issues/5249)) ([957bece](957bece3e9))
* **YouTube:** Always use single threaded layout to resolve layout bugs in unpatched YouTube ([#5226](https://github.com/ReVanced/revanced-patches/issues/5226)) ([ccd1691](ccd169121a))
* **YouTube:** Fix refactoring app startup exception ([0dbd058](0dbd058099))

### Features

* Add `Spoof app signature` patch ([#5158](https://github.com/ReVanced/revanced-patches/issues/5158)) ([fb83e58](fb83e58f79))
* **Cricbuzz:** Add `Hide ads` patch ([#4998](https://github.com/ReVanced/revanced-patches/issues/4998)) ([558bf8b](558bf8bca8))
* **Crunchyroll:** Add `Hide ads` patch ([#5201](https://github.com/ReVanced/revanced-patches/issues/5201)) ([d338989](d338989cb4))
* **YouTube - Hide Shorts components:** Add `Hide Effects button` ([#5255](https://github.com/ReVanced/revanced-patches/issues/5255)) ([29c86ac](29c86ac6a3))
* **YouTube - Hide video action buttons:** Add `Hide Stop ads` ([#5245](https://github.com/ReVanced/revanced-patches/issues/5245)) ([0e63f49](0e63f49e13))
* **YouTube:** Add an option to disable toasts when changing default playback speed or quality ([#5230](https://github.com/ReVanced/revanced-patches/issues/5230)) ([6b719df](6b719dfcd7))
* **YouTube:** Support version `20.13.41` ([#5253](https://github.com/ReVanced/revanced-patches/issues/5253)) ([439ca37](439ca37e99))
2025-06-26 08:06:08 +00:00
LisoUseInAIKyrios
7eb4e62762 chore: Merge branch dev to main (#5227) 2025-06-26 12:02:33 +04:00
LisoUseInAIKyrios
b8e10b5c1f chore: Fix api dump 2025-06-26 11:51:54 +04:00
github-actions[bot]
a7c11b9b08 chore: Sync translations (#5258) 2025-06-26 11:50:50 +04:00
semantic-release-bot
443c0a74d5 chore: Release v5.29.0-dev.11 [skip ci]
# [5.29.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.10...v5.29.0-dev.11) (2025-06-26)

### Features

* **Cricbuzz:** Add `Hide ads` patch ([#4998](https://github.com/ReVanced/revanced-patches/issues/4998)) ([558bf8b](558bf8bca8))
2025-06-26 07:13:07 +00:00
github-actions[bot]
84a0f7f7d7 chore: Sync translations (#5257) 2025-06-26 11:10:05 +04:00
Sourav Agrawal
558bf8bca8 feat(Cricbuzz): Add Hide ads patch (#4998) 2025-06-26 11:08:22 +04:00
semantic-release-bot
e22d4e6a4b chore: Release v5.29.0-dev.10 [skip ci]
# [5.29.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.9...v5.29.0-dev.10) (2025-06-25)

### Features

* **YouTube - Hide Shorts components:** Add `Hide Effects button` ([#5255](https://github.com/ReVanced/revanced-patches/issues/5255)) ([29c86ac](29c86ac6a3))
2025-06-25 08:59:45 +00:00
LisoUseInAIKyrios
a07f946633 chore: Fix typo 2025-06-25 12:56:42 +04:00
LisoUseInAIKyrios
29c86ac6a3 feat(YouTube - Hide Shorts components): Add Hide Effects button (#5255) 2025-06-25 12:54:59 +04:00
semantic-release-bot
19cf5667d8 chore: Release v5.29.0-dev.9 [skip ci]
# [5.29.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.8...v5.29.0-dev.9) (2025-06-25)

### Features

* Add `Spoof app signature` patch ([#5158](https://github.com/ReVanced/revanced-patches/issues/5158)) ([fb83e58](fb83e58f79))
2025-06-25 07:16:33 +00:00
Markus Probst
fb83e58f79 feat: Add Spoof app signature patch (#5158) 2025-06-25 11:13:32 +04:00
semantic-release-bot
9844081d04 chore: Release v5.29.0-dev.8 [skip ci]
# [5.29.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.7...v5.29.0-dev.8) (2025-06-25)

### Features

* **YouTube:** Support version `20.13.41` ([#5253](https://github.com/ReVanced/revanced-patches/issues/5253)) ([439ca37](439ca37e99))
2025-06-25 07:06:01 +00:00
LisoUseInAIKyrios
439ca37e99 feat(YouTube): Support version 20.13.41 (#5253) 2025-06-25 11:03:25 +04:00
semantic-release-bot
113a3d9f19 chore: Release v5.29.0-dev.7 [skip ci]
# [5.29.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.6...v5.29.0-dev.7) (2025-06-24)

### Bug Fixes

* **YouTube - Hide layout components:** Fix "Hide video description attributes" ([#5250](https://github.com/ReVanced/revanced-patches/issues/5250)) ([978c244](978c24458b))
* **YouTube - Hide Shorts components:** Fix "Hide Use this template button" ([#5249](https://github.com/ReVanced/revanced-patches/issues/5249)) ([957bece](957bece3e9))
2025-06-24 18:50:18 +00:00
ILoveOpenSourceApplications
978c24458b fix(YouTube - Hide layout components): Fix "Hide video description attributes" (#5250) 2025-06-24 22:47:46 +04:00
ILoveOpenSourceApplications
957bece3e9 fix(YouTube - Hide Shorts components): Fix "Hide Use this template button" (#5249) 2025-06-24 22:47:05 +04:00
github-actions[bot]
d32c3ac51d chore: Sync translations (#5251) 2025-06-24 22:46:50 +04:00
LisoUseInAIKyrios
26102a70a2 chore: Fix compile warning 2025-06-24 22:43:25 +04:00
semantic-release-bot
2b44bf4c23 chore: Release v5.29.0-dev.6 [skip ci]
# [5.29.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.5...v5.29.0-dev.6) (2025-06-24)

### Features

* **YouTube - Hide video action buttons:** Add `Hide Stop ads` ([#5245](https://github.com/ReVanced/revanced-patches/issues/5245)) ([0e63f49](0e63f49e13))
2025-06-24 11:14:05 +00:00
ILoveOpenSourceApplications
0e63f49e13 feat(YouTube - Hide video action buttons): Add Hide Stop ads (#5245) 2025-06-24 15:10:23 +04:00
semantic-release-bot
674a5b8d29 chore: Release v5.29.0-dev.5 [skip ci]
# [5.29.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.4...v5.29.0-dev.5) (2025-06-23)

### Bug Fixes

* **Google Photos:** Resolve startup crash for Android 5.0 devices ([7be3741](7be374100b))
2025-06-23 12:28:49 +00:00
LisoUseInAIKyrios
7be374100b fix(Google Photos): Resolve startup crash for Android 5.0 devices 2025-06-23 16:24:42 +04:00
semantic-release-bot
e48c152b95 chore: Release v5.29.0-dev.4 [skip ci]
# [5.29.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.3...v5.29.0-dev.4) (2025-06-23)

### Bug Fixes

* **YouTube - Hide Shorts components:** Fix "Hide Use this sound button" ([#5233](https://github.com/ReVanced/revanced-patches/issues/5233)) ([a678f17](a678f178e1))
2025-06-23 09:33:13 +00:00
ILoveOpenSourceApplications
a678f178e1 fix(YouTube - Hide Shorts components): Fix "Hide Use this sound button" (#5233) 2025-06-23 13:29:53 +04:00
semantic-release-bot
2d8f5641f9 chore: Release v5.29.0-dev.3 [skip ci]
# [5.29.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.2...v5.29.0-dev.3) (2025-06-23)

### Bug Fixes

* **YouTube:** Fix refactoring app startup exception ([0dbd058](0dbd058099))
2025-06-23 09:18:27 +00:00
LisoUseInAIKyrios
0dbd058099 fix(YouTube): Fix refactoring app startup exception 2025-06-23 13:15:43 +04:00
semantic-release-bot
c1a8fd0766 chore: Release v5.29.0-dev.2 [skip ci]
# [5.29.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.1...v5.29.0-dev.2) (2025-06-23)

### Features

* **Crunchyroll:** Add `Hide ads` patch ([#5201](https://github.com/ReVanced/revanced-patches/issues/5201)) ([d338989](d338989cb4))
2025-06-23 08:47:04 +00:00
hoodles
d338989cb4 feat(Crunchyroll): Add Hide ads patch (#5201) 2025-06-23 12:44:07 +04:00
github-actions[bot]
b94daacf01 chore: Sync translations (#5236) 2025-06-23 12:43:45 +04:00
semantic-release-bot
c764c4f197 chore: Release v5.29.0-dev.1 [skip ci]
# [5.29.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.28.1-dev.2...v5.29.0-dev.1) (2025-06-23)

### Bug Fixes

* **YouTube:** Always use single threaded layout to resolve layout bugs in unpatched YouTube ([#5226](https://github.com/ReVanced/revanced-patches/issues/5226)) ([ccd1691](ccd169121a))

### Features

* **YouTube:** Add an option to disable toasts when changing default playback speed or quality ([#5230](https://github.com/ReVanced/revanced-patches/issues/5230)) ([6b719df](6b719dfcd7))
2025-06-23 08:24:13 +00:00
MarcaD
6b719dfcd7 feat(YouTube): Add an option to disable toasts when changing default playback speed or quality (#5230) 2025-06-23 12:20:37 +04:00
LisoUseInAIKyrios
ccd169121a fix(YouTube): Always use single threaded layout to resolve layout bugs in unpatched YouTube (#5226) 2025-06-23 12:19:07 +04:00
semantic-release-bot
dcfbd8bf93 chore: Release v5.28.1-dev.2 [skip ci]
## [5.28.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.28.1-dev.1...v5.28.1-dev.2) (2025-06-23)

### Bug Fixes

* **YouTube - Hide ads:** Hide new type of product ad in video description ([#5225](https://github.com/ReVanced/revanced-patches/issues/5225)) ([b656976](b65697603d))
2025-06-23 07:19:38 +00:00
ILoveOpenSourceApplications
b65697603d fix(YouTube - Hide ads): Hide new type of product ad in video description (#5225) 2025-06-23 11:17:08 +04:00
semantic-release-bot
25da5cca8b chore: Release v5.28.1-dev.1 [skip ci]
## [5.28.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.28.0...v5.28.1-dev.1) (2025-06-22)

### Bug Fixes

* Add scrollable content to modern style settings dialogs ([#5211](https://github.com/ReVanced/revanced-patches/issues/5211)) ([2b62fc2](2b62fc2224))
2025-06-22 11:23:52 +00:00
MarcaD
2b62fc2224 fix: Add scrollable content to modern style settings dialogs (#5211) 2025-06-22 15:21:00 +04:00
semantic-release-bot
a9e9456b6b chore: Release v5.28.0 [skip ci]
# [5.28.0](https://github.com/ReVanced/revanced-patches/compare/v5.27.0...v5.28.0) (2025-06-20)

### Bug Fixes

* **Google Photos:** Resolve startup crash if MicroG GmsCore does not already have granted permissions ([1cea6bf](1cea6bfdff))
* **Messenger - Remove Meta AI:** Improve patch logic ([#5153](https://github.com/ReVanced/revanced-patches/issues/5153)) ([a8d2a1e](a8d2a1e028))
* **Pandora - Disable ads:** Support latest app target ([#5185](https://github.com/ReVanced/revanced-patches/issues/5185)) ([90868ff](90868ff025))
* **Spotify:** Fix `Hide Create button` and `Sanitize sharing links` for older but supported app targets ([#5159](https://github.com/ReVanced/revanced-patches/issues/5159)) ([5540136](55401368b8))
* **Threads - Hide ads:** Constrain patch to the last working app target ([#5189](https://github.com/ReVanced/revanced-patches/issues/5189)) ([e138501](e138501657))
* **YouTube:** Remove old app targets that are no longer supported by YouTube ([#5192](https://github.com/ReVanced/revanced-patches/issues/5192)) ([e790cfb](e790cfbf59))

### Features

* **Spotify:** Add `Change lyrics provider` patch ([#4937](https://github.com/ReVanced/revanced-patches/issues/4937)) ([7bbaca7](7bbaca77ad))
* Use modern style settings dialogs ([#5109](https://github.com/ReVanced/revanced-patches/issues/5109)) ([a426e2a](a426e2af50))
2025-06-20 08:17:57 +00:00
LisoUseInAIKyrios
b01523e97d chore: Merge branch dev to main (#5160) 2025-06-20 12:14:29 +04:00
github-actions[bot]
b8afb4e821 chore: Sync translations (#5210) 2025-06-20 12:13:39 +04:00
github-actions[bot]
0d2198faed chore: Sync translations (#5207) 2025-06-20 01:33:20 +04:00
semantic-release-bot
5c7c407b82 chore: Release v5.28.0-dev.8 [skip ci]
# [5.28.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.7...v5.28.0-dev.8) (2025-06-19)

### Bug Fixes

* **Messenger - Remove Meta AI:** Improve patch logic ([#5153](https://github.com/ReVanced/revanced-patches/issues/5153)) ([a8d2a1e](a8d2a1e028))
2025-06-19 06:10:06 +00:00
Dawid Krajcarz
a8d2a1e028 fix(Messenger - Remove Meta AI): Improve patch logic (#5153) 2025-06-19 10:06:46 +04:00
semantic-release-bot
d31624cae8 chore: Release v5.28.0-dev.7 [skip ci]
# [5.28.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.6...v5.28.0-dev.7) (2025-06-18)

### Bug Fixes

* **YouTube:** Remove old app targets that are no longer supported by YouTube ([#5192](https://github.com/ReVanced/revanced-patches/issues/5192)) ([e790cfb](e790cfbf59))
2025-06-18 10:38:43 +00:00
LisoUseInAIKyrios
e790cfbf59 fix(YouTube): Remove old app targets that are no longer supported by YouTube (#5192) 2025-06-18 12:35:43 +02:00
LisoUseInAIKyrios
a54d408d3e chore: Fix string typos, fix missing long/wide returnEarly/returnLate 2025-06-18 11:06:48 +02:00
semantic-release-bot
5d3769e921 chore: Release v5.28.0-dev.6 [skip ci]
# [5.28.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.5...v5.28.0-dev.6) (2025-06-17)

### Bug Fixes

* **Threads - Hide ads:** Constrain patch to the last working app target ([#5189](https://github.com/ReVanced/revanced-patches/issues/5189)) ([e138501](e138501657))
2025-06-17 21:07:00 +00:00
LisoUseInAIKyrios
e138501657 fix(Threads - Hide ads): Constrain patch to the last working app target (#5189) 2025-06-17 23:03:35 +02:00
semantic-release-bot
a9235d6b62 chore: Release v5.28.0-dev.5 [skip ci]
# [5.28.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.4...v5.28.0-dev.5) (2025-06-17)

### Bug Fixes

* **Pandora - Disable ads:** Support latest app target ([#5185](https://github.com/ReVanced/revanced-patches/issues/5185)) ([90868ff](90868ff025))
2025-06-17 06:47:11 +00:00
hoodles
90868ff025 fix(Pandora - Disable ads): Support latest app target (#5185) 2025-06-17 08:44:19 +02:00
github-actions[bot]
345ec5c430 chore: Sync translations (#5188) 2025-06-17 08:43:55 +02:00
semantic-release-bot
c8b95d475c chore: Release v5.28.0-dev.4 [skip ci]
# [5.28.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.3...v5.28.0-dev.4) (2025-06-13)

### Features

* Use modern style settings dialogs ([#5109](https://github.com/ReVanced/revanced-patches/issues/5109)) ([a426e2a](a426e2af50))
2025-06-13 07:33:27 +00:00
github-actions[bot]
0a93f44a5e chore: Sync translations (#5167) 2025-06-13 09:30:30 +02:00
MarcaD
a426e2af50 feat: Use modern style settings dialogs (#5109)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-06-13 09:29:13 +02:00
semantic-release-bot
9e30c34e74 chore: Release v5.28.0-dev.3 [skip ci]
# [5.28.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.2...v5.28.0-dev.3) (2025-06-11)

### Bug Fixes

* **Spotify:** Fix `Hide Create button` and `Sanitize sharing links` for older but supported app targets ([#5159](https://github.com/ReVanced/revanced-patches/issues/5159)) ([5540136](55401368b8))
2025-06-11 20:23:04 +00:00
Nuckyz
55401368b8 fix(Spotify): Fix Hide Create button and Sanitize sharing links for older but supported app targets (#5159) 2025-06-11 17:20:19 -03:00
LisoUseInAIKyrios
c0c56fef23 chore: Fix debug logging if context is not set 2025-06-11 19:57:37 +02:00
semantic-release-bot
69df47602f chore: Release v5.28.0-dev.2 [skip ci]
# [5.28.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.1...v5.28.0-dev.2) (2025-06-11)

### Bug Fixes

* **Google Photos:** Resolve startup crash if MicroG GmsCore does not already have granted permissions ([1cea6bf](1cea6bfdff))
2025-06-11 17:44:07 +00:00
LisoUseInAIKyrios
1cea6bfdff fix(Google Photos): Resolve startup crash if MicroG GmsCore does not already have granted permissions 2025-06-11 19:40:37 +02:00
semantic-release-bot
e2a9552f91 chore: Release v5.28.0-dev.1 [skip ci]
# [5.28.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.27.0...v5.28.0-dev.1) (2025-06-11)

### Features

* **Spotify:** Add `Change lyrics provider` patch ([#4937](https://github.com/ReVanced/revanced-patches/issues/4937)) ([7bbaca7](7bbaca77ad))
2025-06-11 08:28:44 +00:00
brosssh
7bbaca77ad feat(Spotify): Add Change lyrics provider patch (#4937) 2025-06-11 10:25:58 +02:00
semantic-release-bot
246f3efe55 chore: Release v5.27.0 [skip ci]
# [5.27.0](https://github.com/ReVanced/revanced-patches/compare/v5.26.0...v5.27.0) (2025-06-09)

### Bug Fixes

* **Bandcamp - Remove play limits:** Support latest app version ([#5124](https://github.com/ReVanced/revanced-patches/issues/5124)) ([c0448de](c0448dece4))
* **Spotify:** `Hide Create button` patch failing in edge cases ([#5131](https://github.com/ReVanced/revanced-patches/issues/5131)) ([7a432e5](7a432e5741))
* **Spotify:** Prevent hiding all navigation bar buttons ([#5122](https://github.com/ReVanced/revanced-patches/issues/5122)) ([65fc6b4](65fc6b43f5))
* **YouTube - Hide layout components:** Remove broken option 'Hide comments emoji picker' ([#5121](https://github.com/ReVanced/revanced-patches/issues/5121)) ([4b8499f](4b8499ff2c))
* **YouTube - Hide Shorts components:** Disable A/B player flags that prevents hiding buttons ([9d10ab6](9d10ab6c00))
* **YouTube - Video quality:** Remove non-functional Shorts 144p default quality ([6aff8e8](6aff8e8ca4))

### Features

* Add `Hide app icon` patch ([#4977](https://github.com/ReVanced/revanced-patches/issues/4977)) ([6127f48](6127f48a9e))
* **Google Photos:** Add `Enable DCIM folders backup control` patch ([#5138](https://github.com/ReVanced/revanced-patches/issues/5138)) ([af827e2](af827e2f1a))
* **Messenger:** Add `Hide Facebook button` patch ([#5057](https://github.com/ReVanced/revanced-patches/issues/5057)) ([ed0d807](ed0d807d70))
* **YouTube - Hide player overlay buttons:** Add in app setting for "Hide player control buttons background" ([#5147](https://github.com/ReVanced/revanced-patches/issues/5147)) ([adfac8a](adfac8a1f2))
* **YouTube - Hide Shorts components:** Add hide 'New posts' button ([f8e31c8](f8e31c820a))
* **YouTube - Theme:** Add option for black and white splash screen animation ([#5119](https://github.com/ReVanced/revanced-patches/issues/5119)) ([6d5380d](6d5380d44d))
2025-06-09 18:37:59 +00:00
LisoUseInAIKyrios
0fca3e8fb1 chore: Merge branch dev to main (#5115) 2025-06-09 20:35:14 +02:00
semantic-release-bot
c09255eaed chore: Release v5.27.0-dev.9 [skip ci]
# [5.27.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.8...v5.27.0-dev.9) (2025-06-09)

### Features

* **Messenger:** Add `Hide Facebook button` patch ([#5057](https://github.com/ReVanced/revanced-patches/issues/5057)) ([ed0d807](ed0d807d70))
2025-06-09 18:18:19 +00:00
github-actions[bot]
e78d6240ea chore: Sync translations (#5151) 2025-06-09 20:15:28 +02:00
Dawid Krajcarz
ed0d807d70 feat(Messenger): Add Hide Facebook button patch (#5057) 2025-06-09 20:13:05 +02:00
semantic-release-bot
1c39004350 chore: Release v5.27.0-dev.8 [skip ci]
# [5.27.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.7...v5.27.0-dev.8) (2025-06-09)

### Features

* Add `Hide app icon` patch ([#4977](https://github.com/ReVanced/revanced-patches/issues/4977)) ([6127f48](6127f48a9e))
2025-06-09 14:11:00 +00:00
ILoveOpenSourceApplications
6127f48a9e feat: Add Hide app icon patch (#4977) 2025-06-09 11:07:43 -03:00
semantic-release-bot
ad416f4aa7 chore: Release v5.27.0-dev.7 [skip ci]
# [5.27.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.6...v5.27.0-dev.7) (2025-06-08)

### Features

* **YouTube - Hide player overlay buttons:** Add in app setting for "Hide player control buttons background" ([#5147](https://github.com/ReVanced/revanced-patches/issues/5147)) ([adfac8a](adfac8a1f2))
2025-06-08 18:07:53 +00:00
Nuckyz
adfac8a1f2 feat(YouTube - Hide player overlay buttons): Add in app setting for "Hide player control buttons background" (#5147) 2025-06-08 15:04:58 -03:00
semantic-release-bot
498488d45b chore: Release v5.27.0-dev.6 [skip ci]
# [5.27.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.5...v5.27.0-dev.6) (2025-06-08)

### Features

* **YouTube - Hide Shorts components:** Add hide 'New posts' button ([f8e31c8](f8e31c820a))
2025-06-08 12:06:28 +00:00
LisoUseInAIKyrios
f8e31c820a feat(YouTube - Hide Shorts components): Add hide 'New posts' button 2025-06-08 14:03:00 +02:00
semantic-release-bot
826a391591 chore: Release v5.27.0-dev.5 [skip ci]
# [5.27.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.4...v5.27.0-dev.5) (2025-06-08)

### Features

* **Google Photos:** Add `Enable DCIM folders backup control` patch ([#5138](https://github.com/ReVanced/revanced-patches/issues/5138)) ([af827e2](af827e2f1a))
2025-06-08 07:17:04 +00:00
Nuckyz
af827e2f1a feat(Google Photos): Add Enable DCIM folders backup control patch (#5138) 2025-06-08 09:13:53 +02:00
semantic-release-bot
97cd31509e chore: Release v5.27.0-dev.4 [skip ci]
# [5.27.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.3...v5.27.0-dev.4) (2025-06-06)

### Bug Fixes

* **Bandcamp - Remove play limits:** Support latest app version ([#5124](https://github.com/ReVanced/revanced-patches/issues/5124)) ([c0448de](c0448dece4))
2025-06-06 21:22:31 +00:00
hoodles
c0448dece4 fix(Bandcamp - Remove play limits): Support latest app version (#5124) 2025-06-06 23:20:00 +02:00
semantic-release-bot
f00a95c0d8 chore: Release v5.27.0-dev.3 [skip ci]
# [5.27.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.2...v5.27.0-dev.3) (2025-06-06)

### Bug Fixes

* **Spotify:** `Hide Create button` patch failing in edge cases ([#5131](https://github.com/ReVanced/revanced-patches/issues/5131)) ([7a432e5](7a432e5741))
2025-06-06 21:14:18 +00:00
Nuckyz
7a432e5741 fix(Spotify): Hide Create button patch failing in edge cases (#5131) 2025-06-06 18:11:32 -03:00
semantic-release-bot
966a78bd81 chore: Release v5.27.0-dev.2 [skip ci]
# [5.27.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.1...v5.27.0-dev.2) (2025-06-06)

### Bug Fixes

* **YouTube - Video quality:** Remove non-functional Shorts 144p default quality ([6aff8e8](6aff8e8ca4))
2025-06-06 07:06:49 +00:00
LisoUseInAIKyrios
6aff8e8ca4 fix(YouTube - Video quality): Remove non-functional Shorts 144p default quality 2025-06-06 09:04:07 +02:00
github-actions[bot]
11aa463fa6 chore: Sync translations (#5125) 2025-06-06 09:03:38 +02:00
semantic-release-bot
bf1b639a2f chore: Release v5.27.0-dev.1 [skip ci]
# [5.27.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.26.1-dev.3...v5.27.0-dev.1) (2025-06-05)

### Features

* **YouTube - Theme:** Add option for black and white splash screen animation ([#5119](https://github.com/ReVanced/revanced-patches/issues/5119)) ([6d5380d](6d5380d44d))
2025-06-05 20:08:58 +00:00
LisoUseInAIKyrios
6d5380d44d feat(YouTube - Theme): Add option for black and white splash screen animation (#5119) 2025-06-05 22:06:12 +02:00
github-actions[bot]
7e1547b5b9 chore: Sync translations (#5123) 2025-06-05 22:05:56 +02:00
semantic-release-bot
c790b45cc5 chore: Release v5.26.1-dev.3 [skip ci]
## [5.26.1-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.26.1-dev.2...v5.26.1-dev.3) (2025-06-05)

### Bug Fixes

* **Spotify:** Prevent hiding all navigation bar buttons ([#5122](https://github.com/ReVanced/revanced-patches/issues/5122)) ([65fc6b4](65fc6b43f5))
2025-06-05 19:57:52 +00:00
Nuckyz
65fc6b43f5 fix(Spotify): Prevent hiding all navigation bar buttons (#5122) 2025-06-05 16:55:33 -03:00
semantic-release-bot
2257dd90aa chore: Release v5.26.1-dev.2 [skip ci]
## [5.26.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.26.1-dev.1...v5.26.1-dev.2) (2025-06-05)

### Bug Fixes

* **YouTube - Hide layout components:** Remove broken option 'Hide comments emoji picker' ([#5121](https://github.com/ReVanced/revanced-patches/issues/5121)) ([4b8499f](4b8499ff2c))
2025-06-05 19:12:08 +00:00
LisoUseInAIKyrios
4b8499ff2c fix(YouTube - Hide layout components): Remove broken option 'Hide comments emoji picker' (#5121) 2025-06-05 21:09:25 +02:00
Nuckyz
bde3fda972 refactor(Spotify): Add extensions debug logging (#5110) 2025-06-05 16:04:02 -03:00
semantic-release-bot
e2e07b5cb2 chore: Release v5.26.1-dev.1 [skip ci]
## [5.26.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.26.0...v5.26.1-dev.1) (2025-06-05)

### Bug Fixes

* **YouTube - Hide Shorts components:** Disable A/B player flags that prevents hiding buttons ([9d10ab6](9d10ab6c00))
2025-06-05 09:24:29 +00:00
LisoUseInAIKyrios
9d10ab6c00 fix(YouTube - Hide Shorts components): Disable A/B player flags that prevents hiding buttons 2025-06-05 11:21:05 +02:00
github-actions[bot]
d7644152fd chore: Sync translations (#5116) 2025-06-05 11:19:38 +02:00
LisoUseInAIKyrios
9be21f4824 refactor(YouTube - Hide Shorts components): Rename 'Hide comment panel' to 'Hide preview comment' 2025-06-05 11:08:24 +02:00
semantic-release-bot
a2eae0bf04 chore: Release v5.26.0 [skip ci]
# [5.26.0](https://github.com/ReVanced/revanced-patches/compare/v5.25.0...v5.26.0) (2025-06-04)

### Bug Fixes

* **Spotify - Custom theme:** Apply accent color in more places ([#5039](https://github.com/ReVanced/revanced-patches/issues/5039)) ([bc45433](bc45433dcb))
* **YouTube - Hide Shorts components:** Disable A/B player that prevents hiding buttons ([#5104](https://github.com/ReVanced/revanced-patches/issues/5104)) ([1d0c568](1d0c56819b))
* **YouTube:** Support A/B Shorts layout for RYD and component hiding ([#5081](https://github.com/ReVanced/revanced-patches/issues/5081)) ([ff903ba](ff903ba9ac))

### Features

* **Proton Mail:** Add `Remove free accounts limit` patch ([#4970](https://github.com/ReVanced/revanced-patches/issues/4970)) ([49ae0df](49ae0df224))
* **Spotify:** Add `Hide Create button` patch ([#5062](https://github.com/ReVanced/revanced-patches/issues/5062)) ([ce5385b](ce5385b28e))
* **Sync for Reddit:** Add `Fix post thumbnails` patch ([7a53580](7a53580380))
* **YouTube - Hide Shorts components:** Add option to hide comment panel ([#5102](https://github.com/ReVanced/revanced-patches/issues/5102)) ([e435b33](e435b33593))
* **YouTube - Playback Speed:** Use modern custom speed dialog ([#5069](https://github.com/ReVanced/revanced-patches/issues/5069)) ([a320e35](a320e35c32))
2025-06-04 14:05:56 +00:00
LisoUseInAIKyrios
679354b5b3 chore: Merge branch dev to main (#5079) 2025-06-04 16:02:42 +02:00
semantic-release-bot
91dec21033 chore: Release v5.26.0-dev.8 [skip ci]
# [5.26.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.7...v5.26.0-dev.8) (2025-06-04)

### Bug Fixes

* **YouTube - Hide Shorts components:** Disable A/B player that prevents hiding buttons ([#5104](https://github.com/ReVanced/revanced-patches/issues/5104)) ([1d0c568](1d0c56819b))
2025-06-04 11:48:26 +00:00
LisoUseInAIKyrios
1d0c56819b fix(YouTube - Hide Shorts components): Disable A/B player that prevents hiding buttons (#5104) 2025-06-04 13:45:48 +02:00
github-actions[bot]
4410816c22 chore: Sync translations (#5106) 2025-06-04 13:44:35 +02:00
semantic-release-bot
7e4e48bc9f chore: Release v5.26.0-dev.7 [skip ci]
# [5.26.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.6...v5.26.0-dev.7) (2025-06-04)

### Features

* **YouTube - Hide Shorts components:** Add option to hide comment panel ([#5102](https://github.com/ReVanced/revanced-patches/issues/5102)) ([e435b33](e435b33593))
2025-06-04 07:09:39 +00:00
LisoUseInAIKyrios
e435b33593 feat(YouTube - Hide Shorts components): Add option to hide comment panel (#5102) 2025-06-04 09:06:27 +02:00
semantic-release-bot
bf288b83ae chore: Release v5.26.0-dev.6 [skip ci]
# [5.26.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.5...v5.26.0-dev.6) (2025-06-03)

### Features

* **Sync for Reddit:** Add `Fix post thumbnails` patch ([7a53580](7a53580380))
2025-06-03 19:50:57 +00:00
kolpazar
7a53580380 feat(Sync for Reddit): Add Fix post thumbnails patch 2025-06-03 21:48:24 +02:00
semantic-release-bot
6439efa2a9 chore: Release v5.26.0-dev.5 [skip ci]
# [5.26.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.4...v5.26.0-dev.5) (2025-06-03)

### Bug Fixes

* **Spotify - Custom theme:** Apply accent color in more places ([#5039](https://github.com/ReVanced/revanced-patches/issues/5039)) ([bc45433](bc45433dcb))
2025-06-03 08:04:58 +00:00
Cilly Leang
bc45433dcb fix(Spotify - Custom theme): Apply accent color in more places (#5039)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
2025-06-03 10:02:15 +02:00
github-actions[bot]
8871803e83 chore: Sync translations (#5095) 2025-06-03 09:59:31 +02:00
semantic-release-bot
18954a0285 chore: Release v5.26.0-dev.4 [skip ci]
# [5.26.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.3...v5.26.0-dev.4) (2025-06-03)

### Features

* **Spotify:** Add `Hide Create button` patch ([#5062](https://github.com/ReVanced/revanced-patches/issues/5062)) ([ce5385b](ce5385b28e))
2025-06-03 07:18:30 +00:00
Nuckyz
ce5385b28e feat(Spotify): Add Hide Create button patch (#5062) 2025-06-03 09:15:52 +02:00
dependabot[bot]
3f4cdf6f83 chore(deps-dev): bump semantic-release from 24.2.1 to 24.2.5 (#5086) 2025-06-01 20:57:25 +02:00
semantic-release-bot
094b4a1ea8 chore: Release v5.26.0-dev.3 [skip ci]
# [5.26.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.2...v5.26.0-dev.3) (2025-06-01)

### Features

* **YouTube - Playback Speed:** Use modern custom speed dialog ([#5069](https://github.com/ReVanced/revanced-patches/issues/5069)) ([a320e35](a320e35c32))
2025-06-01 09:15:51 +00:00
MarcaD
a320e35c32 feat(YouTube - Playback Speed): Use modern custom speed dialog (#5069)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-06-01 11:12:56 +02:00
semantic-release-bot
5bf5a2d2db chore: Release v5.26.0-dev.2 [skip ci]
# [5.26.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.1...v5.26.0-dev.2) (2025-06-01)

### Bug Fixes

* **YouTube:** Support A/B Shorts layout for RYD and component hiding ([#5081](https://github.com/ReVanced/revanced-patches/issues/5081)) ([ff903ba](ff903ba9ac))
2025-06-01 07:01:35 +00:00
alieRN
ff903ba9ac fix(YouTube): Support A/B Shorts layout for RYD and component hiding (#5081) 2025-06-01 08:59:09 +02:00
github-actions[bot]
1079a54dbe chore: Sync translations (#5084) 2025-06-01 08:58:37 +02:00
ILoveOpenSourceApplications
2b0e3b4553 refactor: Make strings consistent (#5075) 2025-05-31 10:25:04 +02:00
semantic-release-bot
0265a7791b chore: Release v5.26.0-dev.1 [skip ci]
# [5.26.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.25.0...v5.26.0-dev.1) (2025-05-30)

### Features

* **Proton Mail:** Add `Remove free accounts limit` patch ([#4970](https://github.com/ReVanced/revanced-patches/issues/4970)) ([49ae0df](49ae0df224))
2025-05-30 20:53:45 +00:00
ByteEVM
49ae0df224 feat(Proton Mail): Add Remove free accounts limit patch (#4970)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-05-30 22:51:12 +02:00
semantic-release-bot
e279491724 chore: Release v5.25.0 [skip ci]
# [5.25.0](https://github.com/ReVanced/revanced-patches/compare/v5.24.0...v5.25.0) (2025-05-29)

### Bug Fixes

* **Disable Pairip license check:** Change patch to default off ([798596f](798596fd83))
* **Hide ADB status:** Resolve app crash on startup ([#5029](https://github.com/ReVanced/revanced-patches/issues/5029)) ([2984d73](2984d7362d))
* **Messenger:** Remove outdated `Disable switching emoji to sticker` patch ([#5044](https://github.com/ReVanced/revanced-patches/issues/5044)) ([517368e](517368eda7))
* **Spotify Lite:** Remove obsolete `Enable on demand` patch ([#5046](https://github.com/ReVanced/revanced-patches/issues/5046)) ([b712f38](b712f38017))
* **YouTube - GmsCore support:** Restore patch functionality from prior merge ([b6047fa](b6047fa6b3))
* **YouTube - Hide ads:** Hide new type of general ad ([#5004](https://github.com/ReVanced/revanced-patches/issues/5004)) ([bc0c3c4](bc0c3c452d))
* **YouTube - Open Shorts in regular player:** Do not exit app when pressing back button in regular player ([#5020](https://github.com/ReVanced/revanced-patches/issues/5020)) ([4ab1f0c](4ab1f0cfa9))
* **YouTube:** Better handle incorrect duplicate translations ([8d61ba9](8d61ba90c3))
* **Yuka - Unlock premium:** Remove broken patch that is no longer supported ([#5018](https://github.com/ReVanced/revanced-patches/issues/5018)) ([e286dab](e286dab74e))

### Features

* Add `Disable pairip license check` patch ([#4927](https://github.com/ReVanced/revanced-patches/issues/4927)) ([dea7108](dea7108c45))
* **Messenger:** Add `Remove Meta AI` patch ([#4945](https://github.com/ReVanced/revanced-patches/issues/4945)) ([52b9dc5](52b9dc5c9f))
* **Prime Video:** Add `Rename shared permissions` patch ([#5049](https://github.com/ReVanced/revanced-patches/issues/5049)) ([02373b0](02373b0bd2))
* **Spotify:** Add `Fix Facebook login` patch ([#5023](https://github.com/ReVanced/revanced-patches/issues/5023)) ([795016a](795016abce))
* **Threads:** Hide Ads ([#5064](https://github.com/ReVanced/revanced-patches/issues/5064)) ([bf1f26d](bf1f26d8bb))
* **YouTube - Enable debugging:** Add settings menu to share debug logs ([#5021](https://github.com/ReVanced/revanced-patches/issues/5021)) ([83c148a](83c148addc))
* **YouTube - Settings:** Add a color picker ([#4981](https://github.com/ReVanced/revanced-patches/issues/4981)) ([584b00f](584b00fd87))
* **YouTube - Swipe controls:** Add separate color settings for the brightness and volume bars ([#5043](https://github.com/ReVanced/revanced-patches/issues/5043)) ([443b54b](443b54bf09))
* **YouTube:** Add `Disable haptic feedback` patch ([#5033](https://github.com/ReVanced/revanced-patches/issues/5033)) ([5c8ed05](5c8ed05727))
2025-05-29 07:26:20 +00:00
LisoUseInAIKyrios
495260fe2b chore: Merge branch dev to main (#5010) 2025-05-29 09:22:53 +02:00
github-actions[bot]
40f069fff7 chore: Sync translations (#5066) 2025-05-29 09:20:59 +02:00
semantic-release-bot
de263c1061 chore: Release v5.25.0-dev.14 [skip ci]
# [5.25.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.13...v5.25.0-dev.14) (2025-05-29)

### Features

* **Threads:** Hide Ads ([#5064](https://github.com/ReVanced/revanced-patches/issues/5064)) ([bf1f26d](bf1f26d8bb))
2025-05-29 07:17:42 +00:00
scruz
bf1f26d8bb feat(Threads): Hide Ads (#5064) 2025-05-29 09:14:24 +02:00
semantic-release-bot
0ee2ed72d4 chore: Release v5.25.0-dev.13 [skip ci]
# [5.25.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.12...v5.25.0-dev.13) (2025-05-28)

### Features

* **Prime Video:** Add `Rename shared permissions` patch ([#5049](https://github.com/ReVanced/revanced-patches/issues/5049)) ([02373b0](02373b0bd2))
2025-05-28 20:41:45 +00:00
hoodles
02373b0bd2 feat(Prime Video): Add Rename shared permissions patch (#5049) 2025-05-28 22:38:09 +02:00
github-actions[bot]
97c8e2489d chore: Sync translations (#5061) 2025-05-28 22:37:18 +02:00
semantic-release-bot
08b2b2e104 chore: Release v5.25.0-dev.12 [skip ci]
# [5.25.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.11...v5.25.0-dev.12) (2025-05-28)

### Features

* **YouTube - Swipe controls:** Add separate color settings for the brightness and volume bars ([#5043](https://github.com/ReVanced/revanced-patches/issues/5043)) ([443b54b](443b54bf09))
2025-05-28 10:02:34 +00:00
github-actions[bot]
6b386b67d2 chore: Sync translations (#5055) 2025-05-28 11:58:56 +02:00
Nuckyz
f8343ae9f6 refactor(Spotify): Improve protobuf array list mutability patch (#5053)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-05-28 11:55:25 +02:00
LisoUseInAIKyrios
3ba791ac7d refactor(YouTube - Enabling debugging): Adjust logger formatting to preserve backwards compatibility (#5054) 2025-05-28 11:55:02 +02:00
MarcaD
443b54bf09 feat(YouTube - Swipe controls): Add separate color settings for the brightness and volume bars (#5043) 2025-05-28 11:54:28 +02:00
semantic-release-bot
53587f190d chore: Release v5.25.0-dev.11 [skip ci]
# [5.25.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.10...v5.25.0-dev.11) (2025-05-27)

### Features

* **YouTube - Enable debugging:** Add settings menu to share debug logs ([#5021](https://github.com/ReVanced/revanced-patches/issues/5021)) ([83c148a](83c148addc))
* **YouTube:** Add `Disable haptic feedback` patch ([#5033](https://github.com/ReVanced/revanced-patches/issues/5033)) ([5c8ed05](5c8ed05727))
2025-05-27 07:55:39 +00:00
MarcaD
83c148addc feat(YouTube - Enable debugging): Add settings menu to share debug logs (#5021)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-05-27 09:52:24 +02:00
MarcaD
5c8ed05727 feat(YouTube): Add Disable haptic feedback patch (#5033) 2025-05-27 09:52:01 +02:00
semantic-release-bot
33833d7a1e chore: Release v5.25.0-dev.10 [skip ci]
# [5.25.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.9...v5.25.0-dev.10) (2025-05-27)

### Bug Fixes

* **Messenger:** Remove outdated `Disable switching emoji to sticker` patch ([#5044](https://github.com/ReVanced/revanced-patches/issues/5044)) ([517368e](517368eda7))
* **Spotify Lite:** Remove obsolete `Enable on demand` patch ([#5046](https://github.com/ReVanced/revanced-patches/issues/5046)) ([b712f38](b712f38017))
2025-05-27 06:51:05 +00:00
LisoUseInAIKyrios
b712f38017 fix(Spotify Lite): Remove obsolete Enable on demand patch (#5046) 2025-05-27 08:47:56 +02:00
LisoUseInAIKyrios
517368eda7 fix(Messenger): Remove outdated Disable switching emoji to sticker patch (#5044) 2025-05-27 08:47:09 +02:00
LisoUseInAIKyrios
2093c0c175 chore: fix api dump 2025-05-26 12:36:16 +02:00
semantic-release-bot
a7cfd80bfe chore: Release v5.25.0-dev.9 [skip ci]
# [5.25.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.8...v5.25.0-dev.9) (2025-05-26)

### Features

* **Spotify:** Add `Fix Facebook login` patch ([#5023](https://github.com/ReVanced/revanced-patches/issues/5023)) ([795016a](795016abce))
* **YouTube - Settings:** Add a color picker ([#4981](https://github.com/ReVanced/revanced-patches/issues/4981)) ([584b00f](584b00fd87))
2025-05-26 09:48:51 +00:00
github-actions[bot]
2990dc6d4e chore: Sync translations (#5038) 2025-05-26 10:39:02 +02:00
semantic-release-bot
c0e52bb6b3 chore: Release v5.25.0-dev.9 [skip ci]
# [5.25.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.8...v5.25.0-dev.9) (2025-05-26)

### Features

* **Spotify:** Add `Fix Facebook login` patch ([#5023](https://github.com/ReVanced/revanced-patches/issues/5023)) ([795016a](795016abce))
* **YouTube - Settings:** Add a color picker ([#4981](https://github.com/ReVanced/revanced-patches/issues/4981)) ([584b00f](584b00fd87))
2025-05-26 07:53:26 +00:00
github-actions[bot]
93fdd6f538 chore: Sync translations (#5037) 2025-05-26 09:49:50 +02:00
semantic-release-bot
decd249f20 chore: Release v5.25.0-dev.9 [skip ci]
# [5.25.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.8...v5.25.0-dev.9) (2025-05-26)

### Features

* **Spotify:** Add `Fix Facebook login` patch ([#5023](https://github.com/ReVanced/revanced-patches/issues/5023)) ([795016a](795016abce))
* **YouTube - Settings:** Add a color picker ([#4981](https://github.com/ReVanced/revanced-patches/issues/4981)) ([584b00f](584b00fd87))
2025-05-26 07:29:23 +00:00
github-actions[bot]
d79cb3eea8 chore: Sync translations (#5036) 2025-05-26 09:25:54 +02:00
MarcaD
584b00fd87 feat(YouTube - Settings): Add a color picker (#4981)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-05-26 09:08:45 +02:00
Nuckyz
795016abce feat(Spotify): Add Fix Facebook login patch (#5023) 2025-05-26 09:08:15 +02:00
semantic-release-bot
dc1dbd50a8 chore: Release v5.25.0-dev.8 [skip ci]
# [5.25.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.7...v5.25.0-dev.8) (2025-05-25)

### Bug Fixes

* **Hide ADB status:** Resolve app crash on startup ([#5029](https://github.com/ReVanced/revanced-patches/issues/5029)) ([2984d73](2984d7362d))
2025-05-25 17:41:52 +00:00
1fexd
2984d7362d fix(Hide ADB status): Resolve app crash on startup (#5029) 2025-05-25 19:38:19 +02:00
semantic-release-bot
627aed4010 chore: Release v5.25.0-dev.7 [skip ci]
# [5.25.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.6...v5.25.0-dev.7) (2025-05-24)

### Bug Fixes

* **YouTube - Open Shorts in regular player:** Do not exit app when pressing back button in regular player ([#5020](https://github.com/ReVanced/revanced-patches/issues/5020)) ([4ab1f0c](4ab1f0cfa9))
2025-05-24 11:16:03 +00:00
LisoUseInAIKyrios
4ab1f0cfa9 fix(YouTube - Open Shorts in regular player): Do not exit app when pressing back button in regular player (#5020) 2025-05-24 13:12:39 +02:00
semantic-release-bot
86e8e61ab2 chore: Release v5.25.0-dev.6 [skip ci]
# [5.25.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.5...v5.25.0-dev.6) (2025-05-23)

### Bug Fixes

* **Yuka - Unlock premium:** Remove broken patch that is no longer supported ([#5018](https://github.com/ReVanced/revanced-patches/issues/5018)) ([e286dab](e286dab74e))
2025-05-23 19:58:47 +00:00
LisoUseInAIKyrios
e286dab74e fix(Yuka - Unlock premium): Remove broken patch that is no longer supported (#5018) 2025-05-23 21:55:04 +02:00
github-actions[bot]
712a82439f chore: Sync translations (#5019) 2025-05-23 21:54:47 +02:00
semantic-release-bot
4449546c85 chore: Release v5.25.0-dev.5 [skip ci]
# [5.25.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.4...v5.25.0-dev.5) (2025-05-22)

### Bug Fixes

* **YouTube:** Better handle incorrect duplicate translations ([8d61ba9](8d61ba90c3))
2025-05-22 19:23:25 +00:00
LisoUseInAIKyrios
8d61ba90c3 fix(YouTube): Better handle incorrect duplicate translations 2025-05-22 21:20:01 +02:00
semantic-release-bot
689be79f71 chore: Release v5.25.0-dev.4 [skip ci]
# [5.25.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.3...v5.25.0-dev.4) (2025-05-22)

### Bug Fixes

* **YouTube - GmsCore support:** Restore patch functionality from prior merge ([b6047fa](b6047fa6b3))
2025-05-22 17:28:53 +00:00
LisoUseInAIKyrios
b6047fa6b3 fix(YouTube - GmsCore support): Restore patch functionality from prior merge 2025-05-22 19:25:15 +02:00
semantic-release-bot
82bbd603ac chore: Release v5.25.0-dev.3 [skip ci]
# [5.25.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.2...v5.25.0-dev.3) (2025-05-22)

### Bug Fixes

* **YouTube - Hide ads:** Hide new type of general ad ([#5004](https://github.com/ReVanced/revanced-patches/issues/5004)) ([bc0c3c4](bc0c3c452d))
2025-05-22 16:38:22 +00:00
ILoveOpenSourceApplications
bc0c3c452d fix(YouTube - Hide ads): Hide new type of general ad (#5004) 2025-05-22 18:35:11 +02:00
Pun Butrach
fe864d8331 ci: Attest release artifacts (#4816)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-05-22 14:56:33 +02:00
semantic-release-bot
4f686935c3 chore: Release v5.25.0-dev.2 [skip ci]
# [5.25.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.1...v5.25.0-dev.2) (2025-05-22)

### Bug Fixes

* **Disable Pairip license check:** Change patch to default off ([798596f](798596fd83))
2025-05-22 10:18:30 +00:00
LisoUseInAIKyrios
798596fd83 fix(Disable Pairip license check): Change patch to default off 2025-05-22 12:14:33 +02:00
semantic-release-bot
38b37f182a chore: Release v5.25.0-dev.1 [skip ci]
# [5.25.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.24.0...v5.25.0-dev.1) (2025-05-22)

### Features

* Add `Disable pairip license check` patch ([#4927](https://github.com/ReVanced/revanced-patches/issues/4927)) ([dea7108](dea7108c45))
* **Messenger:** Add `Remove Meta AI` patch ([#4945](https://github.com/ReVanced/revanced-patches/issues/4945)) ([52b9dc5](52b9dc5c9f))
2025-05-22 08:39:21 +00:00
Dawid Krajcarz
52b9dc5c9f feat(Messenger): Add Remove Meta AI patch (#4945) 2025-05-22 10:35:31 +02:00
hoodles
dea7108c45 feat: Add Disable pairip license check patch (#4927)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-05-22 10:35:16 +02:00
github-actions[bot]
24b4579cb9 chore: Sync translations (#5009) 2025-05-22 10:33:36 +02:00
semantic-release-bot
0b52f3d192 chore: Release v5.24.0 [skip ci]
# [5.24.0](https://github.com/ReVanced/revanced-patches/compare/v5.23.0...v5.24.0) (2025-05-19)

### Bug Fixes

* **Spotify - Fix third party launchers widgets:** Add missing compatibility annotation ([e68cd70](e68cd70f66))
* **YouTube - Hide layout components:** Fix `Hide video recommendation labels` ([#4956](https://github.com/ReVanced/revanced-patches/issues/4956)) ([9aec199](9aec1999bb))
* **YouTube - Settings:** Correctly show summary text if search box is closed before searching ([e59c9e9](e59c9e9b3c))
* **YouTube - SponsorBlock:** Fix segment category summary not showing category description ([b2b09a2](b2b09a2025))

### Features

* **GmsCore support:** Open vendor specific DontKillMyApp if available ([#4952](https://github.com/ReVanced/revanced-patches/issues/4952)) ([d2b440d](d2b440d800))
* **NU.nl:** Support version `11.3.0` ([#4925](https://github.com/ReVanced/revanced-patches/issues/4925)) ([887c9f0](887c9f0d75))
* **Spotify:** Add `Fix third party launchers widgets` patch ([#4893](https://github.com/ReVanced/revanced-patches/issues/4893)) ([db68c41](db68c41d5e))
* **YouTube - Hide description components:** Add `Hide Ask` ([#4972](https://github.com/ReVanced/revanced-patches/issues/4972)) ([e582908](e58290839f))
* **YouTube - Hide layout components:** Add `Hide ticket shelf` ([#4969](https://github.com/ReVanced/revanced-patches/issues/4969)) ([4cd0ae9](4cd0ae9b92))
* **YouTube - Hide player components:** Hide related video overlay in fullscreen ([#4938](https://github.com/ReVanced/revanced-patches/issues/4938)) ([f454183](f454183646))
* **YouTube - Settings:** Add ability to search in settings ([#4881](https://github.com/ReVanced/revanced-patches/issues/4881)) ([14a8f4f](14a8f4fb96))
2025-05-19 10:32:36 +00:00
LisoUseInAIKyrios
18c374a81e chore: Merge branch dev to main (#4947) 2025-05-19 14:28:47 +04:00
github-actions[bot]
092303e431 chore: Sync translations (#4995) 2025-05-19 14:25:25 +04:00
semantic-release-bot
6bf5bf9d45 chore: Release v5.24.0-dev.9 [skip ci]
# [5.24.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.8...v5.24.0-dev.9) (2025-05-18)

### Bug Fixes

* **YouTube - SponsorBlock:** Fix segment category summary not showing category description ([b2b09a2](b2b09a2025))
2025-05-18 08:29:10 +00:00
LisoUseInAIKyrios
b2b09a2025 fix(YouTube - SponsorBlock): Fix segment category summary not showing category description 2025-05-18 12:25:43 +04:00
semantic-release-bot
4a3a7f1674 chore: Release v5.24.0-dev.8 [skip ci]
# [5.24.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.7...v5.24.0-dev.8) (2025-05-17)

### Bug Fixes

* **YouTube - Settings:** Correctly show summary text if search box is closed before searching ([e59c9e9](e59c9e9b3c))
2025-05-17 18:00:22 +00:00
LisoUseInAIKyrios
e59c9e9b3c fix(YouTube - Settings): Correctly show summary text if search box is closed before searching 2025-05-17 21:57:03 +04:00
github-actions[bot]
dfb552b01a chore: Sync translations (#4978) 2025-05-17 21:56:48 +04:00
github-actions[bot]
94999c56b1 chore: Sync translations (#4975) 2025-05-17 15:19:14 +04:00
semantic-release-bot
c4fd1f0146 chore: Release v5.24.0-dev.7 [skip ci]
# [5.24.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.6...v5.24.0-dev.7) (2025-05-17)

### Features

* **YouTube - Hide layout components:** Add `Hide ticket shelf` ([#4969](https://github.com/ReVanced/revanced-patches/issues/4969)) ([4cd0ae9](4cd0ae9b92))
2025-05-17 10:51:31 +00:00
ILoveOpenSourceApplications
4cd0ae9b92 feat(YouTube - Hide layout components): Add Hide ticket shelf (#4969) 2025-05-17 14:48:05 +04:00
github-actions[bot]
9548d581c1 chore: Sync translations (#4974) 2025-05-17 14:46:10 +04:00
LisoUseInAIKyrios
a2fe3af6be refactor(YouTube - Litho filter): Simplify debug logging code 2025-05-17 12:26:37 +04:00
semantic-release-bot
6ef6504d41 chore: Release v5.24.0-dev.6 [skip ci]
# [5.24.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.5...v5.24.0-dev.6) (2025-05-17)

### Features

* **YouTube - Hide description components:** Add `Hide Ask` ([#4972](https://github.com/ReVanced/revanced-patches/issues/4972)) ([e582908](e58290839f))
2025-05-17 07:56:03 +00:00
ILoveOpenSourceApplications
e58290839f feat(YouTube - Hide description components): Add Hide Ask (#4972) 2025-05-17 11:52:33 +04:00
github-actions[bot]
e18260bd65 chore: Sync translations (#4973) 2025-05-17 11:46:53 +04:00
semantic-release-bot
b2fcd5a846 chore: Release v5.24.0-dev.5 [skip ci]
# [5.24.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.4...v5.24.0-dev.5) (2025-05-17)

### Bug Fixes

* **Spotify - Fix third party launchers widgets:** Add missing compatibility annotation ([e68cd70](e68cd70f66))

### Features

* **YouTube - Settings:** Add ability to search in settings ([#4881](https://github.com/ReVanced/revanced-patches/issues/4881)) ([14a8f4f](14a8f4fb96))
2025-05-17 07:44:42 +00:00
LisoUseInAIKyrios
e68cd70f66 fix(Spotify - Fix third party launchers widgets): Add missing compatibility annotation 2025-05-17 11:40:37 +04:00
MarcaD
14a8f4fb96 feat(YouTube - Settings): Add ability to search in settings (#4881)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-05-17 11:40:16 +04:00
semantic-release-bot
2593c004f4 chore: Release v5.24.0-dev.4 [skip ci]
# [5.24.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.3...v5.24.0-dev.4) (2025-05-16)

### Features

* **Spotify:** Add `Fix third party launchers widgets` patch ([#4893](https://github.com/ReVanced/revanced-patches/issues/4893)) ([db68c41](db68c41d5e))
2025-05-16 17:18:15 +00:00
Nuckyz
db68c41d5e feat(Spotify): Add Fix third party launchers widgets patch (#4893)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-05-16 19:14:26 +02:00
semantic-release-bot
a4f9cb3cef chore: Release v5.24.0-dev.3 [skip ci]
# [5.24.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.2...v5.24.0-dev.3) (2025-05-14)

### Bug Fixes

* **YouTube - Hide layout components:** Fix `Hide video recommendation labels` ([#4956](https://github.com/ReVanced/revanced-patches/issues/4956)) ([9aec199](9aec1999bb))
2025-05-14 20:01:05 +00:00
ILoveOpenSourceApplications
9aec1999bb fix(YouTube - Hide layout components): Fix Hide video recommendation labels (#4956) 2025-05-14 23:57:47 +04:00
github-actions[bot]
26ecbe646e chore: Sync translations (#4960) 2025-05-14 23:57:11 +04:00
semantic-release-bot
46ba0d8a2e chore: Release v5.24.0-dev.2 [skip ci]
# [5.24.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.1...v5.24.0-dev.2) (2025-05-14)

### Features

* **GmsCore support:** Open vendor specific DontKillMyApp if available ([#4952](https://github.com/ReVanced/revanced-patches/issues/4952)) ([d2b440d](d2b440d800))
* **YouTube - Hide player components:** Hide related video overlay in fullscreen ([#4938](https://github.com/ReVanced/revanced-patches/issues/4938)) ([f454183](f454183646))
2025-05-14 07:07:03 +00:00
MarcaD
f454183646 feat(YouTube - Hide player components): Hide related video overlay in fullscreen (#4938) 2025-05-14 11:03:33 +04:00
LisoUseInAIKyrios
d2b440d800 feat(GmsCore support): Open vendor specific DontKillMyApp if available (#4952) 2025-05-14 11:01:32 +04:00
hoodles
494c5f04a4 fix(Instagram) - Fix hide ads fingerprint and add bypass check signature (#4901)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-05-13 10:58:07 +04:00
semantic-release-bot
48d5fdf7e1 chore: Release v5.24.0-dev.1 [skip ci]
# [5.24.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.23.0...v5.24.0-dev.1) (2025-05-12)

### Features

* **NU.nl:** Support version `11.3.0` ([#4925](https://github.com/ReVanced/revanced-patches/issues/4925)) ([887c9f0](887c9f0d75))
2025-05-12 22:40:55 +00:00
Jasper Abbink
887c9f0d75 feat(NU.nl): Support version 11.3.0 (#4925) 2025-05-13 02:37:41 +04:00
github-actions[bot]
7de4c9d41d chore: Sync translations (#4946) 2025-05-13 02:36:53 +04:00
semantic-release-bot
7d3b8d9c42 chore: Release v5.23.0 [skip ci]
# [5.23.0](https://github.com/ReVanced/revanced-patches/compare/v5.22.0...v5.23.0) (2025-05-10)

### Bug Fixes

* Correct incorrect fingerprint ([5f05414](5f0541407c))
* Fix incorrect fingerprints ([#4917](https://github.com/ReVanced/revanced-patches/issues/4917)) ([796c118](796c118fe1))
* **Spotify - Unlock Spotify Premium:** Remove pop up premium ads ([#4842](https://github.com/ReVanced/revanced-patches/issues/4842)) ([5028c1a](5028c1acb3))
* **YouTube:** Improve litho filtering performance ([#4904](https://github.com/ReVanced/revanced-patches/issues/4904)) ([60fdf4c](60fdf4c44c))
* **YouTube:** Simplify litho filtering patch ([#4910](https://github.com/ReVanced/revanced-patches/issues/4910)) ([23fd720](23fd720fa7))

### Features

* **Lightroom:** Constrain patches to last working version ([858c59d](858c59d728))
* **Pandora:** Add `Disable audio ads` and `Unlimited skips` patch ([#4841](https://github.com/ReVanced/revanced-patches/issues/4841)) ([f4f36ff](f4f36ff273))
* **Prime Video:** Add `Skip ads` patch ([#4824](https://github.com/ReVanced/revanced-patches/issues/4824)) ([f8bdf74](f8bdf744ab))
* **Spotify:** Add `Sanitize sharing links` patch ([#4829](https://github.com/ReVanced/revanced-patches/issues/4829)) ([777957e](777957e2d0))
2025-05-10 09:02:41 +00:00
LisoUseInAIKyrios
25e1a965d6 chore: Merge branch dev to main (#4899) 2025-05-10 12:58:55 +04:00
github-actions[bot]
b29c01cee1 chore: Sync translations (#4933) 2025-05-10 12:39:30 +04:00
semantic-release-bot
639850471b chore: Release v5.23.0-dev.7 [skip ci]
# [5.23.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.6...v5.23.0-dev.7) (2025-05-06)

### Bug Fixes

* Fix incorrect fingerprints ([#4917](https://github.com/ReVanced/revanced-patches/issues/4917)) ([796c118](796c118fe1))
2025-05-06 12:13:29 +00:00
Nuckyz
796c118fe1 fix: Fix incorrect fingerprints (#4917) 2025-05-06 16:09:54 +04:00
semantic-release-bot
edf20e397d chore: Release v5.23.0-dev.6 [skip ci]
# [5.23.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.5...v5.23.0-dev.6) (2025-05-06)

### Bug Fixes

* Correct incorrect fingerprint ([5f05414](5f0541407c))
2025-05-06 10:50:54 +00:00
oSumAtrIX
5f0541407c fix: Correct incorrect fingerprint 2025-05-06 12:47:06 +02:00
semantic-release-bot
56b7ba9ba7 chore: Release v5.23.0-dev.5 [skip ci]
# [5.23.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.4...v5.23.0-dev.5) (2025-05-06)

### Bug Fixes

* **Spotify - Unlock Spotify Premium:** Remove pop up premium ads ([#4842](https://github.com/ReVanced/revanced-patches/issues/4842)) ([5028c1a](5028c1acb3))

### Features

* **Pandora:** Add `Disable audio ads` and `Unlimited skips` patch ([#4841](https://github.com/ReVanced/revanced-patches/issues/4841)) ([f4f36ff](f4f36ff273))
* **Prime Video:** Add `Skip ads` patch ([#4824](https://github.com/ReVanced/revanced-patches/issues/4824)) ([f8bdf74](f8bdf744ab))
2025-05-06 07:44:30 +00:00
hoodles
f8bdf744ab feat(Prime Video): Add Skip ads patch (#4824) 2025-05-06 11:40:45 +04:00
hoodles
f4f36ff273 feat(Pandora): Add Disable audio ads and Unlimited skips patch (#4841)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
2025-05-06 11:39:07 +04:00
Nuckyz
5028c1acb3 fix(Spotify - Unlock Spotify Premium): Remove pop up premium ads (#4842) 2025-05-06 11:35:47 +04:00
semantic-release-bot
555c9a5823 chore: Release v5.23.0-dev.4 [skip ci]
# [5.23.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.3...v5.23.0-dev.4) (2025-05-06)

### Features

* **Spotify:** Add `Sanitize sharing links` patch ([#4829](https://github.com/ReVanced/revanced-patches/issues/4829)) ([777957e](777957e2d0))
2025-05-06 07:35:22 +00:00
Dawid Krajcarz
777957e2d0 feat(Spotify): Add Sanitize sharing links patch (#4829) 2025-05-06 11:31:56 +04:00
github-actions[bot]
b3316a5915 chore: Sync translations (#4915) 2025-05-06 11:31:12 +04:00
semantic-release-bot
2ca2bb7692 chore: Release v5.23.0-dev.3 [skip ci]
# [5.23.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.2...v5.23.0-dev.3) (2025-05-05)

### Bug Fixes

* **YouTube:** Simplify litho filtering patch ([#4910](https://github.com/ReVanced/revanced-patches/issues/4910)) ([23fd720](23fd720fa7))
2025-05-05 11:28:44 +00:00
LisoUseInAIKyrios
23fd720fa7 fix(YouTube): Simplify litho filtering patch (#4910) 2025-05-05 15:25:25 +04:00
semantic-release-bot
1f08586ae8 chore: Release v5.23.0-dev.2 [skip ci]
# [5.23.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.1...v5.23.0-dev.2) (2025-05-04)

### Bug Fixes

* **YouTube:** Improve litho filtering performance ([#4904](https://github.com/ReVanced/revanced-patches/issues/4904)) ([60fdf4c](60fdf4c44c))
2025-05-04 09:58:25 +00:00
LisoUseInAIKyrios
60fdf4c44c fix(YouTube): Improve litho filtering performance (#4904) 2025-05-04 13:55:12 +04:00
semantic-release-bot
63f3342815 chore: Release v5.23.0-dev.1 [skip ci]
# [5.23.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.22.0...v5.23.0-dev.1) (2025-05-02)

### Features

* **Lightroom:** Constrain patches to last working version ([858c59d](858c59d728))
2025-05-02 13:02:25 +00:00
oSumAtrIX
858c59d728 feat(Lightroom): Constrain patches to last working version 2025-05-02 14:58:57 +02:00
semantic-release-bot
5debf9936d chore: Release v5.22.0 [skip ci]
# [5.22.0](https://github.com/ReVanced/revanced-patches/compare/v5.21.0...v5.22.0) (2025-05-01)

### Bug Fixes

* **TikTok - Feed filter:** Hide ads in following feed ([#4844](https://github.com/ReVanced/revanced-patches/issues/4844)) ([b2453fe](b2453fecfc))
* **YouTube - Hide layout components:** Hide new type of community posts ([#4888](https://github.com/ReVanced/revanced-patches/issues/4888)) ([9b1013e](9b1013e1c2))
* **YouTube - Hide Shorts components:** Hide action buttons A/B button layout ([#4889](https://github.com/ReVanced/revanced-patches/issues/4889)) ([75d6cd7](75d6cd7c7b))
* **YouTube - Shorts autoplay:** Fix autoplay with YT 20.12 ([ef35ed7](ef35ed7335))
* **YouTube - Spoof app version:** Do not hide spoof version in general settings menu ([#4861](https://github.com/ReVanced/revanced-patches/issues/4861)) ([f69eab3](f69eab3e3b))

### Features

* **TikTok - Feed Filter:** Remove TikTok Shop from feed. ([#4851](https://github.com/ReVanced/revanced-patches/issues/4851)) ([72e0c01](72e0c01922))
* **YouTube - GmsCore support:** Show troubleshooting in app text if the user recently changed their account details ([#4879](https://github.com/ReVanced/revanced-patches/issues/4879)) ([45b5a51](45b5a51da3))
2025-05-01 07:03:38 +00:00
LisoUseInAIKyrios
f1b85d20a1 chore: Merge branch dev to main (#4864) 2025-05-01 11:00:16 +04:00
github-actions[bot]
37d0de5e93 chore: Sync translations (#4894) 2025-05-01 10:59:24 +04:00
semantic-release-bot
96d08d5eb7 chore: Release v5.22.0-dev.4 [skip ci]
# [5.22.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.22.0-dev.3...v5.22.0-dev.4) (2025-04-30)

### Bug Fixes

* **YouTube - Hide layout components:** Hide new type of community posts ([#4888](https://github.com/ReVanced/revanced-patches/issues/4888)) ([9b1013e](9b1013e1c2))
* **YouTube - Hide Shorts components:** Hide action buttons A/B button layout ([#4889](https://github.com/ReVanced/revanced-patches/issues/4889)) ([75d6cd7](75d6cd7c7b))
2025-04-30 22:11:17 +00:00
Bceez
9b1013e1c2 fix(YouTube - Hide layout components): Hide new type of community posts (#4888) 2025-05-01 02:07:52 +04:00
LisoUseInAIKyrios
75d6cd7c7b fix(YouTube - Hide Shorts components): Hide action buttons A/B button layout (#4889) 2025-05-01 02:07:32 +04:00
github-actions[bot]
5a17f5e1c1 chore: Sync translations (#4890) 2025-05-01 02:04:21 +04:00
MarcaD
1d16de6617 bug(YouTube - Theme): Fix white system navigation bar in the ReVanced settings (#4875) 2025-04-29 23:27:14 +04:00
github-actions[bot]
aee7cba46d chore: Sync translations (#4884) 2025-04-29 23:26:47 +04:00
semantic-release-bot
ec3faf30a8 chore: Release v5.22.0-dev.3 [skip ci]
# [5.22.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.22.0-dev.2...v5.22.0-dev.3) (2025-04-29)

### Features

* **YouTube - GmsCore support:** Show troubleshooting in app text if the user recently changed their account details ([#4879](https://github.com/ReVanced/revanced-patches/issues/4879)) ([45b5a51](45b5a51da3))
2025-04-29 09:40:18 +00:00
LisoUseInAIKyrios
45b5a51da3 feat(YouTube - GmsCore support): Show troubleshooting in app text if the user recently changed their account details (#4879) 2025-04-29 13:36:29 +04:00
semantic-release-bot
8abf176bc9 chore: Release v5.22.0-dev.2 [skip ci]
# [5.22.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.22.0-dev.1...v5.22.0-dev.2) (2025-04-27)

### Bug Fixes

* **YouTube - Shorts autoplay:** Fix autoplay with YT 20.12 ([ef35ed7](ef35ed7335))
2025-04-27 14:28:33 +00:00
LisoUseInAIKyrios
ef35ed7335 fix(YouTube - Shorts autoplay): Fix autoplay with YT 20.12 2025-04-27 18:24:37 +04:00
semantic-release-bot
4fd666b667 chore: Release v5.22.0-dev.1 [skip ci]
# [5.22.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.21.0...v5.22.0-dev.1) (2025-04-26)

### Bug Fixes

* **TikTok - Feed filter:** Hide ads in following feed ([#4844](https://github.com/ReVanced/revanced-patches/issues/4844)) ([b2453fe](b2453fecfc))
* **YouTube - Spoof app version:** Do not hide spoof version in general settings menu ([#4861](https://github.com/ReVanced/revanced-patches/issues/4861)) ([f69eab3](f69eab3e3b))

### Features

* **TikTok - Feed Filter:** Remove TikTok Shop from feed. ([#4851](https://github.com/ReVanced/revanced-patches/issues/4851)) ([72e0c01](72e0c01922))
2025-04-26 13:55:47 +00:00
3igcheeze
72e0c01922 feat(TikTok - Feed Filter): Remove TikTok Shop from feed. (#4851) 2025-04-26 17:52:17 +04:00
LisoUseInAIKyrios
f69eab3e3b fix(YouTube - Spoof app version): Do not hide spoof version in general settings menu (#4861) 2025-04-26 17:51:35 +04:00
github-actions[bot]
7c5c2d95bc chore: Sync translations (#4865) 2025-04-26 17:51:18 +04:00
Jaimy Smets
b2453fecfc fix(TikTok - Feed filter): Hide ads in following feed (#4844) 2025-04-26 17:49:28 +04:00
semantic-release-bot
0d54f8bd80 chore: Release v5.21.0 [skip ci]
# [5.21.0](https://github.com/ReVanced/revanced-patches/compare/v5.20.1...v5.21.0) (2025-04-25)

### Bug Fixes

* `Hide ADB status` patch ([#4814](https://github.com/ReVanced/revanced-patches/issues/4814)) ([5e069bd](5e069bde90))
* **GmsCore Support:** Correct the description to refer to the app being patched ([96512de](96512de6c9))
* **Wide search bar:** Fix patching `19.16.39` ([d7c9dd0](d7c9dd0f77))
* **YouTube - Change start page:** Add option to always override start page on app launch ([#4832](https://github.com/ReVanced/revanced-patches/issues/4832)) ([896de89](896de8910a))
* **YouTube - Disable auto captions:** Correctly hide captions with YT 20.12 ([8efbaae](8efbaae65c))
* **YouTube - Hide video action buttons:** Add option to hide 'Ask' button ([#4852](https://github.com/ReVanced/revanced-patches/issues/4852)) ([2d94ba9](2d94ba9df6))
* **YouTube - Hide video action buttons:** Hide A/B layout buttons ([15053e2](15053e2b68))
* **YouTube - Wide search bar:** Do not force phone layout for tablet devices ([#4827](https://github.com/ReVanced/revanced-patches/issues/4827)) ([77ea5c4](77ea5c4033))

### Features

* Add `Hide ADB status` patch ([#4585](https://github.com/ReVanced/revanced-patches/issues/4585)) ([7cc6995](7cc6995682))
* **X / Twitter:** Support version `10.86.0-release.0` ([#4805](https://github.com/ReVanced/revanced-patches/issues/4805)) ([fc6282d](fc6282d0cb))
* **YouTube - Swipe controls:** Add option for vertical progress bar ([#4811](https://github.com/ReVanced/revanced-patches/issues/4811)) ([6d69f01](6d69f01421))
* **YouTube:** Support version `20.12.46` ([#4779](https://github.com/ReVanced/revanced-patches/issues/4779)) ([f216e16](f216e16c0b))
2025-04-25 17:21:09 +00:00
LisoUseInAIKyrios
fda16fad1a chore: Merge branch dev to main (#4803) 2025-04-25 21:17:55 +04:00
github-actions[bot]
ddd43acd73 chore: Sync translations (#4858) 2025-04-25 21:17:12 +04:00
semantic-release-bot
3451318d53 chore: Release v5.21.0-dev.12 [skip ci]
# [5.21.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.21.0-dev.11...v5.21.0-dev.12) (2025-04-24)

### Bug Fixes

* **YouTube - Hide video action buttons:** Add option to hide 'Ask' button ([#4852](https://github.com/ReVanced/revanced-patches/issues/4852)) ([2d94ba9](2d94ba9df6))
2025-04-24 19:26:35 +00:00
LisoUseInAIKyrios
2d94ba9df6 fix(YouTube - Hide video action buttons): Add option to hide 'Ask' button (#4852) 2025-04-24 23:23:10 +04:00
github-actions[bot]
aaf3437a5a chore: Sync translations (#4853) 2025-04-24 23:21:29 +04:00
semantic-release-bot
ec8bf06047 chore: Release v5.21.0-dev.11 [skip ci]
# [5.21.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.21.0-dev.10...v5.21.0-dev.11) (2025-04-24)

### Bug Fixes

* **GmsCore Support:** Correct the description to refer to the app being patched ([96512de](96512de6c9))
2025-04-24 18:51:43 +00:00
oSumAtrIX
96512de6c9 fix(GmsCore Support): Correct the description to refer to the app being patched 2025-04-24 20:48:12 +02:00
semantic-release-bot
6114807c43 chore: Release v5.21.0-dev.10 [skip ci]
# [5.21.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.21.0-dev.9...v5.21.0-dev.10) (2025-04-23)

### Features

* **YouTube - Swipe controls:** Add option for vertical progress bar ([#4811](https://github.com/ReVanced/revanced-patches/issues/4811)) ([6d69f01](6d69f01421))
2025-04-23 11:34:08 +00:00
MarcaD
6d69f01421 feat(YouTube - Swipe controls): Add option for vertical progress bar (#4811) 2025-04-23 15:30:41 +04:00
github-actions[bot]
fd4218154d chore: Sync translations (#4845) 2025-04-23 15:30:22 +04:00
github-actions[bot]
8bed8a6622 chore: Sync translations (#4839) 2025-04-21 22:17:26 +02:00
semantic-release-bot
3174047223 chore: Release v5.21.0-dev.9 [skip ci]
# [5.21.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.21.0-dev.8...v5.21.0-dev.9) (2025-04-21)

### Bug Fixes

* **YouTube - Hide video action buttons:** Hide A/B layout buttons ([15053e2](15053e2b68))
2025-04-21 20:16:36 +00:00
LisoUseInAIKyrios
15053e2b68 fix(YouTube - Hide video action buttons): Hide A/B layout buttons 2025-04-21 22:13:04 +02:00
semantic-release-bot
e5b6aac018 chore: Release v5.21.0-dev.8 [skip ci]
# [5.21.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.21.0-dev.7...v5.21.0-dev.8) (2025-04-20)

### Bug Fixes

* **Wide search bar:** Fix patching `19.16.39` ([d7c9dd0](d7c9dd0f77))
2025-04-20 11:31:26 +00:00
LisoUseInAIKyrios
d7c9dd0f77 fix(Wide search bar): Fix patching 19.16.39 2025-04-20 13:27:55 +02:00
github-actions[bot]
a0eb6d5fdb chore: Sync translations (#4833) 2025-04-20 13:27:33 +02:00
semantic-release-bot
55c5eb3d14 chore: Release v5.21.0-dev.7 [skip ci]
# [5.21.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.21.0-dev.6...v5.21.0-dev.7) (2025-04-20)

### Bug Fixes

* **YouTube - Change start page:** Add option to always override start page on app launch ([#4832](https://github.com/ReVanced/revanced-patches/issues/4832)) ([896de89](896de8910a))
2025-04-20 08:24:42 +00:00
LisoUseInAIKyrios
896de8910a fix(YouTube - Change start page): Add option to always override start page on app launch (#4832) 2025-04-20 10:21:03 +02:00
semantic-release-bot
e2a7e25c66 chore: Release v5.21.0-dev.6 [skip ci]
# [5.21.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.21.0-dev.5...v5.21.0-dev.6) (2025-04-19)

### Bug Fixes

* **YouTube - Wide search bar:** Do not force phone layout for tablet devices ([#4827](https://github.com/ReVanced/revanced-patches/issues/4827)) ([77ea5c4](77ea5c4033))
2025-04-19 16:36:29 +00:00
LisoUseInAIKyrios
77ea5c4033 fix(YouTube - Wide search bar): Do not force phone layout for tablet devices (#4827) 2025-04-19 18:33:12 +02:00
github-actions[bot]
6eea2354f5 chore: Sync translations (#4828) 2025-04-19 12:00:28 +02:00
semantic-release-bot
cce21c4d4a chore: Release v5.21.0-dev.5 [skip ci]
# [5.21.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.21.0-dev.4...v5.21.0-dev.5) (2025-04-18)

### Bug Fixes

* `Hide ADB status` patch ([#4814](https://github.com/ReVanced/revanced-patches/issues/4814)) ([5e069bd](5e069bde90))
2025-04-18 09:18:56 +00:00
1fexd
5e069bde90 fix: Hide ADB status patch (#4814) 2025-04-18 11:15:05 +02:00
github-actions[bot]
6a49208982 chore: Sync translations (#4822) 2025-04-18 09:55:15 +02:00
Snow
404bb2e86e chore(YouTube - Return YouTube Dislike): Clarify settings text for estimated likes (#4818) 2025-04-17 19:10:37 +02:00
github-actions[bot]
bc869fe359 chore: Sync translations (#4820) 2025-04-17 19:09:35 +02:00
semantic-release-bot
7d166cf82c chore: Release v5.21.0-dev.4 [skip ci]
# [5.21.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.21.0-dev.3...v5.21.0-dev.4) (2025-04-17)

### Bug Fixes

* **YouTube - Disable auto captions:** Correctly hide captions with YT 20.12 ([8efbaae](8efbaae65c))
2025-04-17 17:06:19 +00:00
LisoUseInAIKyrios
8efbaae65c fix(YouTube - Disable auto captions): Correctly hide captions with YT 20.12 2025-04-17 19:02:52 +02:00
LisoUseInAIKyrios
e27ab23279 chore(YouTube): Add debug text to toast text if user forgot they enabled debugging 2025-04-16 14:56:31 +02:00
semantic-release-bot
ce42604083 chore: Release v5.21.0-dev.3 [skip ci]
# [5.21.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.21.0-dev.2...v5.21.0-dev.3) (2025-04-16)

### Features

* **X / Twitter:** Support version `10.86.0-release.0` ([#4805](https://github.com/ReVanced/revanced-patches/issues/4805)) ([fc6282d](fc6282d0cb))
2025-04-16 11:09:43 +00:00
cyberboh
fc6282d0cb feat(X / Twitter): Support version 10.86.0-release.0 (#4805) 2025-04-16 13:06:30 +02:00
semantic-release-bot
0559fc7fd0 chore: Release v5.21.0-dev.2 [skip ci]
# [5.21.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.21.0-dev.1...v5.21.0-dev.2) (2025-04-16)

### Features

* Add `Hide ADB status` patch ([#4585](https://github.com/ReVanced/revanced-patches/issues/4585)) ([7cc6995](7cc6995682))
2025-04-16 08:58:30 +00:00
1fexd
7cc6995682 feat: Add Hide ADB status patch (#4585)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-04-16 10:55:07 +02:00
semantic-release-bot
476f13bf98 chore: Release v5.21.0-dev.1 [skip ci]
# [5.21.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.20.1...v5.21.0-dev.1) (2025-04-16)

### Features

* **YouTube:** Support version `20.12.46` ([#4779](https://github.com/ReVanced/revanced-patches/issues/4779)) ([f216e16](f216e16c0b))
2025-04-16 08:28:15 +00:00
LisoUseInAIKyrios
f216e16c0b feat(YouTube): Support version 20.12.46 (#4779) 2025-04-16 10:24:35 +02:00
semantic-release-bot
f2a8789649 chore: Release v5.20.1 [skip ci]
## [5.20.1](https://github.com/ReVanced/revanced-patches/compare/v5.20.0...v5.20.1) (2025-04-15)

### Bug Fixes

* **Spotify - Custom theme:** Support latest app target ([#4800](https://github.com/ReVanced/revanced-patches/issues/4800)) ([2393d0a](2393d0a8f5))
2025-04-15 16:41:52 +00:00
LisoUseInAIKyrios
5973b64f52 chore: Merge branch dev to main (#4801) 2025-04-15 18:38:29 +02:00
semantic-release-bot
102036706e chore: Release v5.20.1-dev.1 [skip ci]
## [5.20.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.20.0...v5.20.1-dev.1) (2025-04-15)

### Bug Fixes

* **Spotify - Custom theme:** Support latest app target ([#4800](https://github.com/ReVanced/revanced-patches/issues/4800)) ([2393d0a](2393d0a8f5))
2025-04-15 16:34:50 +00:00
LisoUseInAIKyrios
2393d0a8f5 fix(Spotify - Custom theme): Support latest app target (#4800) 2025-04-15 18:30:55 +02:00
LisoUseInAIKyrios
aea29b9522 refactor(Spotify - Spoof package info): Replace installer package name of legacy target (#4797) 2025-04-15 17:38:47 +02:00
semantic-release-bot
4db8ef7079 chore: Release v5.20.0 [skip ci]
# [5.20.0](https://github.com/ReVanced/revanced-patches/compare/v5.19.1...v5.20.0) (2025-04-15)

### Bug Fixes

* **Duolingo - Hide ads:**  Support lastest app release ([#4790](https://github.com/ReVanced/revanced-patches/issues/4790)) ([3d6958f](3d6958f157))
* **Spotify - Unlock Spotify Premium:** Remove premium restriction for 'Spotify Connect' ([#4782](https://github.com/ReVanced/revanced-patches/issues/4782)) ([50f5b1a](50f5b1ac54))
* **Spotify:** Fix login by replacing `Spoof signature` patch with new `Spoof package info` patch ([#4794](https://github.com/ReVanced/revanced-patches/issues/4794)) ([0f687ec](0f687ecfd3))
* **YouTube - Remove background playback restrictions:** Restore PiP button functionality after screen is unlocked ([b4e8540](b4e8540bbc))

### Features

* Add `Set target SDK version 34` patch (Disable edge-to-edge display) ([#4780](https://github.com/ReVanced/revanced-patches/issues/4780)) ([9db67a6](9db67a6eb2))
* **Spotify - Custom theme:** Add option to use unmodified player background gradient ([#4741](https://github.com/ReVanced/revanced-patches/issues/4741)) ([c510931](c510931eb0))
* **YouTube - Swipe controls:** Add option to change volume swipe sensitivity (step size) ([#4557](https://github.com/ReVanced/revanced-patches/issues/4557)) ([5ebd449](5ebd449f1f))
2025-04-15 11:02:58 +00:00
oSumAtrIX
7fbd26ccad chore: Merge branch dev to main (#4781) 2025-04-15 12:59:44 +02:00
github-actions[bot]
91995ea01d chore: Sync translations (#4795)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-04-15 12:57:13 +02:00
semantic-release-bot
86f867fe97 chore: Release v5.20.0-dev.7 [skip ci]
# [5.20.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.20.0-dev.6...v5.20.0-dev.7) (2025-04-15)

### Bug Fixes

* **Spotify:** Fix login by replacing `Spoof signature` patch with new `Spoof package info` patch ([#4794](https://github.com/ReVanced/revanced-patches/issues/4794)) ([0f687ec](0f687ecfd3))
2025-04-15 10:56:12 +00:00
oSumAtrIX
0f687ecfd3 fix(Spotify): Fix login by replacing Spoof signature patch with new Spoof package info patch (#4794)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-04-15 12:53:26 +02:00
semantic-release-bot
6c8b7d09c1 chore: Release v5.20.0-dev.6 [skip ci]
# [5.20.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.20.0-dev.5...v5.20.0-dev.6) (2025-04-15)

### Bug Fixes

* **Duolingo - Hide ads:**  Support lastest app release ([#4790](https://github.com/ReVanced/revanced-patches/issues/4790)) ([3d6958f](3d6958f157))
2025-04-15 07:27:06 +00:00
hoodles
3d6958f157 fix(Duolingo - Hide ads): Support lastest app release (#4790) 2025-04-15 09:24:29 +02:00
semantic-release-bot
43d7cc7374 chore: Release v5.20.0-dev.5 [skip ci]
# [5.20.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.20.0-dev.4...v5.20.0-dev.5) (2025-04-14)

### Features

* **YouTube - Swipe controls:** Add option to change volume swipe sensitivity (step size) ([#4557](https://github.com/ReVanced/revanced-patches/issues/4557)) ([5ebd449](5ebd449f1f))
2025-04-14 08:46:51 +00:00
Kamil Kras
5ebd449f1f feat(YouTube - Swipe controls): Add option to change volume swipe sensitivity (step size) (#4557)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-04-14 10:43:21 +02:00
semantic-release-bot
346a061df8 chore: Release v5.20.0-dev.4 [skip ci]
# [5.20.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.20.0-dev.3...v5.20.0-dev.4) (2025-04-14)

### Bug Fixes

* **Spotify - Unlock Spotify Premium:** Remove premium restriction for 'Spotify Connect' ([#4782](https://github.com/ReVanced/revanced-patches/issues/4782)) ([50f5b1a](50f5b1ac54))
2025-04-14 08:10:14 +00:00
semantic-release-bot
13e490a422 chore: Release v5.20.0-dev.3 [skip ci]
# [5.20.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.20.0-dev.2...v5.20.0-dev.3) (2025-04-13)

### Bug Fixes

* **YouTube - Remove background playback restrictions:** Restore PiP button functionality after screen is unlocked ([b4e8540](b4e8540bbc))
2025-04-13 22:11:10 +00:00
LisoUseInAIKyrios
b4e8540bbc fix(YouTube - Remove background playback restrictions): Restore PiP button functionality after screen is unlocked 2025-04-14 00:07:35 +02:00
github-actions[bot]
775c1baec2 chore: Sync translations (#4784) 2025-04-14 00:05:17 +02:00
semantic-release-bot
9419fb8ec4 chore: Release v5.20.0-dev.2 [skip ci]
# [5.20.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.20.0-dev.1...v5.20.0-dev.2) (2025-04-13)

### Features

* **Spotify - Custom theme:** Add option to use unmodified player background gradient ([#4741](https://github.com/ReVanced/revanced-patches/issues/4741)) ([c510931](c510931eb0))
2025-04-13 19:29:33 +00:00
Nuckyz
c510931eb0 feat(Spotify - Custom theme): Add option to use unmodified player background gradient (#4741)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-04-13 21:26:59 +02:00
semantic-release-bot
7160699384 chore: Release v5.20.0-dev.1 [skip ci]
# [5.20.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.19.1...v5.20.0-dev.1) (2025-04-13)

### Features

* Add `Set target SDK version 34` patch (Disable edge-to-edge display) ([#4780](https://github.com/ReVanced/revanced-patches/issues/4780)) ([9db67a6](9db67a6eb2))
2025-04-13 16:26:18 +00:00
LisoUseInAIKyrios
9db67a6eb2 feat: Add Set target SDK version 34 patch (Disable edge-to-edge display) (#4780)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-04-13 18:23:45 +02:00
semantic-release-bot
e684d87dd3 chore: Release v5.19.1 [skip ci]
## [5.19.1](https://github.com/ReVanced/revanced-patches/compare/v5.19.0...v5.19.1) (2025-04-12)

### Bug Fixes

* **Google Photos:** Restore patching with ReVanced Manager ([#4773](https://github.com/ReVanced/revanced-patches/issues/4773)) ([d451bc6](d451bc6d6d))
* **Spotify:** Restore patching with ReVanced Manager ([#4769](https://github.com/ReVanced/revanced-patches/issues/4769)) ([89d44da](89d44da171))
2025-04-12 19:30:34 +00:00
LisoUseInAIKyrios
2d1752a1eb chore: Merge branch dev to main (#4772) 2025-04-12 21:27:49 +02:00
semantic-release-bot
c9ff7092fe chore: Release v5.19.1-dev.2 [skip ci]
## [5.19.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.19.1-dev.1...v5.19.1-dev.2) (2025-04-12)

### Bug Fixes

* **Google Photos:** Restore patching with ReVanced Manager ([#4773](https://github.com/ReVanced/revanced-patches/issues/4773)) ([d451bc6](d451bc6d6d))
2025-04-12 19:23:37 +00:00
LisoUseInAIKyrios
d451bc6d6d fix(Google Photos): Restore patching with ReVanced Manager (#4773) 2025-04-12 21:20:36 +02:00
semantic-release-bot
741fd36872 chore: Release v5.19.1-dev.1 [skip ci]
## [5.19.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.19.0...v5.19.1-dev.1) (2025-04-12)

### Bug Fixes

* **Spotify:** Restore patching with ReVanced Manager ([#4769](https://github.com/ReVanced/revanced-patches/issues/4769)) ([89d44da](89d44da171))
2025-04-12 19:04:28 +00:00
semantic-release-bot
517f8cf59a chore: Release v5.19.0 [skip ci]
# [5.19.0](https://github.com/ReVanced/revanced-patches/compare/v5.18.0...v5.19.0) (2025-04-12)

### Bug Fixes

* **Google Photos - Restore hidden 'Back up while charging' toggle:** Constrain to last working app target ([#4761](https://github.com/ReVanced/revanced-patches/issues/4761)) ([e6ae55f](e6ae55fa99))
* **Google Photos:** Remove obsolete non functional patch `Restore hidden 'Back up while charging' toggle` ([#4764](https://github.com/ReVanced/revanced-patches/issues/4764)) ([f68d06d](f68d06dbf3))
* **Spotify - Custom theme:** Override more color resources ([#4690](https://github.com/ReVanced/revanced-patches/issues/4690)) ([49c8499](49c849979f))
* **Spotify - Unlock Spotify Premium:** Remove restrictions for Google voice assistant ([#4702](https://github.com/ReVanced/revanced-patches/issues/4702)) ([d573386](d573386e0f))
* **Spotify:** Remove ads sections from home ([#4722](https://github.com/ReVanced/revanced-patches/issues/4722)) ([628d184](628d18489c))
* **Twitter - Hide recommended users:** Make hiding work again by filtering for new entryId prefix ([#4456](https://github.com/ReVanced/revanced-patches/issues/4456)) ([3d68c06](3d68c06146))
* **YouTube - Hide layout components:** Do not hide video description music/game links if hide horizontal shelves is enabled ([6005c97](6005c97bf5))
* **YouTube - Hide player flyout menu items:** Show more detailed summary text for 'Hide Audio track' if using Android spoof client ([#4756](https://github.com/ReVanced/revanced-patches/issues/4756)) ([1abed31](1abed31968))
* **YouTube - Remove background playback restrictions:** Do not show media controls when playing Shorts from the feed ([318b55b](318b55b8fe))
* **YouTube - Return YouTube Dislike:** Correctly update label after disliking a Short with 20.07 ([0bff207](0bff207efc))
* **YouTube - Return YouTube Dislike:** Fix inconsistent label after disliking a Short ([3d67d90](3d67d90473))
* **YouTube - Seekbar:** Correctly hide the feed seekbar with target 20.07 ([2035c9e](2035c9e2e9))
* **YouTube:** Combine multiple seekbar patches into a single patch ([#4705](https://github.com/ReVanced/revanced-patches/issues/4705)) ([37984b8](37984b8b99))

### Features

* **Angulus:** Add `Hide ads` patch ([#4604](https://github.com/ReVanced/revanced-patches/issues/4604)) ([2d7b1b0](2d7b1b09af))
* **Messenger:** Add `Remove Meta AI tab` patch ([#4726](https://github.com/ReVanced/revanced-patches/issues/4726)) ([de0d11f](de0d11fcfb))
* **Photomath:** Support latest version ([#4672](https://github.com/ReVanced/revanced-patches/issues/4672)) ([ef3d5ba](ef3d5bafd5))
* **Proton Mail:** Add `Remove 'Sent from' signature` patch ([#4514](https://github.com/ReVanced/revanced-patches/issues/4514)) ([8ed9d5b](8ed9d5bf08))
* **Spotify:** Add `Check environment` patch ([#4765](https://github.com/ReVanced/revanced-patches/issues/4765)) ([854a18f](854a18ff72))
* **Spotify:** Add limited support for version `8.6.98.900` (last version that supports Kenwood and Pioneer car stereos) ([#4750](https://github.com/ReVanced/revanced-patches/issues/4750)) ([e30f593](e30f593af0))
* **Strava - Disable subscription suggestions:** Make compatible with latest version ([#4739](https://github.com/ReVanced/revanced-patches/issues/4739)) ([654587a](654587a75e))
* **YouTube - Settings:** Add icons to the ReVanced settings ([#4496](https://github.com/ReVanced/revanced-patches/issues/4496)) ([fdefb67](fdefb67d02))
2025-04-12 13:27:56 +00:00
oSumAtrIX
b78fb24435 chore: Merge branch dev to main (#4699)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: semantic-release-bot <semantic-release-bot@martynus.net>
Co-authored-by: Denis Kerner <11692788+dtricks@users.noreply.github.com>
Co-authored-by: Denis Kerner <denis.kerner@vodafone.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
Co-authored-by: Dawid Krajcarz <80264606+drobotk@users.noreply.github.com>
Co-authored-by: Jakub Blažej <trogper@gmail.com>
Co-authored-by: MarcaD <152095496+MarcaDian@users.noreply.github.com>
Co-authored-by: Aoife McCullough <92225779+AoifeMcCull@users.noreply.github.com>
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Co-authored-by: user5095 <140261471+user5095@users.noreply.github.com>
Co-authored-by: Brosssh <44944126+Brosssh@users.noreply.github.com>
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: oSumAtrIX <github@osumatrix.me>
2025-04-12 15:24:43 +02:00
github-actions[bot]
a3faccb21b chore: Sync translations (#4766)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-04-12 15:19:43 +02:00
semantic-release-bot
5f0fddc122 chore: Release v5.19.0-dev.17 [skip ci]
# [5.19.0-dev.17](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.16...v5.19.0-dev.17) (2025-04-12)

### Features

* **Spotify:** Add `Check environment` patch ([#4765](https://github.com/ReVanced/revanced-patches/issues/4765)) ([854a18f](854a18ff72))
2025-04-12 12:56:01 +00:00
oSumAtrIX
854a18ff72 feat(Spotify): Add Check environment patch (#4765) 2025-04-12 14:53:27 +02:00
semantic-release-bot
b994a16bdc chore: Release v5.19.0-dev.16 [skip ci]
# [5.19.0-dev.16](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.15...v5.19.0-dev.16) (2025-04-11)

### Bug Fixes

* **Google Photos:** Remove obsolete non functional patch `Restore hidden 'Back up while charging' toggle` ([#4764](https://github.com/ReVanced/revanced-patches/issues/4764)) ([f68d06d](f68d06dbf3))
2025-04-11 20:15:09 +00:00
LisoUseInAIKyrios
f68d06dbf3 fix(Google Photos): Remove obsolete non functional patch Restore hidden 'Back up while charging' toggle (#4764) 2025-04-11 22:12:13 +02:00
semantic-release-bot
04c6a2e5f4 chore: Release v5.19.0-dev.15 [skip ci]
# [5.19.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.14...v5.19.0-dev.15) (2025-04-11)

### Bug Fixes

* **Google Photos - Restore hidden 'Back up while charging' toggle:** Constrain to last working app target ([#4761](https://github.com/ReVanced/revanced-patches/issues/4761)) ([e6ae55f](e6ae55fa99))
2025-04-11 19:42:49 +00:00
LisoUseInAIKyrios
e6ae55fa99 fix(Google Photos - Restore hidden 'Back up while charging' toggle): Constrain to last working app target (#4761) 2025-04-11 21:40:17 +02:00
github-actions[bot]
fb62474ff4 chore: Sync translations (#4763)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-04-11 21:39:34 +02:00
semantic-release-bot
e084f01fd0 chore: Release v5.19.0-dev.14 [skip ci]
# [5.19.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.13...v5.19.0-dev.14) (2025-04-11)

### Bug Fixes

* **Spotify - Unlock Spotify Premium:** Remove restrictions for Google voice assistant ([#4702](https://github.com/ReVanced/revanced-patches/issues/4702)) ([d573386](d573386e0f))
2025-04-11 19:36:38 +00:00
Brosssh
d573386e0f fix(Spotify - Unlock Spotify Premium): Remove restrictions for Google voice assistant (#4702)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-04-11 21:33:59 +02:00
semantic-release-bot
0f3aeb35e5 chore: Release v5.19.0-dev.13 [skip ci]
# [5.19.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.12...v5.19.0-dev.13) (2025-04-11)

### Features

* **Spotify:** Add limited support for version `8.6.98.900` (last version that supports Kenwood and Pioneer car stereos) ([#4750](https://github.com/ReVanced/revanced-patches/issues/4750)) ([e30f593](e30f593af0))
2025-04-11 19:33:53 +00:00
LisoUseInAIKyrios
e30f593af0 feat(Spotify): Add limited support for version 8.6.98.900 (last version that supports Kenwood and Pioneer car stereos) (#4750) 2025-04-11 21:30:47 +02:00
semantic-release-bot
df965b8a9b chore: Release v5.19.0-dev.12 [skip ci]
# [5.19.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.11...v5.19.0-dev.12) (2025-04-11)

### Features

* **Strava - Disable subscription suggestions:** Make compatible with latest version ([#4739](https://github.com/ReVanced/revanced-patches/issues/4739)) ([654587a](654587a75e))
2025-04-11 18:40:46 +00:00
user5095
654587a75e feat(Strava - Disable subscription suggestions): Make compatible with latest version (#4739) 2025-04-11 20:38:07 +02:00
LisoUseInAIKyrios
9956833781 chore(YouTube): Remove obsolete targets overlooked in last version bump 2025-04-11 20:05:45 +02:00
semantic-release-bot
c585b26188 chore: Release v5.19.0-dev.11 [skip ci]
# [5.19.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.10...v5.19.0-dev.11) (2025-04-10)

### Features

* **Messenger:** Add `Remove Meta AI tab` patch ([#4726](https://github.com/ReVanced/revanced-patches/issues/4726)) ([de0d11f](de0d11fcfb))
2025-04-10 21:11:47 +00:00
Dawid Krajcarz
de0d11fcfb feat(Messenger): Add Remove Meta AI tab patch (#4726) 2025-04-10 23:08:43 +02:00
semantic-release-bot
d321504fcf chore: Release v5.19.0-dev.10 [skip ci]
# [5.19.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.9...v5.19.0-dev.10) (2025-04-10)

### Bug Fixes

* **YouTube - Hide layout components:** Do not hide video description music/game links if hide horizontal shelves is enabled ([6005c97](6005c97bf5))
2025-04-10 17:13:49 +00:00
LisoUseInAIKyrios
6005c97bf5 fix(YouTube - Hide layout components): Do not hide video description music/game links if hide horizontal shelves is enabled 2025-04-10 19:11:11 +02:00
semantic-release-bot
e404d84c83 chore: Release v5.19.0-dev.9 [skip ci]
# [5.19.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.8...v5.19.0-dev.9) (2025-04-10)

### Bug Fixes

* **YouTube - Hide player flyout menu items:** Show more detailed summary text for 'Hide Audio track' if using Android spoof client ([#4756](https://github.com/ReVanced/revanced-patches/issues/4756)) ([1abed31](1abed31968))
2025-04-10 14:08:34 +00:00
LisoUseInAIKyrios
1abed31968 fix(YouTube - Hide player flyout menu items): Show more detailed summary text for 'Hide Audio track' if using Android spoof client (#4756) 2025-04-10 16:05:42 +02:00
semantic-release-bot
a75a88d3c6 chore: Release v5.19.0-dev.8 [skip ci]
# [5.19.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.7...v5.19.0-dev.8) (2025-04-09)

### Bug Fixes

* **YouTube - Return YouTube Dislike:** Fix inconsistent label after disliking a Short ([3d67d90](3d67d90473))
2025-04-09 18:38:26 +00:00
LisoUseInAIKyrios
3d67d90473 fix(YouTube - Return YouTube Dislike): Fix inconsistent label after disliking a Short 2025-04-09 20:32:27 +02:00
github-actions[bot]
fa1e137a43 chore: Sync translations (#4753) 2025-04-09 20:31:46 +02:00
semantic-release-bot
ac71a53c73 chore: Release v5.19.0-dev.7 [skip ci]
# [5.19.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.6...v5.19.0-dev.7) (2025-04-07)

### Bug Fixes

* **YouTube - Return YouTube Dislike:** Correctly update label after disliking a Short with 20.07 ([0bff207](0bff207efc))
2025-04-07 14:05:29 +00:00
LisoUseInAIKyrios
0bff207efc fix(YouTube - Return YouTube Dislike): Correctly update label after disliking a Short with 20.07 2025-04-07 16:02:44 +02:00
semantic-release-bot
e1a8b388a5 chore: Release v5.19.0-dev.6 [skip ci]
# [5.19.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.5...v5.19.0-dev.6) (2025-04-04)

### Bug Fixes

* **Spotify:** Remove ads sections from home ([#4722](https://github.com/ReVanced/revanced-patches/issues/4722)) ([628d184](628d18489c))
2025-04-04 12:35:54 +00:00
Nuckyz
628d18489c fix(Spotify): Remove ads sections from home (#4722)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-04-04 14:32:40 +02:00
semantic-release-bot
36772b8b2e chore: Release v5.19.0-dev.5 [skip ci]
# [5.19.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.4...v5.19.0-dev.5) (2025-04-02)

### Bug Fixes

* **Spotify - Custom theme:** Override more color resources ([#4690](https://github.com/ReVanced/revanced-patches/issues/4690)) ([49c8499](49c849979f))
2025-04-02 17:17:59 +00:00
Nuckyz
49c849979f fix(Spotify - Custom theme): Override more color resources (#4690) 2025-04-02 19:14:17 +02:00
semantic-release-bot
0bdb8cdf2b chore: Release v5.19.0-dev.4 [skip ci]
# [5.19.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.3...v5.19.0-dev.4) (2025-04-02)

### Bug Fixes

* **YouTube - Seekbar:** Correctly hide the feed seekbar with target 20.07 ([2035c9e](2035c9e2e9))
2025-04-02 15:58:10 +00:00
LisoUseInAIKyrios
2035c9e2e9 fix(YouTube - Seekbar): Correctly hide the feed seekbar with target 20.07 2025-04-02 17:54:17 +02:00
semantic-release-bot
7cb38fd3fc chore: Release v5.19.0-dev.3 [skip ci]
# [5.19.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.2...v5.19.0-dev.3) (2025-04-02)

### Features

* **Proton Mail:** Add `Remove 'Sent from' signature` patch ([#4514](https://github.com/ReVanced/revanced-patches/issues/4514)) ([8ed9d5b](8ed9d5bf08))
2025-04-02 10:00:45 +00:00
Aoife McCullough
8ed9d5bf08 feat(Proton Mail): Add Remove 'Sent from' signature patch (#4514) 2025-04-02 11:58:10 +02:00
semantic-release-bot
cd467d6244 chore: Release v5.19.0-dev.2 [skip ci]
# [5.19.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.1...v5.19.0-dev.2) (2025-04-02)

### Features

* **YouTube - Settings:** Add icons to the ReVanced settings ([#4496](https://github.com/ReVanced/revanced-patches/issues/4496)) ([fdefb67](fdefb67d02))
2025-04-02 09:42:46 +00:00
MarcaD
fdefb67d02 feat(YouTube - Settings): Add icons to the ReVanced settings (#4496) 2025-04-02 11:39:53 +02:00
semantic-release-bot
5274cd18f0 chore: Release v5.19.0-dev.1 [skip ci]
# [5.19.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.18.1-dev.2...v5.19.0-dev.1) (2025-04-01)

### Bug Fixes

* **Twitter - Hide recommended users:** Make hiding work again by filtering for new entryId prefix ([#4456](https://github.com/ReVanced/revanced-patches/issues/4456)) ([3d68c06](3d68c06146))

### Features

* **Angulus:** Add `Hide ads` patch ([#4604](https://github.com/ReVanced/revanced-patches/issues/4604)) ([2d7b1b0](2d7b1b09af))
* **Photomath:** Support latest version ([#4672](https://github.com/ReVanced/revanced-patches/issues/4672)) ([ef3d5ba](ef3d5bafd5))
2025-04-01 22:33:38 +00:00
Jakub Blažej
3d68c06146 fix(Twitter - Hide recommended users): Make hiding work again by filtering for new entryId prefix (#4456) 2025-04-02 00:31:02 +02:00
Dawid Krajcarz
ef3d5bafd5 feat(Photomath): Support latest version (#4672) 2025-04-02 00:28:57 +02:00
Denis Kerner
2d7b1b09af feat(Angulus): Add Hide ads patch (#4604)
Co-authored-by: Denis Kerner <denis.kerner@vodafone.com>
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-04-02 00:27:59 +02:00
semantic-release-bot
0572d48fde chore: Release v5.18.1-dev.2 [skip ci]
## [5.18.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.18.1-dev.1...v5.18.1-dev.2) (2025-04-01)

### Bug Fixes

* **YouTube:** Combine multiple seekbar patches into a single patch ([#4705](https://github.com/ReVanced/revanced-patches/issues/4705)) ([37984b8](37984b8b99))
2025-04-01 19:00:31 +00:00
LisoUseInAIKyrios
37984b8b99 fix(YouTube): Combine multiple seekbar patches into a single patch (#4705) 2025-04-01 20:57:03 +02:00
github-actions[bot]
6e63193f06 chore: Sync translations (#4713) 2025-04-01 20:51:16 +02:00
LisoUseInAIKyrios
b2384b22a5 refactor: Add more opcodes to findFreeRegister 2025-04-01 20:17:00 +02:00
semantic-release-bot
ccb76983ff chore: Release v5.18.1-dev.1 [skip ci]
## [5.18.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.18.0...v5.18.1-dev.1) (2025-03-31)

### Bug Fixes

* **YouTube - Remove background playback restrictions:** Do not show media controls when playing Shorts from the feed ([318b55b](318b55b8fe))
2025-03-31 13:45:18 +00:00
LisoUseInAIKyrios
318b55b8fe fix(YouTube - Remove background playback restrictions): Do not show media controls when playing Shorts from the feed 2025-03-31 15:36:01 +02:00
github-actions[bot]
49ade9efbc chore: Sync translations (#4703) 2025-03-31 15:35:28 +02:00
LisoUseInAIKyrios
d77515bd68 refactor: Add helper method to find a free register for a specific insert index (#4693) 2025-03-30 18:37:54 +02:00
semantic-release-bot
087bf1e152 chore: Release v5.18.0 [skip ci]
# [5.18.0](https://github.com/ReVanced/revanced-patches/compare/v5.17.0...v5.18.0) (2025-03-28)

### Bug Fixes

* **Spotify:** Ignore optional attributes if not present ([#4688](https://github.com/ReVanced/revanced-patches/issues/4688)) ([84f5854](84f585492e))

### Features

* **YouTube:** Support version `20.07.39` ([#4677](https://github.com/ReVanced/revanced-patches/issues/4677)) ([842ba4f](842ba4fc4d))
2025-03-28 18:51:44 +00:00
LisoUseInAIKyrios
c2994d583d chore: Merge branch dev to main (#4686) 2025-03-28 19:48:32 +01:00
semantic-release-bot
127b0a63fe chore: Release v5.18.0-dev.2 [skip ci]
# [5.18.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.18.0-dev.1...v5.18.0-dev.2) (2025-03-28)

### Bug Fixes

* **Spotify:** Ignore optional attributes if not present ([#4688](https://github.com/ReVanced/revanced-patches/issues/4688)) ([84f5854](84f585492e))
2025-03-28 18:36:38 +00:00
github-actions[bot]
27aafd0ee1 chore: Sync translations (#4689) 2025-03-28 19:31:52 +01:00
semantic-release-bot
49c54c0e54 chore: Release v5.18.0-dev.1 [skip ci]
# [5.18.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.17.0...v5.18.0-dev.1) (2025-03-28)

### Features

* **YouTube:** Support version `20.07.39` ([#4677](https://github.com/ReVanced/revanced-patches/issues/4677)) ([842ba4f](842ba4fc4d))
2025-03-28 17:01:42 +00:00
LisoUseInAIKyrios
842ba4fc4d feat(YouTube): Support version 20.07.39 (#4677) 2025-03-28 17:49:12 +01:00
semantic-release-bot
66ecadce4f chore: Release v5.17.0 [skip ci]
# [5.17.0](https://github.com/ReVanced/revanced-patches/compare/v5.16.1...v5.17.0) (2025-03-28)

### Bug Fixes

* **Facebook - Hide 'Sponsored Stories':** Constrain patch to latest compatible version ([#4657](https://github.com/ReVanced/revanced-patches/issues/4657)) ([4d910fe](4d910fea93))
* **Spotify - Unlock Premium:** Override additional attributes ([#4651](https://github.com/ReVanced/revanced-patches/issues/4651)) ([ca4f960](ca4f960171))
* **Spotify - Unlock Premium:** Use correct patch description convention ([a486522](a4865228f8))
* **X / Twitter:** Constrain patches to latest compatible versions ([#4683](https://github.com/ReVanced/revanced-patches/issues/4683)) ([497291c](497291c478))
* **YouTube - Navigation buttons:** Add user dialog message to 'Disable translucent status bar' ([bf91e12](bf91e127d8))

### Features

* **Spotify - Unlock Premium:** Disable the "Spotify Premium" upsell experiment in context menus ([c84be12](c84be120bd))
2025-03-28 16:39:31 +00:00
LisoUseInAIKyrios
73ca04da5e chore: Merge branch dev to main (#4658) 2025-03-28 17:36:31 +01:00
semantic-release-bot
a5d26208c1 chore: Release v5.17.0-dev.4 [skip ci]
# [5.17.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.17.0-dev.3...v5.17.0-dev.4) (2025-03-28)

### Bug Fixes

* **X / Twitter:** Constrain patches to latest compatible versions ([#4683](https://github.com/ReVanced/revanced-patches/issues/4683)) ([497291c](497291c478))
2025-03-28 16:14:49 +00:00
LisoUseInAIKyrios
497291c478 fix(X / Twitter): Constrain patches to latest compatible versions (#4683) 2025-03-28 17:11:30 +01:00
github-actions[bot]
b24278a544 chore: Sync translations (#4680) 2025-03-28 15:03:07 +01:00
semantic-release-bot
135f9ead3c chore: Release v5.17.0-dev.3 [skip ci]
# [5.17.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.17.0-dev.2...v5.17.0-dev.3) (2025-03-28)

### Bug Fixes

* **Spotify - Unlock Premium:** Override additional attributes ([#4651](https://github.com/ReVanced/revanced-patches/issues/4651)) ([ca4f960](ca4f960171))
2025-03-28 10:55:04 +00:00
xC3FFF0E
ca4f960171 fix(Spotify - Unlock Premium): Override additional attributes (#4651)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-03-28 11:52:23 +01:00
semantic-release-bot
7f228cc535 chore: Release v5.17.0-dev.2 [skip ci]
# [5.17.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.17.0-dev.1...v5.17.0-dev.2) (2025-03-27)

### Bug Fixes

* **YouTube - Navigation buttons:** Add user dialog message to 'Disable translucent status bar' ([bf91e12](bf91e127d8))
2025-03-27 19:23:17 +00:00
LisoUseInAIKyrios
bf91e127d8 fix(YouTube - Navigation buttons): Add user dialog message to 'Disable translucent status bar' 2025-03-27 20:20:05 +01:00
semantic-release-bot
f07fc1ad93 chore: Release v5.17.0-dev.1 [skip ci]
# [5.17.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.16.2-dev.1...v5.17.0-dev.1) (2025-03-27)

### Bug Fixes

* **Spotify - Unlock Premium:** Use correct patch description convention ([a486522](a4865228f8))

### Features

* **Spotify - Unlock Premium:** Disable the "Spotify Premium" upsell experiment in context menus ([c84be12](c84be120bd))
2025-03-27 00:48:00 +00:00
oSumAtrIX
c84be120bd feat(Spotify - Unlock Premium): Disable the "Spotify Premium" upsell experiment in context menus 2025-03-27 01:40:54 +01:00
semantic-release-bot
e67f390e2b chore: Release v5.16.2-dev.1 [skip ci]
## [5.16.2-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.16.1...v5.16.2-dev.1) (2025-03-26)

### Bug Fixes

* **Facebook - Hide 'Sponsored Stories':** Constrain patch to latest compatible version ([#4657](https://github.com/ReVanced/revanced-patches/issues/4657)) ([4d910fe](4d910fea93))
2025-03-26 18:34:18 +00:00
LisoUseInAIKyrios
4d910fea93 fix(Facebook - Hide 'Sponsored Stories'): Constrain patch to latest compatible version (#4657) 2025-03-26 19:30:48 +01:00
semantic-release-bot
72adbe5519 chore: Release v5.16.1 [skip ci]
## [5.16.1](https://github.com/ReVanced/revanced-patches/compare/v5.16.0...v5.16.1) (2025-03-26)

### Bug Fixes

* **Spotify - Unlock Premium:** Override streaming attribute attempting to fix streaming issues ([06be36c](06be36cddf))
2025-03-26 14:10:25 +00:00
oSumAtrIX
54d49b774e chore: Merge branch dev to main (#4650) 2025-03-26 15:08:18 +01:00
semantic-release-bot
283bb31567 chore: Release v5.16.1-dev.1 [skip ci]
## [5.16.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.16.0...v5.16.1-dev.1) (2025-03-26)

### Bug Fixes

* **Spotify - Unlock Premium:** Override streaming attribute attempting to fix streaming issues ([06be36c](06be36cddf))
2025-03-26 13:54:23 +00:00
semantic-release-bot
2724fcbd27 chore: Release v5.16.0 [skip ci]
# [5.16.0](https://github.com/ReVanced/revanced-patches/compare/v5.15.0...v5.16.0) (2025-03-26)

### Bug Fixes

* **YouTube - Settings:** System navigation bar is located above the settings ui on Android 15+ ([54eef22](54eef22ce7))

### Features

* **Spotify:** Add `Unlock premium` patch ([#4644](https://github.com/ReVanced/revanced-patches/issues/4644)) ([5c5a1e4](5c5a1e4b8b))
* **YouTube - Comments:** Add `Hide AI Comments summary` ([#4634](https://github.com/ReVanced/revanced-patches/issues/4634)) ([d5845ab](d5845abd08))
* **YouTube - Video description:** Add `Hide AI-generated video summary` ([#4636](https://github.com/ReVanced/revanced-patches/issues/4636)) ([d8c276c](d8c276cf96))
2025-03-26 03:42:23 +00:00
oSumAtrIX
7c28193579 chore: Merge branch dev to main (#4628)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
Co-authored-by: ILoveOpenSourceApplications <117499019+ILoveOpenSourceApplications@users.noreply.github.com>
Co-authored-by: semantic-release-bot <semantic-release-bot@martynus.net>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Co-authored-by: Brosssh <tiabroch@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-26 04:39:27 +01:00
github-actions[bot]
cd1ee814c4 chore: Sync translations (#4645) 2025-03-26 04:30:29 +01:00
semantic-release-bot
d9ccd73b5f chore: Release v5.16.0-dev.2 [skip ci]
# [5.16.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.16.0-dev.1...v5.16.0-dev.2) (2025-03-26)

### Features

* **Spotify:** Add `Unlock premium` patch ([#4644](https://github.com/ReVanced/revanced-patches/issues/4644)) ([5c5a1e4](5c5a1e4b8b))
2025-03-26 03:27:26 +00:00
oSumAtrIX
5c5a1e4b8b feat(Spotify): Add Unlock premium patch (#4644)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Co-authored-by: Brosssh <tiabroch@gmail.com>
2025-03-26 04:24:47 +01:00
semantic-release-bot
66a2ee2416 chore: Release v5.16.0-dev.1 [skip ci]
# [5.16.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.15.0...v5.16.0-dev.1) (2025-03-24)

### Bug Fixes

* **YouTube - Settings:** System navigation bar is located above the settings ui on Android 15+ ([54eef22](54eef22ce7))

### Features

* **YouTube - Comments:** Add `Hide AI Comments summary` ([#4634](https://github.com/ReVanced/revanced-patches/issues/4634)) ([d5845ab](d5845abd08))
* **YouTube - Video description:** Add `Hide AI-generated video summary` ([#4636](https://github.com/ReVanced/revanced-patches/issues/4636)) ([d8c276c](d8c276cf96))
2025-03-24 16:26:47 +00:00
ILoveOpenSourceApplications
d8c276cf96 feat(YouTube - Video description): Add Hide AI-generated video summary (#4636) 2025-03-24 17:23:57 +01:00
ILoveOpenSourceApplications
d5845abd08 feat(YouTube - Comments): Add Hide AI Comments summary (#4634) 2025-03-24 17:23:12 +01:00
LisoUseInAIKyrios
54eef22ce7 fix(YouTube - Settings): System navigation bar is located above the settings ui on Android 15+ 2025-03-24 17:21:36 +01:00
ILoveOpenSourceApplications
e287bdc59d chore(YouTube): Use consistent strings (#4637) 2025-03-24 17:12:11 +01:00
LisoUseInAIKyrios
20a82ef956 chore: Remove duplicate language entry 2025-03-22 11:01:19 +01:00
semantic-release-bot
1e29da9e06 chore: Release v5.15.0 [skip ci]
# [5.15.0](https://github.com/ReVanced/revanced-patches/compare/v5.14.0...v5.15.0) (2025-03-21)

### Bug Fixes

* **YouTube - Spoof app version:** Change oldest spoof target to 19.01.34 ([54a7afa](54a7afa540))
* **YouTube - Spoof app version:** Remove broken spoof targets that YouTube no longer supports ([#4610](https://github.com/ReVanced/revanced-patches/issues/4610)) ([04a1700](04a170054e))
* **YouTube:** Do not show restart prompt more than once if setting change is canceled ([df838ed](df838ed91d))

### Features

* **YouTube - SponsorBlock:** Add opacity setting to category segment colors ([#4582](https://github.com/ReVanced/revanced-patches/issues/4582)) ([bbf3a34](bbf3a34a2f))
2025-03-21 10:54:27 +00:00
LisoUseInAIKyrios
56e6a90a90 chore: Merge branch dev to main (#4615) 2025-03-21 11:51:03 +01:00
semantic-release-bot
76d32e21c2 chore: Release v5.15.0-dev.4 [skip ci]
# [5.15.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.15.0-dev.3...v5.15.0-dev.4) (2025-03-21)

### Bug Fixes

* **YouTube - Spoof app version:** Change oldest spoof target to 19.01.34 ([54a7afa](54a7afa540))
2025-03-21 09:26:53 +00:00
LisoUseInAIKyrios
54a7afa540 fix(YouTube - Spoof app version): Change oldest spoof target to 19.01.34 2025-03-21 10:23:26 +01:00
LisoUseInAIKyrios
ef86438bac ci: Pull Crowdin strings less often 2025-03-21 10:21:16 +01:00
github-actions[bot]
0683cedac0 chore: Sync translations (#4626) 2025-03-21 10:18:28 +01:00
semantic-release-bot
35753410aa chore: Release v5.15.0-dev.3 [skip ci]
# [5.15.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.15.0-dev.2...v5.15.0-dev.3) (2025-03-20)

### Bug Fixes

* **YouTube:** Do not show restart prompt more than once if setting change is canceled ([df838ed](df838ed91d))
2025-03-20 11:17:31 +00:00
LisoUseInAIKyrios
df838ed91d fix(YouTube): Do not show restart prompt more than once if setting change is canceled 2025-03-20 12:14:31 +01:00
github-actions[bot]
8e494d26d4 chore: Sync translations (#4623) 2025-03-20 12:08:20 +01:00
LisoUseInAIKyrios
7d834e5421 refactor(YouTube - Spoof app version): Allow manually spoofing to 19.01 - 19.25 2025-03-20 09:57:29 +01:00
semantic-release-bot
60a31cf4e1 chore: Release v5.15.0-dev.2 [skip ci]
# [5.15.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.15.0-dev.1...v5.15.0-dev.2) (2025-03-19)

### Bug Fixes

* **YouTube - Spoof app version:** Remove broken spoof targets that YouTube no longer supports ([#4610](https://github.com/ReVanced/revanced-patches/issues/4610)) ([04a1700](04a170054e))
2025-03-19 17:19:19 +00:00
github-actions[bot]
edb8bd66bc chore: Sync translations (#4616) 2025-03-19 18:16:05 +01:00
LisoUseInAIKyrios
04a170054e fix(YouTube - Spoof app version): Remove broken spoof targets that YouTube no longer supports (#4610) 2025-03-19 18:08:51 +01:00
semantic-release-bot
79e6349a69 chore: Release v5.15.0-dev.1 [skip ci]
# [5.15.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.14.0...v5.15.0-dev.1) (2025-03-19)

### Features

* **YouTube - SponsorBlock:** Add opacity setting to category segment colors ([#4582](https://github.com/ReVanced/revanced-patches/issues/4582)) ([bbf3a34](bbf3a34a2f))
2025-03-19 17:06:12 +00:00
LisoUseInAIKyrios
bbf3a34a2f feat(YouTube - SponsorBlock): Add opacity setting to category segment colors (#4582) 2025-03-19 18:02:06 +01:00
github-actions[bot]
1db7c49514 chore: Sync translations (#4614) 2025-03-19 18:00:52 +01:00
semantic-release-bot
ef0506a4f8 chore: Release v5.14.0 [skip ci]
# [5.14.0](https://github.com/ReVanced/revanced-patches/compare/v5.13.0...v5.14.0) (2025-03-09)

### Bug Fixes

* **Boost for reddit - Client spoof:** Use a different user agent to combat Reddit's API issues ([8d0bca3](8d0bca3b03))
* **YouTube - Change form factor:** Restore Automotive form factor watch history menu, channel pages, and community posts ([#4541](https://github.com/ReVanced/revanced-patches/issues/4541)) ([e2de2d8](e2de2d8d44))
* **YouTube - Hide ads:** Hide new type of buttoned ad ([#4528](https://github.com/ReVanced/revanced-patches/issues/4528)) ([67dcd09](67dcd091c4))
* **YouTube - Hide layout components:** Do not hide Movie/Courses start page content if 'Hide horizontal shelves' is enabled ([99879f6](99879f6e0a))
* **YouTube - Theme:** Resolve dark mode startup crash with Android 9.0 ([7adfc63](7adfc637dc))
* **YouTube:** Change language settings menu to use native language names ([#4568](https://github.com/ReVanced/revanced-patches/issues/4568)) ([e9bc201](e9bc201641))
* **YouTube:** Combine `Restore old video quality menu` and `Remember video quality` into `Video quality` patch ([#4552](https://github.com/ReVanced/revanced-patches/issues/4552)) ([ee5c830](ee5c830df8))

### Features

* **Infinity for Reddit:** Add support for package name on IzzyOnDroid ([#4554](https://github.com/ReVanced/revanced-patches/issues/4554)) ([df3dc1c](df3dc1c0b2))
* **Spotify:** Add `Spoof signature` patch ([#4576](https://github.com/ReVanced/revanced-patches/issues/4576)) ([f39e70c](f39e70c648))
* **YouTube - Remember video quality:** Add separate Shorts default quality settings ([#4543](https://github.com/ReVanced/revanced-patches/issues/4543)) ([2a67c31](2a67c312e1))
2025-03-09 12:21:22 +00:00
oSumAtrIX
9b38da35ff chore: Merge branch dev to main (#4540)
Co-authored-by: ILoveOpenSourceApplications <117499019+ILoveOpenSourceApplications@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
Co-authored-by: semantic-release-bot <semantic-release-bot@martynus.net>
Co-authored-by: alieRN <45766489+aliernfrog@users.noreply.github.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-03-09 13:17:57 +01:00
github-actions[bot]
afdb771066 chore: Sync translations (#4577) 2025-03-09 14:14:53 +02:00
semantic-release-bot
1b2b536d2e chore: Release v5.14.0-dev.9 [skip ci]
# [5.14.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.8...v5.14.0-dev.9) (2025-03-09)

### Features

* **Spotify:** Add `Spoof signature` patch ([#4576](https://github.com/ReVanced/revanced-patches/issues/4576)) ([f39e70c](f39e70c648))
2025-03-09 12:10:11 +00:00
oSumAtrIX
f39e70c648 feat(Spotify): Add Spoof signature patch (#4576) 2025-03-09 13:06:53 +01:00
semantic-release-bot
556acdd9c1 chore: Release v5.14.0-dev.8 [skip ci]
# [5.14.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.7...v5.14.0-dev.8) (2025-03-09)

### Bug Fixes

* **YouTube - Theme:** Resolve dark mode startup crash with Android 9.0 ([7adfc63](7adfc637dc))
2025-03-09 09:32:12 +00:00
LisoUseInAIKyrios
7adfc637dc fix(YouTube - Theme): Resolve dark mode startup crash with Android 9.0 2025-03-09 11:29:17 +02:00
github-actions[bot]
9cc0c075ad chore: Sync translations (#4575) 2025-03-09 11:28:52 +02:00
semantic-release-bot
ead11e7f46 chore: Release v5.14.0-dev.7 [skip ci]
# [5.14.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.6...v5.14.0-dev.7) (2025-03-08)

### Bug Fixes

* **YouTube:** Change language settings menu to use native language names ([#4568](https://github.com/ReVanced/revanced-patches/issues/4568)) ([e9bc201](e9bc201641))
2025-03-08 18:45:38 +00:00
LisoUseInAIKyrios
e9bc201641 fix(YouTube): Change language settings menu to use native language names (#4568) 2025-03-08 20:42:48 +02:00
github-actions[bot]
99baedf355 chore: Sync translations (#4573) 2025-03-08 20:42:13 +02:00
semantic-release-bot
0338d0acd3 chore: Release v5.14.0-dev.6 [skip ci]
# [5.14.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.5...v5.14.0-dev.6) (2025-03-07)

### Bug Fixes

* **YouTube - Hide layout components:** Do not hide Movie/Courses start page content if 'Hide horizontal shelves' is enabled ([99879f6](99879f6e0a))
2025-03-07 17:40:21 +00:00
LisoUseInAIKyrios
99879f6e0a fix(YouTube - Hide layout components): Do not hide Movie/Courses start page content if 'Hide horizontal shelves' is enabled 2025-03-07 19:37:23 +02:00
github-actions[bot]
f0c70de602 chore: Sync translations (#4562) 2025-03-07 19:35:55 +02:00
LisoUseInAIKyrios
737ae07a06 refactor(YouTube): Sort no title preference group by first sub preference title 2025-03-06 21:15:29 +02:00
semantic-release-bot
2c51de59de chore: Release v5.14.0-dev.5 [skip ci]
# [5.14.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.4...v5.14.0-dev.5) (2025-03-06)

### Features

* **Infinity for Reddit:** Add support for package name on IzzyOnDroid ([#4554](https://github.com/ReVanced/revanced-patches/issues/4554)) ([df3dc1c](df3dc1c0b2))
2025-03-06 18:27:44 +00:00
ILoveOpenSourceApplications
df3dc1c0b2 feat(Infinity for Reddit): Add support for package name on IzzyOnDroid (#4554) 2025-03-06 20:24:54 +02:00
github-actions[bot]
074c948581 chore: Sync translations (#4556) 2025-03-06 20:24:22 +02:00
semantic-release-bot
2a88b1f895 chore: Release v5.14.0-dev.4 [skip ci]
# [5.14.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.3...v5.14.0-dev.4) (2025-03-06)

### Bug Fixes

* **YouTube:** Combine `Restore old video quality menu` and `Remember video quality` into `Video quality` patch ([#4552](https://github.com/ReVanced/revanced-patches/issues/4552)) ([ee5c830](ee5c830df8))
2025-03-06 12:59:39 +00:00
LisoUseInAIKyrios
ee5c830df8 fix(YouTube): Combine Restore old video quality menu and Remember video quality into Video quality patch (#4552) 2025-03-06 14:56:32 +02:00
semantic-release-bot
e63a4b31f3 chore: Release v5.14.0-dev.3 [skip ci]
# [5.14.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.2...v5.14.0-dev.3) (2025-03-06)

### Bug Fixes

* **Boost for reddit - Client spoof:** Use a different user agent to combat Reddit's API issues ([8d0bca3](8d0bca3b03))
2025-03-06 10:36:52 +00:00
oSumAtrIX
8d0bca3b03 fix(Boost for reddit - Client spoof): Use a different user agent to combat Reddit's API issues 2025-03-06 11:33:06 +01:00
615 changed files with 40438 additions and 24879 deletions

View File

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

View File

@@ -2,7 +2,7 @@ name: Pull strings
on: on:
schedule: schedule:
- cron: "0 */8 * * *" - cron: "0 */12 * * *"
workflow_dispatch: workflow_dispatch:
jobs: jobs:

View File

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

View File

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

File diff suppressed because it is too large Load Diff

3
build.gradle.kts Normal file
View File

@@ -0,0 +1,3 @@
plugins {
alias(libs.plugins.android.library) apply false
}

View File

@@ -0,0 +1,16 @@
android {
namespace = "app.revanced.extension"
defaultConfig {
minSdk = 21
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
dependencies {
compileOnly(libs.annotation)
}

View File

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

View File

@@ -0,0 +1,28 @@
package app.revanced.extension.all.misc.hide.adb;
import android.content.ContentResolver;
import android.provider.Settings;
import java.util.Arrays;
import java.util.List;
@SuppressWarnings("unused")
public final class HideAdbPatch {
private static final List<String> SPOOF_SETTINGS = Arrays.asList("adb_enabled", "adb_wifi_enabled", "development_settings_enabled");
public static int getInt(ContentResolver cr, String name) throws Settings.SettingNotFoundException {
if (SPOOF_SETTINGS.contains(name)) {
return 0;
}
return Settings.Global.getInt(cr, name);
}
public static int getInt(ContentResolver cr, String name, int def) {
if (SPOOF_SETTINGS.contains(name)) {
return 0;
}
return Settings.Global.getInt(cr, name, def);
}
}

View File

@@ -1,4 +1,4 @@
package app.revanced.extension.all.connectivity.wifi.spoof; package app.revanced.extension.all.misc.connectivity.wifi.spoof;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Context; import android.content.Context;

View File

@@ -1,4 +1,4 @@
package app.revanced.extension.all.screencapture.removerestriction; package app.revanced.extension.all.misc.screencapture.removerestriction;
import android.media.AudioAttributes; import android.media.AudioAttributes;
import android.os.Build; import android.os.Build;

View File

@@ -1,4 +1,4 @@
package app.revanced.extension.all.screenshot.removerestriction; package app.revanced.extension.all.misc.screenshot.removerestriction;
import android.view.Window; import android.view.Window;
import android.view.WindowManager; import android.view.WindowManager;

View File

@@ -1,5 +1,5 @@
plugins { plugins {
id(libs.plugins.android.library.get().pluginId) alias(libs.plugins.android.library)
} }
android { android {

View File

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

View File

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

View File

@@ -0,0 +1,28 @@
package app.revanced.extension.cricbuzz.ads;
import com.cricbuzz.android.data.rest.model.BottomBar;
import java.util.List;
import java.util.Iterator;
import app.revanced.extension.shared.Logger;
@SuppressWarnings("unused")
public class HideAdsPatch {
/**
* Injection point.
*/
public static void filterCb11(List<BottomBar> list) {
try {
Iterator<BottomBar> iterator = list.iterator();
while (iterator.hasNext()) {
BottomBar bar = iterator.next();
if (bar.getName().equals("Cricbuzz11")) {
Logger.printInfo(() -> "Removing Cricbuzz11 bar: " + bar);
iterator.remove();
}
}
} catch (Exception ex) {
Logger.printException(() -> "filterCb11 failure", ex);
}
}
}

View File

@@ -0,0 +1,17 @@
plugins {
alias(libs.plugins.android.library)
}
android {
namespace = "app.revanced.extension"
compileSdk = 34
defaultConfig {
minSdk = 21
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}

View File

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

View File

@@ -0,0 +1,5 @@
package com.cricbuzz.android.data.rest.model;
public final class BottomBar {
public final String getName() { throw new UnsupportedOperationException(); }
}

View File

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

View File

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

View File

@@ -0,0 +1,25 @@
package app.revanced.extension.messenger.metaai;
import java.util.*;
import app.revanced.extension.shared.Logger;
@SuppressWarnings("unused")
public class RemoveMetaAIPatch {
private static final Set<Long> loggedIDs = Collections.synchronizedSet(new HashSet<>());
public static boolean overrideBooleanFlag(long id, boolean value) {
try {
if (Long.toString(id).startsWith("REPLACED_BY_PATCH")) {
if (loggedIDs.add(id))
Logger.printInfo(() -> "Overriding " + id + " from " + value + " to false");
return false;
}
} catch (Exception ex) {
Logger.printException(() -> "overrideBooleanFlag failure", ex);
}
return value;
}
}

View File

@@ -82,7 +82,7 @@ public class HideAdsPatch {
// Filter HeaderBlock with known ads until next HeaderBlock. // Filter HeaderBlock with known ads until next HeaderBlock.
if (currentBlock instanceof HeaderBlock headerBlock) { if (currentBlock instanceof HeaderBlock headerBlock) {
StyledText headerText = headerBlock.component20(); StyledText headerText = headerBlock.getTitle();
if (headerText != null) { if (headerText != null) {
skipFullHeader = false; skipFullHeader = false;
for (String blockedHeaderBlock : blockedHeaderBlocks) { for (String blockedHeaderBlock : blockedHeaderBlocks) {

View File

@@ -1,5 +1,5 @@
plugins { plugins {
id(libs.plugins.android.library.get().pluginId) alias(libs.plugins.android.library)
} }
android { android {

View File

@@ -3,8 +3,7 @@ package nl.nu.performance.api.client.objects;
import nl.nu.performance.api.client.interfaces.Block; import nl.nu.performance.api.client.interfaces.Block;
public class HeaderBlock extends Block { public class HeaderBlock extends Block {
// returns title public final StyledText getTitle() {
public final StyledText component20() {
throw new UnsupportedOperationException("Stub"); throw new UnsupportedOperationException("Stub");
} }
} }

View File

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

View File

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

View File

@@ -0,0 +1,36 @@
package app.revanced.extension.primevideo.ads;
import com.amazon.avod.fsm.SimpleTrigger;
import com.amazon.avod.media.ads.AdBreak;
import com.amazon.avod.media.ads.internal.state.AdBreakTrigger;
import com.amazon.avod.media.ads.internal.state.AdEnabledPlayerTriggerType;
import com.amazon.avod.media.playback.VideoPlayer;
import com.amazon.avod.media.ads.internal.state.ServerInsertedAdBreakState;
import app.revanced.extension.shared.Logger;
@SuppressWarnings("unused")
public final class SkipAdsPatch {
public static void enterServerInsertedAdBreakState(ServerInsertedAdBreakState state, AdBreakTrigger trigger, VideoPlayer player) {
try {
AdBreak adBreak = trigger.getBreak();
// There are two scenarios when entering the original method:
// 1. Player naturally entered an ad break while watching a video.
// 2. User is skipped/scrubbed to a position on the timeline. If seek position is past an ad break,
// user is forced to watch an ad before continuing.
//
// Scenario 2 is indicated by trigger.getSeekStartPosition() != null, so skip directly to the scrubbing
// target. Otherwise, just calculate when the ad break should end and skip to there.
if (trigger.getSeekStartPosition() != null)
player.seekTo(trigger.getSeekTarget().getTotalMilliseconds());
else
player.seekTo(player.getCurrentPosition() + adBreak.getDurationExcludingAux().getTotalMilliseconds());
// Send "end of ads" trigger to state machine so everything doesn't get whacky.
state.doTrigger(new SimpleTrigger(AdEnabledPlayerTriggerType.NO_MORE_ADS_SKIP_TRANSITION));
} catch (Exception ex) {
Logger.printException(() -> "Failed skipping ads", ex);
}
}
}

View File

@@ -0,0 +1,17 @@
plugins {
alias(libs.plugins.android.library)
}
android {
namespace = "app.revanced.extension"
compileSdk = 34
defaultConfig {
minSdk = 21
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}

View File

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

View File

@@ -0,0 +1,6 @@
package com.amazon.avod.fsm;
public final class SimpleTrigger<T> implements Trigger<T> {
public SimpleTrigger(T triggerType) {
}
}

View File

@@ -0,0 +1,7 @@
package com.amazon.avod.fsm;
public abstract class StateBase<S, T> {
// This method orginally has protected access (modified in patch code).
public void doTrigger(Trigger<T> trigger) {
}
}

View File

@@ -0,0 +1,4 @@
package com.amazon.avod.fsm;
public interface Trigger<T> {
}

View File

@@ -0,0 +1,7 @@
package com.amazon.avod.media;
public final class TimeSpan {
public long getTotalMilliseconds() {
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,7 @@
package com.amazon.avod.media.ads;
import com.amazon.avod.media.TimeSpan;
public interface AdBreak {
TimeSpan getDurationExcludingAux();
}

View File

@@ -0,0 +1,4 @@
package com.amazon.avod.media.ads.internal.state;
public abstract class AdBreakState extends AdEnabledPlaybackState {
}

View File

@@ -0,0 +1,18 @@
package com.amazon.avod.media.ads.internal.state;
import com.amazon.avod.media.ads.AdBreak;
import com.amazon.avod.media.TimeSpan;
public class AdBreakTrigger {
public AdBreak getBreak() {
throw new UnsupportedOperationException();
}
public TimeSpan getSeekTarget() {
throw new UnsupportedOperationException();
}
public TimeSpan getSeekStartPosition() {
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,8 @@
package com.amazon.avod.media.ads.internal.state;
import com.amazon.avod.fsm.StateBase;
import com.amazon.avod.media.playback.state.PlayerStateType;
import com.amazon.avod.media.playback.state.trigger.PlayerTriggerType;
public class AdEnabledPlaybackState extends StateBase<PlayerStateType, PlayerTriggerType> {
}

View File

@@ -0,0 +1,5 @@
package com.amazon.avod.media.ads.internal.state;
public enum AdEnabledPlayerTriggerType {
NO_MORE_ADS_SKIP_TRANSITION
}

View File

@@ -0,0 +1,4 @@
package com.amazon.avod.media.ads.internal.state;
public class ServerInsertedAdBreakState extends AdBreakState {
}

View File

@@ -0,0 +1,7 @@
package com.amazon.avod.media.playback;
public interface VideoPlayer {
long getCurrentPosition();
void seekTo(long positionMs);
}

View File

@@ -0,0 +1,4 @@
package com.amazon.avod.media.playback.state;
public interface PlayerStateType {
}

View File

@@ -0,0 +1,4 @@
package com.amazon.avod.media.playback.state.trigger;
public interface PlayerTriggerType {
}

View File

@@ -1,5 +1,5 @@
plugins { plugins {
id(libs.plugins.android.library.get().pluginId) alias(libs.plugins.android.library)
} }
android { android {

View File

@@ -1,5 +1,5 @@
plugins { plugins {
id("com.android.library") alias(libs.plugins.android.library)
} }
android { android {

View File

@@ -1,10 +1,11 @@
package app.revanced.extension.shared; package app.revanced.extension.shared;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.requests.Route.Method.GET;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.Dialog;
import android.app.SearchManager; import android.app.SearchManager;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
@@ -14,15 +15,20 @@ import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.PowerManager; import android.os.PowerManager;
import android.provider.Settings; import android.provider.Settings;
import android.util.Pair;
import android.widget.LinearLayout;
import androidx.annotation.RequiresApi; import androidx.annotation.Nullable;
import java.net.HttpURLConnection;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.Locale;
/** import app.revanced.extension.shared.requests.Requester;
* @noinspection unused import app.revanced.extension.shared.requests.Route;
*/
@SuppressWarnings("unused")
public class GmsCoreSupport { public class GmsCoreSupport {
private static final String PACKAGE_NAME_YOUTUBE = "com.google.android.youtube"; private static final String PACKAGE_NAME_YOUTUBE = "com.google.android.youtube";
private static final String PACKAGE_NAME_YOUTUBE_MUSIC = "com.google.android.apps.youtube.music"; private static final String PACKAGE_NAME_YOUTUBE_MUSIC = "com.google.android.apps.youtube.music";
@@ -31,10 +37,24 @@ public class GmsCoreSupport {
= getGmsCoreVendorGroupId() + ".android.gms"; = getGmsCoreVendorGroupId() + ".android.gms";
private static final Uri GMS_CORE_PROVIDER private static final Uri GMS_CORE_PROVIDER
= Uri.parse("content://" + getGmsCoreVendorGroupId() + ".android.gsf.gservices/prefix"); = Uri.parse("content://" + getGmsCoreVendorGroupId() + ".android.gsf.gservices/prefix");
private static final String DONT_KILL_MY_APP_LINK private static final String DONT_KILL_MY_APP_URL
= "https://dontkillmyapp.com"; = "https://dontkillmyapp.com/";
private static final Route DONT_KILL_MY_APP_MANUFACTURER_API
= new Route(GET, "/api/v2/{manufacturer}.json");
private static final String DONT_KILL_MY_APP_NAME_PARAMETER
= "?app=MicroG";
private static final String BUILD_MANUFACTURER
= Build.MANUFACTURER.toLowerCase(Locale.ROOT).replace(" ", "-");
/**
* If a manufacturer specific page exists on DontKillMyApp.
*/
@Nullable
private static volatile Boolean DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED;
private static void open(String queryOrLink) { private static void open(String queryOrLink) {
Logger.printInfo(() -> "Opening link: " + queryOrLink);
Intent intent; Intent intent;
try { try {
// Check if queryOrLink is a valid URL. // Check if queryOrLink is a valid URL.
@@ -59,13 +79,27 @@ public class GmsCoreSupport {
// Use a delay to allow the activity to finish initializing. // Use a delay to allow the activity to finish initializing.
// Otherwise, if device is in dark mode the dialog is shown with wrong color scheme. // Otherwise, if device is in dark mode the dialog is shown with wrong color scheme.
Utils.runOnMainThreadDelayed(() -> { Utils.runOnMainThreadDelayed(() -> {
// Create the custom dialog.
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
context,
str("gms_core_dialog_title"), // Title.
str(dialogMessageRef), // Message.
null, // No EditText.
str(positiveButtonTextRef), // OK button text.
() -> onPositiveClickListener.onClick(null, 0), // Convert DialogInterface.OnClickListener to Runnable.
null, // No Cancel button action.
null, // No Neutral button text.
null, // No Neutral button action.
true // Dismiss dialog when onNeutralClick.
);
Dialog dialog = dialogPair.first;
// Do not set cancelable to false, to allow using back button to skip the action, // Do not set cancelable to false, to allow using back button to skip the action,
// just in case the battery change can never be satisfied. // just in case the battery change can never be satisfied.
var dialog = new AlertDialog.Builder(context) dialog.setCancelable(true);
.setTitle(str("gms_core_dialog_title"))
.setMessage(str(dialogMessageRef)) // Show the dialog
.setPositiveButton(str(positiveButtonTextRef), onPositiveClickListener)
.create();
Utils.showDialog(context, dialog); Utils.showDialog(context, dialog);
}, 100); }, 100);
} }
@@ -73,7 +107,6 @@ public class GmsCoreSupport {
/** /**
* Injection point. * Injection point.
*/ */
@RequiresApi(api = Build.VERSION_CODES.N)
public static void checkGmsCore(Activity context) { public static void checkGmsCore(Activity context) {
try { try {
// Verify the user has not included GmsCore for a root installation. // Verify the user has not included GmsCore for a root installation.
@@ -88,7 +121,7 @@ public class GmsCoreSupport {
// Do not exit. If the app exits before launch completes (and without // Do not exit. If the app exits before launch completes (and without
// opening another activity), then on some devices such as Pixel phone Android 10 // opening another activity), then on some devices such as Pixel phone Android 10
// no toast will be shown and the app will continually be relaunched // no toast will be shown and the app will continually relaunch
// with the appearance of a hung app. // with the appearance of a hung app.
} }
@@ -121,15 +154,20 @@ public class GmsCoreSupport {
} }
// Check if GmsCore is currently running in the background. // Check if GmsCore is currently running in the background.
try (var client = context.getContentResolver().acquireContentProviderClient(GMS_CORE_PROVIDER)) { var client = context.getContentResolver().acquireContentProviderClient(GMS_CORE_PROVIDER);
//noinspection TryFinallyCanBeTryWithResources
try {
if (client == null) { if (client == null) {
Logger.printInfo(() -> "GmsCore is not running in the background"); Logger.printInfo(() -> "GmsCore is not running in the background");
checkIfDontKillMyAppSupportsManufacturer();
showBatteryOptimizationDialog(context, showBatteryOptimizationDialog(context,
"gms_core_dialog_not_whitelisted_not_allowed_in_background_message", "gms_core_dialog_not_whitelisted_not_allowed_in_background_message",
"gms_core_dialog_open_website_text", "gms_core_dialog_open_website_text",
(dialog, id) -> open(DONT_KILL_MY_APP_LINK)); (dialog, id) -> openDontKillMyApp());
} }
} finally {
if (client != null) client.close();
} }
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "checkGmsCore failure", ex); Logger.printException(() -> "checkGmsCore failure", ex);
@@ -143,10 +181,57 @@ public class GmsCoreSupport {
activity.startActivityForResult(intent, 0); activity.startActivityForResult(intent, 0);
} }
private static void checkIfDontKillMyAppSupportsManufacturer() {
Utils.runOnBackgroundThread(() -> {
try {
final long start = System.currentTimeMillis();
HttpURLConnection connection = Requester.getConnectionFromRoute(
DONT_KILL_MY_APP_URL, DONT_KILL_MY_APP_MANUFACTURER_API, BUILD_MANUFACTURER);
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
final boolean supported = connection.getResponseCode() == 200;
Logger.printInfo(() -> "Manufacturer is " + (supported ? "" : "NOT ")
+ "listed on DontKillMyApp: " + BUILD_MANUFACTURER
+ " fetch took: " + (System.currentTimeMillis() - start) + "ms");
DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED = supported;
} catch (Exception ex) {
Logger.printInfo(() -> "Could not check if manufacturer is listed on DontKillMyApp: "
+ BUILD_MANUFACTURER, ex);
DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED = null;
}
});
}
private static void openDontKillMyApp() {
final Boolean manufacturerSupported = DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED;
String manufacturerPageToOpen;
if (manufacturerSupported == null) {
// Fetch has not completed yet. Only happens on extremely slow internet connections
// and the user spends less than 1 second reading what's on screen.
// Instead of waiting for the fetch (which may timeout),
// open the website without a vendor.
manufacturerPageToOpen = "";
} else if (manufacturerSupported) {
manufacturerPageToOpen = BUILD_MANUFACTURER;
} else {
// No manufacturer specific page exists. Open the general page.
manufacturerPageToOpen = "general";
}
open(DONT_KILL_MY_APP_URL + manufacturerPageToOpen + DONT_KILL_MY_APP_NAME_PARAMETER);
}
/** /**
* @return If GmsCore is not whitelisted from battery optimizations. * @return If GmsCore is not whitelisted from battery optimizations.
*/ */
private static boolean batteryOptimizationsEnabled(Context context) { private static boolean batteryOptimizationsEnabled(Context context) {
//noinspection ObsoleteSdkInt
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// Android 5.0 does not have battery optimization settings.
return false;
}
var powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); var powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
return !powerManager.isIgnoringBatteryOptimizations(GMS_CORE_PACKAGE_NAME); return !powerManager.isIgnoringBatteryOptimizations(GMS_CORE_PACKAGE_NAME);
} }

View File

@@ -1,15 +1,27 @@
package app.revanced.extension.shared; package app.revanced.extension.shared;
import static app.revanced.extension.shared.settings.BaseSettings.DEBUG;
import static app.revanced.extension.shared.settings.BaseSettings.DEBUG_STACKTRACE;
import static app.revanced.extension.shared.settings.BaseSettings.DEBUG_TOAST_ON_ERROR;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import app.revanced.extension.shared.settings.BaseSettings;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import static app.revanced.extension.shared.settings.BaseSettings.*; import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.preference.LogBufferManager;
/**
* ReVanced specific logger. Logging is done to standard device log (accessible thru ADB),
* and additionally accessible thru {@link LogBufferManager}.
*
* All methods are thread safe, and are safe to call even
* if {@link Utils#getContext()} is not available.
*/
public class Logger { public class Logger {
/** /**
@@ -17,99 +29,173 @@ public class Logger {
*/ */
@FunctionalInterface @FunctionalInterface
public interface LogMessage { public interface LogMessage {
/**
* @return Logger string message. This method is only called if logging is enabled.
*/
@NonNull @NonNull
String buildMessageString(); String buildMessageString();
}
/** private enum LogLevel {
* @return For outer classes, this returns {@link Class#getSimpleName()}. DEBUG,
* For static, inner, or anonymous classes, this returns the simple name of the enclosing class. INFO,
* <br> ERROR
* For example, each of these classes return 'SomethingView': }
* <code>
* com.company.SomethingView
* com.company.SomethingView$StaticClass
* com.company.SomethingView$1
* </code>
*/
private String findOuterClassSimpleName() {
var selfClass = this.getClass();
String fullClassName = selfClass.getName(); /**
final int dollarSignIndex = fullClassName.indexOf('$'); * Log tag prefix. Only used for system logging.
if (dollarSignIndex < 0) { */
return selfClass.getSimpleName(); // Already an outer class. private static final String REVANCED_LOG_TAG_PREFIX = "revanced: ";
private static final String LOGGER_CLASS_NAME = Logger.class.getName();
/**
* @return For outer classes, this returns {@link Class#getSimpleName()}.
* For static, inner, or anonymous classes, this returns the simple name of the enclosing class.
* <br>
* For example, each of these classes returns 'SomethingView':
* <code>
* com.company.SomethingView
* com.company.SomethingView$StaticClass
* com.company.SomethingView$1
* </code>
*/
private static String getOuterClassSimpleName(Object obj) {
Class<?> logClass = obj.getClass();
String fullClassName = logClass.getName();
final int dollarSignIndex = fullClassName.indexOf('$');
if (dollarSignIndex < 0) {
return logClass.getSimpleName(); // Already an outer class.
}
// Class is inner, static, or anonymous.
// Parse the simple name full name.
// A class with no package returns index of -1, but incrementing gives index zero which is correct.
final int simpleClassNameStartIndex = fullClassName.lastIndexOf('.') + 1;
return fullClassName.substring(simpleClassNameStartIndex, dollarSignIndex);
}
/**
* Internal method to handle logging to Android Log and {@link LogBufferManager}.
* Appends the log message, stack trace (if enabled), and exception (if present) to logBuffer
* with class name but without 'revanced:' prefix.
*
* @param logLevel The log level.
* @param message Log message object.
* @param ex Optional exception.
* @param includeStackTrace If the current stack should be included.
* @param showToast If a toast is to be shown.
*/
private static void logInternal(LogLevel logLevel, LogMessage message, @Nullable Throwable ex,
boolean includeStackTrace, boolean showToast) {
// It's very important that no Settings are used in this method,
// as this code is used when a context is not set and thus referencing
// a setting will crash the app.
String messageString = message.buildMessageString();
String className = getOuterClassSimpleName(message);
String logText = messageString;
// Append exception message if present.
if (ex != null) {
var exceptionMessage = ex.getMessage();
if (exceptionMessage != null) {
logText += "\nException: " + exceptionMessage;
} }
}
// Class is inner, static, or anonymous. if (includeStackTrace) {
// Parse the simple name full name. var sw = new StringWriter();
// A class with no package returns index of -1, but incrementing gives index zero which is correct. new Throwable().printStackTrace(new PrintWriter(sw));
final int simpleClassNameStartIndex = fullClassName.lastIndexOf('.') + 1; String stackTrace = sw.toString();
return fullClassName.substring(simpleClassNameStartIndex, dollarSignIndex); // Remove the stacktrace elements of this class.
final int loggerIndex = stackTrace.lastIndexOf(LOGGER_CLASS_NAME);
final int loggerBegins = stackTrace.indexOf('\n', loggerIndex);
logText += stackTrace.substring(loggerBegins);
}
// Do not include "revanced:" prefix in clipboard logs.
String managerToastString = className + ": " + logText;
LogBufferManager.appendToLogBuffer(managerToastString);
String logTag = REVANCED_LOG_TAG_PREFIX + className;
switch (logLevel) {
case DEBUG:
if (ex == null) Log.d(logTag, logText);
else Log.d(logTag, logText, ex);
break;
case INFO:
if (ex == null) Log.i(logTag, logText);
else Log.i(logTag, logText, ex);
break;
case ERROR:
if (ex == null) Log.e(logTag, logText);
else Log.e(logTag, logText, ex);
break;
}
if (showToast) {
Utils.showToastLong(managerToastString);
} }
} }
private static final String REVANCED_LOG_PREFIX = "revanced: "; private static boolean shouldLogDebug() {
// If the app is still starting up and the context is not yet set,
// then allow debug logging regardless what the debug setting actually is.
return Utils.context == null || DEBUG.get();
}
private static boolean shouldShowErrorToast() {
return Utils.context != null && DEBUG_TOAST_ON_ERROR.get();
}
private static boolean includeStackTrace() {
return Utils.context != null && DEBUG_STACKTRACE.get();
}
/** /**
* Logs debug messages under the outer class name of the code calling this method. * Logs debug messages under the outer class name of the code calling this method.
* Whenever possible, the log string should be constructed entirely inside {@link LogMessage#buildMessageString()} * <p>
* so the performance cost of building strings is paid only if {@link BaseSettings#DEBUG} is enabled. * Whenever possible, the log string should be constructed entirely inside
* {@link LogMessage#buildMessageString()} so the performance cost of
* building strings is paid only if {@link BaseSettings#DEBUG} is enabled.
*/ */
public static void printDebug(@NonNull LogMessage message) { public static void printDebug(LogMessage message) {
printDebug(message, null); printDebug(message, null);
} }
/** /**
* Logs debug messages under the outer class name of the code calling this method. * Logs debug messages under the outer class name of the code calling this method.
* Whenever possible, the log string should be constructed entirely inside {@link LogMessage#buildMessageString()} * <p>
* so the performance cost of building strings is paid only if {@link BaseSettings#DEBUG} is enabled. * Whenever possible, the log string should be constructed entirely inside
* {@link LogMessage#buildMessageString()} so the performance cost of
* building strings is paid only if {@link BaseSettings#DEBUG} is enabled.
*/ */
public static void printDebug(@NonNull LogMessage message, @Nullable Exception ex) { public static void printDebug(LogMessage message, @Nullable Exception ex) {
if (DEBUG.get()) { if (shouldLogDebug()) {
String logMessage = message.buildMessageString(); logInternal(LogLevel.DEBUG, message, ex, includeStackTrace(), false);
String logTag = REVANCED_LOG_PREFIX + message.findOuterClassSimpleName();
if (DEBUG_STACKTRACE.get()) {
var builder = new StringBuilder(logMessage);
var sw = new StringWriter();
new Throwable().printStackTrace(new PrintWriter(sw));
builder.append('\n').append(sw);
logMessage = builder.toString();
}
if (ex == null) {
Log.d(logTag, logMessage);
} else {
Log.d(logTag, logMessage, ex);
}
} }
} }
/** /**
* Logs information messages using the outer class name of the code calling this method. * Logs information messages using the outer class name of the code calling this method.
*/ */
public static void printInfo(@NonNull LogMessage message) { public static void printInfo(LogMessage message) {
printInfo(message, null); printInfo(message, null);
} }
/** /**
* Logs information messages using the outer class name of the code calling this method. * Logs information messages using the outer class name of the code calling this method.
*/ */
public static void printInfo(@NonNull LogMessage message, @Nullable Exception ex) { public static void printInfo(LogMessage message, @Nullable Exception ex) {
String logTag = REVANCED_LOG_PREFIX + message.findOuterClassSimpleName(); logInternal(LogLevel.INFO, message, ex, includeStackTrace(), false);
String logMessage = message.buildMessageString();
if (ex == null) {
Log.i(logTag, logMessage);
} else {
Log.i(logTag, logMessage, ex);
}
} }
/** /**
* Logs exceptions under the outer class name of the code calling this method. * Logs exceptions under the outer class name of the code calling this method.
* Appends the log message, exception (if present), and toast message (if enabled) to logBuffer.
*/ */
public static void printException(@NonNull LogMessage message) { public static void printException(LogMessage message) {
printException(message, null); printException(message, null);
} }
@@ -122,35 +208,7 @@ public class Logger {
* @param message log message * @param message log message
* @param ex exception (optional) * @param ex exception (optional)
*/ */
public static void printException(@NonNull LogMessage message, @Nullable Throwable ex) { public static void printException(LogMessage message, @Nullable Throwable ex) {
String messageString = message.buildMessageString(); logInternal(LogLevel.ERROR, message, ex, includeStackTrace(), shouldShowErrorToast());
String outerClassSimpleName = message.findOuterClassSimpleName();
String logMessage = REVANCED_LOG_PREFIX + outerClassSimpleName;
if (ex == null) {
Log.e(logMessage, messageString);
} else {
Log.e(logMessage, messageString, ex);
}
if (DEBUG_TOAST_ON_ERROR.get()) {
Utils.showToastLong(outerClassSimpleName + ": " + messageString);
}
} }
}
/**
* Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#getContext()} may not be initialized.
* Normally this method should not be used.
*/
public static void initializationInfo(@NonNull Class<?> callingClass, @NonNull String message) {
Log.i(REVANCED_LOG_PREFIX + callingClass.getSimpleName(), message);
}
/**
* Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#getContext()} may not be initialized.
* Normally this method should not be used.
*/
public static void initializationException(@NonNull Class<?> callingClass, @NonNull String message,
@Nullable Exception ex) {
Log.e(REVANCED_LOG_PREFIX + callingClass.getSimpleName(), message, ex);
}
}

View File

@@ -3,15 +3,21 @@ package app.revanced.extension.shared.checks;
import static android.text.Html.FROM_HTML_MODE_COMPACT; import static android.text.Html.FROM_HTML_MODE_COMPACT;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.DialogFragmentOnStartAction; import static app.revanced.extension.shared.Utils.DialogFragmentOnStartAction;
import static app.revanced.extension.shared.Utils.dipToPixels;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.graphics.PorterDuff;
import android.net.Uri; import android.net.Uri;
import android.text.Html; import android.text.Html;
import android.util.Pair;
import android.view.Gravity;
import android.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -86,38 +92,58 @@ abstract class Check {
); );
Utils.runOnMainThreadDelayed(() -> { Utils.runOnMainThreadDelayed(() -> {
AlertDialog alert = new AlertDialog.Builder(activity) // Create the custom dialog.
.setCancelable(false) Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
.setIconAttribute(android.R.attr.alertDialogIcon) activity,
.setTitle(str("revanced_check_environment_failed_title")) str("revanced_check_environment_failed_title"), // Title.
.setMessage(message) message, // Message.
.setPositiveButton( null, // No EditText.
" ", str("revanced_check_environment_dialog_open_official_source_button"), // OK button text.
(dialog, which) -> { () -> {
final var intent = new Intent(Intent.ACTION_VIEW, GOOD_SOURCE); // Action for the OK (website) button.
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); final var intent = new Intent(Intent.ACTION_VIEW, GOOD_SOURCE);
activity.startActivity(intent); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
activity.startActivity(intent);
// Shutdown to prevent the user from navigating back to this app, // Shutdown to prevent the user from navigating back to this app,
// which is no longer showing a warning dialog. // which is no longer showing a warning dialog.
activity.finishAffinity(); activity.finishAffinity();
System.exit(0); System.exit(0);
} },
).setNegativeButton( null, // No cancel button.
" ", str("revanced_check_environment_dialog_ignore_button"), // Neutral button text.
(dialog, which) -> { () -> {
// Cleanup data if the user incorrectly imported a huge negative number. // Neutral button action.
final int current = Math.max(0, BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.get()); // Cleanup data if the user incorrectly imported a huge negative number.
BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.save(current + 1); final int current = Math.max(0, BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.get());
BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.save(current + 1);
},
true // Dismiss dialog when onNeutralClick.
);
dialog.dismiss(); // Get the dialog and main layout.
} Dialog dialog = dialogPair.first;
).create(); LinearLayout mainLayout = dialogPair.second;
Utils.showDialog(activity, alert, false, new DialogFragmentOnStartAction() { // Add icon to the dialog.
ImageView iconView = new ImageView(activity);
iconView.setImageResource(Utils.getResourceIdentifier("revanced_ic_dialog_alert", "drawable"));
iconView.setColorFilter(Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN);
iconView.setPadding(0, 0, 0, 0);
LinearLayout.LayoutParams iconParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
iconParams.gravity = Gravity.CENTER;
mainLayout.addView(iconView, 0); // Add icon at the top.
dialog.setCancelable(false);
// Show the dialog.
Utils.showDialog(activity, dialog, false, new DialogFragmentOnStartAction() {
boolean hasRun; boolean hasRun;
@Override @Override
public void onStart(AlertDialog dialog) { public void onStart(Dialog dialog) {
// Only run this once, otherwise if the user changes to a different app // Only run this once, otherwise if the user changes to a different app
// then changes back, this handler will run again and disable the buttons. // then changes back, this handler will run again and disable the buttons.
if (hasRun) { if (hasRun) {
@@ -125,19 +151,43 @@ abstract class Check {
} }
hasRun = true; hasRun = true;
var openWebsiteButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); // Get the button container to access buttons.
LinearLayout buttonContainer = (LinearLayout) mainLayout.getChildAt(mainLayout.getChildCount() - 1);
Button openWebsiteButton;
Button ignoreButton;
// Check if buttons are in a single-row layout (buttonContainer has one child: rowContainer).
if (buttonContainer.getChildCount() == 1 && buttonContainer.getChildAt(0) instanceof LinearLayout) {
LinearLayout rowContainer = (LinearLayout) buttonContainer.getChildAt(0);
// Neutral button is the first child (index 0).
ignoreButton = (Button) rowContainer.getChildAt(0);
// OK button is the last child.
openWebsiteButton = (Button) rowContainer.getChildAt(rowContainer.getChildCount() - 1);
} else {
// Multi-row layout: buttons are in separate containers, ordered OK, Cancel, Neutral.
LinearLayout okContainer =
(LinearLayout) buttonContainer.getChildAt(0); // OK is first.
openWebsiteButton = (Button) okContainer.getChildAt(0);
LinearLayout neutralContainer =
(LinearLayout)buttonContainer.getChildAt(buttonContainer.getChildCount() - 1); // Neutral is last.
ignoreButton = (Button) neutralContainer.getChildAt(0);
}
// Initially set buttons to INVISIBLE and disabled.
openWebsiteButton.setVisibility(View.INVISIBLE);
openWebsiteButton.setEnabled(false); openWebsiteButton.setEnabled(false);
ignoreButton.setVisibility(View.INVISIBLE);
ignoreButton.setEnabled(false);
var dismissButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE); // Start the countdown for showing and enabling buttons.
dismissButton.setEnabled(false); getCountdownRunnable(ignoreButton, openWebsiteButton).run();
getCountdownRunnable(dismissButton, openWebsiteButton).run();
} }
}); });
}, 1000); // Use a delay, so this dialog is shown on top of any other startup dialogs. }, 1000); // Use a delay, so this dialog is shown on top of any other startup dialogs.
} }
private static Runnable getCountdownRunnable(Button dismissButton, Button openWebsiteButton) { private static Runnable getCountdownRunnable(Button ignoreButton, Button openWebsiteButton) {
return new Runnable() { return new Runnable() {
private int secondsRemaining = SECONDS_BEFORE_SHOWING_IGNORE_BUTTON; private int secondsRemaining = SECONDS_BEFORE_SHOWING_IGNORE_BUTTON;
@@ -146,17 +196,15 @@ abstract class Check {
Utils.verifyOnMainThread(); Utils.verifyOnMainThread();
if (secondsRemaining > 0) { if (secondsRemaining > 0) {
if (secondsRemaining - SECONDS_BEFORE_SHOWING_WEBSITE_BUTTON == 0) { if (secondsRemaining - SECONDS_BEFORE_SHOWING_WEBSITE_BUTTON <= 0) {
openWebsiteButton.setText(str("revanced_check_environment_dialog_open_official_source_button")); openWebsiteButton.setVisibility(View.VISIBLE);
openWebsiteButton.setEnabled(true); openWebsiteButton.setEnabled(true);
} }
secondsRemaining--; secondsRemaining--;
Utils.runOnMainThreadDelayed(this, 1000); Utils.runOnMainThreadDelayed(this, 1000);
} else { } else {
dismissButton.setText(str("revanced_check_environment_dialog_ignore_button")); ignoreButton.setVisibility(View.VISIBLE);
dismissButton.setEnabled(true); ignoreButton.setEnabled(true);
} }
} }
}; };

View File

@@ -52,7 +52,7 @@ public class Route {
private int countMatches(CharSequence seq, char c) { private int countMatches(CharSequence seq, char c) {
int count = 0; int count = 0;
for (int i = 0; i < seq.length(); i++) { for (int i = 0, length = seq.length(); i < length; i++) {
if (seq.charAt(i) == c) if (seq.charAt(i) == c)
count++; count++;
} }

View File

@@ -8,6 +8,9 @@ public enum AppLanguage {
*/ */
DEFAULT, DEFAULT,
// Languages codes not included with YouTube, but are translated on Crowdin
GA,
// Language codes found in locale_config.xml // Language codes found in locale_config.xml
// All region specific variants have been removed. // All region specific variants have been removed.
AF, AF,
@@ -86,9 +89,11 @@ public enum AppLanguage {
ZU; ZU;
private final String language; private final String language;
private final Locale locale;
AppLanguage() { AppLanguage() {
language = name().toLowerCase(Locale.US); language = name().toLowerCase(Locale.US);
locale = Locale.forLanguageTag(language);
} }
/** /**
@@ -109,6 +114,6 @@ public enum AppLanguage {
return Locale.getDefault(); return Locale.getDefault();
} }
return Locale.forLanguageTag(language); return locale;
} }
} }

View File

@@ -23,6 +23,11 @@ public class BaseSettings {
public static final EnumSetting<AppLanguage> REVANCED_LANGUAGE = new EnumSetting<>("revanced_language", AppLanguage.DEFAULT, true, "revanced_language_user_dialog_message"); public static final EnumSetting<AppLanguage> REVANCED_LANGUAGE = new EnumSetting<>("revanced_language", AppLanguage.DEFAULT, true, "revanced_language_user_dialog_message");
/**
* Use the icons declared in the preferences created during patching. If no icons or styles are declared then this setting does nothing.
*/
public static final BooleanSetting SHOW_MENU_ICONS = new BooleanSetting("revanced_show_menu_icons", TRUE, true);
public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("revanced_spoof_video_streams", TRUE, true, "revanced_spoof_video_streams_user_dialog_message"); public static final BooleanSetting SPOOF_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 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 SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE, parent(SPOOF_VIDEO_STREAMS));

View File

@@ -71,15 +71,20 @@ public class EnumSetting<T extends Enum<?>> extends Setting<T> {
json.put(importExportKey, value.name().toLowerCase(Locale.ENGLISH)); json.put(importExportKey, value.name().toLowerCase(Locale.ENGLISH));
} }
@NonNull /**
private T getEnumFromString(String enumName) { * @param enumName Enum name. Casing does not matter.
* @return Enum of this type with the same declared name.
* @throws IllegalArgumentException if the name is not a valid enum of this type.
*/
protected T getEnumFromString(String enumName) {
//noinspection ConstantConditions //noinspection ConstantConditions
for (Enum<?> value : defaultValue.getClass().getEnumConstants()) { for (Enum<?> value : defaultValue.getClass().getEnumConstants()) {
if (value.name().equalsIgnoreCase(enumName)) { if (value.name().equalsIgnoreCase(enumName)) {
// noinspection unchecked //noinspection unchecked
return (T) value; return (T) value;
} }
} }
throw new IllegalArgumentException("Unknown enum value: " + enumName); throw new IllegalArgumentException("Unknown enum value: " + enumName);
} }
@@ -103,7 +108,9 @@ public class EnumSetting<T extends Enum<?>> extends Setting<T> {
* Availability based on if this setting is currently set to any of the provided types. * Availability based on if this setting is currently set to any of the provided types.
*/ */
@SafeVarargs @SafeVarargs
public final Setting.Availability availability(@NonNull T... types) { public final Setting.Availability availability(T... types) {
Objects.requireNonNull(types);
return () -> { return () -> {
T currentEnumType = get(); T currentEnumType = get();
for (T enumType : types) { for (T enumType : types) {

View File

@@ -28,16 +28,14 @@ public abstract class Setting<T> {
/** /**
* Availability based on a single parent setting being enabled. * Availability based on a single parent setting being enabled.
*/ */
@NonNull public static Availability parent(BooleanSetting parent) {
public static Availability parent(@NonNull BooleanSetting parent) {
return parent::get; return parent::get;
} }
/** /**
* Availability based on all parents being enabled. * Availability based on all parents being enabled.
*/ */
@NonNull public static Availability parentsAll(BooleanSetting... parents) {
public static Availability parentsAll(@NonNull BooleanSetting... parents) {
return () -> { return () -> {
for (BooleanSetting parent : parents) { for (BooleanSetting parent : parents) {
if (!parent.get()) return false; if (!parent.get()) return false;
@@ -49,8 +47,7 @@ public abstract class Setting<T> {
/** /**
* Availability based on any parent being enabled. * Availability based on any parent being enabled.
*/ */
@NonNull public static Availability parentsAny(BooleanSetting... parents) {
public static Availability parentsAny(@NonNull BooleanSetting... parents) {
return () -> { return () -> {
for (BooleanSetting parent : parents) { for (BooleanSetting parent : parents) {
if (parent.get()) return true; if (parent.get()) return true;
@@ -79,7 +76,7 @@ public abstract class Setting<T> {
/** /**
* Adds a callback for {@link #importFromJSON(Context, String)} and {@link #exportToJson(Context)}. * Adds a callback for {@link #importFromJSON(Context, String)} and {@link #exportToJson(Context)}.
*/ */
public static void addImportExportCallback(@NonNull ImportExportCallback callback) { public static void addImportExportCallback(ImportExportCallback callback) {
importExportCallbacks.add(Objects.requireNonNull(callback)); importExportCallbacks.add(Objects.requireNonNull(callback));
} }
@@ -100,14 +97,13 @@ public abstract class Setting<T> {
public static final SharedPrefCategory preferences = new SharedPrefCategory("revanced_prefs"); public static final SharedPrefCategory preferences = new SharedPrefCategory("revanced_prefs");
@Nullable @Nullable
public static Setting<?> getSettingFromPath(@NonNull String str) { public static Setting<?> getSettingFromPath(String str) {
return PATH_TO_SETTINGS.get(str); return PATH_TO_SETTINGS.get(str);
} }
/** /**
* @return All settings that have been created. * @return All settings that have been created.
*/ */
@NonNull
public static List<Setting<?>> allLoadedSettings() { public static List<Setting<?>> allLoadedSettings() {
return Collections.unmodifiableList(SETTINGS); return Collections.unmodifiableList(SETTINGS);
} }
@@ -115,7 +111,6 @@ public abstract class Setting<T> {
/** /**
* @return All settings that have been created, sorted by keys. * @return All settings that have been created, sorted by keys.
*/ */
@NonNull
private static List<Setting<?>> allLoadedSettingsSorted() { private static List<Setting<?>> allLoadedSettingsSorted() {
Collections.sort(SETTINGS, (Setting<?> o1, Setting<?> o2) -> o1.key.compareTo(o2.key)); Collections.sort(SETTINGS, (Setting<?> o1, Setting<?> o2) -> o1.key.compareTo(o2.key));
return allLoadedSettings(); return allLoadedSettings();
@@ -124,13 +119,11 @@ public abstract class Setting<T> {
/** /**
* The key used to store the value in the shared preferences. * The key used to store the value in the shared preferences.
*/ */
@NonNull
public final String key; public final String key;
/** /**
* The default value of the setting. * The default value of the setting.
*/ */
@NonNull
public final T defaultValue; public final T defaultValue;
/** /**
@@ -161,7 +154,6 @@ public abstract class Setting<T> {
/** /**
* The value of the setting. * The value of the setting.
*/ */
@NonNull
protected volatile T value; protected volatile T value;
public Setting(String key, T defaultValue) { public Setting(String key, T defaultValue) {
@@ -199,8 +191,8 @@ public abstract class Setting<T> {
* @param userDialogMessage Confirmation message to display, if the user tries to change the setting from the default value. * @param userDialogMessage Confirmation message to display, if the user tries to change the setting from the default value.
* @param availability Condition that must be true, for this setting to be available to configure. * @param availability Condition that must be true, for this setting to be available to configure.
*/ */
public Setting(@NonNull String key, public Setting(String key,
@NonNull T defaultValue, T defaultValue,
boolean rebootApp, boolean rebootApp,
boolean includeWithImportExport, boolean includeWithImportExport,
@Nullable String userDialogMessage, @Nullable String userDialogMessage,
@@ -227,7 +219,7 @@ public abstract class Setting<T> {
/** /**
* Migrate a setting value if the path is renamed but otherwise the old and new settings are identical. * Migrate a setting value if the path is renamed but otherwise the old and new settings are identical.
*/ */
public static <T> void migrateOldSettingToNew(@NonNull Setting<T> oldSetting, @NonNull Setting<T> newSetting) { public static <T> void migrateOldSettingToNew(Setting<T> oldSetting, Setting<T> newSetting) {
if (oldSetting == newSetting) throw new IllegalArgumentException(); if (oldSetting == newSetting) throw new IllegalArgumentException();
if (!oldSetting.isSetToDefault()) { if (!oldSetting.isSetToDefault()) {
@@ -243,7 +235,7 @@ public abstract class Setting<T> {
* This method will be deleted in the future. * This method will be deleted in the future.
*/ */
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public static void migrateFromOldPreferences(@NonNull SharedPrefCategory oldPrefs, @NonNull Setting setting, String settingKey) { public static void migrateFromOldPreferences(SharedPrefCategory oldPrefs, Setting setting, String settingKey) {
if (!oldPrefs.preferences.contains(settingKey)) { if (!oldPrefs.preferences.contains(settingKey)) {
return; // Nothing to do. return; // Nothing to do.
} }
@@ -285,7 +277,7 @@ public abstract class Setting<T> {
* This intentionally is a static method to deter * This intentionally is a static method to deter
* accidental usage when {@link #save(Object)} was intended. * accidental usage when {@link #save(Object)} was intended.
*/ */
public static void privateSetValueFromString(@NonNull Setting<?> setting, @NonNull String newValue) { public static void privateSetValueFromString(Setting<?> setting, String newValue) {
setting.setValueFromString(newValue); setting.setValueFromString(newValue);
// Clear the preference value since default is used, to allow changing // Clear the preference value since default is used, to allow changing
@@ -299,7 +291,7 @@ public abstract class Setting<T> {
/** /**
* Sets the value of {@link #value}, but do not save to {@link #preferences}. * Sets the value of {@link #value}, but do not save to {@link #preferences}.
*/ */
protected abstract void setValueFromString(@NonNull String newValue); protected abstract void setValueFromString(String newValue);
/** /**
* Load and set the value of {@link #value}. * Load and set the value of {@link #value}.
@@ -309,7 +301,7 @@ public abstract class Setting<T> {
/** /**
* Persistently saves the value. * Persistently saves the value.
*/ */
public final void save(@NonNull T newValue) { public final void save(T newValue) {
if (value.equals(newValue)) { if (value.equals(newValue)) {
return; return;
} }
@@ -342,9 +334,12 @@ public abstract class Setting<T> {
/** /**
* Identical to calling {@link #save(Object)} using {@link #defaultValue}. * Identical to calling {@link #save(Object)} using {@link #defaultValue}.
*
* @return The newly saved default value.
*/ */
public void resetToDefault() { public T resetToDefault() {
save(defaultValue); save(defaultValue);
return defaultValue;
} }
/** /**
@@ -403,7 +398,6 @@ public abstract class Setting<T> {
json.put(importExportKey, value); json.put(importExportKey, value);
} }
@NonNull
public static String exportToJson(@Nullable Context alertDialogContext) { public static String exportToJson(@Nullable Context alertDialogContext) {
try { try {
JSONObject json = new JSONObject(); JSONObject json = new JSONObject();
@@ -442,7 +436,7 @@ public abstract class Setting<T> {
/** /**
* @return if any settings that require a reboot were changed. * @return if any settings that require a reboot were changed.
*/ */
public static boolean importFromJSON(@NonNull Context alertDialogContext, @NonNull String settingsJsonString) { public static boolean importFromJSON(Context alertDialogContext, String settingsJsonString) {
try { try {
if (!settingsJsonString.matches("[\\s\\S]*\\{")) { if (!settingsJsonString.matches("[\\s\\S]*\\{")) {
settingsJsonString = '{' + settingsJsonString + '}'; // Restore outer JSON braces settingsJsonString = '{' + settingsJsonString + '}'; // Restore outer JSON braces

View File

@@ -3,12 +3,20 @@ package app.revanced.extension.shared.settings.preference;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.AlertDialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.preference.*; import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceGroup;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.preference.SwitchPreference;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.util.Pair;
import android.widget.LinearLayout;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -22,6 +30,7 @@ import app.revanced.extension.shared.settings.Setting;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public abstract class AbstractPreferenceFragment extends PreferenceFragment { public abstract class AbstractPreferenceFragment extends PreferenceFragment {
/** /**
* Indicates that if a preference changes, * Indicates that if a preference changes,
* to apply the change from the Setting to the UI component. * to apply the change from the Setting to the UI component.
@@ -29,19 +38,29 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
public static boolean settingImportInProgress; public static boolean settingImportInProgress;
/** /**
* Confirm and restart dialog button text and title. * Prevents recursive calls during preference <-> UI syncing from showing extra dialogs.
* Set by subclasses if Strings cannot be added as a resource.
*/ */
@Nullable private static boolean updatingPreference;
protected static String restartDialogButtonText, restartDialogTitle, confirmDialogTitle;
/** /**
* Used to prevent showing reboot dialog, if user cancels a setting user dialog. * Used to prevent showing reboot dialog, if user cancels a setting user dialog.
*/ */
private boolean showingUserDialogMessage; private static boolean showingUserDialogMessage;
/**
* Confirm and restart dialog button text and title.
* Set by subclasses if Strings cannot be added as a resource.
*/
@Nullable
protected static String restartDialogButtonText, restartDialogTitle, confirmDialogTitle, restartDialogMessage;
private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> { private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> {
try { try {
if (updatingPreference) {
Logger.printDebug(() -> "Ignoring preference change as sync is in progress");
return;
}
Setting<?> setting = Setting.getSettingFromPath(Objects.requireNonNull(str)); Setting<?> setting = Setting.getSettingFromPath(Objects.requireNonNull(str));
if (setting == null) { if (setting == null) {
return; return;
@@ -63,16 +82,18 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
} }
} }
updatingPreference = true;
// Apply 'Setting <- Preference', unless during importing when it needs to be 'Setting -> Preference'. // Apply 'Setting <- Preference', unless during importing when it needs to be 'Setting -> Preference'.
// Updating here can cause a recursive call back into this same method.
updatePreference(pref, setting, true, settingImportInProgress); updatePreference(pref, setting, true, settingImportInProgress);
// Update any other preference availability that may now be different. // Update any other preference availability that may now be different.
updateUIAvailability(); updateUIAvailability();
updatingPreference = false;
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "OnSharedPreferenceChangeListener failure", ex); Logger.printException(() -> "OnSharedPreferenceChangeListener failure", ex);
} }
}; };
/** /**
* Initialize this instance, and do any custom behavior. * Initialize this instance, and do any custom behavior.
* <p> * <p>
@@ -81,7 +102,10 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
* so all app specific {@link Setting} instances are loaded before this method returns. * so all app specific {@link Setting} instances are loaded before this method returns.
*/ */
protected void initialize() { protected void initialize() {
final var identifier = Utils.getResourceIdentifier("revanced_prefs", "xml"); String preferenceResourceName = BaseSettings.SHOW_MENU_ICONS.get()
? "revanced_prefs_icons"
: "revanced_prefs";
final var identifier = Utils.getResourceIdentifier(preferenceResourceName, "xml");
if (identifier == 0) return; if (identifier == 0) return;
addPreferencesFromResource(identifier); addPreferencesFromResource(identifier);
@@ -97,12 +121,17 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
if (confirmDialogTitle == null) { if (confirmDialogTitle == null) {
confirmDialogTitle = str("revanced_settings_confirm_user_dialog_title"); confirmDialogTitle = str("revanced_settings_confirm_user_dialog_title");
} }
showingUserDialogMessage = true; showingUserDialogMessage = true;
new AlertDialog.Builder(context)
.setTitle(confirmDialogTitle) Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
.setMessage(Objects.requireNonNull(setting.userDialogMessage).toString()) context,
.setPositiveButton(android.R.string.ok, (dialog, id) -> { confirmDialogTitle, // Title.
// User confirmed, save to the Setting. Objects.requireNonNull(setting.userDialogMessage).toString(), // No message.
null, // No EditText.
null, // OK button text.
() -> {
// OK button action. User confirmed, save to the Setting.
updatePreference(pref, setting, true, false); updatePreference(pref, setting, true, false);
// Update availability of other preferences that may be changed. // Update availability of other preferences that may be changed.
@@ -111,23 +140,27 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
if (setting.rebootApp) { if (setting.rebootApp) {
showRestartDialog(context); showRestartDialog(context);
} }
}) },
.setNegativeButton(android.R.string.cancel, (dialog, id) -> { () -> {
// Restore whatever the setting was before the change. // Cancel button action. Restore whatever the setting was before the change.
updatePreference(pref, setting, true, true); updatePreference(pref, setting, true, true);
}) },
.setOnDismissListener(dialog -> { null, // No Neutral button.
showingUserDialogMessage = false; null, // No Neutral button action.
}) true // Dismiss dialog when onNeutralClick.
.setCancelable(false) );
.show();
dialogPair.first.setOnDismissListener(d -> showingUserDialogMessage = false);
// Show the dialog.
dialogPair.first.show();
} }
/** /**
* Updates all Preferences values and their availability using the current values in {@link Setting}. * Updates all Preferences values and their availability using the current values in {@link Setting}.
*/ */
protected void updateUIToSettingValues() { protected void updateUIToSettingValues() {
updatePreferenceScreen(getPreferenceScreen(), true,true); updatePreferenceScreen(getPreferenceScreen(), true, true);
} }
/** /**
@@ -141,14 +174,16 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
* @return If the preference is currently set to the default value of the Setting. * @return If the preference is currently set to the default value of the Setting.
*/ */
protected boolean prefIsSetToDefault(Preference pref, Setting<?> setting) { protected boolean prefIsSetToDefault(Preference pref, Setting<?> setting) {
Object defaultValue = setting.defaultValue;
if (pref instanceof SwitchPreference switchPref) { if (pref instanceof SwitchPreference switchPref) {
return switchPref.isChecked() == (Boolean) setting.defaultValue; return switchPref.isChecked() == (Boolean) defaultValue;
} }
String defaultValueString = defaultValue.toString();
if (pref instanceof EditTextPreference editPreference) { if (pref instanceof EditTextPreference editPreference) {
return editPreference.getText().equals(setting.defaultValue.toString()); return editPreference.getText().equals(defaultValueString);
} }
if (pref instanceof ListPreference listPref) { if (pref instanceof ListPreference listPref) {
return listPref.getValue().equals(setting.defaultValue.toString()); return listPref.getValue().equals(defaultValueString);
} }
throw new IllegalStateException("Must override method to handle " throw new IllegalStateException("Must override method to handle "
@@ -255,21 +290,32 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
} }
} }
public static void showRestartDialog(@NonNull final Context context) { public static void showRestartDialog(Context context) {
Utils.verifyOnMainThread(); Utils.verifyOnMainThread();
if (restartDialogTitle == null) { if (restartDialogTitle == null) {
restartDialogTitle = str("revanced_settings_restart_title"); restartDialogTitle = str("revanced_settings_restart_title");
} }
if (restartDialogMessage == null) {
restartDialogMessage = str("revanced_settings_restart_dialog_message");
}
if (restartDialogButtonText == null) { if (restartDialogButtonText == null) {
restartDialogButtonText = str("revanced_settings_restart"); restartDialogButtonText = str("revanced_settings_restart");
} }
new AlertDialog.Builder(context)
.setMessage(restartDialogTitle) Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(context,
.setPositiveButton(restartDialogButtonText, (dialog, id) restartDialogTitle, // Title.
-> Utils.restartApp(context)) restartDialogMessage, // Message.
.setNegativeButton(android.R.string.cancel, null) null, // No EditText.
.setCancelable(false) restartDialogButtonText, // OK button text.
.show(); () -> Utils.restartApp(context), // OK button action.
() -> {}, // Cancel button action (dismiss only).
null, // No Neutral button text.
null, // No Neutral button action.
true // Dismiss dialog when onNeutralClick.
);
// Show the dialog.
dialogPair.first.show();
} }
@SuppressLint("ResourceType") @SuppressLint("ResourceType")

View File

@@ -0,0 +1,449 @@
package app.revanced.extension.shared.settings.preference;
import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
import static app.revanced.extension.shared.Utils.dipToPixels;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Build;
import android.os.Bundle;
import android.preference.EditTextPreference;
import android.text.Editable;
import android.text.InputType;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextWatcher;
import android.text.style.ForegroundColorSpan;
import android.text.style.RelativeSizeSpan;
import android.util.AttributeSet;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.*;
import androidx.annotation.ColorInt;
import java.util.Locale;
import java.util.regex.Pattern;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.settings.StringSetting;
/**
* A custom preference for selecting a color via a hexadecimal code or a color picker dialog.
* Extends {@link EditTextPreference} to display a colored dot in the widget area,
* reflecting the currently selected color. The dot is dimmed when the preference is disabled.
*/
@SuppressWarnings({"unused", "deprecation"})
public class ColorPickerPreference extends EditTextPreference {
/**
* Character to show the color appearance.
*/
public static final String COLOR_DOT_STRING = "";
/**
* Length of a valid color string of format #RRGGBB.
*/
public static final int COLOR_STRING_LENGTH = 7;
/**
* Matches everything that is not a hex number/letter.
*/
private static final Pattern PATTERN_NOT_HEX = Pattern.compile("[^0-9A-Fa-f]");
/**
* Alpha for dimming when the preference is disabled.
*/
private static final float DISABLED_ALPHA = 0.5f; // 50%
/**
* View displaying a colored dot in the widget area.
*/
private View widgetColorDot;
/**
* Current color in RGB format (without alpha).
*/
@ColorInt
private int currentColor;
/**
* Associated setting for storing the color value.
*/
private StringSetting colorSetting;
/**
* Dialog TextWatcher for the EditText to monitor color input changes.
*/
private TextWatcher colorTextWatcher;
/**
* Dialog TextView displaying a colored dot for the selected color preview in the dialog.
*/
private TextView dialogColorPreview;
/**
* Dialog color picker view.
*/
private ColorPickerView dialogColorPickerView;
/**
* Removes non valid hex characters, converts to all uppercase,
* and adds # character to the start if not present.
*/
public static String cleanupColorCodeString(String colorString) {
// Remove non-hex chars, convert to uppercase, and ensure correct length
String result = "#" + PATTERN_NOT_HEX.matcher(colorString)
.replaceAll("").toUpperCase(Locale.ROOT);
if (result.length() < COLOR_STRING_LENGTH) {
return result;
}
return result.substring(0, COLOR_STRING_LENGTH);
}
/**
* @param color RGB color, without an alpha channel.
* @return #RRGGBB hex color string
*/
public static String getColorString(@ColorInt int color) {
String colorString = String.format("#%06X", color);
if ((color & 0xFF000000) != 0) {
// Likely a bug somewhere.
Logger.printException(() -> "getColorString: color has alpha channel: " + colorString);
}
return colorString;
}
/**
* Creates a Spanned object for a colored dot using SpannableString.
*
* @param color The RGB color (without alpha).
* @return A Spanned object with the colored dot.
*/
public static Spanned getColorDot(@ColorInt int color) {
SpannableString spannable = new SpannableString(COLOR_DOT_STRING);
spannable.setSpan(new ForegroundColorSpan(color | 0xFF000000), 0, COLOR_DOT_STRING.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spannable.setSpan(new RelativeSizeSpan(1.5f), 0, 1,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
return spannable;
}
public ColorPickerPreference(Context context) {
super(context);
init();
}
public ColorPickerPreference(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ColorPickerPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
/**
* Initializes the preference by setting up the EditText, loading the color, and set the widget layout.
*/
private void init() {
colorSetting = (StringSetting) Setting.getSettingFromPath(getKey());
if (colorSetting == null) {
Logger.printException(() -> "Could not find color setting for: " + getKey());
}
EditText editText = getEditText();
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
editText.setAutofillHints((String) null);
}
// Set the widget layout to a custom layout containing the colored dot.
setWidgetLayoutResource(getResourceIdentifier("revanced_color_dot_widget", "layout"));
}
/**
* Sets the selected color and updates the UI and settings.
*
* @param colorString The color in hexadecimal format (e.g., "#RRGGBB").
* @throws IllegalArgumentException If the color string is invalid.
*/
@Override
public final void setText(String colorString) {
try {
Logger.printDebug(() -> "setText: " + colorString);
super.setText(colorString);
currentColor = Color.parseColor(colorString) & 0x00FFFFFF;
if (colorSetting != null) {
colorSetting.save(getColorString(currentColor));
}
updateColorPreview();
updateWidgetColorDot();
} catch (IllegalArgumentException ex) {
// This code is reached if the user pastes settings json with an invalid color
// since this preference is updated with the new setting text.
Logger.printDebug(() -> "Parse color error: " + colorString, ex);
Utils.showToastShort(str("revanced_settings_color_invalid"));
setText(colorSetting.resetToDefault());
} catch (Exception ex) {
Logger.printException(() -> "setText failure: " + colorString, ex);
}
}
@Override
protected void onBindView(View view) {
super.onBindView(view);
widgetColorDot = view.findViewById(getResourceIdentifier(
"revanced_color_dot_widget", "id"));
widgetColorDot.setBackgroundResource(getResourceIdentifier(
"revanced_settings_circle_background", "drawable"));
widgetColorDot.getBackground().setTint(currentColor | 0xFF000000);
widgetColorDot.setAlpha(isEnabled() ? 1.0f : DISABLED_ALPHA);
}
/**
* Updates the color preview TextView with a colored dot.
*/
private void updateColorPreview() {
if (dialogColorPreview != null) {
dialogColorPreview.setText(getColorDot(currentColor));
}
}
private void updateWidgetColorDot() {
if (widgetColorDot != null) {
widgetColorDot.getBackground().setTint(currentColor | 0xFF000000);
widgetColorDot.setAlpha(isEnabled() ? 1.0f : DISABLED_ALPHA);
}
}
/**
* Creates a TextWatcher to monitor changes in the EditText for color input.
*
* @return A TextWatcher that updates the color preview on valid input.
*/
private TextWatcher createColorTextWatcher(ColorPickerView colorPickerView) {
return new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable edit) {
try {
String colorString = edit.toString();
String sanitizedColorString = cleanupColorCodeString(colorString);
if (!sanitizedColorString.equals(colorString)) {
edit.replace(0, colorString.length(), sanitizedColorString);
return;
}
if (sanitizedColorString.length() != COLOR_STRING_LENGTH) {
// User is still typing out the color.
return;
}
final int newColor = Color.parseColor(colorString);
if (currentColor != newColor) {
Logger.printDebug(() -> "afterTextChanged: " + sanitizedColorString);
currentColor = newColor;
updateColorPreview();
updateWidgetColorDot();
colorPickerView.setColor(newColor);
}
} catch (Exception ex) {
// Should never be reached since input is validated before using.
Logger.printException(() -> "afterTextChanged failure", ex);
}
}
};
}
/**
* Creates a Dialog with a color preview and EditText for hex color input.
*/
@Override
protected void showDialog(Bundle state) {
Context context = getContext();
// Inflate color picker view.
View colorPicker = LayoutInflater.from(context).inflate(
getResourceIdentifier("revanced_color_picker", "layout"), null);
dialogColorPickerView = colorPicker.findViewById(
getResourceIdentifier("revanced_color_picker_view", "id"));
dialogColorPickerView.setColor(currentColor);
// Horizontal layout for preview and EditText.
LinearLayout inputLayout = new LinearLayout(context);
inputLayout.setOrientation(LinearLayout.HORIZONTAL);
dialogColorPreview = new TextView(context);
LinearLayout.LayoutParams previewParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
previewParams.setMargins(dipToPixels(15), 0, dipToPixels(10), 0); // text dot has its own indents so 15, instead 16.
dialogColorPreview.setLayoutParams(previewParams);
inputLayout.addView(dialogColorPreview);
updateColorPreview();
EditText editText = getEditText();
ViewParent parent = editText.getParent();
if (parent instanceof ViewGroup parentViewGroup) {
parentViewGroup.removeView(editText);
}
editText.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
));
String currentColorString = getColorString(currentColor);
editText.setText(currentColorString);
editText.setSelection(currentColorString.length());
editText.setTypeface(Typeface.MONOSPACE);
colorTextWatcher = createColorTextWatcher(dialogColorPickerView);
editText.addTextChangedListener(colorTextWatcher);
inputLayout.addView(editText);
// Add a dummy view to take up remaining horizontal space,
// otherwise it will show an oversize underlined text view.
View paddingView = new View(context);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
0,
LinearLayout.LayoutParams.MATCH_PARENT,
1f
);
paddingView.setLayoutParams(params);
inputLayout.addView(paddingView);
// Create content container for color picker and input layout.
LinearLayout contentContainer = new LinearLayout(context);
contentContainer.setOrientation(LinearLayout.VERTICAL);
contentContainer.addView(colorPicker);
contentContainer.addView(inputLayout);
// Create ScrollView to wrap the content container.
ScrollView contentScrollView = new ScrollView(context);
contentScrollView.setVerticalScrollBarEnabled(false); // Disable vertical scrollbar.
contentScrollView.setOverScrollMode(View.OVER_SCROLL_NEVER); // Disable overscroll effect.
LinearLayout.LayoutParams scrollViewParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
0,
1.0f
);
contentScrollView.setLayoutParams(scrollViewParams);
contentScrollView.addView(contentContainer);
// Create custom dialog.
final int originalColor = currentColor & 0x00FFFFFF;
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
context,
getTitle() != null ? getTitle().toString() : str("revanced_settings_color_picker_title"), // Title.
null, // No message.
null, // No EditText.
null, // OK button text.
() -> {
// OK button action.
try {
String colorString = editText.getText().toString();
if (colorString.length() != COLOR_STRING_LENGTH) {
Utils.showToastShort(str("revanced_settings_color_invalid"));
setText(getColorString(originalColor));
return;
}
setText(colorString);
} catch (Exception ex) {
// Should never happen due to a bad color string,
// since the text is validated and fixed while the user types.
Logger.printException(() -> "OK button failure", ex);
}
},
() -> {
// Cancel button action.
try {
// Restore the original color.
setText(getColorString(originalColor));
} catch (Exception ex) {
Logger.printException(() -> "Cancel button failure", ex);
}
},
str("revanced_settings_reset_color"), // Neutral button text.
() -> {
// Neutral button action.
try {
final int defaultColor = Color.parseColor(colorSetting.defaultValue) & 0x00FFFFFF;
// Setting view color causes listener callback into this class.
dialogColorPickerView.setColor(defaultColor);
} catch (Exception ex) {
Logger.printException(() -> "Reset button failure", ex);
}
},
false // Do not dismiss dialog when onNeutralClick.
);
// Add the ScrollView to the dialog's main layout.
LinearLayout dialogMainLayout = dialogPair.second;
dialogMainLayout.addView(contentScrollView, dialogMainLayout.getChildCount() - 1);
// Set up color picker listener with debouncing.
// Add listener last to prevent callbacks from set calls above.
dialogColorPickerView.setOnColorChangedListener(color -> {
// Check if it actually changed, since this callback
// can be caused by updates in afterTextChanged().
if (currentColor == color) {
return;
}
String updatedColorString = getColorString(color);
Logger.printDebug(() -> "onColorChanged: " + updatedColorString);
currentColor = color;
editText.setText(updatedColorString);
editText.setSelection(updatedColorString.length());
updateColorPreview();
updateWidgetColorDot();
});
// Configure and show the dialog.
Dialog dialog = dialogPair.first;
dialog.setCanceledOnTouchOutside(false);
dialog.show();
}
@Override
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
if (colorTextWatcher != null) {
getEditText().removeTextChangedListener(colorTextWatcher);
colorTextWatcher = null;
}
dialogColorPreview = null;
dialogColorPickerView = null;
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
updateWidgetColorDot();
}
}

View File

@@ -0,0 +1,500 @@
package app.revanced.extension.shared.settings.preference;
import static app.revanced.extension.shared.Utils.dipToPixels;
import static app.revanced.extension.shared.settings.preference.ColorPickerPreference.getColorString;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ComposeShader;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.RectF;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.ColorInt;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
/**
* A custom color picker view that allows the user to select a color using a hue slider and a saturation-value selector.
* This implementation is density-independent and responsive across different screen sizes and DPIs.
*
* <p>
* This view displays two main components for color selection:
* <ul>
* <li><b>Hue Bar:</b> A horizontal bar at the bottom that allows the user to select the hue component of the color.
* <li><b>Saturation-Value Selector:</b> A rectangular area above the hue bar that allows the user to select the saturation and value (brightness)
* components of the color based on the selected hue.
* </ul>
*
* <p>
* The view uses {@link LinearGradient} and {@link ComposeShader} to create the color gradients for the hue bar and the
* saturation-value selector. It also uses {@link Paint} to draw the selectors (draggable handles).
*
* <p>
* The selected color can be retrieved using {@link #getColor()} and can be set using {@link #setColor(int)}.
* An {@link OnColorChangedListener} can be registered to receive notifications when the selected color changes.
*/
public class ColorPickerView extends View {
/**
* Interface definition for a callback to be invoked when the selected color changes.
*/
public interface OnColorChangedListener {
/**
* Called when the selected color has changed.
*
* Important: Callback color uses RGB format with zero alpha channel.
*
* @param color The new selected color.
*/
void onColorChanged(@ColorInt int color);
}
/** Expanded touch area for the hue bar to increase the touch-sensitive area. */
public static final float TOUCH_EXPANSION = dipToPixels(20f);
private static final float MARGIN_BETWEEN_AREAS = dipToPixels(24);
private static final float VIEW_PADDING = dipToPixels(16);
private static final float HUE_BAR_HEIGHT = dipToPixels(12);
private static final float HUE_CORNER_RADIUS = dipToPixels(6);
private static final float SELECTOR_RADIUS = dipToPixels(12);
private static final float SELECTOR_STROKE_WIDTH = 8;
/**
* Hue fill radius. Use slightly smaller radius for the selector handle fill,
* otherwise the anti-aliasing causes the fill color to bleed past the selector outline.
*/
private static final float SELECTOR_FILL_RADIUS = SELECTOR_RADIUS - SELECTOR_STROKE_WIDTH / 2;
/** Thin dark outline stroke width for the selector rings. */
private static final float SELECTOR_EDGE_STROKE_WIDTH = 1;
public static final float SELECTOR_EDGE_RADIUS =
SELECTOR_RADIUS + SELECTOR_STROKE_WIDTH / 2 + SELECTOR_EDGE_STROKE_WIDTH / 2;
/** Selector outline inner color. */
@ColorInt
private static final int SELECTOR_OUTLINE_COLOR = Color.WHITE;
/** Dark edge color for the selector rings. */
@ColorInt
private static final int SELECTOR_EDGE_COLOR = Color.parseColor("#CFCFCF");
private static final int[] HUE_COLORS = new int[361];
static {
for (int i = 0; i < 361; i++) {
HUE_COLORS[i] = Color.HSVToColor(new float[]{i, 1, 1});
}
}
/** Hue bar. */
private final Paint huePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
/** Saturation-value selector. */
private final Paint saturationValuePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
/** Draggable selector. */
private final Paint selectorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
{
selectorPaint.setStrokeWidth(SELECTOR_STROKE_WIDTH);
}
/** Bounds of the hue bar. */
private final RectF hueRect = new RectF();
/** Bounds of the saturation-value selector. */
private final RectF saturationValueRect = new RectF();
/** HSV color calculations to avoid allocations during drawing. */
private final float[] hsvArray = {1, 1, 1};
/** Current hue value (0-360). */
private float hue = 0f;
/** Current saturation value (0-1). */
private float saturation = 1f;
/** Current value (brightness) value (0-1). */
private float value = 1f;
/** The currently selected color in RGB format with no alpha channel. */
@ColorInt
private int selectedColor;
private OnColorChangedListener colorChangedListener;
/** Track if we're currently dragging the hue or saturation handle. */
private boolean isDraggingHue;
private boolean isDraggingSaturation;
public ColorPickerView(Context context) {
super(context);
}
public ColorPickerView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ColorPickerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final float DESIRED_ASPECT_RATIO = 0.8f; // height = width * 0.8
final int minWidth = Utils.dipToPixels(250);
final int minHeight = (int) (minWidth * DESIRED_ASPECT_RATIO) + (int) (HUE_BAR_HEIGHT + MARGIN_BETWEEN_AREAS);
int width = resolveSize(minWidth, widthMeasureSpec);
int height = resolveSize(minHeight, heightMeasureSpec);
// Ensure minimum dimensions for usability.
width = Math.max(width, minWidth);
height = Math.max(height, minHeight);
// Adjust height to maintain desired aspect ratio if possible.
final int desiredHeight = (int) (width * DESIRED_ASPECT_RATIO) + (int) (HUE_BAR_HEIGHT + MARGIN_BETWEEN_AREAS);
if (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY) {
height = desiredHeight;
}
setMeasuredDimension(width, height);
}
/**
* Called when the size of the view changes.
* This method calculates and sets the bounds of the hue bar and saturation-value selector.
* It also creates the necessary shaders for the gradients.
*/
@Override
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
super.onSizeChanged(width, height, oldWidth, oldHeight);
// Calculate bounds with hue bar at the bottom.
final float effectiveWidth = width - (2 * VIEW_PADDING);
final float effectiveHeight = height - (2 * VIEW_PADDING) - HUE_BAR_HEIGHT - MARGIN_BETWEEN_AREAS;
// Adjust rectangles to account for padding and density-independent dimensions.
saturationValueRect.set(
VIEW_PADDING,
VIEW_PADDING,
VIEW_PADDING + effectiveWidth,
VIEW_PADDING + effectiveHeight
);
hueRect.set(
VIEW_PADDING,
height - VIEW_PADDING - HUE_BAR_HEIGHT,
VIEW_PADDING + effectiveWidth,
height - VIEW_PADDING
);
// Update the shaders.
updateHueShader();
updateSaturationValueShader();
}
/**
* Updates the hue full spectrum (0-360 degrees).
*/
private void updateHueShader() {
LinearGradient hueShader = new LinearGradient(
hueRect.left, hueRect.top,
hueRect.right, hueRect.top,
HUE_COLORS,
null,
Shader.TileMode.CLAMP
);
huePaint.setShader(hueShader);
}
/**
* Updates the shader for the saturation-value selector based on the currently selected hue.
* This method creates a combined shader that blends a saturation gradient with a value gradient.
*/
private void updateSaturationValueShader() {
// Create a saturation-value gradient based on the current hue.
// Calculate the start color (white with the selected hue) for the saturation gradient.
final int startColor = Color.HSVToColor(new float[]{hue, 0f, 1f});
// Calculate the middle color (fully saturated color with the selected hue) for the saturation gradient.
final int midColor = Color.HSVToColor(new float[]{hue, 1f, 1f});
// Create a linear gradient for the saturation from startColor to midColor (horizontal).
LinearGradient satShader = new LinearGradient(
saturationValueRect.left, saturationValueRect.top,
saturationValueRect.right, saturationValueRect.top,
startColor,
midColor,
Shader.TileMode.CLAMP
);
// Create a linear gradient for the value (brightness) from white to black (vertical).
//noinspection ExtractMethodRecommender
LinearGradient valShader = new LinearGradient(
saturationValueRect.left, saturationValueRect.top,
saturationValueRect.left, saturationValueRect.bottom,
Color.WHITE,
Color.BLACK,
Shader.TileMode.CLAMP
);
// Combine the saturation and value shaders using PorterDuff.Mode.MULTIPLY to create the final color.
ComposeShader combinedShader = new ComposeShader(satShader, valShader, PorterDuff.Mode.MULTIPLY);
// Set the combined shader for the saturation-value paint.
saturationValuePaint.setShader(combinedShader);
}
/**
* Draws the color picker view on the canvas.
* This method draws the saturation-value selector, the hue bar with rounded corners,
* and the draggable handles.
*
* @param canvas The canvas on which to draw.
*/
@Override
protected void onDraw(Canvas canvas) {
// Draw the saturation-value selector rectangle.
canvas.drawRect(saturationValueRect, saturationValuePaint);
// Draw the hue bar.
canvas.drawRoundRect(hueRect, HUE_CORNER_RADIUS, HUE_CORNER_RADIUS, huePaint);
final float hueSelectorX = hueRect.left + (hue / 360f) * hueRect.width();
final float hueSelectorY = hueRect.centerY();
final float satSelectorX = saturationValueRect.left + saturation * saturationValueRect.width();
final float satSelectorY = saturationValueRect.top + (1 - value) * saturationValueRect.height();
// Draw the saturation and hue selector handle filled with the selected color.
hsvArray[0] = hue;
final int hueHandleColor = Color.HSVToColor(0xFF, hsvArray);
selectorPaint.setStyle(Paint.Style.FILL_AND_STROKE);
selectorPaint.setColor(hueHandleColor);
canvas.drawCircle(hueSelectorX, hueSelectorY, SELECTOR_FILL_RADIUS, selectorPaint);
selectorPaint.setColor(selectedColor | 0xFF000000);
canvas.drawCircle(satSelectorX, satSelectorY, SELECTOR_FILL_RADIUS, selectorPaint);
// Draw white outlines for the handles.
selectorPaint.setColor(SELECTOR_OUTLINE_COLOR);
selectorPaint.setStyle(Paint.Style.STROKE);
selectorPaint.setStrokeWidth(SELECTOR_STROKE_WIDTH);
canvas.drawCircle(hueSelectorX, hueSelectorY, SELECTOR_RADIUS, selectorPaint);
canvas.drawCircle(satSelectorX, satSelectorY, SELECTOR_RADIUS, selectorPaint);
// Draw thin dark outlines for the handles at the outer edge of the white outline.
selectorPaint.setColor(SELECTOR_EDGE_COLOR);
selectorPaint.setStrokeWidth(SELECTOR_EDGE_STROKE_WIDTH);
canvas.drawCircle(hueSelectorX, hueSelectorY, SELECTOR_EDGE_RADIUS, selectorPaint);
canvas.drawCircle(satSelectorX, satSelectorY, SELECTOR_EDGE_RADIUS, selectorPaint);
}
/**
* Handles touch events on the view.
* This method determines whether the touch event occurred within the hue bar or the saturation-value selector,
* updates the corresponding values (hue, saturation, value), and invalidates the view to trigger a redraw.
* <p>
* In addition to testing if the touch is within the strict rectangles, an expanded hit area (by selectorRadius)
* is used so that the draggable handles remain active even when half of the handle is outside the drawn bounds.
*
* @param event The motion event.
* @return True if the event was handled, false otherwise.
*/
@SuppressLint("ClickableViewAccessibility") // performClick is not overridden, but not needed in this case.
@Override
public boolean onTouchEvent(MotionEvent event) {
try {
final float x = event.getX();
final float y = event.getY();
final int action = event.getAction();
Logger.printDebug(() -> "onTouchEvent action: " + action + " x: " + x + " y: " + y);
// Define touch expansion for the hue bar.
RectF expandedHueRect = new RectF(
hueRect.left,
hueRect.top - TOUCH_EXPANSION,
hueRect.right,
hueRect.bottom + TOUCH_EXPANSION
);
switch (action) {
case MotionEvent.ACTION_DOWN:
// Calculate current handle positions.
final float hueSelectorX = hueRect.left + (hue / 360f) * hueRect.width();
final float hueSelectorY = hueRect.centerY();
final float satSelectorX = saturationValueRect.left + saturation * saturationValueRect.width();
final float valSelectorY = saturationValueRect.top + (1 - value) * saturationValueRect.height();
// Create hit areas for both handles.
RectF hueHitRect = new RectF(
hueSelectorX - SELECTOR_RADIUS,
hueSelectorY - SELECTOR_RADIUS,
hueSelectorX + SELECTOR_RADIUS,
hueSelectorY + SELECTOR_RADIUS
);
RectF satValHitRect = new RectF(
satSelectorX - SELECTOR_RADIUS,
valSelectorY - SELECTOR_RADIUS,
satSelectorX + SELECTOR_RADIUS,
valSelectorY + SELECTOR_RADIUS
);
// Check if the touch started on a handle or within the expanded hue bar area.
if (hueHitRect.contains(x, y)) {
isDraggingHue = true;
updateHueFromTouch(x);
} else if (satValHitRect.contains(x, y)) {
isDraggingSaturation = true;
updateSaturationValueFromTouch(x, y);
} else if (expandedHueRect.contains(x, y)) {
// Handle touch within the expanded hue bar area.
isDraggingHue = true;
updateHueFromTouch(x);
} else if (saturationValueRect.contains(x, y)) {
isDraggingSaturation = true;
updateSaturationValueFromTouch(x, y);
}
break;
case MotionEvent.ACTION_MOVE:
// Continue updating values even if touch moves outside the view.
if (isDraggingHue) {
updateHueFromTouch(x);
} else if (isDraggingSaturation) {
updateSaturationValueFromTouch(x, y);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
isDraggingHue = false;
isDraggingSaturation = false;
break;
}
} catch (Exception ex) {
Logger.printException(() -> "onTouchEvent failure", ex);
}
return true;
}
/**
* Updates the hue value based on touch position, clamping to valid range.
*
* @param x The x-coordinate of the touch position.
*/
private void updateHueFromTouch(float x) {
// Clamp x to the hue rectangle bounds.
final float clampedX = Utils.clamp(x, hueRect.left, hueRect.right);
final float updatedHue = ((clampedX - hueRect.left) / hueRect.width()) * 360f;
if (hue == updatedHue) {
return;
}
hue = updatedHue;
updateSaturationValueShader();
updateSelectedColor();
}
/**
* Updates saturation and value based on touch position, clamping to valid range.
*
* @param x The x-coordinate of the touch position.
* @param y The y-coordinate of the touch position.
*/
private void updateSaturationValueFromTouch(float x, float y) {
// Clamp x and y to the saturation-value rectangle bounds.
final float clampedX = Utils.clamp(x, saturationValueRect.left, saturationValueRect.right);
final float clampedY = Utils.clamp(y, saturationValueRect.top, saturationValueRect.bottom);
final float updatedSaturation = (clampedX - saturationValueRect.left) / saturationValueRect.width();
final float updatedValue = 1 - ((clampedY - saturationValueRect.top) / saturationValueRect.height());
if (saturation == updatedSaturation && value == updatedValue) {
return;
}
saturation = updatedSaturation;
value = updatedValue;
updateSelectedColor();
}
/**
* Updates the selected color and notifies listeners.
*/
private void updateSelectedColor() {
final int updatedColor = Color.HSVToColor(0, new float[]{hue, saturation, value});
if (selectedColor != updatedColor) {
selectedColor = updatedColor;
if (colorChangedListener != null) {
colorChangedListener.onColorChanged(updatedColor);
}
}
// Must always redraw, otherwise if saturation is pure grey or black
// then the hue slider cannot be changed.
invalidate();
}
/**
* Sets the currently selected color.
*
* @param color The color to set in either ARGB or RGB format.
*/
public void setColor(@ColorInt int color) {
color &= 0x00FFFFFF;
if (selectedColor == color) {
return;
}
// Update the selected color.
selectedColor = color;
Logger.printDebug(() -> "setColor: " + getColorString(selectedColor));
// Convert the ARGB color to HSV values.
float[] hsv = new float[3];
Color.colorToHSV(color, hsv);
// Update the hue, saturation, and value.
hue = hsv[0];
saturation = hsv[1];
value = hsv[2];
// Update the saturation-value shader based on the new hue.
updateSaturationValueShader();
// Notify the listener if it's set.
if (colorChangedListener != null) {
colorChangedListener.onColorChanged(selectedColor);
}
// Invalidate the view to trigger a redraw.
invalidate();
}
/**
* Gets the currently selected color.
*
* @return The selected color in RGB format with no alpha channel.
*/
@ColorInt
public int getColor() {
return selectedColor;
}
/**
* Sets the listener to be notified when the selected color changes.
*
* @param listener The listener to set.
*/
public void setOnColorChangedListener(OnColorChangedListener listener) {
colorChangedListener = listener;
}
}

View File

@@ -0,0 +1,173 @@
package app.revanced.extension.shared.settings.preference;
import static app.revanced.extension.shared.Utils.dipToPixels;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.preference.ListPreference;
import android.util.AttributeSet;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.*;
import androidx.annotation.NonNull;
import app.revanced.extension.shared.Utils;
/**
* A custom ListPreference that uses a styled custom dialog with a custom checkmark indicator.
*/
@SuppressWarnings({"unused", "deprecation"})
public class CustomDialogListPreference extends ListPreference {
/**
* Custom ArrayAdapter to handle checkmark visibility.
*/
private static class ListPreferenceArrayAdapter extends ArrayAdapter<CharSequence> {
private static class SubViewDataContainer {
ImageView checkIcon;
View placeholder;
TextView itemText;
}
final int layoutResourceId;
final CharSequence[] entryValues;
String selectedValue;
public ListPreferenceArrayAdapter(Context context, int resource, CharSequence[] entries,
CharSequence[] entryValues, String selectedValue) {
super(context, resource, entries);
this.layoutResourceId = resource;
this.entryValues = entryValues;
this.selectedValue = selectedValue;
}
@NonNull
@Override
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
View view = convertView;
SubViewDataContainer holder;
if (view == null) {
LayoutInflater inflater = LayoutInflater.from(getContext());
view = inflater.inflate(layoutResourceId, parent, false);
holder = new SubViewDataContainer();
holder.checkIcon = view.findViewById(Utils.getResourceIdentifier(
"revanced_check_icon", "id"));
holder.placeholder = view.findViewById(Utils.getResourceIdentifier(
"revanced_check_icon_placeholder", "id"));
holder.itemText = view.findViewById(Utils.getResourceIdentifier(
"revanced_item_text", "id"));
view.setTag(holder);
} else {
holder = (SubViewDataContainer) view.getTag();
}
// Set text.
holder.itemText.setText(getItem(position));
holder.itemText.setTextColor(Utils.getAppForegroundColor());
// Show or hide checkmark and placeholder.
String currentValue = entryValues[position].toString();
boolean isSelected = currentValue.equals(selectedValue);
holder.checkIcon.setVisibility(isSelected ? View.VISIBLE : View.GONE);
holder.checkIcon.setColorFilter(Utils.getAppForegroundColor());
holder.placeholder.setVisibility(isSelected ? View.GONE : View.VISIBLE);
return view;
}
public void setSelectedValue(String value) {
this.selectedValue = value;
}
}
public CustomDialogListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public CustomDialogListPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public CustomDialogListPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomDialogListPreference(Context context) {
super(context);
}
@Override
protected void showDialog(Bundle state) {
Context context = getContext();
// Create ListView.
ListView listView = new ListView(context);
listView.setId(android.R.id.list);
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
// Create custom adapter for the ListView.
ListPreferenceArrayAdapter adapter = new ListPreferenceArrayAdapter(
context,
Utils.getResourceIdentifier("revanced_custom_list_item_checked", "layout"),
getEntries(),
getEntryValues(),
getValue()
);
listView.setAdapter(adapter);
// Set checked item.
String currentValue = getValue();
if (currentValue != null) {
CharSequence[] entryValues = getEntryValues();
for (int i = 0, length = entryValues.length; i < length; i++) {
if (currentValue.equals(entryValues[i].toString())) {
listView.setItemChecked(i, true);
listView.setSelection(i);
break;
}
}
}
// Create the custom dialog without OK button.
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
context,
getTitle() != null ? getTitle().toString() : "",
null,
null,
null, // No OK button text.
null, // No OK button action.
() -> {}, // Cancel button action (just dismiss).
null,
null,
true
);
// Add the ListView to the main layout.
LinearLayout mainLayout = dialogPair.second;
LinearLayout.LayoutParams listViewParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
0,
1.0f
);
mainLayout.addView(listView, mainLayout.getChildCount() - 1, listViewParams);
// Handle item click to select value and dismiss dialog.
listView.setOnItemClickListener((parent, view, position, id) -> {
String selectedValue = getEntryValues()[position].toString();
if (callChangeListener(selectedValue)) {
setValue(selectedValue);
adapter.setSelectedValue(selectedValue);
adapter.notifyDataSetChanged();
}
dialogPair.first.dismiss();
});
// Show the dialog.
dialogPair.first.show();
}
}

View File

@@ -1,19 +1,30 @@
package app.revanced.extension.shared.settings.preference; package app.revanced.extension.shared.settings.preference;
import android.app.AlertDialog; import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.dipToPixels;
import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.os.Build; import android.os.Build;
import android.os.Bundle;
import android.preference.EditTextPreference; import android.preference.EditTextPreference;
import android.preference.Preference; import android.preference.Preference;
import android.text.InputType; import android.text.InputType;
import android.text.TextUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Pair;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText; import android.widget.EditText;
import app.revanced.extension.shared.settings.Setting; import android.widget.LinearLayout;
import android.widget.TextView;
import android.graphics.Color;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RoundRectShape;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.Setting;
import static app.revanced.extension.shared.StringRef.str;
@SuppressWarnings({"unused", "deprecation"}) @SuppressWarnings({"unused", "deprecation"})
public class ImportExportPreference extends EditTextPreference implements Preference.OnPreferenceClickListener { public class ImportExportPreference extends EditTextPreference implements Preference.OnPreferenceClickListener {
@@ -54,7 +65,8 @@ public class ImportExportPreference extends EditTextPreference implements Prefer
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
try { try {
// Must set text before preparing dialog, otherwise text is non selectable if this preference is later reopened. // Must set text before showing dialog,
// otherwise text is non-selectable if this preference is later reopened.
existingSettings = Setting.exportToJson(getContext()); existingSettings = Setting.exportToJson(getContext());
getEditText().setText(existingSettings); getEditText().setText(existingSettings);
} catch (Exception ex) { } catch (Exception ex) {
@@ -64,18 +76,32 @@ public class ImportExportPreference extends EditTextPreference implements Prefer
} }
@Override @Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { protected void showDialog(Bundle state) {
try { try {
Utils.setEditTextDialogTheme(builder); Context context = getContext();
EditText editText = getEditText();
// Show the user the settings in JSON format. // Create a custom dialog with the EditText.
builder.setNeutralButton(str("revanced_settings_import_copy"), (dialog, which) -> { Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
Utils.setClipboard(getEditText().getText().toString()); context,
}).setPositiveButton(str("revanced_settings_import"), (dialog, which) -> { str("revanced_pref_import_export_title"), // Title.
importSettings(builder.getContext(), getEditText().getText().toString()); null, // No message (EditText replaces it).
}); editText, // Pass the EditText.
str("revanced_settings_import"), // OK button text.
() -> importSettings(context, editText.getText().toString()), // OK button action.
() -> {}, // Cancel button action (dismiss only).
str("revanced_settings_import_copy"), // Neutral button (Copy) text.
() -> {
// Neutral button (Copy) action. Show the user the settings in JSON format.
Utils.setClipboard(editText.getText());
},
true // Dismiss dialog when onNeutralClick.
);
// Show the dialog.
dialogPair.first.show();
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "onPrepareDialogBuilder failure", ex); Logger.printException(() -> "showDialog failure", ex);
} }
} }
@@ -88,7 +114,7 @@ public class ImportExportPreference extends EditTextPreference implements Prefer
final boolean rebootNeeded = Setting.importFromJSON(context, replacementSettings); final boolean rebootNeeded = Setting.importFromJSON(context, replacementSettings);
if (rebootNeeded) { if (rebootNeeded) {
AbstractPreferenceFragment.showRestartDialog(getContext()); AbstractPreferenceFragment.showRestartDialog(context);
} }
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "importSettings failure", ex); Logger.printException(() -> "importSettings failure", ex);
@@ -96,5 +122,4 @@ public class ImportExportPreference extends EditTextPreference implements Prefer
AbstractPreferenceFragment.settingImportInProgress = false; AbstractPreferenceFragment.settingImportInProgress = false;
} }
} }
}
}

View File

@@ -0,0 +1,113 @@
package app.revanced.extension.shared.settings.preference;
import static app.revanced.extension.shared.StringRef.str;
import java.util.Deque;
import java.util.Objects;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicInteger;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings;
/**
* Manages a buffer for storing debug logs from {@link Logger}.
* Stores just under 1MB of the most recent log data.
*
* All methods are thread-safe.
*/
public final class LogBufferManager {
/** Maximum byte size of all buffer entries. Must be less than Android's 1 MB Binder transaction limit. */
private static final int BUFFER_MAX_BYTES = 900_000;
/** Limit number of log lines. */
private static final int BUFFER_MAX_SIZE = 10_000;
private static final Deque<String> logBuffer = new ConcurrentLinkedDeque<>();
private static final AtomicInteger logBufferByteSize = new AtomicInteger();
/**
* Appends a log message to the internal buffer if debugging is enabled.
* The buffer is limited to approximately {@link #BUFFER_MAX_BYTES} or {@link #BUFFER_MAX_SIZE}
* to prevent excessive memory usage.
*
* @param message The log message to append.
*/
public static void appendToLogBuffer(String message) {
Objects.requireNonNull(message);
// It's very important that no Settings are used in this method,
// as this code is used when a context is not set and thus referencing
// a setting will crash the app.
logBuffer.addLast(message);
int newSize = logBufferByteSize.addAndGet(message.length());
// Remove oldest entries if over the log size limits.
while (newSize > BUFFER_MAX_BYTES || logBuffer.size() > BUFFER_MAX_SIZE) {
String removed = logBuffer.pollFirst();
if (removed == null) {
// Thread race of two different calls to this method, and the other thread won.
return;
}
newSize = logBufferByteSize.addAndGet(-removed.length());
}
}
/**
* Exports all logs from the internal buffer to the clipboard.
* Displays a toast with the result.
*/
public static void exportToClipboard() {
try {
if (!BaseSettings.DEBUG.get()) {
Utils.showToastShort(str("revanced_debug_logs_disabled"));
return;
}
if (logBuffer.isEmpty()) {
Utils.showToastShort(str("revanced_debug_logs_none_found"));
clearLogBufferData(); // Clear toast log entry that was just created.
return;
}
// Most (but not all) Android 13+ devices always show a "copied to clipboard" toast
// and there is no way to programmatically detect if a toast will show or not.
// Show a toast even if using Android 13+, but show ReVanced toast first (before copying to clipboard).
Utils.showToastShort(str("revanced_debug_logs_copied_to_clipboard"));
Utils.setClipboard(String.join("\n", logBuffer));
} catch (Exception ex) {
// Handle security exception if clipboard access is denied.
String errorMessage = String.format(str("revanced_debug_logs_failed_to_export"), ex.getMessage());
Utils.showToastLong(errorMessage);
Logger.printDebug(() -> errorMessage, ex);
}
}
private static void clearLogBufferData() {
// Cannot simply clear the log buffer because there is no
// write lock for both the deque and the atomic int.
// Instead pop off log entries and decrement the size one by one.
while (!logBuffer.isEmpty()) {
String removed = logBuffer.pollFirst();
if (removed != null) {
logBufferByteSize.addAndGet(-removed.length());
}
}
}
/**
* Clears the internal log buffer and displays a toast with the result.
*/
public static void clearLogBuffer() {
if (!BaseSettings.DEBUG.get()) {
Utils.showToastShort(str("revanced_debug_logs_disabled"));
return;
}
// Show toast before clearing, otherwise toast log will still remain.
Utils.showToastShort(str("revanced_debug_logs_clear_toast"));
clearLogBufferData();
}
}

View File

@@ -12,6 +12,7 @@ import android.view.ViewGroup;
*/ */
@SuppressWarnings({"unused", "deprecation"}) @SuppressWarnings({"unused", "deprecation"})
public class NoTitlePreferenceCategory extends PreferenceCategory { public class NoTitlePreferenceCategory extends PreferenceCategory {
public NoTitlePreferenceCategory(Context context, AttributeSet attrs) { public NoTitlePreferenceCategory(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
} }
@@ -20,6 +21,10 @@ public class NoTitlePreferenceCategory extends PreferenceCategory {
super(context, attrs, defStyleAttr); super(context, attrs, defStyleAttr);
} }
public NoTitlePreferenceCategory(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public NoTitlePreferenceCategory(Context context) { public NoTitlePreferenceCategory(Context context) {
super(context); super(context);
} }
@@ -27,8 +32,27 @@ public class NoTitlePreferenceCategory extends PreferenceCategory {
@Override @Override
@SuppressLint("MissingSuperCall") @SuppressLint("MissingSuperCall")
protected View onCreateView(ViewGroup parent) { protected View onCreateView(ViewGroup parent) {
// Return an empty, zero-height view to eliminate spacing // Return an zero-height view to eliminate empty title space.
return new View(getContext()); return new View(getContext());
} }
@Override
public CharSequence getTitle() {
// Title can be used for sorting. Return the first sub preference title.
if (getPreferenceCount() > 0) {
return getPreference(0).getTitle();
}
return super.getTitle();
}
@Override
public int getTitleRes() {
if (getPreferenceCount() > 0) {
return getPreference(0).getTitleRes();
}
return super.getTitleRes();
}
} }

View File

@@ -1,6 +1,7 @@
package app.revanced.extension.shared.settings.preference; package app.revanced.extension.shared.settings.preference;
import static app.revanced.extension.shared.StringRef.str; 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 static app.revanced.extension.shared.requests.Route.Method.GET;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
@@ -8,17 +9,19 @@ import android.app.Dialog;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.Configuration; import android.graphics.drawable.ShapeDrawable;
import android.graphics.Color; import android.graphics.drawable.shapes.RoundRectShape;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.preference.Preference; import android.preference.Preference;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View;
import android.view.Window; import android.view.Window;
import android.webkit.WebView; import android.webkit.WebView;
import android.webkit.WebViewClient; import android.webkit.WebViewClient;
import android.widget.LinearLayout;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -49,28 +52,6 @@ public class ReVancedAboutPreference extends Preference {
return text.replace("-", "&#8209;"); // #8209 = non breaking hyphen. return text.replace("-", "&#8209;"); // #8209 = non breaking hyphen.
} }
private static String getColorHexString(int color) {
return String.format("#%06X", (0x00FFFFFF & color));
}
protected boolean isDarkModeEnabled() {
return Utils.isDarkModeEnabled(getContext());
}
/**
* Subclasses can override this and provide a themed color.
*/
protected int getLightColor() {
return Color.WHITE;
}
/**
* Subclasses can override this and provide a themed color.
*/
protected int getDarkColor() {
return Color.BLACK;
}
/** /**
* Apps that do not support bundling resources must override this. * Apps that do not support bundling resources must override this.
* *
@@ -87,9 +68,8 @@ public class ReVancedAboutPreference extends Preference {
builder.append("<html>"); builder.append("<html>");
builder.append("<body style=\"text-align: center; padding: 10px;\">"); builder.append("<body style=\"text-align: center; padding: 10px;\">");
final boolean isDarkMode = isDarkModeEnabled(); String foregroundColorHex = Utils.getColorHexString(Utils.getAppForegroundColor());
String backgroundColorHex = getColorHexString(isDarkMode ? getDarkColor() : getLightColor()); String backgroundColorHex = Utils.getColorHexString(Utils.getDialogBackgroundColor());
String foregroundColorHex = getColorHexString(isDarkMode ? getLightColor() : getDarkColor());
// Apply light/dark mode colors. // Apply light/dark mode colors.
builder.append(String.format( builder.append(String.format(
"<style> body { background-color: %s; color: %s; } a { color: %s; } </style>", "<style> body { background-color: %s; color: %s; } a { color: %s; } </style>",
@@ -221,14 +201,38 @@ class WebViewDialog extends Dialog {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE); requestWindowFeature(Window.FEATURE_NO_TITLE); // Remove default title bar.
// Create main layout.
LinearLayout mainLayout = new LinearLayout(getContext());
mainLayout.setOrientation(LinearLayout.VERTICAL);
final int padding = dipToPixels(10);
mainLayout.setPadding(padding, padding, padding, padding);
// Set rounded rectangle background.
ShapeDrawable mainBackground = new ShapeDrawable(new RoundRectShape(
Utils.createCornerRadii(28), null, null));
mainBackground.getPaint().setColor(Utils.getDialogBackgroundColor());
mainLayout.setBackground(mainBackground);
// Create WebView.
WebView webView = new WebView(getContext()); WebView webView = new WebView(getContext());
webView.setVerticalScrollBarEnabled(false); // Disable the vertical scrollbar.
webView.setOverScrollMode(View.OVER_SCROLL_NEVER);
webView.getSettings().setJavaScriptEnabled(true); webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new OpenLinksExternallyWebClient()); webView.setWebViewClient(new OpenLinksExternallyWebClient());
webView.loadDataWithBaseURL(null, htmlContent, "text/html", "utf-8", null); webView.loadDataWithBaseURL(null, htmlContent, "text/html", "utf-8", null);
setContentView(webView); // Add WebView to layout.
mainLayout.addView(webView);
setContentView(mainLayout);
// Set dialog window attributes
Window window = getWindow();
if (window != null) {
Utils.setDialogWindowParameters(window);
}
} }
private class OpenLinksExternallyWebClient extends WebViewClient { private class OpenLinksExternallyWebClient extends WebViewClient {
@@ -316,7 +320,7 @@ class AboutLinksRoutes {
// Do not show an exception toast if the server is down // Do not show an exception toast if the server is down
final int responseCode = connection.getResponseCode(); final int responseCode = connection.getResponseCode();
if (responseCode != 200) { if (responseCode != 200) {
Logger.printDebug(() -> "Failed to get social links. Response code: " + responseCode); Logger.printDebug(() -> "Failed to get social links. Response code: " + responseCode);
return NO_CONNECTION_STATIC_LINKS; return NO_CONNECTION_STATIC_LINKS;
} }

View File

@@ -1,24 +1,46 @@
package app.revanced.extension.shared.settings.preference; package app.revanced.extension.shared.settings.preference;
import android.app.AlertDialog; import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.dipToPixels;
import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.shapes.RectShape;
import android.graphics.drawable.shapes.RoundRectShape;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.Paint.Style;
import android.os.Bundle; import android.os.Bundle;
import android.preference.EditTextPreference; import android.preference.EditTextPreference;
import android.text.TextUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Pair;
import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import app.revanced.extension.shared.Utils; import androidx.annotation.Nullable;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.Logger;
import java.util.Objects; import java.util.Objects;
import static app.revanced.extension.shared.StringRef.str; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.Setting;
@SuppressWarnings({"unused", "deprecation"}) @SuppressWarnings({"unused", "deprecation"})
public class ResettableEditTextPreference extends EditTextPreference { public class ResettableEditTextPreference extends EditTextPreference {
/**
* Setting to reset.
*/
@Nullable
private Setting<?> setting;
public ResettableEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { public ResettableEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes); super(context, attrs, defStyleAttr, defStyleRes);
} }
@@ -32,36 +54,65 @@ public class ResettableEditTextPreference extends EditTextPreference {
super(context); super(context);
} }
@Override public void setSetting(@Nullable Setting<?> setting) {
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { this.setting = setting;
super.onPrepareDialogBuilder(builder);
Utils.setEditTextDialogTheme(builder);
Setting<?> setting = Setting.getSettingFromPath(getKey());
if (setting != null) {
builder.setNeutralButton(str("revanced_settings_reset"), null);
}
} }
@Override @Override
protected void showDialog(Bundle state) { protected void showDialog(Bundle state) {
super.showDialog(state); try {
Context context = getContext();
EditText editText = getEditText();
// Override the button click listener to prevent dismissing the dialog. // Resolve setting if not already set.
Button button = ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_NEUTRAL); if (setting == null) {
if (button == null) { String key = getKey();
return; if (key != null) {
} setting = Setting.getSettingFromPath(key);
button.setOnClickListener(v -> { }
try {
Setting<?> setting = Objects.requireNonNull(Setting.getSettingFromPath(getKey()));
String defaultStringValue = setting.defaultValue.toString();
EditText editText = getEditText();
editText.setText(defaultStringValue);
editText.setSelection(defaultStringValue.length()); // move cursor to end of text
} catch (Exception ex) {
Logger.printException(() -> "reset failure", ex);
} }
});
// Set initial EditText value to the current persisted value or empty string.
String initialValue = getText() != null ? getText() : "";
editText.setText(initialValue);
editText.setSelection(initialValue.length()); // Move cursor to end.
// Create custom dialog.
String neutralButtonText = (setting != null) ? str("revanced_settings_reset") : null;
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
context,
getTitle() != null ? getTitle().toString() : "", // Title.
null, // Message is replaced by EditText.
editText, // Pass the EditText.
null, // OK button text.
() -> {
// OK button action. Persist the EditText value when OK is clicked.
String newValue = editText.getText().toString();
if (callChangeListener(newValue)) {
setText(newValue);
}
},
() -> {}, // Cancel button action (dismiss only).
neutralButtonText, // Neutral button text (Reset).
() -> {
// Neutral button action.
if (setting != null) {
try {
String defaultStringValue = Objects.requireNonNull(setting).defaultValue.toString();
editText.setText(defaultStringValue);
editText.setSelection(defaultStringValue.length()); // Move cursor to end of text.
} catch (Exception ex) {
Logger.printException(() -> "reset failure", ex);
}
}
},
false // Do not dismiss dialog when onNeutralClick.
);
// Show the dialog.
dialogPair.first.show();
} catch (Exception ex) {
Logger.printException(() -> "showDialog failure", ex);
}
} }
} }

View File

@@ -0,0 +1,124 @@
package app.revanced.extension.shared.settings.preference;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Pair;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import app.revanced.extension.shared.Utils;
/**
* PreferenceList that sorts itself.
* By default the first entry is preserved in its original position,
* and all other entries are sorted alphabetically.
*
* Ideally the 'keep first entries to preserve' is an xml parameter,
* but currently that's not so simple since Extensions code cannot use
* generated code from the Patches repo (which is required for custom xml parameters).
*
* If any class wants to use a different getFirstEntriesToPreserve value,
* it needs to subclass this preference and override {@link #getFirstEntriesToPreserve}.
*/
@SuppressWarnings({"unused", "deprecation"})
public class SortedListPreference extends CustomDialogListPreference {
/**
* Sorts the current list entries.
*
* @param firstEntriesToPreserve The number of entries to preserve in their original position,
* or a negative value to not sort and leave entries
* as they current are.
*/
public void sortEntryAndValues(int firstEntriesToPreserve) {
CharSequence[] entries = getEntries();
CharSequence[] entryValues = getEntryValues();
if (entries == null || entryValues == null) {
return;
}
final int entrySize = entries.length;
if (entrySize != entryValues.length) {
// Xml array declaration has a missing/extra entry.
throw new IllegalStateException();
}
if (firstEntriesToPreserve < 0) {
return; // Nothing to do.
}
List<Pair<CharSequence, CharSequence>> firstEntries = new ArrayList<>(firstEntriesToPreserve);
// Android does not have a triple class like Kotlin, So instead use a nested pair.
// Cannot easily use a SortedMap, because if two entries incorrectly have
// identical names then the duplicates entries are not preserved.
List<Pair<String, Pair<CharSequence, CharSequence>>> lastEntries = new ArrayList<>();
for (int i = 0; i < entrySize; i++) {
Pair<CharSequence, CharSequence> pair = new Pair<>(entries[i], entryValues[i]);
if (i < firstEntriesToPreserve) {
firstEntries.add(pair);
} else {
lastEntries.add(new Pair<>(Utils.removePunctuationToLowercase(pair.first), pair));
}
}
//noinspection ComparatorCombinators
Collections.sort(lastEntries, (pair1, pair2)
-> pair1.first.compareTo(pair2.first));
CharSequence[] sortedEntries = new CharSequence[entrySize];
CharSequence[] sortedEntryValues = new CharSequence[entrySize];
int i = 0;
for (Pair<CharSequence, CharSequence> pair : firstEntries) {
sortedEntries[i] = pair.first;
sortedEntryValues[i] = pair.second;
i++;
}
for (Pair<String, Pair<CharSequence, CharSequence>> outer : lastEntries) {
Pair<CharSequence, CharSequence> inner = outer.second;
sortedEntries[i] = inner.first;
sortedEntryValues[i] = inner.second;
i++;
}
super.setEntries(sortedEntries);
super.setEntryValues(sortedEntryValues);
}
public SortedListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
sortEntryAndValues(getFirstEntriesToPreserve());
}
public SortedListPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
sortEntryAndValues(getFirstEntriesToPreserve());
}
public SortedListPreference(Context context, AttributeSet attrs) {
super(context, attrs);
sortEntryAndValues(getFirstEntriesToPreserve());
}
public SortedListPreference(Context context) {
super(context);
sortEntryAndValues(getFirstEntriesToPreserve());
}
/**
* @return The number of first entries to leave exactly where they are, and do not sort them.
* A negative value indicates do not sort any entries.
*/
protected int getFirstEntriesToPreserve() {
return 1;
}
}

View File

@@ -122,6 +122,21 @@ public class SpoofVideoStreamsPatch {
return false; return false;
} }
/**
* Injection point.
* Turns off a feature flag that interferes with video playback.
*/
public static boolean usePlaybackStartFeatureFlag(boolean original) {
if (original) {
Logger.printDebug(() -> "usePlaybackStartFeatureFlag is set on");
}
if (!SPOOF_STREAMING_DATA) {
return original;
}
return false;
}
/** /**
* Injection point. * Injection point.
*/ */

View File

@@ -71,9 +71,7 @@ final class PlayerRoutes {
return innerTubeBody.toString(); return innerTubeBody.toString();
} }
/** @SuppressWarnings("SameParameterValue")
* @noinspection SameParameterValue
*/
static HttpURLConnection getPlayerResponseConnectionFromRoute(Route.CompiledRoute route, ClientType clientType) throws IOException { static HttpURLConnection getPlayerResponseConnectionFromRoute(Route.CompiledRoute route, ClientType clientType) throws IOException {
var connection = Requester.getConnectionFromCompiledRoute(YT_API_URL, route); var connection = Requester.getConnectionFromCompiledRoute(YT_API_URL, route);

View File

@@ -204,7 +204,7 @@ public class StreamingDataRequest {
// but empty response body does. // but empty response body does.
if (connection.getContentLength() == 0) { if (connection.getContentLength() == 0) {
if (BaseSettings.DEBUG.get() && BaseSettings.DEBUG_TOAST_ON_ERROR.get()) { if (BaseSettings.DEBUG.get() && BaseSettings.DEBUG_TOAST_ON_ERROR.get()) {
Utils.showToastShort("Ignoring empty spoof stream client: " + clientType); Utils.showToastShort("Debug: Ignoring empty spoof stream client " + clientType);
} }
} else { } else {
try (InputStream inputStream = new BufferedInputStream(connection.getInputStream()); try (InputStream inputStream = new BufferedInputStream(connection.getInputStream());

View File

@@ -0,0 +1,40 @@
plugins {
alias(libs.plugins.protobuf)
}
dependencies {
compileOnly(project(":extensions:shared:library"))
compileOnly(project(":extensions:spotify:stub"))
compileOnly(libs.annotation)
implementation(project(":extensions:spotify:utils"))
implementation(libs.nanohttpd)
implementation(libs.protobuf.javalite)
}
android {
defaultConfig {
minSdk = 21
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}
protobuf {
protoc {
artifact = libs.protobuf.protoc.get().toString()
}
generateProtoTasks {
all().forEach { task ->
task.builtins {
create("java") {
option("lite")
}
}
}
}
}

View File

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

View File

@@ -0,0 +1,82 @@
package app.revanced.extension.spotify.layout.hide.createbutton;
import java.util.List;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.spotify.shared.ComponentFilters.*;
@SuppressWarnings("unused")
public final class HideCreateButtonPatch {
/**
* A list of component filters that match whether a navigation bar item is the Create button.
* The main approach used is matching the resource id for the Create button title.
*/
private static final List<ComponentFilter> CREATE_BUTTON_COMPONENT_FILTERS = List.of(
new ResourceIdComponentFilter("navigationbar_musicappitems_create_title", "string"),
// Temporary fallback and fix for APKs merged with AntiSplit-M not having resources properly encoded,
// and thus getting the resource identifier for the Create button title always return 0.
// FIXME: Remove this once the above issue is no longer relevant.
new StringComponentFilter("spotify:create-menu")
);
/**
* A component filter for the old id of the resource which contained the Create button title.
* Used in older versions of the app.
*/
private static final ResourceIdComponentFilter OLD_CREATE_BUTTON_COMPONENT_FILTER =
new ResourceIdComponentFilter("bottom_navigation_bar_create_tab_title", "string");
/**
* Injection point. This method is called on every navigation bar item to check whether it is the Create button.
* If the navigation bar item is the Create button, it returns null to erase it.
* The method fingerprint used to patch ensures we can safely return null here.
*/
public static Object returnNullIfIsCreateButton(Object navigationBarItem) {
if (navigationBarItem == null) {
return null;
}
try {
String stringifiedNavigationBarItem = navigationBarItem.toString();
for (ComponentFilter componentFilter : CREATE_BUTTON_COMPONENT_FILTERS) {
if (componentFilter.filterUnavailable()) {
Logger.printInfo(() -> "returnNullIfIsCreateButton: Filter " +
componentFilter.getFilterRepresentation() + " not available, skipping");
continue;
}
if (stringifiedNavigationBarItem.contains(componentFilter.getFilterValue())) {
Logger.printInfo(() -> "Hiding Create button because the navigation bar item " +
navigationBarItem + " matched the filter " + componentFilter.getFilterRepresentation());
return null;
}
}
} catch (Exception ex) {
Logger.printException(() -> "returnNullIfIsCreateButton failure", ex);
}
return navigationBarItem;
}
/**
* Injection point. Called in older versions of the app. Returns whether the old navigation bar item is the old
* Create button.
*/
public static boolean isOldCreateButton(int oldNavigationBarItemTitleResId) {
if (OLD_CREATE_BUTTON_COMPONENT_FILTER.filterUnavailable()) {
Logger.printInfo(() -> "Skipping hiding old Create button because the resource id for " +
OLD_CREATE_BUTTON_COMPONENT_FILTER.resourceName + " is not available");
return false;
}
if (oldNavigationBarItemTitleResId == OLD_CREATE_BUTTON_COMPONENT_FILTER.getResourceId()) {
Logger.printInfo(() -> "Hiding old Create button because the navigation bar item title resource id" +
" matched " + OLD_CREATE_BUTTON_COMPONENT_FILTER.getFilterRepresentation());
return true;
}
return false;
}
}

View File

@@ -0,0 +1,61 @@
package app.revanced.extension.spotify.layout.theme;
import android.graphics.Color;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
@SuppressWarnings("unused")
public final class CustomThemePatch {
private static final int BACKGROUND_COLOR = getColorFromString("@color/gray_7");
private static final int BACKGROUND_COLOR_SECONDARY = getColorFromString("@color/gray_15");
private static final int ACCENT_COLOR = getColorFromString("@color/spotify_green_157");
private static final int ACCENT_PRESSED_COLOR =
getColorFromString("@color/dark_brightaccent_background_press");
/**
* Returns an int representation of the color resource or hex code.
*/
private static int getColorFromString(String colorString) {
try {
return Utils.getColorFromString(colorString);
} catch (Exception ex) {
Logger.printException(() -> "Invalid color string: " + colorString, ex);
return Color.BLACK;
}
}
/**
* Injection point. Returns an int representation of the replaced color from the original color.
*/
public static int replaceColor(int originalColor) {
switch (originalColor) {
// Playlist background color.
case 0xFF121212:
return BACKGROUND_COLOR;
// Share menu background color.
case 0xFF1F1F1F:
// Home category pills background color.
case 0xFF333333:
// Settings header background color.
case 0xFF282828:
// Spotify Connect device list background color.
case 0xFF2A2A2A:
return BACKGROUND_COLOR_SECONDARY;
// Some Lottie animations have a color that's slightly off due to rounding errors.
case 0xFF1ED760: case 0xFF1ED75F:
// Intermediate color used in some animations, same rounding issue.
case 0xFF1DB954: case 0xFF1CB854:
return ACCENT_COLOR;
case 0xFF1ABC54:
return ACCENT_PRESSED_COLOR;
default:
return originalColor;
}
}
}

View File

@@ -0,0 +1,158 @@
package app.revanced.extension.spotify.misc.fix;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.spotify.login5.v4.proto.Login5.*;
import com.google.protobuf.ByteString;
import com.google.protobuf.MessageLite;
import fi.iki.elonen.NanoHTTPD;
import java.io.ByteArrayInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Objects;
import static app.revanced.extension.spotify.misc.fix.Session.FAILED_TO_RENEW_SESSION;
import static fi.iki.elonen.NanoHTTPD.Response.Status.INTERNAL_ERROR;
class LoginRequestListener extends NanoHTTPD {
LoginRequestListener(int port) {
super(port);
try {
start();
} catch (IOException ex) {
Logger.printException(() -> "Failed to start login request listener on port " + port, ex);
throw new RuntimeException(ex);
}
}
@NonNull
@Override
public Response serve(IHTTPSession request) {
Logger.printInfo(() -> "Serving request for URI: " + request.getUri());
InputStream requestBodyInputStream = getRequestBodyInputStream(request);
LoginRequest loginRequest;
try {
loginRequest = LoginRequest.parseFrom(requestBodyInputStream);
} catch (IOException ex) {
Logger.printException(() -> "Failed to parse LoginRequest", ex);
return newResponse(INTERNAL_ERROR);
}
MessageLite loginResponse;
// A request may be made concurrently by Spotify,
// however a webview can only handle one request at a time due to singleton cookie manager.
// Therefore, synchronize to ensure that only one webview handles the request at a time.
synchronized (this) {
try {
loginResponse = getLoginResponse(loginRequest);
} catch (Exception ex) {
Logger.printException(() -> "Failed to get login response", ex);
return newResponse(INTERNAL_ERROR);
}
}
return newResponse(Response.Status.OK, loginResponse);
}
private static LoginResponse getLoginResponse(@NonNull LoginRequest loginRequest) {
Session session;
if (!loginRequest.hasStoredCredential()) {
Logger.printInfo(() -> "Received request for initial login");
session = WebApp.currentSession; // Session obtained from WebApp.launchLogin, can be null if still in progress.
} else {
Logger.printInfo(() -> "Received request to restore saved session");
session = Session.read(loginRequest.getStoredCredential().getUsername());
}
return toLoginResponse(session);
}
private static LoginResponse toLoginResponse(@Nullable Session session) {
LoginResponse.Builder builder = LoginResponse.newBuilder();
if (session == null) {
Logger.printException(() -> "Session is null. An initial login may still be in progress, returning try again later error");
builder.setError(LoginError.TRY_AGAIN_LATER);
} else if (session.accessTokenExpired()) {
Logger.printInfo(() -> "Access token expired, renewing session");
WebApp.renewSessionBlocking(session.cookies);
return toLoginResponse(WebApp.currentSession);
} else if (session.username == null) {
Logger.printException(() -> "Session username is null, likely caused by invalid cookies, returning invalid credentials error");
session.delete();
builder.setError(LoginError.INVALID_CREDENTIALS);
} else if (session == FAILED_TO_RENEW_SESSION) {
Logger.printException(() -> "Failed to renew session, likely caused by a timeout, returning try again later error");
builder.setError(LoginError.TRY_AGAIN_LATER);
} else {
session.save();
Logger.printInfo(() -> "Returning session for username: " + session.username);
builder.setOk(LoginOk.newBuilder()
.setUsername(session.username)
.setAccessToken(session.accessToken)
.setStoredCredential(ByteString.fromHex("00")) // Placeholder, as it cannot be null or empty.
.setAccessTokenExpiresIn(session.accessTokenExpiresInSeconds())
.build());
}
return builder.build();
}
@NonNull
private static InputStream limitedInputStream(InputStream inputStream, long contentLength) {
return new FilterInputStream(inputStream) {
private long remaining = contentLength;
@Override
public int read() throws IOException {
if (remaining <= 0) return -1;
int result = super.read();
if (result != -1) remaining--;
return result;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (remaining <= 0) return -1;
len = (int) Math.min(len, remaining);
int result = super.read(b, off, len);
if (result != -1) remaining -= result;
return result;
}
};
}
@NonNull
private static InputStream getRequestBodyInputStream(@NonNull IHTTPSession request) {
long requestContentLength =
Long.parseLong(Objects.requireNonNull(request.getHeaders().get("content-length")));
return limitedInputStream(request.getInputStream(), requestContentLength);
}
@SuppressWarnings("SameParameterValue")
@NonNull
private static Response newResponse(Response.Status status) {
return newResponse(status, null);
}
@NonNull
private static Response newResponse(Response.IStatus status, MessageLite messageLite) {
if (messageLite == null) {
return newFixedLengthResponse(status, "application/x-protobuf", null);
}
byte[] messageBytes = messageLite.toByteArray();
InputStream stream = new ByteArrayInputStream(messageBytes);
return newFixedLengthResponse(status, "application/x-protobuf", stream, messageBytes.length);
}
}

View File

@@ -0,0 +1,136 @@
package app.revanced.extension.spotify.misc.fix;
import android.content.SharedPreferences;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import org.json.JSONException;
import org.json.JSONObject;
import static android.content.Context.MODE_PRIVATE;
class Session {
/**
* Username of the account. Null if this session does not have an authenticated user.
*/
@Nullable
final String username;
/**
* Access token for this session.
*/
final String accessToken;
/**
* Session expiration timestamp in milliseconds.
*/
final Long expirationTime;
/**
* Authentication cookies for this session.
*/
final String cookies;
/**
* Session that represents a failed attempt to renew the session.
*/
static final Session FAILED_TO_RENEW_SESSION = new Session("", "", "");
/**
* @param username Username of the account. Empty if this session does not have an authenticated user.
* @param accessToken Access token for this session.
* @param cookies Authentication cookies for this session.
*/
Session(@Nullable String username, String accessToken, String cookies) {
this(username, accessToken, System.currentTimeMillis() + 60 * 60 * 1000, cookies);
}
private Session(@Nullable String username, String accessToken, long expirationTime, String cookies) {
this.username = username;
this.accessToken = accessToken;
this.expirationTime = expirationTime;
this.cookies = cookies;
}
/**
* @return The number of milliseconds until the access token expires.
*/
long accessTokenExpiresInMillis() {
long currentTime = System.currentTimeMillis();
return expirationTime - currentTime;
}
/**
* @return The number of seconds until the access token expires.
*/
int accessTokenExpiresInSeconds() {
return (int) accessTokenExpiresInMillis() / 1000;
}
/**
* @return True if the access token has expired, false otherwise.
*/
boolean accessTokenExpired() {
return accessTokenExpiresInMillis() <= 0;
}
void save() {
Logger.printInfo(() -> "Saving session: " + this);
SharedPreferences.Editor editor = Utils.getContext().getSharedPreferences("revanced", MODE_PRIVATE).edit();
String json;
try {
json = new JSONObject()
.put("accessToken", accessToken)
.put("expirationTime", expirationTime)
.put("cookies", cookies).toString();
} catch (JSONException ex) {
Logger.printException(() -> "Failed to convert session to stored credential", ex);
return;
}
editor.putString("session_" + username, json);
editor.apply();
}
void delete() {
Logger.printInfo(() -> "Deleting saved session for username: " + username);
SharedPreferences.Editor editor = Utils.getContext().getSharedPreferences("revanced", MODE_PRIVATE).edit();
editor.remove("session_" + username);
editor.apply();
}
@Nullable
static Session read(String username) {
Logger.printInfo(() -> "Reading saved session for username: " + username);
SharedPreferences sharedPreferences = Utils.getContext().getSharedPreferences("revanced", MODE_PRIVATE);
String savedJson = sharedPreferences.getString("session_" + username, null);
if (savedJson == null) {
Logger.printInfo(() -> "No session found in shared preferences");
return null;
}
try {
JSONObject json = new JSONObject(savedJson);
String accessToken = json.getString("accessToken");
long expirationTime = json.getLong("expirationTime");
String cookies = json.getString("cookies");
return new Session(username, accessToken, expirationTime, cookies);
} catch (JSONException ex) {
Logger.printException(() -> "Failed to read session from shared preferences", ex);
return null;
}
}
@NonNull
@Override
public String toString() {
return "Session(" +
"username=" + username +
", accessToken=" + accessToken +
", expirationTime=" + expirationTime +
", cookies=" + cookies +
')';
}
}

View File

@@ -0,0 +1,54 @@
package app.revanced.extension.spotify.misc.fix;
import android.view.LayoutInflater;
import android.view.View;
import app.revanced.extension.shared.Logger;
@SuppressWarnings("unused")
public class SpoofClientPatch {
private static LoginRequestListener listener;
/**
* Injection point.
* <br>
* Launch login server.
*/
public static void launchListener(int port) {
if (listener != null) {
Logger.printInfo(() -> "Listener already running on port " + port);
return;
}
try {
Logger.printInfo(() -> "Launching listener on port " + port);
listener = new LoginRequestListener(port);
} catch (Exception ex) {
Logger.printException(() -> "launchListener failure", ex);
}
}
/**
* Injection point.
* <br>
* Launch login web view.
*/
public static void launchLogin(LayoutInflater inflater) {
try {
WebApp.launchLogin(inflater.getContext());
} catch (Exception ex) {
Logger.printException(() -> "launchLogin failure", ex);
}
}
/**
* Injection point.
* <br>
* Set handler to call the native login after the webview login.
*/
public static void setNativeLoginHandler(View startLoginButton) {
WebApp.nativeLoginHandler = (() -> {
startLoginButton.setSoundEffectsEnabled(false);
startLoginButton.performClick();
});
}
}

View File

@@ -0,0 +1,297 @@
package app.revanced.extension.spotify.misc.fix;
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Build;
import android.view.Window;
import android.view.WindowInsets;
import android.webkit.*;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.spotify.UserAgent;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static app.revanced.extension.spotify.misc.fix.Session.FAILED_TO_RENEW_SESSION;
class WebApp {
private static final String OPEN_SPOTIFY_COM = "open.spotify.com";
private static final String OPEN_SPOTIFY_COM_URL = "https://" + OPEN_SPOTIFY_COM;
private static final String OPEN_SPOTIFY_COM_PREFERENCES_URL = OPEN_SPOTIFY_COM_URL + "/preferences";
private static final String ACCOUNTS_SPOTIFY_COM_LOGIN_URL = "https://accounts.spotify.com/login?allow_password=1"
+ "&continue=https%3A%2F%2Fopen.spotify.com%2Fpreferences";
private static final int GET_SESSION_TIMEOUT_SECONDS = 10;
private static final String JAVASCRIPT_INTERFACE_NAME = "androidInterface";
private static final String USER_AGENT = getWebUserAgent();
/**
* A session obtained from the webview after logging in.
*/
@Nullable
static volatile Session currentSession = null;
/**
* Current webview in use. Any use of the object must be done on the main thread.
*/
@SuppressLint("StaticFieldLeak")
private static volatile WebView currentWebView;
interface NativeLoginHandler {
void login();
}
static NativeLoginHandler nativeLoginHandler;
static void launchLogin(Context context) {
final Dialog dialog = newDialog(context);
Utils.runOnBackgroundThread(() -> {
Logger.printInfo(() -> "Launching login");
// A session must be obtained from a login. Repeat until a session is acquired.
boolean isAcquired = false;
do {
CountDownLatch onLoggedInLatch = new CountDownLatch(1);
CountDownLatch getSessionLatch = new CountDownLatch(1);
// Can't use Utils.getContext() here, because autofill won't work.
// See https://stackoverflow.com/a/79182053/11213244.
launchWebView(context, ACCOUNTS_SPOTIFY_COM_LOGIN_URL, new WebViewCallback() {
@Override
void onInitialized(WebView webView) {
super.onInitialized(webView);
dialog.setContentView(webView);
dialog.show();
}
@Override
void onLoggedIn(String cookies) {
onLoggedInLatch.countDown();
}
@Override
void onReceivedSession(Session session) {
super.onReceivedSession(session);
getSessionLatch.countDown();
dialog.dismiss();
try {
nativeLoginHandler.login();
} catch (Exception ex) {
Logger.printException(() -> "nativeLoginHandler failure", ex);
}
}
});
try {
// Wait indefinitely until the user logs in.
onLoggedInLatch.await();
// Wait until the session is received, or timeout.
isAcquired = getSessionLatch.await(GET_SESSION_TIMEOUT_SECONDS, TimeUnit.SECONDS);
} catch (InterruptedException ex) {
Logger.printException(() -> "Login interrupted", ex);
Thread.currentThread().interrupt();
}
} while (!isAcquired);
});
}
static void renewSessionBlocking(String cookies) {
Logger.printInfo(() -> "Renewing session with cookies: " + cookies);
CountDownLatch getSessionLatch = new CountDownLatch(1);
launchWebView(Utils.getContext(), OPEN_SPOTIFY_COM_PREFERENCES_URL, new WebViewCallback() {
@Override
public void onInitialized(WebView webView) {
setCookies(cookies);
super.onInitialized(webView);
}
public void onReceivedSession(Session session) {
super.onReceivedSession(session);
getSessionLatch.countDown();
}
});
boolean isAcquired = false;
try {
isAcquired = getSessionLatch.await(GET_SESSION_TIMEOUT_SECONDS, TimeUnit.SECONDS);
} catch (InterruptedException ex) {
Logger.printException(() -> "Session renewal interrupted", ex);
Thread.currentThread().interrupt();
}
if (!isAcquired) {
Logger.printException(() -> "Failed to retrieve session within " + GET_SESSION_TIMEOUT_SECONDS + " seconds");
currentSession = FAILED_TO_RENEW_SESSION;
destructWebView();
}
}
/**
* All methods are called on the main thread.
*/
abstract static class WebViewCallback {
void onInitialized(WebView webView) {
currentWebView = webView;
currentSession = null; // Reset current session.
}
void onLoggedIn(String cookies) {
}
void onReceivedSession(Session session) {
Logger.printInfo(() -> "Received session: " + session);
currentSession = session;
destructWebView();
}
}
@SuppressLint("SetJavaScriptEnabled")
private static void launchWebView(
Context context,
String initialUrl,
WebViewCallback webViewCallback
) {
Utils.runOnMainThreadNowOrLater(() -> {
WebView webView = new WebView(context);
WebSettings settings = webView.getSettings();
settings.setDomStorageEnabled(true);
settings.setJavaScriptEnabled(true);
settings.setUserAgentString(USER_AGENT);
// WebViewClient is always called off the main thread,
// but callback interface methods are called on the main thread.
webView.setWebViewClient(new WebViewClient() {
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
if (OPEN_SPOTIFY_COM.equals(request.getUrl().getHost())) {
Utils.runOnMainThread(() -> webViewCallback.onLoggedIn(getCurrentCookies()));
}
return super.shouldInterceptRequest(view, request);
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
Logger.printInfo(() -> "Page started loading: " + url);
if (!url.startsWith(OPEN_SPOTIFY_COM_URL)) {
return;
}
Logger.printInfo(() -> "Evaluating script to get session on url: " + url);
String getSessionScript = "Object.defineProperty(Object.prototype, \"_username\", {" +
" configurable: true," +
" set(username) {" +
" accessToken = this._builder?.accessToken;" +
" if (accessToken) {" +
" " + JAVASCRIPT_INTERFACE_NAME + ".getSession(username, accessToken);" +
" delete Object.prototype._username;" +
" }" +
" " +
" Object.defineProperty(this, \"_username\", {" +
" configurable: true," +
" enumerable: true," +
" writable: true," +
" value: username" +
" })" +
" " +
" }" +
"});" +
"if (new URLSearchParams(window.location.search).get('_authfailed') != null) {" +
" " + JAVASCRIPT_INTERFACE_NAME + ".getSession(null, null);" +
"}";
view.evaluateJavascript(getSessionScript, null);
}
});
webView.addJavascriptInterface(new Object() {
@SuppressWarnings("unused")
@JavascriptInterface
public void getSession(String username, String accessToken) {
Session session = new Session(username, accessToken, getCurrentCookies());
Utils.runOnMainThread(() -> webViewCallback.onReceivedSession(session));
}
}, JAVASCRIPT_INTERFACE_NAME);
CookieManager.getInstance().removeAllCookies((anyRemoved) -> {
Logger.printInfo(() -> "Loading URL: " + initialUrl);
webView.loadUrl(initialUrl);
Logger.printInfo(() -> "WebView initialized with user agent: " + USER_AGENT);
webViewCallback.onInitialized(webView);
});
});
}
private static void destructWebView() {
Utils.runOnMainThreadNowOrLater(() -> {
currentWebView.stopLoading();
currentWebView.destroy();
currentWebView = null;
});
}
private static String getWebUserAgent() {
String userAgentString = WebSettings.getDefaultUserAgent(Utils.getContext());
try {
return new UserAgent(userAgentString)
.withCommentReplaced("Android", "Windows NT 10.0; Win64; x64")
.withoutProduct("Mobile")
.toString();
} catch (IllegalArgumentException ex) {
userAgentString = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edge/137.0.0.0";
String fallback = userAgentString;
Logger.printException(() -> "Failed to get user agent, falling back to " + fallback, ex);
}
return userAgentString;
}
@NonNull
private static Dialog newDialog(Context context) {
Dialog dialog = new Dialog(context, android.R.style.Theme_Black_NoTitleBar_Fullscreen);
dialog.setCancelable(false);
// Ensure that the keyboard does not cover the webview content.
Window window = dialog.getWindow();
//noinspection StatementWithEmptyBody
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.getDecorView().setOnApplyWindowInsetsListener((v, insets) -> {
v.setPadding(0, 0, 0, insets.getInsets(WindowInsets.Type.ime()).bottom);
return WindowInsets.CONSUMED;
});
} else {
// TODO: Implement for lower Android versions.
}
return dialog;
}
private static String getCurrentCookies() {
CookieManager cookieManager = CookieManager.getInstance();
return cookieManager.getCookie(OPEN_SPOTIFY_COM_URL);
}
private static void setCookies(@NonNull String cookies) {
CookieManager cookieManager = CookieManager.getInstance();
String[] cookiesList = cookies.split(";");
for (String cookie : cookiesList) {
cookieManager.setCookie(OPEN_SPOTIFY_COM_URL, cookie);
}
}
}

View File

@@ -0,0 +1,44 @@
package app.revanced.extension.spotify.misc.privacy;
import android.net.Uri;
import java.util.List;
import app.revanced.extension.shared.Logger;
@SuppressWarnings("unused")
public final class SanitizeSharingLinksPatch {
/**
* Parameters that are considered undesirable and should be stripped away.
*/
private static final List<String> SHARE_PARAMETERS_TO_REMOVE = List.of(
"si", // Share tracking parameter.
"utm_source" // Share source, such as "copy-link".
);
/**
* Injection point.
*/
public static String sanitizeUrl(String url) {
try {
Uri uri = Uri.parse(url);
Uri.Builder builder = uri.buildUpon().clearQuery();
for (String paramName : uri.getQueryParameterNames()) {
if (!SHARE_PARAMETERS_TO_REMOVE.contains(paramName)) {
for (String value : uri.getQueryParameters(paramName)) {
builder.appendQueryParameter(paramName, value);
}
}
}
String sanitizedUrl = builder.build().toString();
Logger.printInfo(() -> "Sanitized url " + url + " to " + sanitizedUrl);
return sanitizedUrl;
} catch (Exception ex) {
Logger.printException(() -> "sanitizeUrl failure with " + url, ex);
return url;
}
}
}

View File

@@ -0,0 +1,85 @@
package app.revanced.extension.spotify.shared;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
public final class ComponentFilters {
public interface ComponentFilter {
@NonNull
String getFilterValue();
String getFilterRepresentation();
default boolean filterUnavailable() {
return false;
}
}
public static final class ResourceIdComponentFilter implements ComponentFilter {
public final String resourceName;
public final String resourceType;
// Android resources are always positive, so -1 is a valid sentinel value to indicate it has not been loaded.
// 0 is returned when a resource has not been found.
private int resourceId = -1;
@Nullable
private String stringfiedResourceId;
public ResourceIdComponentFilter(String resourceName, String resourceType) {
this.resourceName = resourceName;
this.resourceType = resourceType;
}
public int getResourceId() {
if (resourceId == -1) {
resourceId = Utils.getResourceIdentifier(resourceName, resourceType);
}
return resourceId;
}
@NonNull
@Override
public String getFilterValue() {
if (stringfiedResourceId == null) {
stringfiedResourceId = Integer.toString(getResourceId());
}
return stringfiedResourceId;
}
@Override
public String getFilterRepresentation() {
boolean resourceFound = getResourceId() != 0;
return (resourceFound ? getFilterValue() + " (" : "") + resourceName + (resourceFound ? ")" : "");
}
@Override
public boolean filterUnavailable() {
boolean resourceNotFound = getResourceId() == 0;
if (resourceNotFound) {
Logger.printInfo(() -> "Resource id for " + resourceName + " was not found");
}
return resourceNotFound;
}
}
public static final class StringComponentFilter implements ComponentFilter {
public final String string;
public StringComponentFilter(String string) {
this.string = string;
}
@NonNull
@Override
public String getFilterValue() {
return string;
}
@Override
public String getFilterRepresentation() {
return string;
}
}
}

View File

@@ -0,0 +1,43 @@
syntax = "proto3";
package spotify.login5.v4;
option optimize_for = LITE_RUNTIME;
option java_package = "app.revanced.extension.spotify.login5.v4.proto";
message StoredCredential {
string username = 1;
bytes data = 2;
}
message LoginRequest {
oneof login_method {
StoredCredential stored_credential = 100;
}
}
message LoginOk {
string username = 1;
string access_token = 2;
bytes stored_credential = 3;
int32 access_token_expires_in = 4;
}
message LoginResponse {
oneof response {
LoginOk ok = 1;
LoginError error = 2;
}
}
enum LoginError {
UNKNOWN_ERROR = 0;
INVALID_CREDENTIALS = 1;
BAD_REQUEST = 2;
UNSUPPORTED_LOGIN_PROTOCOL = 3;
TIMEOUT = 4;
UNKNOWN_IDENTIFIER = 5;
TOO_MANY_ATTEMPTS = 6;
INVALID_PHONENUMBER = 7;
TRY_AGAIN_LATER = 8;
}

View File

@@ -0,0 +1,17 @@
plugins {
alias(libs.plugins.android.library)
}
android {
namespace = "app.revanced.extension"
compileSdk = 34
defaultConfig {
minSdk = 21
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}

View File

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

View File

@@ -0,0 +1,5 @@
package app.revanced;
public interface ContextMenuItemPlaceholder {
Object getViewModel();
}

View File

@@ -0,0 +1,8 @@
package com.spotify.browsita.v1.resolved;
public final class Section {
public static final int BRAND_ADS_FIELD_NUMBER = 6;
public static final int PROMOTION_V1_FIELD_NUMBER = 3;
public static final int PROMOTION_V3_FIELD_NUMBER = 5;
public int sectionTypeCase_;
}

View File

@@ -0,0 +1,7 @@
package com.spotify.home.evopage.homeapi.proto;
public final class Section {
public static final int VIDEO_BRAND_AD_FIELD_NUMBER = 20;
public static final int IMAGE_BRAND_AD_FIELD_NUMBER = 21;
public int featureTypeCase_;
}

View File

@@ -0,0 +1,5 @@
package com.spotify.remoteconfig.internal;
public final class AccountAttribute {
public Object value_;
}

View File

@@ -0,0 +1,8 @@
package com.spotify.useraccount.v1;
/**
* Used for target 8.6.98.900. Class is still present in newer app targets.
*/
public class AccountAttribute {
public Object value_;
}

View File

@@ -0,0 +1,19 @@
plugins {
java
antlr
}
dependencies {
antlr(libs.antlr4)
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
tasks {
generateGrammarSource {
arguments = listOf("-visitor")
}
}

View File

@@ -0,0 +1,35 @@
grammar UserAgent;
@header { package app.revanced.extension.spotify; }
userAgent
: product (WS product)* EOF
;
product
: name ('/' version)? (WS comment)?
;
name
: STRING
;
version
: STRING ('.' STRING)*
;
comment
: COMMENT
;
COMMENT
: '(' ~ ')'* ')'
;
STRING
: [a-zA-Z0-9]+
;
WS
: [ \r\n]+
;

View File

@@ -0,0 +1,60 @@
package app.revanced.extension.spotify;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.TokenStreamRewriter;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
public class UserAgent {
private final UserAgentParser.UserAgentContext tree;
private final TokenStreamRewriter rewriter;
private final ParseTreeWalker walker;
public UserAgent(String userAgentString) {
CharStream input = CharStreams.fromString(userAgentString);
UserAgentLexer lexer = new UserAgentLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
tree = new UserAgentParser(tokens).userAgent();
walker = new ParseTreeWalker();
rewriter = new TokenStreamRewriter(tokens);
}
public UserAgent withoutProduct(String name) {
walker.walk(new UserAgentBaseListener() {
@Override
public void exitProduct(UserAgentParser.ProductContext ctx) {
if (!ctx.name().getText().contains(name)) return;
int startIndex = ctx.getStart().getTokenIndex();
if (startIndex != 0) startIndex -= 1; // Also remove the preceding whitespace.
int stopIndex = ctx.getStop().getTokenIndex();
rewriter.delete(startIndex, stopIndex);
}
}, tree);
return new UserAgent(rewriter.getText().trim());
}
public UserAgent withCommentReplaced(String containing, String replacement) {
walker.walk(new UserAgentBaseListener() {
@Override
public void exitComment(UserAgentParser.CommentContext ctx) {
if (ctx.getText().contains(containing)) {
rewriter.replace(ctx.getStart(), ctx.getStop(), "(" + replacement + ")");
}
}
}, tree);
return new UserAgent(rewriter.getText());
}
@Override
public String toString() {
return rewriter.getText();
}
}

View File

@@ -1,5 +1,5 @@
plugins { plugins {
id(libs.plugins.android.library.get().pluginId) alias(libs.plugins.android.library)
} }
android { android {

View File

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

View File

@@ -2,6 +2,7 @@ package app.revanced.extension.tiktok.feedfilter;
import com.ss.android.ugc.aweme.feed.model.Aweme; import com.ss.android.ugc.aweme.feed.model.Aweme;
import com.ss.android.ugc.aweme.feed.model.FeedItemList; import com.ss.android.ugc.aweme.feed.model.FeedItemList;
import com.ss.android.ugc.aweme.follow.presenter.FollowFeedList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@@ -13,22 +14,41 @@ public final class FeedItemsFilter {
new StoryFilter(), new StoryFilter(),
new ImageVideoFilter(), new ImageVideoFilter(),
new ViewCountFilter(), new ViewCountFilter(),
new LikeCountFilter() new LikeCountFilter(),
new ShopFilter()
); );
public static void filter(FeedItemList feedItemList) { public static void filter(FeedItemList feedItemList) {
Iterator<Aweme> feedItemListIterator = feedItemList.items.iterator(); filterFeedList(feedItemList.items, item -> item);
while (feedItemListIterator.hasNext()) { }
Aweme item = feedItemListIterator.next();
if (item == null) continue;
for (IFilter filter : FILTERS) { public static void filter(FollowFeedList followFeedList) {
boolean enabled = filter.getEnabled(); filterFeedList(followFeedList.mItems, feed -> (feed != null) ? feed.aweme : null);
if (enabled && filter.getFiltered(item)) { }
feedItemListIterator.remove();
break; private static <T> void filterFeedList(List<T> list, AwemeExtractor<T> extractor) {
} // Could be simplified with removeIf() but requires Android 7.0+ while TikTok supports 4.0+.
Iterator<T> iterator = list.iterator();
while (iterator.hasNext()) {
T container = iterator.next();
Aweme item = extractor.extract(container);
if (item != null && shouldFilter(item)) {
iterator.remove();
} }
} }
} }
}
private static boolean shouldFilter(Aweme item) {
for (IFilter filter : FILTERS) {
if (filter.getEnabled() && filter.getFiltered(item)) {
return true;
}
}
return false;
}
@FunctionalInterface
interface AwemeExtractor<T> {
Aweme extract(T source);
}
}

View File

@@ -0,0 +1,17 @@
package app.revanced.extension.tiktok.feedfilter;
import app.revanced.extension.tiktok.settings.Settings;
import com.ss.android.ugc.aweme.feed.model.Aweme;
public class ShopFilter implements IFilter {
private static final String SHOP_INFO = "placeholder_product_id";
@Override
public boolean getEnabled() {
return Settings.HIDE_SHOP.get();
}
@Override
public boolean getFiltered(Aweme item) {
return item.getShareUrl().contains(SHOP_INFO);
}
}

View File

@@ -11,6 +11,7 @@ import app.revanced.extension.shared.settings.StringSetting;
public class Settings extends BaseSettings { public class Settings extends BaseSettings {
public static final BooleanSetting REMOVE_ADS = new BooleanSetting("remove_ads", TRUE, true); public static final BooleanSetting REMOVE_ADS = new BooleanSetting("remove_ads", TRUE, true);
public static final BooleanSetting HIDE_LIVE = new BooleanSetting("hide_live", FALSE, true); public static final BooleanSetting HIDE_LIVE = new BooleanSetting("hide_live", FALSE, true);
public static final BooleanSetting HIDE_SHOP = new BooleanSetting("hide_shop", FALSE, true);
public static final BooleanSetting HIDE_STORY = new BooleanSetting("hide_story", FALSE, true); public static final BooleanSetting HIDE_STORY = new BooleanSetting("hide_story", FALSE, true);
public static final BooleanSetting HIDE_IMAGE = new BooleanSetting("hide_image", FALSE, true); public static final BooleanSetting HIDE_IMAGE = new BooleanSetting("hide_image", FALSE, true);
public static final StringSetting MIN_MAX_VIEWS = new StringSetting("min_max_views", "0-" + Long.MAX_VALUE, true); public static final StringSetting MIN_MAX_VIEWS = new StringSetting("min_max_views", "0-" + Long.MAX_VALUE, true);

View File

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

View File

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

View File

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

View File

@@ -9,7 +9,6 @@ import app.revanced.extension.tiktok.settings.preference.categories.DownloadsPre
import app.revanced.extension.tiktok.settings.preference.categories.FeedFilterPreferenceCategory; import app.revanced.extension.tiktok.settings.preference.categories.FeedFilterPreferenceCategory;
import app.revanced.extension.tiktok.settings.preference.categories.ExtensionPreferenceCategory; import app.revanced.extension.tiktok.settings.preference.categories.ExtensionPreferenceCategory;
import app.revanced.extension.tiktok.settings.preference.categories.SimSpoofPreferenceCategory; import app.revanced.extension.tiktok.settings.preference.categories.SimSpoofPreferenceCategory;
import org.jetbrains.annotations.NotNull;
/** /**
* Preference fragment for ReVanced settings * Preference fragment for ReVanced settings

View File

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

View File

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

View File

@@ -26,6 +26,11 @@ public class FeedFilterPreferenceCategory extends ConditionalPreferenceCategory
"Remove feed ads", "Remove ads from feed.", "Remove feed ads", "Remove ads from feed.",
Settings.REMOVE_ADS Settings.REMOVE_ADS
)); ));
addPreference(new TogglePreference(
context,
"Hide TikTok Shop", "Hide TikTok shop from feed.",
Settings.HIDE_SHOP
));
addPreference(new TogglePreference( addPreference(new TogglePreference(
context, context,
"Hide livestreams", "Hide livestreams from feed.", "Hide livestreams", "Hide livestreams from feed.",

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