Compare commits

..

97 Commits

Author SHA1 Message Date
semantic-release-bot
f925479a93 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)) ([dc89be0](dc89be0e94))
* **GmsCore Support:** Correct the description to refer to the app being patched ([2bbcf9d](2bbcf9d82c))
* **Wide search bar:** Fix patching `19.16.39` ([433dbc3](433dbc3bf8))
* **YouTube - Change start page:** Add option to always override start page on app launch ([#4832](https://github.com/ReVanced/revanced-patches/issues/4832)) ([5062e24](5062e24433))
* **YouTube - Disable auto captions:** Correctly hide captions with YT 20.12 ([5ecbe82](5ecbe823ed))
* **YouTube - Hide video action buttons:** Add option to hide 'Ask' button ([#4852](https://github.com/ReVanced/revanced-patches/issues/4852)) ([43bcf5a](43bcf5a098))
* **YouTube - Hide video action buttons:** Hide A/B layout buttons ([4db5d3c](4db5d3c3d5))
* **YouTube - Wide search bar:** Do not force phone layout for tablet devices ([#4827](https://github.com/ReVanced/revanced-patches/issues/4827)) ([0cb38f9](0cb38f9f36))

### Features

* Add `Hide ADB status` patch ([#4585](https://github.com/ReVanced/revanced-patches/issues/4585)) ([1ea8047](1ea8047aef))
* **X / Twitter:** Support version `10.86.0-release.0` ([#4805](https://github.com/ReVanced/revanced-patches/issues/4805)) ([655b390](655b39043a))
* **YouTube - Swipe controls:** Add option for vertical progress bar ([#4811](https://github.com/ReVanced/revanced-patches/issues/4811)) ([ebee07e](ebee07ec3a))
* **YouTube:** Support version `20.12.46` ([#4779](https://github.com/ReVanced/revanced-patches/issues/4779)) ([703359f](703359f0c1))
2025-04-25 17:21:09 +00:00
LisoUseInAIKyrios
6514420719
chore: Merge branch dev to main (#4803) 2025-04-25 21:17:55 +04:00
github-actions[bot]
0e064cd607
chore: Sync translations (#4858) 2025-04-25 21:17:12 +04:00
semantic-release-bot
a50edf06f6 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)) ([43bcf5a](43bcf5a098))
2025-04-24 19:26:35 +00:00
LisoUseInAIKyrios
43bcf5a098
fix(YouTube - Hide video action buttons): Add option to hide 'Ask' button (#4852) 2025-04-24 23:23:10 +04:00
github-actions[bot]
f6f06dcd02
chore: Sync translations (#4853) 2025-04-24 23:21:29 +04:00
semantic-release-bot
9d30474318 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 ([2bbcf9d](2bbcf9d82c))
2025-04-24 18:51:43 +00:00
oSumAtrIX
2bbcf9d82c
fix(GmsCore Support): Correct the description to refer to the app being patched 2025-04-24 20:48:12 +02:00
semantic-release-bot
ba83c01700 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)) ([ebee07e](ebee07ec3a))
2025-04-23 11:34:08 +00:00
MarcaD
ebee07ec3a
feat(YouTube - Swipe controls): Add option for vertical progress bar (#4811) 2025-04-23 15:30:41 +04:00
github-actions[bot]
81999d8cd5
chore: Sync translations (#4845) 2025-04-23 15:30:22 +04:00
github-actions[bot]
d21c1b2a94
chore: Sync translations (#4839) 2025-04-21 22:17:26 +02:00
semantic-release-bot
eaceab5ee5 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 ([4db5d3c](4db5d3c3d5))
2025-04-21 20:16:36 +00:00
LisoUseInAIKyrios
4db5d3c3d5 fix(YouTube - Hide video action buttons): Hide A/B layout buttons 2025-04-21 22:13:04 +02:00
semantic-release-bot
9ab0338a68 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` ([433dbc3](433dbc3bf8))
2025-04-20 11:31:26 +00:00
LisoUseInAIKyrios
433dbc3bf8 fix(Wide search bar): Fix patching 19.16.39 2025-04-20 13:27:55 +02:00
github-actions[bot]
255cb5874c
chore: Sync translations (#4833) 2025-04-20 13:27:33 +02:00
semantic-release-bot
fd214046f2 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)) ([5062e24](5062e24433))
2025-04-20 08:24:42 +00:00
LisoUseInAIKyrios
5062e24433
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
ee9039428c 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)) ([0cb38f9](0cb38f9f36))
2025-04-19 16:36:29 +00:00
LisoUseInAIKyrios
0cb38f9f36
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]
3e64a4c18f
chore: Sync translations (#4828) 2025-04-19 12:00:28 +02:00
semantic-release-bot
cc3b94a3cb 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)) ([dc89be0](dc89be0e94))
2025-04-18 09:18:56 +00:00
1fexd
dc89be0e94
fix: Hide ADB status patch (#4814) 2025-04-18 11:15:05 +02:00
github-actions[bot]
84bc40cba0
chore: Sync translations (#4822) 2025-04-18 09:55:15 +02:00
Snow
f62bcbf42a
chore(YouTube - Return YouTube Dislike): Clarify settings text for estimated likes (#4818) 2025-04-17 19:10:37 +02:00
github-actions[bot]
11027ec551
chore: Sync translations (#4820) 2025-04-17 19:09:35 +02:00
semantic-release-bot
79a2c0db3d 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 ([5ecbe82](5ecbe823ed))
2025-04-17 17:06:19 +00:00
LisoUseInAIKyrios
5ecbe823ed fix(YouTube - Disable auto captions): Correctly hide captions with YT 20.12 2025-04-17 19:02:52 +02:00
LisoUseInAIKyrios
409f98cf65 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
4c8bf7fbf7 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)) ([655b390](655b39043a))
2025-04-16 11:09:43 +00:00
cyberboh
655b39043a
feat(X / Twitter): Support version 10.86.0-release.0 (#4805) 2025-04-16 13:06:30 +02:00
semantic-release-bot
564cfd2e38 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)) ([1ea8047](1ea8047aef))
2025-04-16 08:58:30 +00:00
1fexd
1ea8047aef
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
4c619fb346 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)) ([703359f](703359f0c1))
2025-04-16 08:28:15 +00:00
LisoUseInAIKyrios
703359f0c1
feat(YouTube): Support version 20.12.46 (#4779) 2025-04-16 10:24:35 +02:00
semantic-release-bot
e2b9d65bc6 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)) ([03d0eb2](03d0eb2f8c))
2025-04-15 16:41:52 +00:00
LisoUseInAIKyrios
1050c77efd
chore: Merge branch dev to main (#4801) 2025-04-15 18:38:29 +02:00
semantic-release-bot
f28efcf965 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)) ([03d0eb2](03d0eb2f8c))
2025-04-15 16:34:50 +00:00
LisoUseInAIKyrios
03d0eb2f8c
fix(Spotify - Custom theme): Support latest app target (#4800) 2025-04-15 18:30:55 +02:00
LisoUseInAIKyrios
ffc74be822
refactor(Spotify - Spoof package info): Replace installer package name of legacy target (#4797) 2025-04-15 17:38:47 +02:00
semantic-release-bot
c5d1702411 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)) ([215fccb](215fccbaf2))
* **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)) ([d639151](d639151641))
* **YouTube - Remove background playback restrictions:** Restore PiP button functionality after screen is unlocked ([6837348](6837348c45))

### Features

* Add `Set target SDK version 34` patch (Disable edge-to-edge display) ([#4780](https://github.com/ReVanced/revanced-patches/issues/4780)) ([dcf6178](dcf6178f19))
* **Spotify - Custom theme:** Add option to use unmodified player background gradient ([#4741](https://github.com/ReVanced/revanced-patches/issues/4741)) ([0ee3693](0ee36939f4))
* **YouTube - Swipe controls:** Add option to change volume swipe sensitivity (step size) ([#4557](https://github.com/ReVanced/revanced-patches/issues/4557)) ([8957325](8957325d78))
2025-04-15 11:02:58 +00:00
oSumAtrIX
42230b0291
chore: Merge branch dev to main (#4781) 2025-04-15 12:59:44 +02:00
github-actions[bot]
b02928cdc6
chore: Sync translations (#4795)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-04-15 12:57:13 +02:00
semantic-release-bot
7b58010076 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)) ([d639151](d639151641))
2025-04-15 10:56:12 +00:00
oSumAtrIX
d639151641
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
36e25966c8 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)) ([215fccb](215fccbaf2))
2025-04-15 07:27:06 +00:00
hoodles
215fccbaf2
fix(Duolingo - Hide ads): Support lastest app release (#4790) 2025-04-15 09:24:29 +02:00
semantic-release-bot
780822f1bd 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)) ([8957325](8957325d78))
2025-04-14 08:46:51 +00:00
Kamil Kras
8957325d78
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
aba6a6b5c5 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
Brosssh
50f5b1ac54
fix(Spotify - Unlock Spotify Premium): Remove premium restriction for 'Spotify Connect' (#4782) 2025-04-14 10:07:51 +02:00
semantic-release-bot
5b5449e07a 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 ([6837348](6837348c45))
2025-04-13 22:11:10 +00:00
LisoUseInAIKyrios
6837348c45 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]
95440273a6
chore: Sync translations (#4784) 2025-04-14 00:05:17 +02:00
semantic-release-bot
a6b5d043fd 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)) ([0ee3693](0ee36939f4))
2025-04-13 19:29:33 +00:00
Nuckyz
0ee36939f4
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
b99c2e5eb7 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)) ([dcf6178](dcf6178f19))
2025-04-13 16:26:18 +00:00
LisoUseInAIKyrios
dcf6178f19
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
065cc2a015 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)) ([3e18e86](3e18e868bb))
* **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
26b25ef479
chore: Merge branch dev to main (#4772) 2025-04-12 21:27:49 +02:00
semantic-release-bot
7f98474d37 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)) ([3e18e86](3e18e868bb))
2025-04-12 19:23:37 +00:00
LisoUseInAIKyrios
3e18e868bb
fix(Google Photos): Restore patching with ReVanced Manager (#4773) 2025-04-12 21:20:36 +02:00
semantic-release-bot
618e3276e4 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
LisoUseInAIKyrios
89d44da171
fix(Spotify): Restore patching with ReVanced Manager (#4769) 2025-04-12 21:01:51 +02:00
semantic-release-bot
4b84742210 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)) ([152bb7c](152bb7c3ee))
* **Google Photos:** Remove obsolete non functional patch `Restore hidden 'Back up while charging' toggle` ([#4764](https://github.com/ReVanced/revanced-patches/issues/4764)) ([56e48f4](56e48f4c89))
* **Spotify - Custom theme:** Override more color resources ([#4690](https://github.com/ReVanced/revanced-patches/issues/4690)) ([d7a7a0b](d7a7a0b982))
* **Spotify - Unlock Spotify Premium:** Remove restrictions for Google voice assistant ([#4702](https://github.com/ReVanced/revanced-patches/issues/4702)) ([106202f](106202f9eb))
* **Spotify:** Remove ads sections from home ([#4722](https://github.com/ReVanced/revanced-patches/issues/4722)) ([0b9a5e7](0b9a5e7f89))
* **Twitter - Hide recommended users:** Make hiding work again by filtering for new entryId prefix ([#4456](https://github.com/ReVanced/revanced-patches/issues/4456)) ([ff846b0](ff846b0b7e))
* **YouTube - Hide layout components:** Do not hide video description music/game links if hide horizontal shelves is enabled ([3864f35](3864f35501))
* **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)) ([b67bbb2](b67bbb2996))
* **YouTube - Remove background playback restrictions:** Do not show media controls when playing Shorts from the feed ([2ed675c](2ed675cdd0))
* **YouTube - Return YouTube Dislike:** Correctly update label after disliking a Short with 20.07 ([0bb3e32](0bb3e32244))
* **YouTube - Return YouTube Dislike:** Fix inconsistent label after disliking a Short ([ea92a2e](ea92a2e36c))
* **YouTube - Seekbar:** Correctly hide the feed seekbar with target 20.07 ([ddc6e4c](ddc6e4c34f))
* **YouTube:** Combine multiple seekbar patches into a single patch ([#4705](https://github.com/ReVanced/revanced-patches/issues/4705)) ([503b7eb](503b7eb8d4))

### Features

* **Angulus:** Add `Hide ads` patch ([#4604](https://github.com/ReVanced/revanced-patches/issues/4604)) ([87c86b5](87c86b53a9))
* **Messenger:** Add `Remove Meta AI tab` patch ([#4726](https://github.com/ReVanced/revanced-patches/issues/4726)) ([e3fad97](e3fad97484))
* **Photomath:** Support latest version ([#4672](https://github.com/ReVanced/revanced-patches/issues/4672)) ([8e16483](8e16483229))
* **Proton Mail:** Add `Remove 'Sent from' signature` patch ([#4514](https://github.com/ReVanced/revanced-patches/issues/4514)) ([34c14c9](34c14c9b44))
* **Spotify:** Add `Check environment` patch ([#4765](https://github.com/ReVanced/revanced-patches/issues/4765)) ([6d7101c](6d7101cb2e))
* **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)) ([a3fde87](a3fde874af))
* **Strava - Disable subscription suggestions:** Make compatible with latest version ([#4739](https://github.com/ReVanced/revanced-patches/issues/4739)) ([649a2c0](649a2c0616))
* **YouTube - Settings:** Add icons to the ReVanced settings ([#4496](https://github.com/ReVanced/revanced-patches/issues/4496)) ([d0c85f0](d0c85f0440))
2025-04-12 13:27:56 +00:00
oSumAtrIX
47ebec532e
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]
48358ff45d
chore: Sync translations (#4766)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-04-12 15:19:43 +02:00
semantic-release-bot
a22ba8e4d6 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)) ([6d7101c](6d7101cb2e))
2025-04-12 12:56:01 +00:00
oSumAtrIX
6d7101cb2e
feat(Spotify): Add Check environment patch (#4765) 2025-04-12 14:53:27 +02:00
semantic-release-bot
d42320d49f 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)) ([56e48f4](56e48f4c89))
2025-04-11 20:15:09 +00:00
LisoUseInAIKyrios
56e48f4c89
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
67fe151d77 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)) ([152bb7c](152bb7c3ee))
2025-04-11 19:42:49 +00:00
LisoUseInAIKyrios
152bb7c3ee
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]
9387aae7ba
chore: Sync translations (#4763)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-04-11 21:39:34 +02:00
semantic-release-bot
e8e5a6776a 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)) ([106202f](106202f9eb))
2025-04-11 19:36:38 +00:00
Brosssh
106202f9eb
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
e5ffd2c353 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)) ([a3fde87](a3fde874af))
2025-04-11 19:33:53 +00:00
LisoUseInAIKyrios
a3fde874af
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
b91285ec20 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)) ([649a2c0](649a2c0616))
2025-04-11 18:40:46 +00:00
user5095
649a2c0616
feat(Strava - Disable subscription suggestions): Make compatible with latest version (#4739) 2025-04-11 20:38:07 +02:00
LisoUseInAIKyrios
fe1f3ed169 chore(YouTube): Remove obsolete targets overlooked in last version bump 2025-04-11 20:05:45 +02:00
semantic-release-bot
b6e568f719 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)) ([e3fad97](e3fad97484))
2025-04-10 21:11:47 +00:00
Dawid Krajcarz
e3fad97484
feat(Messenger): Add Remove Meta AI tab patch (#4726) 2025-04-10 23:08:43 +02:00
semantic-release-bot
ebc5ebc0a9 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 ([3864f35](3864f35501))
2025-04-10 17:13:49 +00:00
LisoUseInAIKyrios
3864f35501 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
8e91507b95 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)) ([b67bbb2](b67bbb2996))
2025-04-10 14:08:34 +00:00
LisoUseInAIKyrios
b67bbb2996
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
7d86856713 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 ([ea92a2e](ea92a2e36c))
2025-04-09 18:38:26 +00:00
LisoUseInAIKyrios
ea92a2e36c fix(YouTube - Return YouTube Dislike): Fix inconsistent label after disliking a Short 2025-04-09 20:32:27 +02:00
github-actions[bot]
935226f8a4
chore: Sync translations (#4753) 2025-04-09 20:31:46 +02:00
semantic-release-bot
d1ed583cc1 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 ([0bb3e32](0bb3e32244))
2025-04-07 14:05:29 +00:00
LisoUseInAIKyrios
0bb3e32244 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
4ecd7e0a23 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)) ([0b9a5e7](0b9a5e7f89))
2025-04-04 12:35:54 +00:00
Nuckyz
0b9a5e7f89
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
eaee621831 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)) ([d7a7a0b](d7a7a0b982))
2025-04-02 17:17:59 +00:00
Nuckyz
d7a7a0b982
fix(Spotify - Custom theme): Override more color resources (#4690) 2025-04-02 19:14:17 +02:00
223 changed files with 5023 additions and 1822 deletions

View File

@ -1,3 +1,333 @@
# [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)) ([dc89be0](https://github.com/ReVanced/revanced-patches/commit/dc89be0e94880733f862b250d95d4848f02c594d))
* **GmsCore Support:** Correct the description to refer to the app being patched ([2bbcf9d](https://github.com/ReVanced/revanced-patches/commit/2bbcf9d82ca2f442572a6aa886cc611b0d56ff0a))
* **Wide search bar:** Fix patching `19.16.39` ([433dbc3](https://github.com/ReVanced/revanced-patches/commit/433dbc3bf81823369e146035c954281e84d3a436))
* **YouTube - Change start page:** Add option to always override start page on app launch ([#4832](https://github.com/ReVanced/revanced-patches/issues/4832)) ([5062e24](https://github.com/ReVanced/revanced-patches/commit/5062e24433ba38eba397438e8fde32099109d3c3))
* **YouTube - Disable auto captions:** Correctly hide captions with YT 20.12 ([5ecbe82](https://github.com/ReVanced/revanced-patches/commit/5ecbe823ed5197533328cc37f1de5cd1f048a217))
* **YouTube - Hide video action buttons:** Add option to hide 'Ask' button ([#4852](https://github.com/ReVanced/revanced-patches/issues/4852)) ([43bcf5a](https://github.com/ReVanced/revanced-patches/commit/43bcf5a098c9008cc11dc7df9680437d5effbb32))
* **YouTube - Hide video action buttons:** Hide A/B layout buttons ([4db5d3c](https://github.com/ReVanced/revanced-patches/commit/4db5d3c3d5ac04faf70cc07fb309b324d752e7e3))
* **YouTube - Wide search bar:** Do not force phone layout for tablet devices ([#4827](https://github.com/ReVanced/revanced-patches/issues/4827)) ([0cb38f9](https://github.com/ReVanced/revanced-patches/commit/0cb38f9f367a7fe742d8ca336150049181d637b6))
### Features
* Add `Hide ADB status` patch ([#4585](https://github.com/ReVanced/revanced-patches/issues/4585)) ([1ea8047](https://github.com/ReVanced/revanced-patches/commit/1ea8047aefdaa358e9af8038923ac54d68a39176))
* **X / Twitter:** Support version `10.86.0-release.0` ([#4805](https://github.com/ReVanced/revanced-patches/issues/4805)) ([655b390](https://github.com/ReVanced/revanced-patches/commit/655b39043ad77efcb4380de67c3f603666e7bc49))
* **YouTube - Swipe controls:** Add option for vertical progress bar ([#4811](https://github.com/ReVanced/revanced-patches/issues/4811)) ([ebee07e](https://github.com/ReVanced/revanced-patches/commit/ebee07ec3aba6fd3adbd8e0af30390e197879d89))
* **YouTube:** Support version `20.12.46` ([#4779](https://github.com/ReVanced/revanced-patches/issues/4779)) ([703359f](https://github.com/ReVanced/revanced-patches/commit/703359f0c16b613c204cf16cf42227b628f664fa))
# [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)) ([43bcf5a](https://github.com/ReVanced/revanced-patches/commit/43bcf5a098c9008cc11dc7df9680437d5effbb32))
# [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 ([2bbcf9d](https://github.com/ReVanced/revanced-patches/commit/2bbcf9d82ca2f442572a6aa886cc611b0d56ff0a))
# [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)) ([ebee07e](https://github.com/ReVanced/revanced-patches/commit/ebee07ec3aba6fd3adbd8e0af30390e197879d89))
# [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 ([4db5d3c](https://github.com/ReVanced/revanced-patches/commit/4db5d3c3d5ac04faf70cc07fb309b324d752e7e3))
# [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` ([433dbc3](https://github.com/ReVanced/revanced-patches/commit/433dbc3bf81823369e146035c954281e84d3a436))
# [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)) ([5062e24](https://github.com/ReVanced/revanced-patches/commit/5062e24433ba38eba397438e8fde32099109d3c3))
# [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)) ([0cb38f9](https://github.com/ReVanced/revanced-patches/commit/0cb38f9f367a7fe742d8ca336150049181d637b6))
# [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)) ([dc89be0](https://github.com/ReVanced/revanced-patches/commit/dc89be0e94880733f862b250d95d4848f02c594d))
# [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 ([5ecbe82](https://github.com/ReVanced/revanced-patches/commit/5ecbe823ed5197533328cc37f1de5cd1f048a217))
# [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)) ([655b390](https://github.com/ReVanced/revanced-patches/commit/655b39043ad77efcb4380de67c3f603666e7bc49))
# [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)) ([1ea8047](https://github.com/ReVanced/revanced-patches/commit/1ea8047aefdaa358e9af8038923ac54d68a39176))
# [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)) ([703359f](https://github.com/ReVanced/revanced-patches/commit/703359f0c16b613c204cf16cf42227b628f664fa))
## [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)) ([03d0eb2](https://github.com/ReVanced/revanced-patches/commit/03d0eb2f8c0f3e48d53bdab38d34057f2020bb65))
## [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)) ([03d0eb2](https://github.com/ReVanced/revanced-patches/commit/03d0eb2f8c0f3e48d53bdab38d34057f2020bb65))
# [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)) ([215fccb](https://github.com/ReVanced/revanced-patches/commit/215fccbaf2fdd54251c46cbda106029eb304996b))
* **Spotify - Unlock Spotify Premium:** Remove premium restriction for 'Spotify Connect' ([#4782](https://github.com/ReVanced/revanced-patches/issues/4782)) ([50f5b1a](https://github.com/ReVanced/revanced-patches/commit/50f5b1ac54372542d76e87626f00ddefb54da125))
* **Spotify:** Fix login by replacing `Spoof signature` patch with new `Spoof package info` patch ([#4794](https://github.com/ReVanced/revanced-patches/issues/4794)) ([d639151](https://github.com/ReVanced/revanced-patches/commit/d639151641352ce651037b17fb65bd58953cd51c))
* **YouTube - Remove background playback restrictions:** Restore PiP button functionality after screen is unlocked ([6837348](https://github.com/ReVanced/revanced-patches/commit/6837348c45156d6743a63fef8b6e045087afbda8))
### Features
* Add `Set target SDK version 34` patch (Disable edge-to-edge display) ([#4780](https://github.com/ReVanced/revanced-patches/issues/4780)) ([dcf6178](https://github.com/ReVanced/revanced-patches/commit/dcf6178f19f86dd1b57d54c855b8c47b086dd33a))
* **Spotify - Custom theme:** Add option to use unmodified player background gradient ([#4741](https://github.com/ReVanced/revanced-patches/issues/4741)) ([0ee3693](https://github.com/ReVanced/revanced-patches/commit/0ee36939f43f325afca37119db1cf1af3b63be27))
* **YouTube - Swipe controls:** Add option to change volume swipe sensitivity (step size) ([#4557](https://github.com/ReVanced/revanced-patches/issues/4557)) ([8957325](https://github.com/ReVanced/revanced-patches/commit/8957325d78eb42e087c4c1ff0abedb2146aa4423))
# [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)) ([d639151](https://github.com/ReVanced/revanced-patches/commit/d639151641352ce651037b17fb65bd58953cd51c))
# [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)) ([215fccb](https://github.com/ReVanced/revanced-patches/commit/215fccbaf2fdd54251c46cbda106029eb304996b))
# [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)) ([8957325](https://github.com/ReVanced/revanced-patches/commit/8957325d78eb42e087c4c1ff0abedb2146aa4423))
# [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](https://github.com/ReVanced/revanced-patches/commit/50f5b1ac54372542d76e87626f00ddefb54da125))
# [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 ([6837348](https://github.com/ReVanced/revanced-patches/commit/6837348c45156d6743a63fef8b6e045087afbda8))
# [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)) ([0ee3693](https://github.com/ReVanced/revanced-patches/commit/0ee36939f43f325afca37119db1cf1af3b63be27))
# [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)) ([dcf6178](https://github.com/ReVanced/revanced-patches/commit/dcf6178f19f86dd1b57d54c855b8c47b086dd33a))
## [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)) ([3e18e86](https://github.com/ReVanced/revanced-patches/commit/3e18e868bbd9fd0600fe81a7fe8767b4bd89a00e))
* **Spotify:** Restore patching with ReVanced Manager ([#4769](https://github.com/ReVanced/revanced-patches/issues/4769)) ([89d44da](https://github.com/ReVanced/revanced-patches/commit/89d44da171c3f56f13112d1d82bc4ea4a56c7c06))
## [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)) ([3e18e86](https://github.com/ReVanced/revanced-patches/commit/3e18e868bbd9fd0600fe81a7fe8767b4bd89a00e))
## [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](https://github.com/ReVanced/revanced-patches/commit/89d44da171c3f56f13112d1d82bc4ea4a56c7c06))
# [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)) ([152bb7c](https://github.com/ReVanced/revanced-patches/commit/152bb7c3ee7cf36bc07460e7a3444631ec540441))
* **Google Photos:** Remove obsolete non functional patch `Restore hidden 'Back up while charging' toggle` ([#4764](https://github.com/ReVanced/revanced-patches/issues/4764)) ([56e48f4](https://github.com/ReVanced/revanced-patches/commit/56e48f4c89da51f81ff11a79a164eaa5b440690e))
* **Spotify - Custom theme:** Override more color resources ([#4690](https://github.com/ReVanced/revanced-patches/issues/4690)) ([d7a7a0b](https://github.com/ReVanced/revanced-patches/commit/d7a7a0b982dbafa181b04f984a5f7618fb067c2a))
* **Spotify - Unlock Spotify Premium:** Remove restrictions for Google voice assistant ([#4702](https://github.com/ReVanced/revanced-patches/issues/4702)) ([106202f](https://github.com/ReVanced/revanced-patches/commit/106202f9ebb7699c4ba4ae46b82133e35f1ac6b9))
* **Spotify:** Remove ads sections from home ([#4722](https://github.com/ReVanced/revanced-patches/issues/4722)) ([0b9a5e7](https://github.com/ReVanced/revanced-patches/commit/0b9a5e7f89a89d971762b3539166d4f145111481))
* **Twitter - Hide recommended users:** Make hiding work again by filtering for new entryId prefix ([#4456](https://github.com/ReVanced/revanced-patches/issues/4456)) ([ff846b0](https://github.com/ReVanced/revanced-patches/commit/ff846b0b7ef5060caaffedb08c1f901172f5b2d1))
* **YouTube - Hide layout components:** Do not hide video description music/game links if hide horizontal shelves is enabled ([3864f35](https://github.com/ReVanced/revanced-patches/commit/3864f3550153617e23ad9979fb543d8a7fb4dc0a))
* **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)) ([b67bbb2](https://github.com/ReVanced/revanced-patches/commit/b67bbb299669336addb68cf52a8ce5b39c68cec0))
* **YouTube - Remove background playback restrictions:** Do not show media controls when playing Shorts from the feed ([2ed675c](https://github.com/ReVanced/revanced-patches/commit/2ed675cdd058fb5876381a9d30dee5263f6b2e26))
* **YouTube - Return YouTube Dislike:** Correctly update label after disliking a Short with 20.07 ([0bb3e32](https://github.com/ReVanced/revanced-patches/commit/0bb3e32244fa10809aee5c4e549f77ed4054537e))
* **YouTube - Return YouTube Dislike:** Fix inconsistent label after disliking a Short ([ea92a2e](https://github.com/ReVanced/revanced-patches/commit/ea92a2e36c7aab3bd115f7d0ec40467179485b32))
* **YouTube - Seekbar:** Correctly hide the feed seekbar with target 20.07 ([ddc6e4c](https://github.com/ReVanced/revanced-patches/commit/ddc6e4c34fe35fa34bd859bf34e25645a23dbdc9))
* **YouTube:** Combine multiple seekbar patches into a single patch ([#4705](https://github.com/ReVanced/revanced-patches/issues/4705)) ([503b7eb](https://github.com/ReVanced/revanced-patches/commit/503b7eb8d413ef7f248394f128f3b2a6f3192ba6))
### Features
* **Angulus:** Add `Hide ads` patch ([#4604](https://github.com/ReVanced/revanced-patches/issues/4604)) ([87c86b5](https://github.com/ReVanced/revanced-patches/commit/87c86b53a91b0054ac892a3f02bbe7bf83bbf813))
* **Messenger:** Add `Remove Meta AI tab` patch ([#4726](https://github.com/ReVanced/revanced-patches/issues/4726)) ([e3fad97](https://github.com/ReVanced/revanced-patches/commit/e3fad97484d7eb962aeb53d44a0047b34a881071))
* **Photomath:** Support latest version ([#4672](https://github.com/ReVanced/revanced-patches/issues/4672)) ([8e16483](https://github.com/ReVanced/revanced-patches/commit/8e1648322948151e4565fb0d86e0f37d0a02d73f))
* **Proton Mail:** Add `Remove 'Sent from' signature` patch ([#4514](https://github.com/ReVanced/revanced-patches/issues/4514)) ([34c14c9](https://github.com/ReVanced/revanced-patches/commit/34c14c9b443092824d035afd77adb678c6f89e3e))
* **Spotify:** Add `Check environment` patch ([#4765](https://github.com/ReVanced/revanced-patches/issues/4765)) ([6d7101c](https://github.com/ReVanced/revanced-patches/commit/6d7101cb2e546e01a934eff9cad1264367aeafe3))
* **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)) ([a3fde87](https://github.com/ReVanced/revanced-patches/commit/a3fde874af993125ba7a741820e7bd48e3641b84))
* **Strava - Disable subscription suggestions:** Make compatible with latest version ([#4739](https://github.com/ReVanced/revanced-patches/issues/4739)) ([649a2c0](https://github.com/ReVanced/revanced-patches/commit/649a2c06161c72a2040b179dbed5b415847d7527))
* **YouTube - Settings:** Add icons to the ReVanced settings ([#4496](https://github.com/ReVanced/revanced-patches/issues/4496)) ([d0c85f0](https://github.com/ReVanced/revanced-patches/commit/d0c85f044083d720c63a8ea4ff15d42eefeb9db7))
# [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)) ([6d7101c](https://github.com/ReVanced/revanced-patches/commit/6d7101cb2e546e01a934eff9cad1264367aeafe3))
# [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)) ([56e48f4](https://github.com/ReVanced/revanced-patches/commit/56e48f4c89da51f81ff11a79a164eaa5b440690e))
# [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)) ([152bb7c](https://github.com/ReVanced/revanced-patches/commit/152bb7c3ee7cf36bc07460e7a3444631ec540441))
# [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)) ([106202f](https://github.com/ReVanced/revanced-patches/commit/106202f9ebb7699c4ba4ae46b82133e35f1ac6b9))
# [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)) ([a3fde87](https://github.com/ReVanced/revanced-patches/commit/a3fde874af993125ba7a741820e7bd48e3641b84))
# [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)) ([649a2c0](https://github.com/ReVanced/revanced-patches/commit/649a2c06161c72a2040b179dbed5b415847d7527))
# [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)) ([e3fad97](https://github.com/ReVanced/revanced-patches/commit/e3fad97484d7eb962aeb53d44a0047b34a881071))
# [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 ([3864f35](https://github.com/ReVanced/revanced-patches/commit/3864f3550153617e23ad9979fb543d8a7fb4dc0a))
# [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)) ([b67bbb2](https://github.com/ReVanced/revanced-patches/commit/b67bbb299669336addb68cf52a8ce5b39c68cec0))
# [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 ([ea92a2e](https://github.com/ReVanced/revanced-patches/commit/ea92a2e36c7aab3bd115f7d0ec40467179485b32))
# [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 ([0bb3e32](https://github.com/ReVanced/revanced-patches/commit/0bb3e32244fa10809aee5c4e549f77ed4054537e))
# [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)) ([0b9a5e7](https://github.com/ReVanced/revanced-patches/commit/0b9a5e7f89a89d971762b3539166d4f145111481))
# [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)) ([d7a7a0b](https://github.com/ReVanced/revanced-patches/commit/d7a7a0b982dbafa181b04f984a5f7618fb067c2a))
# [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) # [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)

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

@ -9,6 +9,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Color;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
@ -799,4 +800,14 @@ public class Utils {
builder.getContext().setTheme(editTextDialogStyle); builder.getContext().setTheme(editTextDialogStyle);
} }
} }
/**
* Parse a color resource or hex code to an int representation of the color.
*/
public static int getColorFromString(String colorString) throws IllegalArgumentException, Resources.NotFoundException {
if (colorString.startsWith("#")) {
return Color.parseColor(colorString);
}
return getResourceColor(colorString);
}
} }

View File

@ -342,9 +342,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;
} }
/** /**

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,22 @@
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 {
/**
* Injection point.
*/
public static long getThemeColor(String colorString) {
try {
return Utils.getColorFromString(colorString);
} catch (Exception ex) {
Logger.printException(() -> "Invalid custom color: " + colorString, ex);
return Color.BLACK;
}
}
}

View File

@ -3,7 +3,7 @@ package app.revanced.extension.spotify.misc;
import static java.lang.Boolean.FALSE; import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE; import static java.lang.Boolean.TRUE;
import com.spotify.remoteconfig.internal.AccountAttribute; import com.spotify.home.evopage.homeapi.proto.Section;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -14,6 +14,25 @@ import app.revanced.extension.shared.Logger;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public final class UnlockPremiumPatch { public final class UnlockPremiumPatch {
private static final String SPOTIFY_MAIN_ACTIVITY_LEGACY = "com.spotify.music.MainActivity";
/**
* If the app target is 8.6.98.900.
*/
private static final boolean IS_SPOTIFY_LEGACY_APP_TARGET;
static {
boolean legacy;
try {
Class.forName(SPOTIFY_MAIN_ACTIVITY_LEGACY);
legacy = true;
} catch (ClassNotFoundException ex) {
legacy = false;
}
IS_SPOTIFY_LEGACY_APP_TARGET = legacy;
}
private static class OverrideAttribute { private static class OverrideAttribute {
/** /**
* Account attribute key. * Account attribute key.
@ -54,13 +73,16 @@ public final class UnlockPremiumPatch {
// Make sure playing songs is not disabled remotely and playlists show up. // Make sure playing songs is not disabled remotely and playlists show up.
new OverrideAttribute("streaming", TRUE), new OverrideAttribute("streaming", TRUE),
// Allows adding songs to queue and removes the smart shuffle mode restriction, // Allows adding songs to queue and removes the smart shuffle mode restriction,
// allowing to pick any of the other modes. // allowing to pick any of the other modes. Flag is not present in legacy app target.
new OverrideAttribute("pick-and-shuffle", FALSE), new OverrideAttribute("pick-and-shuffle", FALSE, !IS_SPOTIFY_LEGACY_APP_TARGET),
// Disables shuffle-mode streaming-rule, which forces songs to be played shuffled // Disables shuffle-mode streaming-rule, which forces songs to be played shuffled
// and breaks the player when other patches are applied. // and breaks the player when other patches are applied.
new OverrideAttribute("streaming-rules", ""), new OverrideAttribute("streaming-rules", ""),
// Enables premium UI in settings and removes the premium button in the nav-bar. // Enables premium UI in settings and removes the premium button in the nav-bar.
new OverrideAttribute("nft-disabled", "1"), new OverrideAttribute("nft-disabled", "1"),
// Enable Spotify Connect and disable other premium related UI, like buying premium.
// It also removes the download button.
new OverrideAttribute("type", "premium"),
// Enable Spotify Car Thing hardware device. // Enable Spotify Car Thing hardware device.
// Device is discontinued and no longer works with the latest releases, // Device is discontinued and no longer works with the latest releases,
// but it might still work with older app targets. // but it might still work with older app targets.
@ -69,23 +91,51 @@ public final class UnlockPremiumPatch {
new OverrideAttribute("tablet-free", FALSE, false) new OverrideAttribute("tablet-free", FALSE, false)
); );
private static final List<Integer> REMOVED_HOME_SECTIONS = List.of(
Section.VIDEO_BRAND_AD_FIELD_NUMBER,
Section.IMAGE_BRAND_AD_FIELD_NUMBER
);
/** /**
* Injection point. * Injection point. Override account attributes.
*/ */
public static void overrideAttribute(Map<String, AccountAttribute> attributes) { public static void overrideAttribute(Map<String, /*AccountAttribute*/ Object> attributes) {
try { try {
for (var override : OVERRIDES) { for (var override : OVERRIDES) {
var attribute = attributes.get(override.key); var attribute = attributes.get(override.key);
if (attribute == null) { if (attribute == null) {
if (override.isExpected) { if (override.isExpected) {
Logger.printException(() -> "''" + override.key + "' expected but not found"); Logger.printException(() -> "'" + override.key + "' expected but not found");
} }
} else { } else {
attribute.value_ = override.overrideValue; Object overrideValue = override.overrideValue;
if (IS_SPOTIFY_LEGACY_APP_TARGET) {
((com.spotify.useraccount.v1.AccountAttribute) attribute).value_ = overrideValue;
} else {
((com.spotify.remoteconfig.internal.AccountAttribute) attribute).value_ = overrideValue;
}
} }
} }
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "overrideAttribute failure", ex); Logger.printException(() -> "overrideAttribute failure", ex);
} }
} }
/**
* Injection point. Remove station data from Google assistant URI.
*/
public static String removeStationString(String spotifyUriOrUrl) {
return spotifyUriOrUrl.replace("spotify:station:", "spotify:");
}
/**
* Injection point. Remove ads sections from home.
*/
public static void removeHomeSections(List<Section> sections) {
try {
sections.removeIf(section -> REMOVED_HOME_SECTIONS.contains(section.featureTypeCase_));
} catch (Exception ex) {
Logger.printException(() -> "Remove home sections failure", ex);
}
}
} }

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,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

@ -45,13 +45,24 @@ public class ThemeHelper {
return "@color/yt_black3"; return "@color/yt_black3";
} }
private static int getThemeColor(String resourceName, int defaultColor) {
try {
return Utils.getColorFromString(resourceName);
} catch (Exception ex) {
// User entered an invalid custom theme color.
// Normally this should never be reached, and no localized strings are needed.
Utils.showToastLong("Invalid custom theme color: " + resourceName);
return defaultColor;
}
}
/** /**
* @return The dark theme color as specified by the Theme patch (if included), * @return The dark theme color as specified by the Theme patch (if included),
* or the dark mode background color unpatched YT uses. * or the dark mode background color unpatched YT uses.
*/ */
public static int getDarkThemeColor() { public static int getDarkThemeColor() {
if (darkThemeColor == null) { if (darkThemeColor == null) {
darkThemeColor = getColorInt(darkThemeResourceName()); darkThemeColor = getThemeColor(darkThemeResourceName(), Color.BLACK);
} }
return darkThemeColor; return darkThemeColor;
} }
@ -71,18 +82,11 @@ public class ThemeHelper {
*/ */
public static int getLightThemeColor() { public static int getLightThemeColor() {
if (lightThemeColor == null) { if (lightThemeColor == null) {
lightThemeColor = getColorInt(lightThemeResourceName()); lightThemeColor = getThemeColor(lightThemeResourceName(), Color.WHITE);
} }
return lightThemeColor; return lightThemeColor;
} }
private static int getColorInt(String colorString) {
if (colorString.startsWith("#")) {
return Color.parseColor(colorString);
}
return Utils.getResourceColor(colorString);
}
public static int getBackgroundColor() { public static int getBackgroundColor() {
return isDarkTheme() ? getDarkThemeColor() : getLightThemeColor(); return isDarkTheme() ? getDarkThemeColor() : getLightThemeColor();
} }
@ -96,6 +100,6 @@ public class ThemeHelper {
? "yt_black3" ? "yt_black3"
: "yt_white1"; : "yt_white1";
return getColorInt(colorName); return Utils.getColorFromString(colorName);
} }
} }

View File

@ -9,6 +9,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ -81,6 +82,13 @@ public final class ChangeStartPagePatch {
} }
} }
public static class ChangeStartPageTypeAvailability implements Setting.Availability {
@Override
public boolean isAvailable() {
return Settings.CHANGE_START_PAGE.get() != StartPage.DEFAULT;
}
}
/** /**
* Intent action when YouTube is cold started from the launcher. * Intent action when YouTube is cold started from the launcher.
* <p> * <p>
@ -93,6 +101,8 @@ public final class ChangeStartPagePatch {
private static final StartPage START_PAGE = Settings.CHANGE_START_PAGE.get(); private static final StartPage START_PAGE = Settings.CHANGE_START_PAGE.get();
private static final boolean CHANGE_START_PAGE_ALWAYS = Settings.CHANGE_START_PAGE_ALWAYS.get();
/** /**
* There is an issue where the back button on the toolbar doesn't work properly. * There is an issue where the back button on the toolbar doesn't work properly.
* As a workaround for this issue, instead of overriding the browserId multiple times, just override it once. * As a workaround for this issue, instead of overriding the browserId multiple times, just override it once.
@ -104,13 +114,13 @@ public final class ChangeStartPagePatch {
return original; return original;
} }
if (appLaunched) { if (!CHANGE_START_PAGE_ALWAYS && appLaunched) {
Logger.printDebug(() -> "Ignore override browseId as the app already launched"); Logger.printDebug(() -> "Ignore override browseId as the app already launched");
return original; return original;
} }
appLaunched = true; appLaunched = true;
Logger.printDebug(() -> "Changing browseId to " + START_PAGE.id); Logger.printDebug(() -> "Changing browseId to: " + START_PAGE.id);
return START_PAGE.id; return START_PAGE.id;
} }
@ -125,14 +135,14 @@ public final class ChangeStartPagePatch {
return; return;
} }
if (appLaunched) { if (!CHANGE_START_PAGE_ALWAYS && appLaunched) {
Logger.printDebug(() -> "Ignore override intent action as the app already launched"); Logger.printDebug(() -> "Ignore override intent action as the app already launched");
return; return;
} }
appLaunched = true; appLaunched = true;
String intentAction = START_PAGE.id; String intentAction = START_PAGE.id;
Logger.printDebug(() -> "Changing intent action to " + intentAction); Logger.printDebug(() -> "Changing intent action to: " + intentAction);
intent.setAction(intentAction); intent.setAction(intentAction);
} }
} }

View File

@ -17,8 +17,7 @@ public class CustomPlayerOverlayOpacityPatch {
if (opacity < 0 || opacity > 100) { if (opacity < 0 || opacity > 100) {
Utils.showToastLong(str("revanced_player_overlay_opacity_invalid_toast")); Utils.showToastLong(str("revanced_player_overlay_opacity_invalid_toast"));
Settings.PLAYER_OVERLAY_OPACITY.resetToDefault(); opacity = Settings.PLAYER_OVERLAY_OPACITY.resetToDefault();
opacity = Settings.PLAYER_OVERLAY_OPACITY.defaultValue;
} }
PLAYER_OVERLAY_OPACITY_LEVEL = (opacity * 255) / 100; PLAYER_OVERLAY_OPACITY_LEVEL = (opacity * 255) / 100;

View File

@ -1,20 +1,23 @@
package app.revanced.extension.youtube.patches; package app.revanced.extension.youtube.patches;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.ShortsPlayerState;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class DisableAutoCaptionsPatch { public class DisableAutoCaptionsPatch {
private static volatile boolean captionsButtonStatus;
/** /**
* Used by injected code. Do not delete. * Injection point.
*/ */
public static boolean captionsButtonDisabled; public static boolean disableAutoCaptions() {
return Settings.DISABLE_AUTO_CAPTIONS.get() && !captionsButtonStatus;
public static boolean autoCaptionsEnabled() {
return Settings.AUTO_CAPTIONS.get()
// Do not use auto captions for Shorts.
&& ShortsPlayerState.isOpen();
} }
/**
* Injection point.
*/
public static void setCaptionsButtonStatus(boolean status) {
captionsButtonStatus = status;
}
} }

View File

@ -162,8 +162,7 @@ public final class MiniplayerPatch {
if (opacity < 0 || opacity > 100) { if (opacity < 0 || opacity > 100) {
Utils.showToastLong(str("revanced_miniplayer_opacity_invalid_toast")); Utils.showToastLong(str("revanced_miniplayer_opacity_invalid_toast"));
Settings.MINIPLAYER_OPACITY.resetToDefault(); opacity = Settings.MINIPLAYER_OPACITY.resetToDefault();
opacity = Settings.MINIPLAYER_OPACITY.defaultValue;
} }
OPACITY_LEVEL = (opacity * 255) / 100; OPACITY_LEVEL = (opacity * 255) / 100;

View File

@ -2,20 +2,16 @@ package app.revanced.extension.youtube.patches;
import static app.revanced.extension.youtube.returnyoutubedislike.ReturnYouTubeDislike.Vote; import static app.revanced.extension.youtube.returnyoutubedislike.ReturnYouTubeDislike.Vote;
import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.ShapeDrawable;
import android.text.Spannable;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.Spanned; import android.text.Spanned;
import android.view.View; import android.view.View;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
@ -60,12 +56,12 @@ public class ReturnYouTubeDislikePatch {
private static volatile ReturnYouTubeDislike lastLithoShortsVideoData; private static volatile ReturnYouTubeDislike lastLithoShortsVideoData;
/** /**
* Because the litho Shorts spans are created after {@link ReturnYouTubeDislikeFilterPatch} * Because litho Shorts spans are created offscreen after {@link ReturnYouTubeDislikeFilterPatch}
* detects the video ids, after the user votes the litho will update * detects the video ids, but the current Short can arbitrarily reload the same span,
* but {@link #lastLithoShortsVideoData} is not the correct data to use. * then use the {@link #lastLithoShortsVideoData} if this value is greater than zero.
* If this is true, then instead use {@link #currentVideoData}.
*/ */
private static volatile boolean lithoShortsShouldUseCurrentData; @GuardedBy("ReturnYouTubeDislikePatch.class")
private static int useLithoShortsVideoDataCount;
/** /**
* Last video id prefetched. Field is to prevent prefetching the same video id multiple times in a row. * Last video id prefetched. Field is to prevent prefetching the same video id multiple times in a row.
@ -83,12 +79,28 @@ public class ReturnYouTubeDislikePatch {
private static void clearData() { private static void clearData() {
currentVideoData = null; currentVideoData = null;
lastLithoShortsVideoData = null; lastLithoShortsVideoData = null;
lithoShortsShouldUseCurrentData = false; synchronized (ReturnYouTubeDislike.class) {
useLithoShortsVideoDataCount = 0;
}
// Rolling number text should not be cleared, // Rolling number text should not be cleared,
// as it's used if incognito Short is opened/closed // as it's used if incognito Short is opened/closed
// while a regular video is on screen. // while a regular video is on screen.
} }
/**
* @return If {@link #useLithoShortsVideoDataCount} was greater than zero.
*/
private static boolean decrementUseLithoDataIfNeeded() {
synchronized (ReturnYouTubeDislikePatch.class) {
if (useLithoShortsVideoDataCount > 0) {
useLithoShortsVideoDataCount--;
return true;
}
return false;
}
}
// //
// Litho player for both regular videos and Shorts. // Litho player for both regular videos and Shorts.
@ -152,10 +164,13 @@ public class ReturnYouTubeDislikePatch {
return getShortsSpan(original, true); return getShortsSpan(original, true);
} }
if (conversionContextString.contains("|shorts_like_button.eml") if (conversionContextString.contains("|shorts_like_button.eml")) {
&& !Utils.containsNumber(original)) { if (!Utils.containsNumber(original)) {
Logger.printDebug(() -> "Replacing hidden likes count"); Logger.printDebug(() -> "Replacing hidden likes count");
return getShortsSpan(original, false); return getShortsSpan(original, false);
} else {
decrementUseLithoDataIfNeeded();
}
} }
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "onLithoTextLoaded failure", ex); Logger.printException(() -> "onLithoTextLoaded failure", ex);
@ -170,7 +185,14 @@ public class ReturnYouTubeDislikePatch {
return original; return original;
} }
ReturnYouTubeDislike videoData = lastLithoShortsVideoData; final ReturnYouTubeDislike videoData;
if (decrementUseLithoDataIfNeeded()) {
// New Short is loading off screen.
videoData = lastLithoShortsVideoData;
} else {
videoData = currentVideoData;
}
if (videoData == null) { if (videoData == null) {
// The Shorts litho video id filter did not detect the video id. // The Shorts litho video id filter did not detect the video id.
// This is normal in incognito mode, but otherwise is abnormal. // This is normal in incognito mode, but otherwise is abnormal.
@ -178,19 +200,6 @@ public class ReturnYouTubeDislikePatch {
return original; return original;
} }
// Use the correct dislikes data after voting.
if (lithoShortsShouldUseCurrentData) {
if (isDislikesSpan) {
lithoShortsShouldUseCurrentData = false;
}
videoData = currentVideoData;
if (videoData == null) {
Logger.printException(() -> "currentVideoData is null"); // Should never happen
return original;
}
Logger.printDebug(() -> "Using current video data for litho span");
}
return isDislikesSpan return isDislikesSpan
? videoData.getDislikeSpanForShort((Spanned) original) ? videoData.getDislikeSpanForShort((Spanned) original)
: videoData.getLikeSpanForShort((Spanned) original); : videoData.getLikeSpanForShort((Spanned) original);
@ -445,7 +454,10 @@ public class ReturnYouTubeDislikePatch {
ReturnYouTubeDislike videoData = ReturnYouTubeDislike.getFetchForVideoId(videoId); ReturnYouTubeDislike videoData = ReturnYouTubeDislike.getFetchForVideoId(videoId);
videoData.setVideoIdIsShort(true); videoData.setVideoIdIsShort(true);
lastLithoShortsVideoData = videoData; lastLithoShortsVideoData = videoData;
lithoShortsShouldUseCurrentData = false; synchronized (ReturnYouTubeDislikePatch.class) {
// Use litho Shorts data for the next like and dislike spans.
useLithoShortsVideoDataCount = 2;
}
} }
private static boolean videoIdIsSame(@Nullable ReturnYouTubeDislike fetch, @Nullable String videoId) { private static boolean videoIdIsSame(@Nullable ReturnYouTubeDislike fetch, @Nullable String videoId) {
@ -480,13 +492,6 @@ public class ReturnYouTubeDislikePatch {
for (Vote v : Vote.values()) { for (Vote v : Vote.values()) {
if (v.value == vote) { if (v.value == vote) {
videoData.sendVote(v); videoData.sendVote(v);
if (isNoneHiddenOrMinimized) {
if (lastLithoShortsVideoData != null) {
lithoShortsShouldUseCurrentData = true;
}
}
return; return;
} }
} }

View File

@ -1,11 +1,48 @@
package app.revanced.extension.youtube.patches; package app.revanced.extension.youtube.patches;
import android.content.res.Resources;
import android.util.TypedValue;
import android.view.View;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public final class WideSearchbarPatch { public final class WideSearchbarPatch {
private static final Boolean WIDE_SEARCHBAR_ENABLED = Settings.WIDE_SEARCHBAR.get();
/**
* Injection point.
*/
public static boolean enableWideSearchbar(boolean original) { public static boolean enableWideSearchbar(boolean original) {
return Settings.WIDE_SEARCHBAR.get() || original; return WIDE_SEARCHBAR_ENABLED || original;
}
/**
* Injection point.
*/
public static void setActionBar(View view) {
try {
if (!WIDE_SEARCHBAR_ENABLED) return;
View searchBarView = Utils.getChildViewByResourceName(view, "search_bar");
final int paddingLeft = searchBarView.getPaddingLeft();
final int paddingRight = searchBarView.getPaddingRight();
final int paddingTop = searchBarView.getPaddingTop();
final int paddingBottom = searchBarView.getPaddingBottom();
final int paddingStart = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
8, Resources.getSystem().getDisplayMetrics());
if (Utils.isRightToLeftTextLayout()) {
searchBarView.setPadding(paddingLeft, paddingTop, paddingStart, paddingBottom);
} else {
searchBarView.setPadding(paddingStart, paddingTop, paddingRight, paddingBottom);
}
} catch (Exception ex) {
Logger.printException(() -> "setActionBar failure", ex);
}
} }
} }

View File

@ -31,7 +31,7 @@ final class ButtonsFilter extends Filter {
bufferFilterPathGroup = new StringFilterGroup( bufferFilterPathGroup = new StringFilterGroup(
null, null,
"|ContainerType|button.eml|" "|ContainerType|button.eml"
); );
addPathCallbacks( addPathCallbacks(
@ -43,7 +43,7 @@ final class ButtonsFilter extends Filter {
), ),
new StringFilterGroup( new StringFilterGroup(
Settings.HIDE_DOWNLOAD_BUTTON, Settings.HIDE_DOWNLOAD_BUTTON,
"|download_button.eml|" "|download_button.eml"
), ),
new StringFilterGroup( new StringFilterGroup(
Settings.HIDE_PLAYLIST_BUTTON, Settings.HIDE_PLAYLIST_BUTTON,
@ -51,7 +51,7 @@ final class ButtonsFilter extends Filter {
), ),
new StringFilterGroup( new StringFilterGroup(
Settings.HIDE_CLIP_BUTTON, Settings.HIDE_CLIP_BUTTON,
"|clip_button.eml|" "|clip_button.eml"
) )
); );
@ -68,15 +68,19 @@ final class ButtonsFilter extends Filter {
Settings.HIDE_REMIX_BUTTON, Settings.HIDE_REMIX_BUTTON,
"yt_outline_youtube_shorts_plus" "yt_outline_youtube_shorts_plus"
), ),
new ByteArrayFilterGroup(
Settings.HIDE_THANKS_BUTTON,
"yt_outline_dollar_sign_heart"
),
new ByteArrayFilterGroup(
Settings.HIDE_ASK_BUTTON,
"yt_fill_spark"
),
// Check for clip button both here and using a path filter, // Check for clip button both here and using a path filter,
// as there's a chance the path is a generic action button and won't contain 'clip_button' // as there's a chance the path is a generic action button and won't contain 'clip_button'
new ByteArrayFilterGroup( new ByteArrayFilterGroup(
Settings.HIDE_CLIP_BUTTON, Settings.HIDE_CLIP_BUTTON,
"yt_outline_scissors" "yt_outline_scissors"
),
new ByteArrayFilterGroup(
Settings.HIDE_THANKS_BUTTON,
"yt_outline_dollar_sign_heart"
) )
); );
} }

View File

@ -211,7 +211,7 @@ public final class LayoutComponentsFilter extends Filter {
compactChannelBarInnerButton = new StringFilterGroup( compactChannelBarInnerButton = new StringFilterGroup(
null, null,
"|button.eml|" "|button.eml"
); );
joinMembershipButton = new ByteArrayFilterGroup( joinMembershipButton = new ByteArrayFilterGroup(
@ -454,16 +454,20 @@ public final class LayoutComponentsFilter extends Filter {
} }
private static boolean hideShelves() { private static boolean hideShelves() {
// If the player is opened while library is selected, // Horizontal shelves are used for music/game links in video descriptions,
// then filter any recommendations below the player. // such as https://youtube.com/watch?v=W8kI1na3S2M
if (PlayerType.getCurrent().isMaximizedOrFullscreen() if (PlayerType.getCurrent().isMaximizedOrFullscreen()) {
// Or if the search is active while library is selected, then also filter. return false;
|| NavigationBar.isSearchBarActive()) { }
// Must check search bar after player type, since search results
// can be in the background behind an open player.
if (NavigationBar.isSearchBarActive()) {
return true; return true;
} }
// Do not hide if the navigation back button is visible, // Do not hide if the navigation back button is visible,
// otherwise the content shelves in the YouTube Movie/Courses pages is hidden. // otherwise the content shelves in the explore/music/courses pages are hidde.
if (NavigationBar.isBackButtonVisible()) { if (NavigationBar.isBackButtonVisible()) {
return false; return false;
} }

View File

@ -2,12 +2,25 @@ package app.revanced.extension.youtube.patches.components;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.PlayerType; import app.revanced.extension.youtube.shared.PlayerType;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class PlayerFlyoutMenuItemsFilter extends Filter { public class PlayerFlyoutMenuItemsFilter extends Filter {
public static final class HideAudioFlyoutMenuAvailability implements Setting.Availability {
private static final boolean AVAILABLE_ON_LAUNCH = SpoofVideoStreamsPatch.notSpoofingToAndroid();
@Override
public boolean isAvailable() {
// Check conditions of launch and now. Otherwise if spoofing is changed
// without a restart the setting will show as available when it's not.
return AVAILABLE_ON_LAUNCH && SpoofVideoStreamsPatch.notSpoofingToAndroid();
}
}
private final ByteArrayFilterGroupList flyoutFilterGroupList = new ByteArrayFilterGroupList(); private final ByteArrayFilterGroupList flyoutFilterGroupList = new ByteArrayFilterGroupList();
private final ByteArrayFilterGroup exception; private final ByteArrayFilterGroup exception;
@ -27,7 +40,7 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
addPathCallbacks( addPathCallbacks(
videoQualityMenuFooter, videoQualityMenuFooter,
new StringFilterGroup(null, "overflow_menu_item.eml|") new StringFilterGroup(null, "overflow_menu_item.eml")
); );
flyoutFilterGroupList.addAll( flyoutFilterGroupList.addAll(

View File

@ -54,12 +54,12 @@ public class CustomPlaybackSpeedPatch {
static { static {
final float holdSpeed = Settings.SPEED_TAP_AND_HOLD.get(); final float holdSpeed = Settings.SPEED_TAP_AND_HOLD.get();
if (holdSpeed > 0 && holdSpeed <= PLAYBACK_SPEED_MAXIMUM) { if (holdSpeed > 0 && holdSpeed <= PLAYBACK_SPEED_MAXIMUM) {
TAP_AND_HOLD_SPEED = holdSpeed; TAP_AND_HOLD_SPEED = holdSpeed;
} else { } else {
showInvalidCustomSpeedToast(); showInvalidCustomSpeedToast();
Settings.SPEED_TAP_AND_HOLD.resetToDefault(); TAP_AND_HOLD_SPEED = Settings.SPEED_TAP_AND_HOLD.resetToDefault();
TAP_AND_HOLD_SPEED = Settings.SPEED_TAP_AND_HOLD.get();
} }
loadCustomSpeeds(); loadCustomSpeeds();

View File

@ -7,6 +7,7 @@ import static app.revanced.extension.shared.settings.Setting.migrateOldSettingTo
import static app.revanced.extension.shared.settings.Setting.parent; import static app.revanced.extension.shared.settings.Setting.parent;
import static app.revanced.extension.shared.settings.Setting.parentsAny; import static app.revanced.extension.shared.settings.Setting.parentsAny;
import static app.revanced.extension.youtube.patches.ChangeFormFactorPatch.FormFactor; import static app.revanced.extension.youtube.patches.ChangeFormFactorPatch.FormFactor;
import static app.revanced.extension.youtube.patches.ChangeStartPagePatch.ChangeStartPageTypeAvailability;
import static app.revanced.extension.youtube.patches.ChangeStartPagePatch.StartPage; import static app.revanced.extension.youtube.patches.ChangeStartPagePatch.StartPage;
import static app.revanced.extension.youtube.patches.ExitFullscreenPatch.FullscreenMode; import static app.revanced.extension.youtube.patches.ExitFullscreenPatch.FullscreenMode;
import static app.revanced.extension.youtube.patches.ForceOriginalAudioPatch.ForceOriginalAudioAvailability; import static app.revanced.extension.youtube.patches.ForceOriginalAudioPatch.ForceOriginalAudioAvailability;
@ -19,10 +20,12 @@ import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerT
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType.MODERN_4; import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType.MODERN_4;
import static app.revanced.extension.youtube.patches.OpenShortsInRegularPlayerPatch.ShortsPlayerType; import static app.revanced.extension.youtube.patches.OpenShortsInRegularPlayerPatch.ShortsPlayerType;
import static app.revanced.extension.youtube.patches.SeekbarThumbnailsPatch.SeekbarThumbnailsHighQualityAvailability; import static app.revanced.extension.youtube.patches.SeekbarThumbnailsPatch.SeekbarThumbnailsHighQualityAvailability;
import static app.revanced.extension.youtube.patches.components.PlayerFlyoutMenuItemsFilter.HideAudioFlyoutMenuAvailability;
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.IGNORE; import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.IGNORE;
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.MANUAL_SKIP; import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.MANUAL_SKIP;
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY; import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY;
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY_ONCE; import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY_ONCE;
import app.revanced.extension.youtube.swipecontrols.SwipeControlsConfigurationProvider.SwipeOverlayStyle;
import android.graphics.Color; import android.graphics.Color;
@ -123,6 +126,7 @@ public class Settings extends BaseSettings {
// Player // Player
public static final BooleanSetting COPY_VIDEO_URL = new BooleanSetting("revanced_copy_video_url", FALSE); public static final BooleanSetting COPY_VIDEO_URL = new BooleanSetting("revanced_copy_video_url", FALSE);
public static final BooleanSetting COPY_VIDEO_URL_TIMESTAMP = new BooleanSetting("revanced_copy_video_url_timestamp", TRUE); public static final BooleanSetting COPY_VIDEO_URL_TIMESTAMP = new BooleanSetting("revanced_copy_video_url_timestamp", TRUE);
public static final BooleanSetting DISABLE_AUTO_CAPTIONS = new BooleanSetting("revanced_disable_auto_captions", FALSE, true);
public static final BooleanSetting DISABLE_FULLSCREEN_AMBIENT_MODE = new BooleanSetting("revanced_disable_fullscreen_ambient_mode", TRUE, true); public static final BooleanSetting DISABLE_FULLSCREEN_AMBIENT_MODE = new BooleanSetting("revanced_disable_fullscreen_ambient_mode", TRUE, true);
public static final BooleanSetting DISABLE_ROLLING_NUMBER_ANIMATIONS = new BooleanSetting("revanced_disable_rolling_number_animations", FALSE); public static final BooleanSetting DISABLE_ROLLING_NUMBER_ANIMATIONS = new BooleanSetting("revanced_disable_rolling_number_animations", FALSE);
public static final EnumSetting<FullscreenMode> EXIT_FULLSCREEN = new EnumSetting<>("revanced_exit_fullscreen", FullscreenMode.DISABLED); public static final EnumSetting<FullscreenMode> EXIT_FULLSCREEN = new EnumSetting<>("revanced_exit_fullscreen", FullscreenMode.DISABLED);
@ -195,10 +199,11 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_REPORT_BUTTON = new BooleanSetting("revanced_hide_report_button", FALSE); public static final BooleanSetting HIDE_REPORT_BUTTON = new BooleanSetting("revanced_hide_report_button", FALSE);
public static final BooleanSetting HIDE_SHARE_BUTTON = new BooleanSetting("revanced_hide_share_button", FALSE); public static final BooleanSetting HIDE_SHARE_BUTTON = new BooleanSetting("revanced_hide_share_button", FALSE);
public static final BooleanSetting HIDE_THANKS_BUTTON = new BooleanSetting("revanced_hide_thanks_button", TRUE); public static final BooleanSetting HIDE_THANKS_BUTTON = new BooleanSetting("revanced_hide_thanks_button", TRUE);
public static final BooleanSetting HIDE_ASK_BUTTON = new BooleanSetting("revanced_hide_ask_button", FALSE);
// Player flyout menu items // Player flyout menu items
public static final BooleanSetting HIDE_PLAYER_FLYOUT_ADDITIONAL_SETTINGS = new BooleanSetting("revanced_hide_player_flyout_additional_settings", FALSE); public static final BooleanSetting HIDE_PLAYER_FLYOUT_ADDITIONAL_SETTINGS = new BooleanSetting("revanced_hide_player_flyout_additional_settings", FALSE);
public static final BooleanSetting HIDE_PLAYER_FLYOUT_AMBIENT_MODE = new BooleanSetting("revanced_hide_player_flyout_ambient_mode", FALSE); public static final BooleanSetting HIDE_PLAYER_FLYOUT_AMBIENT_MODE = new BooleanSetting("revanced_hide_player_flyout_ambient_mode", FALSE);
public static final BooleanSetting HIDE_PLAYER_FLYOUT_AUDIO_TRACK = new BooleanSetting("revanced_hide_player_flyout_audio_track", FALSE); public static final BooleanSetting HIDE_PLAYER_FLYOUT_AUDIO_TRACK = new BooleanSetting("revanced_hide_player_flyout_audio_track", FALSE, new HideAudioFlyoutMenuAvailability());
public static final BooleanSetting HIDE_PLAYER_FLYOUT_CAPTIONS = new BooleanSetting("revanced_hide_player_flyout_captions", FALSE); public static final BooleanSetting HIDE_PLAYER_FLYOUT_CAPTIONS = new BooleanSetting("revanced_hide_player_flyout_captions", FALSE);
public static final BooleanSetting HIDE_PLAYER_FLYOUT_HELP = new BooleanSetting("revanced_hide_player_flyout_help", TRUE); public static final BooleanSetting HIDE_PLAYER_FLYOUT_HELP = new BooleanSetting("revanced_hide_player_flyout_help", TRUE);
public static final BooleanSetting HIDE_PLAYER_FLYOUT_LOCK_SCREEN = new BooleanSetting("revanced_hide_player_flyout_lock_screen", FALSE); public static final BooleanSetting HIDE_PLAYER_FLYOUT_LOCK_SCREEN = new BooleanSetting("revanced_hide_player_flyout_lock_screen", FALSE);
@ -220,6 +225,8 @@ public class Settings extends BaseSettings {
public static final BooleanSetting SPOOF_APP_VERSION = new BooleanSetting("revanced_spoof_app_version", FALSE, true, "revanced_spoof_app_version_user_dialog_message"); public static final BooleanSetting SPOOF_APP_VERSION = new BooleanSetting("revanced_spoof_app_version", FALSE, true, "revanced_spoof_app_version_user_dialog_message");
public static final BooleanSetting WIDE_SEARCHBAR = new BooleanSetting("revanced_wide_searchbar", FALSE, true); public static final BooleanSetting WIDE_SEARCHBAR = new BooleanSetting("revanced_wide_searchbar", FALSE, true);
public static final EnumSetting<StartPage> CHANGE_START_PAGE = new EnumSetting<>("revanced_change_start_page", StartPage.DEFAULT, true); public static final EnumSetting<StartPage> CHANGE_START_PAGE = new EnumSetting<>("revanced_change_start_page", StartPage.DEFAULT, true);
public static final BooleanSetting CHANGE_START_PAGE_ALWAYS = new BooleanSetting("revanced_change_start_page_always", FALSE, true,
new ChangeStartPageTypeAvailability());
public static final StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target", "19.01.34", true, parent(SPOOF_APP_VERSION)); public static final StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target", "19.01.34", true, parent(SPOOF_APP_VERSION));
// Custom filter // Custom filter
public static final BooleanSetting CUSTOM_FILTER = new BooleanSetting("revanced_custom_filter", FALSE); public static final BooleanSetting CUSTOM_FILTER = new BooleanSetting("revanced_custom_filter", FALSE);
@ -293,7 +300,6 @@ public class Settings extends BaseSettings {
// Misc // Misc
public static final BooleanSetting ANNOUNCEMENTS = new BooleanSetting("revanced_announcements", TRUE); public static final BooleanSetting ANNOUNCEMENTS = new BooleanSetting("revanced_announcements", TRUE);
public static final IntegerSetting ANNOUNCEMENT_LAST_ID = new IntegerSetting("revanced_announcement_last_id", -1, false, false); public static final IntegerSetting ANNOUNCEMENT_LAST_ID = new IntegerSetting("revanced_announcement_last_id", -1, false, false);
public static final BooleanSetting AUTO_CAPTIONS = new BooleanSetting("revanced_auto_captions", FALSE);
public static final BooleanSetting AUTO_REPEAT = new BooleanSetting("revanced_auto_repeat", FALSE); public static final BooleanSetting AUTO_REPEAT = new BooleanSetting("revanced_auto_repeat", FALSE);
public static final BooleanSetting BYPASS_URL_REDIRECTS = new BooleanSetting("revanced_bypass_url_redirects", TRUE); public static final BooleanSetting BYPASS_URL_REDIRECTS = new BooleanSetting("revanced_bypass_url_redirects", TRUE);
public static final BooleanSetting CHECK_WATCH_HISTORY_DOMAIN_NAME = new BooleanSetting("revanced_check_watch_history_domain_name", TRUE, false, false); public static final BooleanSetting CHECK_WATCH_HISTORY_DOMAIN_NAME = new BooleanSetting("revanced_check_watch_history_domain_name", TRUE, false, false);
@ -318,12 +324,15 @@ public class Settings extends BaseSettings {
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME)); parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
public static final IntegerSetting SWIPE_MAGNITUDE_THRESHOLD = new IntegerSetting("revanced_swipe_threshold", 30, true, public static final IntegerSetting SWIPE_MAGNITUDE_THRESHOLD = new IntegerSetting("revanced_swipe_threshold", 30, true,
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME)); parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
public static final BooleanSetting SWIPE_SHOW_CIRCULAR_OVERLAY = new BooleanSetting("revanced_swipe_show_circular_overlay", FALSE, true, public static final IntegerSetting SWIPE_VOLUME_SENSITIVITY = new IntegerSetting("revanced_swipe_volume_sensitivity", 1, true, parent(SWIPE_VOLUME));
public static final EnumSetting<SwipeOverlayStyle> SWIPE_OVERLAY_STYLE = new EnumSetting<>("revanced_swipe_overlay_style", SwipeOverlayStyle.HORIZONTAL,true,
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME)); parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
public static final BooleanSetting SWIPE_OVERLAY_MINIMAL_STYLE = new BooleanSetting("revanced_swipe_overlay_minimal_style", FALSE, true, public static final IntegerSetting SWIPE_OVERLAY_TEXT_SIZE = new IntegerSetting("revanced_swipe_text_overlay_size", 14, true,
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME)); parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
public static final IntegerSetting SWIPE_OVERLAY_OPACITY = new IntegerSetting("revanced_swipe_overlay_background_opacity", 60, true, public static final IntegerSetting SWIPE_OVERLAY_OPACITY = new IntegerSetting("revanced_swipe_overlay_background_opacity", 60, true,
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME)); parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
public static final StringSetting SWIPE_OVERLAY_PROGRESS_COLOR = new StringSetting("revanced_swipe_overlay_progress_color", "#FFFFFF", true,
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
public static final LongSetting SWIPE_OVERLAY_TIMEOUT = new LongSetting("revanced_swipe_overlay_timeout", 500L, true, public static final LongSetting SWIPE_OVERLAY_TIMEOUT = new LongSetting("revanced_swipe_overlay_timeout", 500L, true,
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME)); parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
public static final BooleanSetting SWIPE_SAVE_AND_RESTORE_BRIGHTNESS = new BooleanSetting("revanced_swipe_save_and_restore_brightness", TRUE, true, parent(SWIPE_BRIGHTNESS)); public static final BooleanSetting SWIPE_SAVE_AND_RESTORE_BRIGHTNESS = new BooleanSetting("revanced_swipe_save_and_restore_brightness", TRUE, true, parent(SWIPE_BRIGHTNESS));
@ -401,6 +410,7 @@ public class Settings extends BaseSettings {
private static final StringSetting DEPRECATED_SEEKBAR_CUSTOM_COLOR_PRIMARY = new StringSetting("revanced_seekbar_custom_color_value", "#FF0033"); private static final StringSetting DEPRECATED_SEEKBAR_CUSTOM_COLOR_PRIMARY = new StringSetting("revanced_seekbar_custom_color_value", "#FF0033");
private static final BooleanSetting DEPRECATED_DISABLE_SUGGESTED_VIDEO_END_SCREEN = new BooleanSetting("revanced_disable_suggested_video_end_screen", FALSE); private static final BooleanSetting DEPRECATED_DISABLE_SUGGESTED_VIDEO_END_SCREEN = new BooleanSetting("revanced_disable_suggested_video_end_screen", FALSE);
private static final BooleanSetting DEPRECATED_RESTORE_OLD_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_restore_old_video_quality_menu", TRUE); private static final BooleanSetting DEPRECATED_RESTORE_OLD_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_restore_old_video_quality_menu", TRUE);
private static final BooleanSetting DEPRECATED_AUTO_CAPTIONS = new BooleanSetting("revanced_auto_captions", FALSE);
static { static {
// region Migration // region Migration
@ -454,6 +464,11 @@ public class Settings extends BaseSettings {
SPOOF_APP_VERSION_TARGET.resetToDefault(); SPOOF_APP_VERSION_TARGET.resetToDefault();
} }
if (!DEPRECATED_AUTO_CAPTIONS.isSetToDefault()) {
DISABLE_AUTO_CAPTIONS.save(true);
DEPRECATED_AUTO_CAPTIONS.resetToDefault();
}
// endregion // endregion
// region SB import/export callbacks // region SB import/export callbacks

View File

@ -0,0 +1,36 @@
package app.revanced.extension.youtube.settings.preference;
import static app.revanced.extension.shared.StringRef.str;
import android.content.Context;
import android.preference.SwitchPreference;
import android.util.AttributeSet;
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
@SuppressWarnings({"deprecation", "unused"})
public class HideAudioFlyoutMenuPreference extends SwitchPreference {
{
// Audio menu is not available if spoofing to Android client type.
if (!SpoofVideoStreamsPatch.notSpoofingToAndroid()) {
String summary = str("revanced_hide_player_flyout_audio_track_not_available");
setSummary(summary);
setSummaryOn(summary);
setSummaryOff(summary);
}
}
public HideAudioFlyoutMenuPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public HideAudioFlyoutMenuPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public HideAudioFlyoutMenuPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public HideAudioFlyoutMenuPreference(Context context) {
super(context);
}
}

View File

@ -1,39 +1,38 @@
package app.revanced.extension.youtube.swipecontrols package app.revanced.extension.youtube.swipecontrols
import android.content.Context import android.annotation.SuppressLint
import android.graphics.Color import android.graphics.Color
import app.revanced.extension.shared.Logger
import app.revanced.extension.shared.StringRef.str import app.revanced.extension.shared.StringRef.str
import app.revanced.extension.shared.Utils import app.revanced.extension.shared.Utils
import app.revanced.extension.youtube.settings.Settings import app.revanced.extension.youtube.settings.Settings
import app.revanced.extension.youtube.shared.PlayerType import app.revanced.extension.youtube.shared.PlayerType
/** /**
* provider for configuration for volume and brightness swipe controls * Provides configuration settings for volume and brightness swipe controls in the YouTube player.
* * Manages enabling/disabling gestures, overlay appearance, and behavior preferences.
* @param context the context to create in
*/ */
class SwipeControlsConfigurationProvider( class SwipeControlsConfigurationProvider {
private val context: Context,
) {
//region swipe enable //region swipe enable
/** /**
* should swipe controls be enabled? (global setting) * Indicates whether swipe controls are enabled globally.
* Returns true if either volume or brightness controls are enabled and the video is in fullscreen mode.
*/ */
val enableSwipeControls: Boolean val enableSwipeControls: Boolean
get() = (enableVolumeControls || enableBrightnessControl) && isFullscreenVideo get() = (enableVolumeControls || enableBrightnessControl) && isFullscreenVideo
/** /**
* should swipe controls for volume be enabled? * Indicates whether swipe controls for adjusting volume are enabled.
*/ */
val enableVolumeControls = Settings.SWIPE_VOLUME.get() val enableVolumeControls = Settings.SWIPE_VOLUME.get()
/** /**
* should swipe controls for volume be enabled? * Indicates whether swipe controls for adjusting brightness are enabled.
*/ */
val enableBrightnessControl = Settings.SWIPE_BRIGHTNESS.get() val enableBrightnessControl = Settings.SWIPE_BRIGHTNESS.get()
/** /**
* is the video player currently in fullscreen mode? * Checks if the video player is currently in fullscreen mode.
*/ */
private val isFullscreenVideo: Boolean private val isFullscreenVideo: Boolean
get() = PlayerType.current == PlayerType.WATCH_WHILE_FULLSCREEN get() = PlayerType.current == PlayerType.WATCH_WHILE_FULLSCREEN
@ -41,7 +40,8 @@ class SwipeControlsConfigurationProvider(
//region keys enable //region keys enable
/** /**
* should volume key controls be overwritten? (global setting) * Indicates whether volume key controls should be overridden by swipe controls.
* Returns true if volume controls are enabled and the video is in fullscreen mode.
*/ */
val overwriteVolumeKeyControls: Boolean val overwriteVolumeKeyControls: Boolean
get() = enableVolumeControls && isFullscreenVideo get() = enableVolumeControls && isFullscreenVideo
@ -49,35 +49,50 @@ class SwipeControlsConfigurationProvider(
//region gesture adjustments //region gesture adjustments
/** /**
* should press-to-swipe be enabled? * Indicates whether press-to-swipe mode is enabled, requiring a press before swiping to activate controls.
*/ */
val shouldEnablePressToSwipe: Boolean val shouldEnablePressToSwipe: Boolean
get() = Settings.SWIPE_PRESS_TO_ENGAGE.get() get() = Settings.SWIPE_PRESS_TO_ENGAGE.get()
/** /**
* threshold for swipe detection * The threshold for detecting swipe gestures, in pixels.
* this may be called rapidly in onScroll, so we have to load it once and then leave it constant * Loaded once to ensure consistent behavior during rapid scroll events.
*/ */
val swipeMagnitudeThreshold: Int val swipeMagnitudeThreshold: Int
get() = Settings.SWIPE_MAGNITUDE_THRESHOLD.get() get() = Settings.SWIPE_MAGNITUDE_THRESHOLD.get()
/**
* The sensitivity of volume swipe gestures, determining how much volume changes per swipe.
* Resets to default if set to 0, as it would disable swiping.
*/
val volumeSwipeSensitivity: Int
get() {
val sensitivity = Settings.SWIPE_VOLUME_SENSITIVITY.get()
if (sensitivity < 1) {
return Settings.SWIPE_VOLUME_SENSITIVITY.resetToDefault()
}
return sensitivity
}
//endregion //endregion
//region overlay adjustments //region overlay adjustments
/** /**
* should the overlay enable haptic feedback? * Indicates whether haptic feedback should be enabled for swipe control interactions.
*/ */
val shouldEnableHapticFeedback: Boolean val shouldEnableHapticFeedback: Boolean
get() = Settings.SWIPE_HAPTIC_FEEDBACK.get() get() = Settings.SWIPE_HAPTIC_FEEDBACK.get()
/** /**
* how long the overlay should be shown on changes * The duration in milliseconds that the overlay should remain visible after a change.
*/ */
val overlayShowTimeoutMillis: Long val overlayShowTimeoutMillis: Long
get() = Settings.SWIPE_OVERLAY_TIMEOUT.get() get() = Settings.SWIPE_OVERLAY_TIMEOUT.get()
/** /**
* Gets the opacity value (0-100%) is converted to an alpha value (0-255) for transparency. * The background opacity of the overlay, converted from a percentage (0-100) to an alpha value (0-255).
* If the opacity value is out of range, it resets to the default and displays a warning message. * Resets to default and shows a toast if the value is out of range.
*/ */
val overlayBackgroundOpacity: Int val overlayBackgroundOpacity: Int
get() { get() {
@ -85,8 +100,7 @@ class SwipeControlsConfigurationProvider(
if (opacity < 0 || opacity > 100) { if (opacity < 0 || opacity > 100) {
Utils.showToastLong(str("revanced_swipe_overlay_background_opacity_invalid_toast")) Utils.showToastLong(str("revanced_swipe_overlay_background_opacity_invalid_toast"))
Settings.SWIPE_OVERLAY_OPACITY.resetToDefault() opacity = Settings.SWIPE_OVERLAY_OPACITY.resetToDefault()
opacity = Settings.SWIPE_OVERLAY_OPACITY.get()
} }
opacity = opacity * 255 / 100 opacity = opacity * 255 / 100
@ -94,52 +108,122 @@ class SwipeControlsConfigurationProvider(
} }
/** /**
* The color of the progress overlay. * The color of the progress bar in the overlay.
* Resets to default and shows a toast if the color string is invalid or empty.
*/ */
val overlayProgressColor: Int val overlayProgressColor: Int
get() = 0xBFFFFFFF.toInt() get() {
try {
@SuppressLint("UseKtx")
val color = Color.parseColor(Settings.SWIPE_OVERLAY_PROGRESS_COLOR.get())
return (0xBF000000.toInt() or (color and 0xFFFFFF))
} catch (ex: IllegalArgumentException) {
Logger.printDebug({ "Could not parse color" }, ex)
Utils.showToastLong(str("revanced_swipe_overlay_progress_color_invalid_toast"))
Settings.SWIPE_OVERLAY_PROGRESS_COLOR.resetToDefault()
return overlayProgressColor // Recursively return.
}
}
/** /**
* The color used for the background of the progress overlay fill. * The background color used for the filled portion of the progress bar in the overlay.
*/ */
val overlayFillBackgroundPaint: Int val overlayFillBackgroundPaint: Int
get() = 0x80D3D3D3.toInt() get() = 0x80D3D3D3.toInt()
/** /**
* The color used for the text and icons in the overlay. * The color used for text and icons in the overlay.
*/ */
val overlayTextColor: Int val overlayTextColor: Int
get() = Color.WHITE get() = Color.WHITE
/** /**
* A flag that determines if the overlay should only show the icon. * The text size in the overlay, in density-independent pixels (dp).
* Must be between 1 and 30 dp; resets to default and shows a toast if invalid.
*/ */
val overlayShowOverlayMinimalStyle: Boolean val overlayTextSize: Int
get() = Settings.SWIPE_OVERLAY_MINIMAL_STYLE.get() get() {
val size = Settings.SWIPE_OVERLAY_TEXT_SIZE.get()
if (size < 1 || size > 30) {
Utils.showToastLong(str("revanced_swipe_text_overlay_size_invalid_toast"))
return Settings.SWIPE_OVERLAY_TEXT_SIZE.resetToDefault()
}
return size
}
/** /**
* A flag that determines if the progress bar should be circular. * Defines the style of the swipe controls overlay, determining its layout and appearance.
*
* @property isMinimal Indicates whether the style is minimalistic, omitting detailed progress indicators.
* @property isHorizontalMinimalCenter Indicates whether the style is a minimal horizontal bar centered vertically.
* @property isCircular Indicates whether the style uses a circular progress bar.
* @property isVertical Indicates whether the style uses a vertical progress bar.
*/ */
val isCircularProgressBar: Boolean @Suppress("unused")
get() = Settings.SWIPE_SHOW_CIRCULAR_OVERLAY.get() enum class SwipeOverlayStyle(
val isMinimal: Boolean = false,
val isHorizontalMinimalCenter: Boolean = false,
val isCircular: Boolean = false,
val isVertical: Boolean = false
) {
/**
* A full horizontal progress bar with detailed indicators.
*/
HORIZONTAL,
/**
* A minimal horizontal progress bar positioned at the top.
*/
HORIZONTAL_MINIMAL_TOP(isMinimal = true),
/**
* A minimal horizontal progress bar centered vertically.
*/
HORIZONTAL_MINIMAL_CENTER(isMinimal = true, isHorizontalMinimalCenter = true),
/**
* A full circular progress bar with detailed indicators.
*/
CIRCULAR(isCircular = true),
/**
* A minimal circular progress bar.
*/
CIRCULAR_MINIMAL(isMinimal = true, isCircular = true),
/**
* A full vertical progress bar with detailed indicators.
*/
VERTICAL(isVertical = true),
/**
* A minimal vertical progress bar.
*/
VERTICAL_MINIMAL(isMinimal = true, isVertical = true)
}
/**
* The current style of the overlay, determining its layout and appearance.
*/
val overlayStyle: SwipeOverlayStyle
get() = Settings.SWIPE_OVERLAY_STYLE.get()
//endregion //endregion
//region behaviour //region behaviour
/** /**
* should the brightness be saved and restored when exiting or entering fullscreen * Indicates whether the brightness level should be saved and restored when entering or exiting fullscreen mode.
*/ */
val shouldSaveAndRestoreBrightness: Boolean val shouldSaveAndRestoreBrightness: Boolean
get() = Settings.SWIPE_SAVE_AND_RESTORE_BRIGHTNESS.get() get() = Settings.SWIPE_SAVE_AND_RESTORE_BRIGHTNESS.get()
/** /**
* should auto-brightness be enabled at the lowest value of the brightness gesture * Indicates whether auto-brightness should be enabled when the brightness gesture reaches its lowest value.
*/ */
val shouldLowestValueEnableAutoBrightness: Boolean val shouldLowestValueEnableAutoBrightness: Boolean
get() = Settings.SWIPE_LOWEST_VALUE_ENABLE_AUTO_BRIGHTNESS.get() get() = Settings.SWIPE_LOWEST_VALUE_ENABLE_AUTO_BRIGHTNESS.get()
/** /**
* variable that stores the brightness gesture value in the settings * The saved brightness value for the swipe gesture, used to restore brightness in fullscreen mode.
*/ */
var savedScreenBrightnessValue: Float var savedScreenBrightnessValue: Float
get() = Settings.SWIPE_BRIGHTNESS_VALUE.get() get() = Settings.SWIPE_BRIGHTNESS_VALUE.get()

View File

@ -23,9 +23,7 @@ import java.lang.ref.WeakReference
/** /**
* The main controller for volume and brightness swipe controls. * The main controller for volume and brightness swipe controls.
* note that the superclass is overwritten to the superclass of the MainActivity at patch time * note that the superclass is overwritten to the superclass of the MainActivity at patch time.
*
* @smali Lapp/revanced/extension/swipecontrols/SwipeControlsHostActivity;
*/ */
class SwipeControlsHostActivity : Activity() { class SwipeControlsHostActivity : Activity() {
/** /**
@ -127,7 +125,7 @@ class SwipeControlsHostActivity : Activity() {
private fun initialize() { private fun initialize() {
// create controllers // create controllers
printDebug { "initializing swipe controls controllers" } printDebug { "initializing swipe controls controllers" }
config = SwipeControlsConfigurationProvider(this) config = SwipeControlsConfigurationProvider()
keys = VolumeKeysController(this) keys = VolumeKeysController(this)
audio = createAudioController() audio = createAudioController()
screen = createScreenController() screen = createScreenController()

View File

@ -41,7 +41,7 @@ class VolumeKeysController(
private fun handleVolumeKeyEvent(event: KeyEvent, volumeUp: Boolean): Boolean { private fun handleVolumeKeyEvent(event: KeyEvent, volumeUp: Boolean): Boolean {
if (event.action == KeyEvent.ACTION_DOWN) { if (event.action == KeyEvent.ACTION_DOWN) {
controller.audio?.apply { controller.audio?.apply {
volume += if (volumeUp) 1 else -1 volume += controller.config.volumeSwipeSensitivity * if (volumeUp) 1 else -1
controller.overlay.onVolumeChanged(volume, maxVolume) controller.overlay.onVolumeChanged(volume, maxVolume)
} }
} }

View File

@ -24,6 +24,7 @@ abstract class BaseGestureController(
controller.overlay, controller.overlay,
10, 10,
1, 1,
controller.config.volumeSwipeSensitivity,
) { ) {
/** /**

View File

@ -41,6 +41,7 @@ interface VolumeAndBrightnessScroller {
* @param overlayController overlay controller instance * @param overlayController overlay controller instance
* @param volumeDistance unit distance for volume scrolling, in dp * @param volumeDistance unit distance for volume scrolling, in dp
* @param brightnessDistance unit distance for brightness scrolling, in dp * @param brightnessDistance unit distance for brightness scrolling, in dp
* @param volumeSwipeSensitivity how much volume will change by single swipe
*/ */
class VolumeAndBrightnessScrollerImpl( class VolumeAndBrightnessScrollerImpl(
context: Context, context: Context,
@ -49,6 +50,7 @@ class VolumeAndBrightnessScrollerImpl(
private val overlayController: SwipeControlsOverlay, private val overlayController: SwipeControlsOverlay,
volumeDistance: Int = 10, volumeDistance: Int = 10,
brightnessDistance: Int = 1, brightnessDistance: Int = 1,
private val volumeSwipeSensitivity: Int,
) : VolumeAndBrightnessScroller { ) : VolumeAndBrightnessScroller {
// region volume // region volume
@ -60,7 +62,7 @@ class VolumeAndBrightnessScrollerImpl(
), ),
) { _, _, direction -> ) { _, _, direction ->
volumeController?.run { volumeController?.run {
volume += direction volume += direction * volumeSwipeSensitivity
overlayController.onVolumeChanged(volume, maxVolume) overlayController.onVolumeChanged(volume, maxVolume)
} }
} }

View File

@ -1,8 +1,11 @@
package app.revanced.extension.youtube.swipecontrols.views package app.revanced.extension.youtube.swipecontrols.views
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.res.Resources
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Paint import android.graphics.Paint
import android.graphics.Rect
import android.graphics.RectF import android.graphics.RectF
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Handler import android.os.Handler
@ -11,21 +14,30 @@ import android.util.AttributeSet
import android.view.HapticFeedbackConstants import android.view.HapticFeedbackConstants
import android.view.View import android.view.View
import android.widget.RelativeLayout import android.widget.RelativeLayout
import app.revanced.extension.shared.StringRef.str
import app.revanced.extension.shared.Utils import app.revanced.extension.shared.Utils
import app.revanced.extension.youtube.swipecontrols.SwipeControlsConfigurationProvider import app.revanced.extension.youtube.swipecontrols.SwipeControlsConfigurationProvider
import app.revanced.extension.youtube.swipecontrols.misc.SwipeControlsOverlay import app.revanced.extension.youtube.swipecontrols.misc.SwipeControlsOverlay
import kotlin.math.min import kotlin.math.min
import kotlin.math.max
import kotlin.math.round import kotlin.math.round
/** /**
* Main overlay layout for displaying volume and brightness level with both circular and horizontal progress bars. * Convert dp to pixels based on system display density.
*/
fun Float.toDisplayPixels(): Float {
return this * Resources.getSystem().displayMetrics.density
}
/**
* Main overlay layout for displaying volume and brightness level with circular, horizontal and vertical progress bars.
*/ */
class SwipeControlsOverlayLayout( class SwipeControlsOverlayLayout(
context: Context, context: Context,
private val config: SwipeControlsConfigurationProvider, private val config: SwipeControlsConfigurationProvider,
) : RelativeLayout(context), SwipeControlsOverlay { ) : RelativeLayout(context), SwipeControlsOverlay {
constructor(context: Context) : this(context, SwipeControlsConfigurationProvider(context)) constructor(context: Context) : this(context, SwipeControlsConfigurationProvider())
// Drawable icons for brightness and volume // Drawable icons for brightness and volume
private val autoBrightnessIcon: Drawable = getDrawable("revanced_ic_sc_brightness_auto") private val autoBrightnessIcon: Drawable = getDrawable("revanced_ic_sc_brightness_auto")
@ -51,18 +63,21 @@ class SwipeControlsOverlayLayout(
// Initialize progress bars // Initialize progress bars
private val circularProgressView: CircularProgressView private val circularProgressView: CircularProgressView
private val horizontalProgressView: HorizontalProgressView private val horizontalProgressView: HorizontalProgressView
private val verticalBrightnessProgressView: VerticalProgressView
private val verticalVolumeProgressView: VerticalProgressView
init { init {
// Initialize circular progress bar // Initialize circular progress bar
circularProgressView = CircularProgressView( circularProgressView = CircularProgressView(
context, context,
config.overlayBackgroundOpacity, config.overlayBackgroundOpacity,
config.overlayShowOverlayMinimalStyle, config.overlayStyle.isMinimal,
config.overlayProgressColor, config.overlayProgressColor,
config.overlayFillBackgroundPaint, config.overlayFillBackgroundPaint,
config.overlayTextColor config.overlayTextColor,
config.overlayTextSize
).apply { ).apply {
layoutParams = LayoutParams(300, 300).apply { layoutParams = LayoutParams(100f.toDisplayPixels().toInt(), 100f.toDisplayPixels().toInt()).apply {
addRule(CENTER_IN_PARENT, TRUE) addRule(CENTER_IN_PARENT, TRUE)
} }
visibility = GONE // Initially hidden visibility = GONE // Initially hidden
@ -71,22 +86,65 @@ class SwipeControlsOverlayLayout(
// Initialize horizontal progress bar // Initialize horizontal progress bar
val screenWidth = resources.displayMetrics.widthPixels val screenWidth = resources.displayMetrics.widthPixels
val layoutWidth = (screenWidth * 2 / 3).toInt() // 2/3 of screen width val layoutWidth = (screenWidth * 4 / 5).toInt() // Cap at ~360dp
horizontalProgressView = HorizontalProgressView( horizontalProgressView = HorizontalProgressView(
context, context,
config.overlayBackgroundOpacity, config.overlayBackgroundOpacity,
config.overlayShowOverlayMinimalStyle, config.overlayStyle.isMinimal,
config.overlayProgressColor, config.overlayProgressColor,
config.overlayFillBackgroundPaint, config.overlayFillBackgroundPaint,
config.overlayTextColor config.overlayTextColor,
config.overlayTextSize
).apply { ).apply {
layoutParams = LayoutParams(layoutWidth, 100).apply { layoutParams = LayoutParams(layoutWidth, 32f.toDisplayPixels().toInt()).apply {
addRule(CENTER_HORIZONTAL) addRule(CENTER_HORIZONTAL)
topMargin = 40 // Top margin if (config.overlayStyle.isHorizontalMinimalCenter) {
addRule(CENTER_VERTICAL)
} else {
topMargin = 20f.toDisplayPixels().toInt()
}
} }
visibility = GONE // Initially hidden visibility = GONE // Initially hidden
} }
addView(horizontalProgressView) addView(horizontalProgressView)
// Initialize vertical progress bar for brightness (right side)
verticalBrightnessProgressView = VerticalProgressView(
context,
config.overlayBackgroundOpacity,
config.overlayStyle.isMinimal,
config.overlayProgressColor,
config.overlayFillBackgroundPaint,
config.overlayTextColor,
config.overlayTextSize
).apply {
layoutParams = LayoutParams(40f.toDisplayPixels().toInt(), 150f.toDisplayPixels().toInt()).apply {
addRule(ALIGN_PARENT_RIGHT)
rightMargin = 40f.toDisplayPixels().toInt()
addRule(CENTER_VERTICAL)
}
visibility = GONE // Initially hidden
}
addView(verticalBrightnessProgressView)
// Initialize vertical progress bar for volume (left side)
verticalVolumeProgressView = VerticalProgressView(
context,
config.overlayBackgroundOpacity,
config.overlayStyle.isMinimal,
config.overlayProgressColor,
config.overlayFillBackgroundPaint,
config.overlayTextColor,
config.overlayTextSize
).apply {
layoutParams = LayoutParams(40f.toDisplayPixels().toInt(), 150f.toDisplayPixels().toInt()).apply {
addRule(ALIGN_PARENT_LEFT)
leftMargin = 40f.toDisplayPixels().toInt()
addRule(CENTER_VERTICAL)
}
visibility = GONE // Initially hidden
}
addView(verticalVolumeProgressView)
} }
// Handler and callback for hiding progress bars // Handler and callback for hiding progress bars
@ -94,6 +152,8 @@ class SwipeControlsOverlayLayout(
private val feedbackHideCallback = Runnable { private val feedbackHideCallback = Runnable {
circularProgressView.visibility = GONE circularProgressView.visibility = GONE
horizontalProgressView.visibility = GONE horizontalProgressView.visibility = GONE
verticalBrightnessProgressView.visibility = GONE
verticalVolumeProgressView.visibility = GONE
} }
/** /**
@ -103,7 +163,11 @@ class SwipeControlsOverlayLayout(
feedbackHideHandler.removeCallbacks(feedbackHideCallback) feedbackHideHandler.removeCallbacks(feedbackHideCallback)
feedbackHideHandler.postDelayed(feedbackHideCallback, config.overlayShowTimeoutMillis) feedbackHideHandler.postDelayed(feedbackHideCallback, config.overlayShowTimeoutMillis)
val viewToShow = if (config.isCircularProgressBar) circularProgressView else horizontalProgressView val viewToShow = when {
config.overlayStyle.isCircular -> circularProgressView
config.overlayStyle.isVertical -> if (isBrightness) verticalBrightnessProgressView else verticalVolumeProgressView
else -> horizontalProgressView
}
viewToShow.apply { viewToShow.apply {
setProgress(progress, max, value, isBrightness) setProgress(progress, max, value, isBrightness)
this.icon = icon this.icon = icon
@ -126,7 +190,9 @@ class SwipeControlsOverlayLayout(
// Handle brightness change // Handle brightness change
override fun onBrightnessChanged(brightness: Double) { override fun onBrightnessChanged(brightness: Double) {
if (config.shouldLowestValueEnableAutoBrightness && brightness <= 0) { if (config.shouldLowestValueEnableAutoBrightness && brightness <= 0) {
showFeedbackView("Auto", 0, 100, autoBrightnessIcon, isBrightness = true) val displayText = if (config.overlayStyle.isVertical) "А"
else str("revanced_swipe_lowest_value_enable_auto_brightness_overlay_text")
showFeedbackView(displayText, 0, 100, autoBrightnessIcon, isBrightness = true)
} else { } else {
val brightnessValue = round(brightness).toInt() val brightnessValue = round(brightness).toInt()
val icon = when { val icon = when {
@ -135,7 +201,8 @@ class SwipeControlsOverlayLayout(
brightnessValue < 75 -> highBrightnessIcon brightnessValue < 75 -> highBrightnessIcon
else -> fullBrightnessIcon else -> fullBrightnessIcon
} }
showFeedbackView("$brightnessValue%", brightnessValue, 100, icon, isBrightness = true) val displayText = if (config.overlayStyle.isVertical) "$brightnessValue" else "$brightnessValue%"
showFeedbackView(displayText, brightnessValue, 100, icon, isBrightness = true)
} }
} }
@ -156,11 +223,12 @@ class SwipeControlsOverlayLayout(
*/ */
abstract class AbstractProgressView( abstract class AbstractProgressView(
context: Context, context: Context,
protected val overlayBackgroundOpacity: Int, overlayBackgroundOpacity: Int,
protected val overlayShowOverlayMinimalStyle: Boolean, protected val isMinimalStyle: Boolean,
protected val overlayProgressColor: Int, overlayProgressColor: Int,
protected val overlayFillBackgroundPaint: Int, overlayFillBackgroundPaint: Int,
protected val overlayTextColor: Int, private val overlayTextColor: Int,
protected val overlayTextSize: Int,
attrs: AttributeSet? = null, attrs: AttributeSet? = null,
defStyleAttr: Int = 0 defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) { ) : View(context, attrs, defStyleAttr) {
@ -174,26 +242,25 @@ abstract class AbstractProgressView(
} }
// Initialize paints // Initialize paints
public val backgroundPaint = createPaint(overlayBackgroundOpacity, style = Paint.Style.FILL) val backgroundPaint = createPaint(overlayBackgroundOpacity, style = Paint.Style.FILL)
public val progressPaint = createPaint(overlayProgressColor, style = Paint.Style.STROKE, strokeCap = Paint.Cap.ROUND, strokeWidth = 20f) val progressPaint = createPaint(overlayProgressColor, style = Paint.Style.STROKE, strokeCap = Paint.Cap.ROUND, strokeWidth = 6f.toDisplayPixels())
public val fillBackgroundPaint = createPaint(overlayFillBackgroundPaint, style = Paint.Style.FILL) val fillBackgroundPaint = createPaint(overlayFillBackgroundPaint, style = Paint.Style.FILL)
public val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = overlayTextColor color = overlayTextColor
textAlign = Paint.Align.CENTER textAlign = Paint.Align.CENTER
textSize = 40f // Can adjust based on need textSize = overlayTextSize.toFloat().toDisplayPixels()
} }
// Rect for text measurement
protected val textBounds = Rect()
protected var progress = 0 protected var progress = 0
protected var maxProgress = 100 protected var maxProgress = 100
protected var displayText: String = "0" protected var displayText: String = "0"
protected var isBrightness = true protected var isBrightness = true
public var icon: Drawable? = null var icon: Drawable? = null
init { open fun setProgress(value: Int, max: Int, text: String, isBrightnessMode: Boolean) {
// Stroke widths are now set in createPaint for progressPaint and fillBackgroundPaint
}
fun setProgress(value: Int, max: Int, text: String, isBrightnessMode: Boolean) {
progress = value progress = value
maxProgress = max maxProgress = max
displayText = text displayText = text
@ -201,6 +268,11 @@ abstract class AbstractProgressView(
invalidate() invalidate()
} }
protected fun measureTextWidth(text: String, paint: Paint): Int {
paint.getTextBounds(text, 0, text.length, textBounds)
return textBounds.width()
}
override fun onDraw(canvas: Canvas) { override fun onDraw(canvas: Canvas) {
// Base class implementation can be empty // Base class implementation can be empty
} }
@ -209,31 +281,33 @@ abstract class AbstractProgressView(
/** /**
* Custom view for rendering a circular progress indicator with icons and text. * Custom view for rendering a circular progress indicator with icons and text.
*/ */
@SuppressLint("ViewConstructor")
class CircularProgressView( class CircularProgressView(
context: Context, context: Context,
overlayBackgroundOpacity: Int, overlayBackgroundOpacity: Int,
overlayShowOverlayMinimalStyle: Boolean, isMinimalStyle: Boolean,
overlayProgressColor: Int, overlayProgressColor: Int,
overlayFillBackgroundPaint: Int, overlayFillBackgroundPaint: Int,
overlayTextColor: Int, overlayTextColor: Int,
overlayTextSize: Int,
attrs: AttributeSet? = null, attrs: AttributeSet? = null,
defStyleAttr: Int = 0 defStyleAttr: Int = 0
) : AbstractProgressView( ) : AbstractProgressView(
context, context,
overlayBackgroundOpacity, overlayBackgroundOpacity,
overlayShowOverlayMinimalStyle, isMinimalStyle,
overlayProgressColor, overlayProgressColor,
overlayFillBackgroundPaint, overlayFillBackgroundPaint,
overlayTextColor, overlayTextColor,
overlayTextSize,
attrs, attrs,
defStyleAttr defStyleAttr
) { ) {
private val rectF = RectF() private val rectF = RectF()
init { init {
textPaint.textSize = 40f // Override default text size for circular view progressPaint.strokeWidth = 6f.toDisplayPixels()
progressPaint.strokeWidth = 20f fillBackgroundPaint.strokeWidth = 6f.toDisplayPixels()
fillBackgroundPaint.strokeWidth = 20f
progressPaint.strokeCap = Paint.Cap.ROUND progressPaint.strokeCap = Paint.Cap.ROUND
fillBackgroundPaint.strokeCap = Paint.Cap.BUTT fillBackgroundPaint.strokeCap = Paint.Cap.BUTT
progressPaint.style = Paint.Style.STROKE progressPaint.style = Paint.Style.STROKE
@ -244,7 +318,8 @@ class CircularProgressView(
super.onDraw(canvas) super.onDraw(canvas)
val size = min(width, height).toFloat() val size = min(width, height).toFloat()
rectF.set(20f, 20f, size - 20f, size - 20f) val inset = 6f.toDisplayPixels()
rectF.set(inset, inset, size - inset, size - inset)
canvas.drawOval(rectF, fillBackgroundPaint) // Draw the outer ring. canvas.drawOval(rectF, fillBackgroundPaint) // Draw the outer ring.
canvas.drawCircle(width / 2f, height / 2f, size / 3, backgroundPaint) // Draw the inner circle. canvas.drawCircle(width / 2f, height / 2f, size / 3, backgroundPaint) // Draw the inner circle.
@ -255,124 +330,307 @@ class CircularProgressView(
// Draw the icon in the center. // Draw the icon in the center.
icon?.let { icon?.let {
val iconSize = if (overlayShowOverlayMinimalStyle) 100 else 80 val iconSize = (if (isMinimalStyle) 36f else 24f).toDisplayPixels().toInt()
val iconX = (width - iconSize) / 2 val iconX = (width - iconSize) / 2
val iconY = (height / 2) - if (overlayShowOverlayMinimalStyle) 50 else 80 val iconY = if (isMinimalStyle) {
(height - iconSize) / 2
} else {
(height / 2) - 24f.toDisplayPixels().toInt()
}
it.setBounds(iconX, iconY, iconX + iconSize, iconY + iconSize) it.setBounds(iconX, iconY, iconX + iconSize, iconY + iconSize)
it.draw(canvas) it.draw(canvas)
} }
// If not a minimal style mode, draw the text inside the ring. // If not a minimal style mode, draw the text inside the ring.
if (!overlayShowOverlayMinimalStyle) { if (!isMinimalStyle) {
canvas.drawText(displayText, width / 2f, height / 2f + 60f, textPaint) canvas.drawText(displayText, width / 2f, height / 2f + 20f.toDisplayPixels(), textPaint)
} }
} }
override fun setProgress(value: Int, max: Int, text: String, isBrightnessMode: Boolean) {
super.setProgress(value, max, text, isBrightnessMode)
requestLayout()
}
} }
/** /**
* Custom view for rendering a rectangular progress bar with icons and text. * Custom view for rendering a rectangular progress bar with icons and text.
*/ */
@SuppressLint("ViewConstructor")
class HorizontalProgressView( class HorizontalProgressView(
context: Context, context: Context,
overlayBackgroundOpacity: Int, overlayBackgroundOpacity: Int,
overlayShowOverlayMinimalStyle: Boolean, isMinimalStyle: Boolean,
overlayProgressColor: Int, overlayProgressColor: Int,
overlayFillBackgroundPaint: Int, overlayFillBackgroundPaint: Int,
overlayTextColor: Int, overlayTextColor: Int,
overlayTextSize: Int,
attrs: AttributeSet? = null, attrs: AttributeSet? = null,
defStyleAttr: Int = 0 defStyleAttr: Int = 0
) : AbstractProgressView( ) : AbstractProgressView(
context, context,
overlayBackgroundOpacity, overlayBackgroundOpacity,
overlayShowOverlayMinimalStyle, isMinimalStyle,
overlayProgressColor, overlayProgressColor,
overlayFillBackgroundPaint, overlayFillBackgroundPaint,
overlayTextColor, overlayTextColor,
overlayTextSize,
attrs, attrs,
defStyleAttr defStyleAttr
) { ) {
private val iconSize = 60f private val iconSize = 20f.toDisplayPixels()
private val padding = 40f private val padding = 12f.toDisplayPixels()
private var textWidth = 0f
private val progressBarHeight = 3f.toDisplayPixels()
private val progressBarWidth: Float = resources.displayMetrics.widthPixels / 4f
init { init {
textPaint.textSize = 36f // Override default text size for horizontal view
progressPaint.strokeWidth = 0f progressPaint.strokeWidth = 0f
progressPaint.strokeCap = Paint.Cap.BUTT progressPaint.strokeCap = Paint.Cap.BUTT
progressPaint.style = Paint.Style.FILL progressPaint.style = Paint.Style.FILL
fillBackgroundPaint.style = Paint.Style.FILL fillBackgroundPaint.style = Paint.Style.FILL
} }
/**
* Calculate required width based on content
* @return Required width to display all elements
*/
private fun calculateRequiredWidth(): Float {
textWidth = measureTextWidth(displayText, textPaint).toFloat()
return if (!isMinimalStyle) {
padding + iconSize + padding + progressBarWidth + padding + textWidth + padding
} else {
padding + iconSize + padding + textWidth + padding
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val suggestedWidth = MeasureSpec.getSize(widthMeasureSpec)
val suggestedHeight = MeasureSpec.getSize(heightMeasureSpec)
val height = suggestedHeight
val requiredWidth = calculateRequiredWidth().toInt()
val width = min(max(100, requiredWidth), suggestedWidth)
setMeasuredDimension(width, height)
}
override fun onDraw(canvas: Canvas) { override fun onDraw(canvas: Canvas) {
super.onDraw(canvas) super.onDraw(canvas)
val width = width.toFloat() val viewWidth = width.toFloat()
val height = height.toFloat() val viewHeight = height.toFloat()
val viewHeightHalf = viewHeight / 2
// Radius for rounded corners textWidth = measureTextWidth(displayText, textPaint).toFloat()
val cornerRadius = min(width, height) / 2
// Calculate the total width for the elements val cornerRadius = viewHeightHalf
val minimalElementWidth = 5 * padding + iconSize
// Calculate the starting point (X) to center the elements val startX = padding
val minimalStartX = (width - minimalElementWidth) / 2 val iconEndX = startX + iconSize
// Draw the background val textStartX = (viewWidth - 1.5f * padding - textWidth)
if (!overlayShowOverlayMinimalStyle) {
canvas.drawRoundRect(0f, 0f, width, height, cornerRadius, cornerRadius, backgroundPaint)
} else {
canvas.drawRoundRect(minimalStartX, 0f, minimalStartX + minimalElementWidth, height, cornerRadius, cornerRadius, backgroundPaint)
}
if (!overlayShowOverlayMinimalStyle) {
// Draw the fill background
val startX = 2 * padding + iconSize
val endX = width - 4 * padding
val fillWidth = endX - startX
canvas.drawRoundRect( canvas.drawRoundRect(
startX, 0f, 0f, viewWidth, viewHeight,
height / 2 - 5f, cornerRadius, cornerRadius, backgroundPaint
endX, )
height / 2 + 5f,
10f, 10f, icon?.let {
val iconY = viewHeightHalf - iconSize / 2
it.setBounds(
startX.toInt(),
iconY.toInt(),
(startX + iconSize).toInt(),
(iconY + iconSize).toInt()
)
it.draw(canvas)
}
val textY = viewHeightHalf + textPaint.textSize / 3
textPaint.textAlign = Paint.Align.LEFT
if (isMinimalStyle) {
canvas.drawText(displayText, textStartX, textY, textPaint)
} else {
val progressStartX = iconEndX + padding
val progressEndX = textStartX - padding
val progressWidth = progressEndX - progressStartX
if (progressWidth > 50) {
val progressBarHeightHalf = progressBarHeight / 2.0f
val viewHeightHalfMinusProgressBarHeightHalf = viewHeightHalf - progressBarHeightHalf
val viewHeightHalfPlusProgressBarHeightHalf = viewHeightHalf + progressBarHeightHalf
canvas.drawRoundRect(
progressStartX,
viewHeightHalfMinusProgressBarHeightHalf,
progressEndX,
viewHeightHalfPlusProgressBarHeightHalf,
progressBarHeightHalf,
progressBarHeightHalf,
fillBackgroundPaint fillBackgroundPaint
) )
// Draw the progress val progressValue = (progress.toFloat() / maxProgress) * progressWidth
val progressWidth = (progress.toFloat() / maxProgress) * fillWidth
canvas.drawRoundRect( canvas.drawRoundRect(
startX, progressStartX,
height / 2 - 5f, viewHeightHalfMinusProgressBarHeightHalf,
startX + progressWidth, progressStartX + progressValue,
height / 2 + 5f, viewHeightHalfPlusProgressBarHeightHalf,
10f, 10f, progressBarHeightHalf,
progressBarHeightHalf,
progressPaint progressPaint
) )
} }
// Draw the icon canvas.drawText(displayText, textStartX, textY, textPaint)
icon?.let {
val iconX = if (!overlayShowOverlayMinimalStyle) {
padding
} else {
padding + minimalStartX
} }
val iconY = height / 2 - iconSize / 2 }
it.setBounds(iconX.toInt(), iconY.toInt(), (iconX + iconSize).toInt(), (iconY + iconSize).toInt())
override fun setProgress(value: Int, max: Int, text: String, isBrightnessMode: Boolean) {
super.setProgress(value, max, text, isBrightnessMode)
requestLayout()
}
}
/**
* Custom view for rendering a vertical progress bar with icons and text.
*/
@SuppressLint("ViewConstructor")
class VerticalProgressView(
context: Context,
overlayBackgroundOpacity: Int,
isMinimalStyle: Boolean,
overlayProgressColor: Int,
overlayFillBackgroundPaint: Int,
overlayTextColor: Int,
overlayTextSize: Int,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AbstractProgressView(
context,
overlayBackgroundOpacity,
isMinimalStyle,
overlayProgressColor,
overlayFillBackgroundPaint,
overlayTextColor,
overlayTextSize,
attrs,
defStyleAttr
) {
private val iconSize = 20f.toDisplayPixels()
private val padding = 12f.toDisplayPixels()
private val progressBarWidth = 3f.toDisplayPixels()
private val progressBarHeight: Float = resources.displayMetrics.widthPixels / 3f
init {
progressPaint.strokeWidth = 0f
progressPaint.strokeCap = Paint.Cap.BUTT
progressPaint.style = Paint.Style.FILL
fillBackgroundPaint.style = Paint.Style.FILL
}
/**
* Calculate required height based on content
* @return Required height to display all elements
*/
private fun calculateRequiredHeight(): Float {
return if (!isMinimalStyle) {
padding + iconSize + padding + progressBarHeight + padding + textPaint.textSize + padding
} else {
padding + iconSize + padding + textPaint.textSize + padding
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val suggestedWidth = MeasureSpec.getSize(widthMeasureSpec)
val suggestedHeight = MeasureSpec.getSize(heightMeasureSpec)
val requiredHeight = calculateRequiredHeight().toInt()
val height = min(max(100, requiredHeight), suggestedHeight)
setMeasuredDimension(suggestedWidth, height)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val viewWidth = width.toFloat()
val viewHeight = height.toFloat()
val viewWidthHalf = viewWidth / 2
val cornerRadius = viewWidthHalf
val startY = padding
val iconEndY = startY + iconSize
val textStartY = viewHeight - padding - textPaint.textSize / 2
canvas.drawRoundRect(
0f, 0f, viewWidth, viewHeight,
cornerRadius, cornerRadius, backgroundPaint
)
icon?.let {
val iconX = viewWidthHalf - iconSize / 2
it.setBounds(
iconX.toInt(),
startY.toInt(),
(iconX + iconSize).toInt(),
(startY + iconSize).toInt()
)
it.draw(canvas) it.draw(canvas)
} }
// Draw the text on the right val textX = viewWidthHalf
val textX = if (!overlayShowOverlayMinimalStyle) { textPaint.textAlign = Paint.Align.CENTER
width - 2 * padding
} else {
minimalStartX + minimalElementWidth - 2 * padding
}
val textY = height / 2 + textPaint.textSize / 3
// Draw the text if (isMinimalStyle) {
canvas.drawText(displayText, textX, textY, textPaint) canvas.drawText(displayText, textX, textStartY, textPaint)
} else {
val progressStartY = (iconEndY + padding).toFloat()
val progressEndY = textStartY - textPaint.textSize - padding
val progressHeight = progressEndY - progressStartY
if (progressHeight > 50) {
val progressBarWidthHalf = progressBarWidth / 2
val viewHeightHalfMinusProgressBarHeightHalf = viewWidthHalf - progressBarWidthHalf
val viewHeightHalfPlusProgressBarHeightHalf = viewWidthHalf + progressBarWidthHalf
canvas.drawRoundRect(
viewHeightHalfMinusProgressBarHeightHalf,
progressStartY,
viewHeightHalfPlusProgressBarHeightHalf,
progressEndY,
progressBarWidthHalf,
progressBarWidthHalf,
fillBackgroundPaint
)
val progressValue = (progress.toFloat() / maxProgress) * progressHeight
canvas.drawRoundRect(
viewHeightHalfMinusProgressBarHeightHalf,
progressEndY - progressValue,
viewHeightHalfPlusProgressBarHeightHalf,
progressEndY,
progressBarWidthHalf,
progressBarWidthHalf,
progressPaint
)
}
canvas.drawText(displayText, textX, textStartY, textPaint)
}
}
override fun setProgress(value: Int, max: Int, text: String, isBrightnessMode: Boolean) {
super.setProgress(value, max, text, isBrightnessMode)
requestLayout()
} }
} }

View File

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

View File

@ -2,6 +2,10 @@ public final class app/revanced/patches/all/misc/activity/exportall/ExportAllAct
public static final fun getExportAllActivitiesPatch ()Lapp/revanced/patcher/patch/ResourcePatch; public static final fun getExportAllActivitiesPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
} }
public final class app/revanced/patches/all/misc/adb/HideAdbPatchKt {
public static final fun getHideAdbStatusPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/all/misc/build/BaseSpoofBuildInfoPatchKt { public final class app/revanced/patches/all/misc/build/BaseSpoofBuildInfoPatchKt {
public static final fun baseSpoofBuildInfoPatch (Lkotlin/jvm/functions/Function0;)Lapp/revanced/patcher/patch/BytecodePatch; public static final fun baseSpoofBuildInfoPatch (Lkotlin/jvm/functions/Function0;)Lapp/revanced/patcher/patch/BytecodePatch;
} }
@ -108,6 +112,10 @@ public final class app/revanced/patches/all/misc/shortcut/sharetargets/RemoveSha
public static final fun getRemoveShareTargetsPatch ()Lapp/revanced/patcher/patch/ResourcePatch; public static final fun getRemoveShareTargetsPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
} }
public final class app/revanced/patches/all/misc/targetSdk/SetTargetSdkVersion34Kt {
public static final fun getSetTargetSdkVersion34 ()Lapp/revanced/patcher/patch/ResourcePatch;
}
public abstract interface class app/revanced/patches/all/misc/transformation/IMethodCall { public abstract interface class app/revanced/patches/all/misc/transformation/IMethodCall {
public abstract fun getDefinedClassName ()Ljava/lang/String; public abstract fun getDefinedClassName ()Ljava/lang/String;
public abstract fun getMethodName ()Ljava/lang/String; public abstract fun getMethodName ()Ljava/lang/String;
@ -272,6 +280,10 @@ public final class app/revanced/patches/messenger/inputfield/DisableTypingIndica
public static final fun getDisableTypingIndicatorPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getDisableTypingIndicatorPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/messenger/navbar/RemoveMetaAITabPatchKt {
public static final fun getRemoveMetaAITabPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/mifitness/misc/locale/ForceEnglishLocalePatchKt { public final class app/revanced/patches/mifitness/misc/locale/ForceEnglishLocalePatchKt {
public static final fun getForceEnglishLocalePatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getForceEnglishLocalePatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
@ -564,7 +576,9 @@ public final class app/revanced/patches/shared/misc/extension/ExtensionHook {
} }
public final class app/revanced/patches/shared/misc/extension/SharedExtensionPatchKt { public final class app/revanced/patches/shared/misc/extension/SharedExtensionPatchKt {
public static final fun extensionHook (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lapp/revanced/patcher/Fingerprint;)Lapp/revanced/patches/shared/misc/extension/ExtensionHook;
public static final fun extensionHook (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lapp/revanced/patches/shared/misc/extension/ExtensionHook; public static final fun extensionHook (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lapp/revanced/patches/shared/misc/extension/ExtensionHook;
public static synthetic fun extensionHook$default (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lapp/revanced/patcher/Fingerprint;ILjava/lang/Object;)Lapp/revanced/patches/shared/misc/extension/ExtensionHook;
public static synthetic fun extensionHook$default (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patches/shared/misc/extension/ExtensionHook; public static synthetic fun extensionHook$default (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patches/shared/misc/extension/ExtensionHook;
public static final fun sharedExtensionPatch (Ljava/lang/String;[Lapp/revanced/patches/shared/misc/extension/ExtensionHook;)Lapp/revanced/patcher/patch/BytecodePatch; public static final fun sharedExtensionPatch (Ljava/lang/String;[Lapp/revanced/patches/shared/misc/extension/ExtensionHook;)Lapp/revanced/patcher/patch/BytecodePatch;
public static final fun sharedExtensionPatch ([Lapp/revanced/patches/shared/misc/extension/ExtensionHook;)Lapp/revanced/patcher/patch/BytecodePatch; public static final fun sharedExtensionPatch ([Lapp/revanced/patches/shared/misc/extension/ExtensionHook;)Lapp/revanced/patcher/patch/BytecodePatch;
@ -832,6 +846,10 @@ public final class app/revanced/patches/spotify/misc/extension/ExtensionPatchKt
public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/spotify/misc/fix/SpoofPackageInfoPatchKt {
public static final fun getSpoofPackageInfoPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/spotify/misc/fix/SpoofSignaturePatchKt { public final class app/revanced/patches/spotify/misc/fix/SpoofSignaturePatchKt {
public static final fun getSpoofSignaturePatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getSpoofSignaturePatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
@ -1402,6 +1420,8 @@ public final class app/revanced/patches/youtube/misc/playservice/VersionCheckPat
public static final fun is_20_07_or_greater ()Z public static final fun is_20_07_or_greater ()Z
public static final fun is_20_09_or_greater ()Z public static final fun is_20_09_or_greater ()Z
public static final fun is_20_10_or_greater ()Z public static final fun is_20_10_or_greater ()Z
public static final fun is_20_14_or_greater ()Z
public static final fun is_20_15_or_greater ()Z
} }
public final class app/revanced/patches/youtube/misc/privacy/RemoveTrackingQueryParameterPatchKt { public final class app/revanced/patches/youtube/misc/privacy/RemoveTrackingQueryParameterPatchKt {
@ -1515,7 +1535,11 @@ public final class app/revanced/patches/yuka/misc/unlockpremium/UnlockPremiumPat
} }
public final class app/revanced/util/BytecodeUtilsKt { public final class app/revanced/util/BytecodeUtilsKt {
public static final fun addInstructionsAtControlFlowLabel (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILjava/lang/String;)V
public static final fun containsLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;D)Z
public static final fun containsLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;F)Z
public static final fun containsLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;J)Z public static final fun containsLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;J)Z
public static final fun findFreeRegister (Lcom/android/tools/smali/dexlib2/iface/Method;I[I)I
public static final fun findInstructionIndicesReversed (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/Opcode;)Ljava/util/List; public static final fun findInstructionIndicesReversed (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/Opcode;)Ljava/util/List;
public static final fun findInstructionIndicesReversed (Lcom/android/tools/smali/dexlib2/iface/Method;Lkotlin/jvm/functions/Function1;)Ljava/util/List; public static final fun findInstructionIndicesReversed (Lcom/android/tools/smali/dexlib2/iface/Method;Lkotlin/jvm/functions/Function1;)Ljava/util/List;
public static final fun findInstructionIndicesReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/Opcode;)Ljava/util/List; public static final fun findInstructionIndicesReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/Opcode;)Ljava/util/List;
@ -1542,9 +1566,17 @@ public final class app/revanced/util/BytecodeUtilsKt {
public static final fun indexOfFirstInstructionReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lkotlin/jvm/functions/Function1;)I public static final fun indexOfFirstInstructionReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lkotlin/jvm/functions/Function1;)I
public static synthetic fun indexOfFirstInstructionReversedOrThrow$default (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lcom/android/tools/smali/dexlib2/Opcode;ILjava/lang/Object;)I public static synthetic fun indexOfFirstInstructionReversedOrThrow$default (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lcom/android/tools/smali/dexlib2/Opcode;ILjava/lang/Object;)I
public static synthetic fun indexOfFirstInstructionReversedOrThrow$default (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)I public static synthetic fun indexOfFirstInstructionReversedOrThrow$default (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)I
public static final fun indexOfFirstLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;D)I
public static final fun indexOfFirstLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;F)I
public static final fun indexOfFirstLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;J)I public static final fun indexOfFirstLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;J)I
public static final fun indexOfFirstLiteralInstructionOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;D)I
public static final fun indexOfFirstLiteralInstructionOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;F)I
public static final fun indexOfFirstLiteralInstructionOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;J)I public static final fun indexOfFirstLiteralInstructionOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;J)I
public static final fun indexOfFirstLiteralInstructionReversed (Lcom/android/tools/smali/dexlib2/iface/Method;D)I
public static final fun indexOfFirstLiteralInstructionReversed (Lcom/android/tools/smali/dexlib2/iface/Method;F)I
public static final fun indexOfFirstLiteralInstructionReversed (Lcom/android/tools/smali/dexlib2/iface/Method;J)I public static final fun indexOfFirstLiteralInstructionReversed (Lcom/android/tools/smali/dexlib2/iface/Method;J)I
public static final fun indexOfFirstLiteralInstructionReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;D)I
public static final fun indexOfFirstLiteralInstructionReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;F)I
public static final fun indexOfFirstLiteralInstructionReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;J)I public static final fun indexOfFirstLiteralInstructionReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;J)I
public static final fun indexOfFirstResourceId (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;)I public static final fun indexOfFirstResourceId (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;)I
public static final fun indexOfFirstResourceIdOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;)I public static final fun indexOfFirstResourceIdOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;)I

View File

@ -0,0 +1,75 @@
package app.revanced.patches.all.misc.adb
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
import app.revanced.util.getReference
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableMethodReference
import com.android.tools.smali.dexlib2.util.MethodUtil
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/all/misc/hide/adb/HideAdbPatch;"
private val SETTINGS_GLOBAL_GET_INT_OR_THROW_METHOD_REFERENCE = ImmutableMethodReference(
"Landroid/provider/Settings\$Global;",
"getInt",
listOf("Landroid/content/ContentResolver;", "Ljava/lang/String;"),
"I"
)
private val SETTINGS_GLOBAL_GET_INT_OR_DEFAULT_METHOD_REFERENCE = ImmutableMethodReference(
"Landroid/provider/Settings\$Global;",
"getInt",
listOf("Landroid/content/ContentResolver;", "Ljava/lang/String;", "I"),
"I"
)
private fun MethodReference.anyMethodSignatureMatches(vararg anyOf: MethodReference): Boolean {
return anyOf.any {
MethodUtil.methodSignaturesMatch(it, this)
}
}
@Suppress("unused")
val hideAdbStatusPatch = bytecodePatch(
name = "Hide ADB status",
description = "Hides enabled development settings and/or ADB.",
use = false,
) {
extendWith("extensions/all/misc/adb/hide-adb.rve")
dependsOn(
transformInstructionsPatch(
filterMap = filterMap@{ classDef, method, instruction, instructionIndex ->
val reference = instruction
.takeIf { it.opcode == Opcode.INVOKE_STATIC }
?.getReference<MethodReference>()
?.takeIf {
it.anyMethodSignatureMatches(it,
SETTINGS_GLOBAL_GET_INT_OR_THROW_METHOD_REFERENCE,
SETTINGS_GLOBAL_GET_INT_OR_DEFAULT_METHOD_REFERENCE
)
}
?: return@filterMap null
Triple(instruction as Instruction35c, instructionIndex, reference.parameterTypes)
},
transform = { method, entry ->
val (instruction, index, parameterTypes) = entry
val parameterString = parameterTypes.joinToString(separator = "")
val registerString = when (parameterTypes.size) {
2 -> "v${instruction.registerC}, v${instruction.registerD}"
else -> "v${instruction.registerC}, v${instruction.registerD}, v${instruction.registerE}"
}
method.replaceInstruction(
index,
"invoke-static { $registerString }, $EXTENSION_CLASS_DESCRIPTOR->getInt($parameterString)I"
)
}
)
)
}

View File

@ -6,7 +6,7 @@ import app.revanced.patches.all.misc.transformation.filterMapInstruction35c
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
private const val EXTENSION_CLASS_DESCRIPTOR_PREFIX = private const val EXTENSION_CLASS_DESCRIPTOR_PREFIX =
"Lapp/revanced/extension/all/connectivity/wifi/spoof/SpoofWifiPatch" "Lapp/revanced/extension/all/misc/connectivity/wifi/spoof/SpoofWifiPatch"
private const val EXTENSION_CLASS_DESCRIPTOR = "$EXTENSION_CLASS_DESCRIPTOR_PREFIX;" private const val EXTENSION_CLASS_DESCRIPTOR = "$EXTENSION_CLASS_DESCRIPTOR_PREFIX;"

View File

@ -25,7 +25,7 @@ private val removeCaptureRestrictionResourcePatch = resourcePatch(
} }
private const val EXTENSION_CLASS_DESCRIPTOR_PREFIX = private const val EXTENSION_CLASS_DESCRIPTOR_PREFIX =
"Lapp/revanced/extension/all/screencapture/removerestriction/RemoveScreenCaptureRestrictionPatch" "Lapp/revanced/extension/all/misc/screencapture/removerestriction/RemoveScreenCaptureRestrictionPatch"
private const val EXTENSION_CLASS_DESCRIPTOR = "$EXTENSION_CLASS_DESCRIPTOR_PREFIX;" private const val EXTENSION_CLASS_DESCRIPTOR = "$EXTENSION_CLASS_DESCRIPTOR_PREFIX;"
@Suppress("unused") @Suppress("unused")

View File

@ -10,7 +10,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction22c
import com.android.tools.smali.dexlib2.iface.reference.FieldReference import com.android.tools.smali.dexlib2.iface.reference.FieldReference
private const val EXTENSION_CLASS_DESCRIPTOR_PREFIX = private const val EXTENSION_CLASS_DESCRIPTOR_PREFIX =
"Lapp/revanced/extension/all/screenshot/removerestriction/RemoveScreenshotRestrictionPatch" "Lapp/revanced/extension/all/misc/screenshot/removerestriction/RemoveScreenshotRestrictionPatch"
private const val EXTENSION_CLASS_DESCRIPTOR = "$EXTENSION_CLASS_DESCRIPTOR_PREFIX;" private const val EXTENSION_CLASS_DESCRIPTOR = "$EXTENSION_CLASS_DESCRIPTOR_PREFIX;"
@Suppress("unused") @Suppress("unused")

View File

@ -0,0 +1,48 @@
package app.revanced.patches.all.misc.targetSdk
import app.revanced.patcher.patch.resourcePatch
import app.revanced.util.getNode
import org.w3c.dom.Element
import java.util.logging.Logger
@Suppress("unused")
val setTargetSdkVersion34 = resourcePatch(
name = "Set target SDK version 34",
description = "Changes the target SDK to version 34 (Android 14). " +
"For devices running Android 15+, this will disable edge-to-edge display.",
use = false,
) {
execute {
val targetSdkOverride = 34 // Android 14.
document("AndroidManifest.xml").use { document ->
fun getLogger() = Logger.getLogger(this::class.java.name)
// Ideally, the override should only be applied if the existing target is higher.
// But since ApkTool does not add targetSdkVersion to the decompiled AndroidManifest,
// there is no way to check targetSdkVersion. Instead, check compileSdkVersion and print a warning.
try {
val manifestElement = document.getNode("manifest") as Element
val compileSdkVersion = Integer.parseInt(
manifestElement.getAttribute("android:compileSdkVersion")
)
if (compileSdkVersion <= targetSdkOverride) {
getLogger().warning(
"This app does not appear to use a target SDK above $targetSdkOverride: " +
"(compileSdkVersion: $compileSdkVersion)"
)
}
} catch (_: Exception) {
getLogger().warning("Could not check compileSdkVersion")
}
// Change targetSdkVersion to override value.
document.getElementsByTagName("manifest").item(0).let {
var element = it.ownerDocument.createElement("uses-sdk")
element.setAttribute("android:targetSdkVersion", targetSdkOverride.toString())
it.appendChild(element)
}
}
}
}

View File

@ -12,7 +12,8 @@ internal val initializeMonetizationDebugSettingsFingerprint = fingerprint {
"Z", // useDebugBilling "Z", // useDebugBilling
"Z", // showManageSubscriptions "Z", // showManageSubscriptions
"Z", // alwaysShowSuperAds "Z", // alwaysShowSuperAds
"Lcom/duolingo/debug/FamilyQuestOverride;", // matches "Lcom/duolingo/debug/FamilyQuestOverride;" or "Lcom/duolingo/data/debug/monetization/FamilyQuestOverride;"
"Lcom/duolingo/",
) )
opcodes(Opcode.IPUT_BOOLEAN) opcodes(Opcode.IPUT_BOOLEAN)
} }

View File

@ -27,4 +27,5 @@ private fun gmsCoreSupportResourcePatch(
toPackageName = REVANCED_MAGAZINES_PACKAGE_NAME, toPackageName = REVANCED_MAGAZINES_PACKAGE_NAME,
spoofedPackageSignature = "24bb24c05e47e0aefa68a58a766179d9b613a666", spoofedPackageSignature = "24bb24c05e47e0aefa68a58a766179d9b613a666",
gmsCoreVendorGroupIdOption = gmsCoreVendorGroupIdOption, gmsCoreVendorGroupIdOption = gmsCoreVendorGroupIdOption,
addStringResources = false,
) )

View File

@ -22,6 +22,7 @@ private fun gmsCoreSupportResourcePatch(
) = app.revanced.patches.shared.misc.gms.gmsCoreSupportResourcePatch( ) = app.revanced.patches.shared.misc.gms.gmsCoreSupportResourcePatch(
fromPackageName = PHOTOS_PACKAGE_NAME, fromPackageName = PHOTOS_PACKAGE_NAME,
toPackageName = REVANCED_PHOTOS_PACKAGE_NAME, toPackageName = REVANCED_PHOTOS_PACKAGE_NAME,
addStringResources = false,
spoofedPackageSignature = "24bb24c05e47e0aefa68a58a766179d9b613a600", spoofedPackageSignature = "24bb24c05e47e0aefa68a58a766179d9b613a600",
gmsCoreVendorGroupIdOption = gmsCoreVendorGroupIdOption, gmsCoreVendorGroupIdOption = gmsCoreVendorGroupIdOption,
) )

View File

@ -3,23 +3,28 @@ package app.revanced.patches.googlephotos.misc.preferences
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@Deprecated("This patch no longer works and this code will soon be deleted")
@Suppress("unused") @Suppress("unused")
val restoreHiddenBackUpWhileChargingTogglePatch = bytecodePatch( val restoreHiddenBackUpWhileChargingTogglePatch = bytecodePatch(
name = "Restore hidden 'Back up while charging' toggle", description = "Restores a hidden toggle to only run backups when the device is charging."
description = "Restores a hidden toggle to only run backups when the device is charging.",
) { ) {
compatibleWith("com.google.android.apps.photos") compatibleWith("com.google.android.apps.photos"("7.11.0.705590205"))
execute { execute {
// Patches 'backup_prefs_had_backup_only_when_charging_enabled' to always be true. // Patches 'backup_prefs_had_backup_only_when_charging_enabled' to always be true.
val chargingPrefStringIndex = backupPreferencesFingerprint.stringMatches!!.first().index backupPreferencesFingerprint.let {
backupPreferencesFingerprint.method.apply { it.method.apply {
// Get the register of move-result. val index = indexOfFirstInstructionOrThrow(
val resultRegister = getInstruction<OneRegisterInstruction>(chargingPrefStringIndex + 2).registerA it.stringMatches!!.first().index,
// Insert const after move-result to override register as true. Opcode.MOVE_RESULT
addInstruction(chargingPrefStringIndex + 3, "const/4 v$resultRegister, 0x1") )
val register = getInstruction<OneRegisterInstruction>(index).registerA
addInstruction(index + 1, "const/4 v$register, 0x1")
}
} }
} }
} }

View File

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

View File

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

View File

@ -41,11 +41,12 @@ fun sharedExtensionPatch(
execute { execute {
if (classes.none { EXTENSION_CLASS_DESCRIPTOR == it.type }) { if (classes.none { EXTENSION_CLASS_DESCRIPTOR == it.type }) {
throw PatchException( throw PatchException("Shared extension is not available. This patch can not succeed without it.")
"Shared extension has not been merged yet. This patch can not succeed without merging it.", }
)
} }
finalize {
// The hooks are made in finalize to ensure that the context is hooked before any other patches.
hooks.forEach { hook -> hook(EXTENSION_CLASS_DESCRIPTOR) } hooks.forEach { hook -> hook(EXTENSION_CLASS_DESCRIPTOR) }
// Modify Utils method to include the patches release version. // Modify Utils method to include the patches release version.
@ -92,7 +93,7 @@ fun sharedExtensionPatch(
} }
class ExtensionHook internal constructor( class ExtensionHook internal constructor(
private val fingerprint: Fingerprint, internal val fingerprint: Fingerprint,
private val insertIndexResolver: ((Method) -> Int), private val insertIndexResolver: ((Method) -> Int),
private val contextRegisterResolver: (Method) -> String, private val contextRegisterResolver: (Method) -> String,
) { ) {
@ -109,8 +110,14 @@ class ExtensionHook internal constructor(
} }
} }
fun extensionHook(
insertIndexResolver: ((Method) -> Int) = { 0 },
contextRegisterResolver: (Method) -> String = { "p0" },
fingerprint: Fingerprint,
) = ExtensionHook(fingerprint, insertIndexResolver, contextRegisterResolver)
fun extensionHook( fun extensionHook(
insertIndexResolver: ((Method) -> Int) = { 0 }, insertIndexResolver: ((Method) -> Int) = { 0 },
contextRegisterResolver: (Method) -> String = { "p0" }, contextRegisterResolver: (Method) -> String = { "p0" },
fingerprintBuilderBlock: FingerprintBuilder.() -> Unit, fingerprintBuilderBlock: FingerprintBuilder.() -> Unit,
) = ExtensionHook(fingerprint(block = fingerprintBuilderBlock), insertIndexResolver, contextRegisterResolver) ) = extensionHook(insertIndexResolver, contextRegisterResolver, fingerprint(block = fingerprintBuilderBlock))

View File

@ -17,7 +17,6 @@ import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21c import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21c
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction21c import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction21c
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.iface.reference.StringReference import com.android.tools.smali.dexlib2.iface.reference.StringReference
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableStringReference import com.android.tools.smali.dexlib2.immutable.reference.ImmutableStringReference
import com.android.tools.smali.dexlib2.util.MethodUtil import com.android.tools.smali.dexlib2.util.MethodUtil
@ -53,8 +52,8 @@ fun gmsCoreSupportPatch(
block: BytecodePatchBuilder.() -> Unit = {}, block: BytecodePatchBuilder.() -> Unit = {},
) = bytecodePatch( ) = bytecodePatch(
name = "GmsCore support", name = "GmsCore support",
description = "Allows patched Google apps to run without root and under a different package name " + description = "Allows the app to work without root by using a different package name when patched " +
"by using GmsCore instead of Google Play Services.", "using a GmsCore instead of Google Play Services.",
) { ) {
val gmsCoreVendorGroupIdOption = stringOption( val gmsCoreVendorGroupIdOption = stringOption(
key = "gmsCoreVendorGroupId", key = "gmsCoreVendorGroupId",
@ -110,8 +109,7 @@ fun gmsCoreSupportPatch(
// region Collection of transformations that are applied to all strings. // region Collection of transformations that are applied to all strings.
fun commonTransform(referencedString: String): String? = fun commonTransform(referencedString: String): String? = when (referencedString) {
when (referencedString) {
"com.google", "com.google",
"com.google.android.gms", "com.google.android.gms",
in PERMISSIONS, in PERMISSIONS,
@ -205,16 +203,8 @@ fun gmsCoreSupportPatch(
// Verify GmsCore is installed and whitelisted for power optimizations and background usage. // Verify GmsCore is installed and whitelisted for power optimizations and background usage.
mainActivityOnCreateFingerprint.method.apply { mainActivityOnCreateFingerprint.method.apply {
// Temporary fix for patches with an extension patch that hook the onCreate method as well.
val setContextIndex = indexOfFirstInstruction {
val reference = getReference<MethodReference>() ?: return@indexOfFirstInstruction false
reference.toString() == "Lapp/revanced/extension/shared/Utils;->setContext(Landroid/content/Context;)V"
}
// Add after setContext call, because this patch needs the context.
addInstructions( addInstructions(
if (setContextIndex < 0) 0 else setContextIndex + 1, 0,
"invoke-static/range { p0 .. p0 }, Lapp/revanced/extension/shared/GmsCoreSupport;->" + "invoke-static/range { p0 .. p0 }, Lapp/revanced/extension/shared/GmsCoreSupport;->" +
"checkGmsCore(Landroid/app/Activity;)V", "checkGmsCore(Landroid/app/Activity;)V",
) )
@ -510,13 +500,44 @@ private object Constants {
* @param executeBlock The additional execution block of the patch. * @param executeBlock The additional execution block of the patch.
* @param block The additional block to build the patch. * @param block The additional block to build the patch.
*/ */
fun gmsCoreSupportResourcePatch( fun gmsCoreSupportResourcePatch( // This is here only for binary compatibility.
fromPackageName: String, fromPackageName: String,
toPackageName: String, toPackageName: String,
spoofedPackageSignature: String, spoofedPackageSignature: String,
gmsCoreVendorGroupIdOption: Option<String>, gmsCoreVendorGroupIdOption: Option<String>,
executeBlock: ResourcePatchContext.() -> Unit = {}, executeBlock: ResourcePatchContext.() -> Unit = {},
block: ResourcePatchBuilder.() -> Unit = {}, block: ResourcePatchBuilder.() -> Unit = {},
) = gmsCoreSupportResourcePatch(
fromPackageName,
toPackageName,
spoofedPackageSignature,
gmsCoreVendorGroupIdOption,
true,
executeBlock,
block
)
/**
* Abstract resource patch that allows Google apps to run without root and under a different package name
* by using GmsCore instead of Google Play Services.
*
* @param fromPackageName The package name of the original app.
* @param toPackageName The package name to fall back to if no custom package name is specified in patch options.
* @param spoofedPackageSignature The signature of the package to spoof to.
* @param gmsCoreVendorGroupIdOption The option to get the vendor group ID of GmsCore.
* @param addStringResources If the GmsCore shared strings should be added to the patched app.
* @param executeBlock The additional execution block of the patch.
* @param block The additional block to build the patch.
*/
// TODO: On the next major release make this public and delete the public overloaded constructor.
internal fun gmsCoreSupportResourcePatch(
fromPackageName: String,
toPackageName: String,
spoofedPackageSignature: String,
gmsCoreVendorGroupIdOption: Option<String>,
addStringResources: Boolean = true,
executeBlock: ResourcePatchContext.() -> Unit = {},
block: ResourcePatchBuilder.() -> Unit = {},
) = resourcePatch { ) = resourcePatch {
dependsOn( dependsOn(
changePackageNamePatch, changePackageNamePatch,
@ -526,7 +547,10 @@ fun gmsCoreSupportResourcePatch(
val gmsCoreVendorGroupId by gmsCoreVendorGroupIdOption val gmsCoreVendorGroupId by gmsCoreVendorGroupIdOption
execute { execute {
// Some patches don't use shared String resources so there's no need to add them.
if (addStringResources) {
addResources("shared", "misc.gms.gmsCoreSupportResourcePatch") addResources("shared", "misc.gms.gmsCoreSupportResourcePatch")
}
/** /**
* Add metadata to manifest to support spoofing the package name and signature of GmsCore. * Add metadata to manifest to support spoofing the package name and signature of GmsCore.

View File

@ -14,7 +14,7 @@ import app.revanced.util.findFreeRegister
import app.revanced.util.findInstructionIndicesReversedOrThrow import app.revanced.util.findInstructionIndicesReversedOrThrow
import app.revanced.util.getReference import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.insertFeatureFlagBooleanOverride import app.revanced.util.insertLiteralOverride
import app.revanced.util.returnEarly import app.revanced.util.returnEarly
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
@ -235,7 +235,7 @@ fun spoofVideoStreamsPatch(
// region Fix iOS livestream current time. // region Fix iOS livestream current time.
hlsCurrentTimeFingerprint.method.insertFeatureFlagBooleanOverride( hlsCurrentTimeFingerprint.method.insertLiteralOverride(
HLS_CURRENT_TIME_FEATURE_FLAG, HLS_CURRENT_TIME_FEATURE_FLAG,
"$EXTENSION_CLASS_DESCRIPTOR->fixHLSCurrentTime(Z)Z" "$EXTENSION_CLASS_DESCRIPTOR->fixHLSCurrentTime(Z)Z"
) )
@ -245,21 +245,21 @@ fun spoofVideoStreamsPatch(
// region turn off stream config replacement feature flag. // region turn off stream config replacement feature flag.
if (fixMediaFetchHotConfigChanges()) { if (fixMediaFetchHotConfigChanges()) {
mediaFetchHotConfigFingerprint.method.insertFeatureFlagBooleanOverride( mediaFetchHotConfigFingerprint.method.insertLiteralOverride(
MEDIA_FETCH_HOT_CONFIG_FEATURE_FLAG, MEDIA_FETCH_HOT_CONFIG_FEATURE_FLAG,
"$EXTENSION_CLASS_DESCRIPTOR->useMediaFetchHotConfigReplacement(Z)Z" "$EXTENSION_CLASS_DESCRIPTOR->useMediaFetchHotConfigReplacement(Z)Z"
) )
} }
if (fixMediaFetchHotConfigAlternativeChanges()) { if (fixMediaFetchHotConfigAlternativeChanges()) {
mediaFetchHotConfigAlternativeFingerprint.method.insertFeatureFlagBooleanOverride( mediaFetchHotConfigAlternativeFingerprint.method.insertLiteralOverride(
MEDIA_FETCH_HOT_CONFIG_ALTERNATIVE_FEATURE_FLAG, MEDIA_FETCH_HOT_CONFIG_ALTERNATIVE_FEATURE_FLAG,
"$EXTENSION_CLASS_DESCRIPTOR->useMediaFetchHotConfigReplacement(Z)Z" "$EXTENSION_CLASS_DESCRIPTOR->useMediaFetchHotConfigReplacement(Z)Z"
) )
} }
if (fixParsePlaybackResponseFeatureFlag()) { if (fixParsePlaybackResponseFeatureFlag()) {
playbackStartDescriptorFeatureFlagFingerprint.method.insertFeatureFlagBooleanOverride( playbackStartDescriptorFeatureFlagFingerprint.method.insertLiteralOverride(
PLAYBACK_START_CHECK_ENDPOINT_USED_FEATURE_FLAG, PLAYBACK_START_CHECK_ENDPOINT_USED_FEATURE_FLAG,
"$EXTENSION_CLASS_DESCRIPTOR->usePlaybackStartFeatureFlag(Z)Z" "$EXTENSION_CLASS_DESCRIPTOR->usePlaybackStartFeatureFlag(Z)Z"
) )

View File

@ -1,20 +1,25 @@
@file:Suppress("NAME_SHADOWING")
package app.revanced.patches.spotify.layout.theme package app.revanced.patches.spotify.layout.theme
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.fingerprint
import app.revanced.patcher.patch.booleanOption
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.patch.stringOption import app.revanced.patcher.patch.stringOption
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.spotify.misc.extension.IS_SPOTIFY_LEGACY_APP_TARGET
import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
import app.revanced.util.*
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import org.w3c.dom.Element import org.w3c.dom.Element
@Suppress("unused") private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/spotify/layout/theme/CustomThemePatch;"
val customThemePatch = resourcePatch(
name = "Custom theme",
description = "Applies a custom theme.",
use = false,
) {
compatibleWith("com.spotify.music")
val backgroundColor by stringOption( internal val spotifyBackgroundColor = stringOption(
key = "backgroundColor", key = "backgroundColor",
default = "@android:color/black", default = "@android:color/black",
title = "Primary background color", title = "Primary background color",
@ -22,60 +27,179 @@ val customThemePatch = resourcePatch(
required = true, required = true,
) )
val backgroundColorSecondary by stringOption( internal val overridePlayerGradientColor = booleanOption(
key = "overridePlayerGradientColor",
default = false,
title = "Override player gradient color",
description = "Apply primary background color to the player gradient color, which changes dynamically with the song.",
required = false
)
internal val spotifyBackgroundColorSecondary = stringOption(
key = "backgroundColorSecondary", key = "backgroundColorSecondary",
default = "#ff282828", default = "#FF121212",
title = "Secondary background color", title = "Secondary background color",
description = "The secondary background color. (e.g. search box, artist & podcast). Can be a hex color or a resource reference.", description =
"The secondary background color. (e.g. playlist list in home, player artist, song credits). Can be a hex color or a resource reference.",
required = true, required = true,
) )
val accentColor by stringOption( internal val spotifyAccentColor = stringOption(
key = "accentColor", key = "accentColor",
default = "#ff1ed760", default = "#FF1ED760",
title = "Accent color", title = "Accent color",
description = "The accent color ('Spotify green' by default). Can be a hex color or a resource reference.", description = "The accent color ('Spotify green' by default). Can be a hex color or a resource reference.",
required = true, required = true,
) )
val accentColorPressed by stringOption( internal val spotifyAccentColorPressed = stringOption(
key = "accentColorPressed", key = "accentColorPressed",
default = "#ff169c46", default = "#FF169C46",
title = "Pressed dark theme accent color", title = "Pressed dark theme accent color",
description = description =
"The color when accented buttons are pressed, by default slightly darker than accent. " + "The color when accented buttons are pressed, by default slightly darker than accent. Can be a hex color or a resource reference.",
"Can be a hex color or a resource reference.",
required = true, required = true,
) )
execute { private val customThemeBytecodePatch = bytecodePatch {
val backgroundColor = backgroundColor!! dependsOn(sharedExtensionPatch)
val backgroundColorSecondary = backgroundColorSecondary!!
val accentColor = accentColor!!
val accentColorPressed = accentColorPressed!!
execute {
if (IS_SPOTIFY_LEGACY_APP_TARGET) {
// Bytecode changes are not needed for legacy app target.
// Player background color is changed with existing resource patch.
return@execute
}
fun MutableMethod.addColorChangeInstructions(literal: Long, colorString: String) {
val index = indexOfFirstLiteralInstructionOrThrow(literal)
val register = getInstruction<OneRegisterInstruction>(index).registerA
addInstructions(
index + 1,
"""
const-string v$register, "$colorString"
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->getThemeColor(Ljava/lang/String;)J
move-result-wide v$register
"""
)
}
val encoreColorsClassName = with(encoreThemeFingerprint.originalMethod) {
// "Encore" colors are referenced right before the value of POSITIVE_INFINITY is returned.
// Begin the instruction find using the index of where POSITIVE_INFINITY is set into the register.
val positiveInfinityIndex = indexOfFirstLiteralInstructionOrThrow(
Float.POSITIVE_INFINITY
)
val encoreColorsFieldReferenceIndex = indexOfFirstInstructionReversedOrThrow(
positiveInfinityIndex,
Opcode.SGET_OBJECT
)
getInstruction(encoreColorsFieldReferenceIndex)
.getReference<FieldReference>()!!.definingClass
}
val encoreColorsConstructorFingerprint = fingerprint {
accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR)
custom { method, classDef ->
classDef.type == encoreColorsClassName &&
method.containsLiteralInstruction(PLAYLIST_BACKGROUND_COLOR_LITERAL)
}
}
val backgroundColor by spotifyBackgroundColor
val backgroundColorSecondary by spotifyBackgroundColorSecondary
encoreColorsConstructorFingerprint.method.apply {
addColorChangeInstructions(PLAYLIST_BACKGROUND_COLOR_LITERAL, backgroundColor!!)
addColorChangeInstructions(SHARE_MENU_BACKGROUND_COLOR_LITERAL, backgroundColorSecondary!!)
}
homeCategoryPillColorsFingerprint.method.addColorChangeInstructions(
HOME_CATEGORY_PILL_COLOR_LITERAL,
backgroundColorSecondary!!
)
settingsHeaderColorFingerprint.method.addColorChangeInstructions(
SETTINGS_HEADER_COLOR_LITERAL,
backgroundColorSecondary!!
)
}
}
@Suppress("unused")
val customThemePatch = resourcePatch(
name = "Custom theme",
description = "Applies a custom theme (defaults to amoled black)",
use = false,
) {
compatibleWith("com.spotify.music")
dependsOn(customThemeBytecodePatch)
val backgroundColor by spotifyBackgroundColor()
val overridePlayerGradientColor by overridePlayerGradientColor()
val backgroundColorSecondary by spotifyBackgroundColorSecondary()
val accentColor by spotifyAccentColor()
val accentColorPressed by spotifyAccentColorPressed()
execute {
document("res/values/colors.xml").use { document -> document("res/values/colors.xml").use { document ->
val resourcesNode = document.getElementsByTagName("resources").item(0) as Element val resourcesNode = document.getElementsByTagName("resources").item(0) as Element
val childNodes = resourcesNode.childNodes val childNodes = resourcesNode.childNodes
for (i in 0 until childNodes.length) { for (i in 0 until childNodes.length) {
val node = childNodes.item(i) as? Element ?: continue val node = childNodes.item(i) as? Element ?: continue
val name = node.getAttribute("name")
node.textContent = // Skip overriding song/player gradient start color if the option is disabled.
when (node.getAttribute("name")) { // Gradient end color should be themed regardless to allow the gradient to connect with
"dark_base_background_elevated_base", "design_dark_default_color_background", // our primary background color.
"design_dark_default_color_surface", "gray_7", "gray_background", "gray_layer", if (name == "bg_gradient_start_color" && !overridePlayerGradientColor!!) {
"sthlm_blk", continue
}
node.textContent = when (name) {
// Gradient next to user photo and "All" in home page.
"dark_base_background_base",
// Main background.
"gray_7",
// Left sidebar background in tablet mode.
"gray_10",
// "Add account", "Settings and privacy", "View Profile" left sidebar background.
"dark_base_background_elevated_base",
// Song/player gradient start/end color.
"bg_gradient_start_color", "bg_gradient_end_color",
// Login screen background and gradient start.
"sthlm_blk", "sthlm_blk_grad_start",
// Misc.
"image_placeholder_color",
-> backgroundColor -> backgroundColor
"gray_15" -> backgroundColorSecondary // Track credits, merch background in song player.
"track_credits_card_bg", "benefit_list_default_color", "merch_card_background",
// Playlist list background in home page.
"opacity_white_10",
// "About the artist" background in song player.
"gray_15",
// "What's New" pills background.
"dark_base_background_tinted_highlight"
-> backgroundColorSecondary
"dark_brightaccent_background_base", "dark_base_text_brightaccent", "green_light" -> accentColor "dark_brightaccent_background_base", "dark_base_text_brightaccent", "green_light" -> accentColor
"dark_brightaccent_background_press" -> accentColorPressed "dark_brightaccent_background_press" -> accentColorPressed
else -> continue else -> continue
} }
} }
} }
// Login screen gradient.
document("res/drawable/start_screen_gradient.xml").use { document ->
val gradientNode = document.getElementsByTagName("gradient").item(0) as Element
gradientNode.setAttribute("android:startColor", backgroundColor)
gradientNode.setAttribute("android:endColor", backgroundColor)
}
} }
} }

View File

@ -0,0 +1,33 @@
package app.revanced.patches.spotify.layout.theme
import app.revanced.patcher.fingerprint
import app.revanced.util.containsLiteralInstruction
import com.android.tools.smali.dexlib2.AccessFlags
internal val encoreThemeFingerprint = fingerprint {
strings("Encore theme was not provided.") // Partial string match.
custom { method, _ ->
method.name == "invoke"
}
}
internal const val PLAYLIST_BACKGROUND_COLOR_LITERAL = 0xFF121212
internal const val SHARE_MENU_BACKGROUND_COLOR_LITERAL = 0xFF1F1F1F
internal const val HOME_CATEGORY_PILL_COLOR_LITERAL = 0xFF333333
internal const val SETTINGS_HEADER_COLOR_LITERAL = 0xFF282828
internal val homeCategoryPillColorsFingerprint = fingerprint{
accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR)
custom { method, _ ->
method.containsLiteralInstruction(HOME_CATEGORY_PILL_COLOR_LITERAL) &&
method.containsLiteralInstruction(0x33000000)
}
}
internal val settingsHeaderColorFingerprint = fingerprint {
accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR)
custom { method, _ ->
method.containsLiteralInstruction(SETTINGS_HEADER_COLOR_LITERAL) &&
method.containsLiteralInstruction(0)
}
}

View File

@ -1,15 +1,28 @@
package app.revanced.patches.spotify.misc package app.revanced.patches.spotify.misc
import app.revanced.patcher.fingerprint import app.revanced.patcher.fingerprint
import app.revanced.patches.spotify.misc.extension.IS_SPOTIFY_LEGACY_APP_TARGET
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val accountAttributeFingerprint = fingerprint { internal val accountAttributeFingerprint = fingerprint {
custom { _, c -> c.endsWith("internal/AccountAttribute;") } custom { _, classDef ->
classDef.type == if (IS_SPOTIFY_LEGACY_APP_TARGET) {
"Lcom/spotify/useraccount/v1/AccountAttribute;"
} else {
"Lcom/spotify/remoteconfig/internal/AccountAttribute;"
}
}
} }
internal val productStateProtoFingerprint = fingerprint { internal val productStateProtoFingerprint = fingerprint {
returns("Ljava/util/Map;") returns("Ljava/util/Map;")
custom { _, classDef -> custom { _, classDef ->
classDef.endsWith("ProductStateProto;") classDef.type == if (IS_SPOTIFY_LEGACY_APP_TARGET) {
"Lcom/spotify/ucs/proto/v0/UcsResponseWrapper${'$'}AccountAttributesResponse;"
} else {
"Lcom/spotify/remoteconfig/internal/ProductStateProto;"
}
} }
} }
@ -21,3 +34,38 @@ internal val contextMenuExperimentsFingerprint = fingerprint {
parameters("L") parameters("L")
strings("remove_ads_upsell_enabled") strings("remove_ads_upsell_enabled")
} }
internal val contextFromJsonFingerprint = fingerprint {
opcodes(
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_STATIC
)
custom { methodDef, classDef ->
methodDef.name == "fromJson" &&
classDef.endsWith("voiceassistants/playermodels/ContextJsonAdapter;")
}
}
internal val readPlayerOptionOverridesFingerprint = fingerprint {
custom { methodDef, classDef ->
methodDef.name == "readPlayerOptionOverrides" &&
classDef.endsWith("voiceassistants/playermodels/PreparePlayOptionsJsonAdapter;")
}
}
internal val homeSectionFingerprint = fingerprint {
custom { _, classDef -> classDef.endsWith("homeapi/proto/Section;") }
}
internal val protobufListsFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
custom { method, _ -> method.name == "emptyProtobufList" }
}
internal val homeStructureFingerprint = fingerprint {
opcodes(Opcode.IGET_OBJECT, Opcode.RETURN_OBJECT)
custom { _, classDef -> classDef.endsWith("homeapi/proto/HomeStructure;") }
}

View File

@ -1,12 +1,26 @@
package app.revanced.patches.spotify.misc package app.revanced.patches.spotify.misc
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.fingerprint
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.spotify.misc.check.checkEnvironmentPatch
import app.revanced.patches.spotify.misc.extension.IS_SPOTIFY_LEGACY_APP_TARGET
import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import java.util.logging.Logger
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/spotify/misc/UnlockPremiumPatch;" private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/spotify/misc/UnlockPremiumPatch;"
@ -17,7 +31,15 @@ val unlockPremiumPatch = bytecodePatch(
) { ) {
compatibleWith("com.spotify.music") compatibleWith("com.spotify.music")
dependsOn(sharedExtensionPatch) dependsOn(
sharedExtensionPatch,
// Currently there is no easy way to make a mandatory patch,
// so for now this is a dependent of this patch.
//
// FIXME: Modifying string resources (such as adding patch strings)
// is currently failing with ReVanced manager.
// checkEnvironmentPatch,
)
execute { execute {
// Make _value accessible so that it can be overridden in the extension. // Make _value accessible so that it can be overridden in the extension.
@ -27,23 +49,124 @@ val unlockPremiumPatch = bytecodePatch(
} }
// Override the attributes map in the getter method. // Override the attributes map in the getter method.
val attributesMapRegister = 0 productStateProtoFingerprint.method.apply {
val instantiateUnmodifiableMapIndex = 1 val getAttributesMapIndex = indexOfFirstInstructionOrThrow(Opcode.IGET_OBJECT)
productStateProtoFingerprint.method.addInstruction( val attributesMapRegister = getInstruction<TwoRegisterInstruction>(getAttributesMapIndex).registerA
instantiateUnmodifiableMapIndex,
addInstruction(
getAttributesMapIndex + 1,
"invoke-static { v$attributesMapRegister }, " + "invoke-static { v$attributesMapRegister }, " +
"$EXTENSION_CLASS_DESCRIPTOR->overrideAttribute(Ljava/util/Map;)V", "$EXTENSION_CLASS_DESCRIPTOR->overrideAttribute(Ljava/util/Map;)V"
) )
}
// Add the query parameter trackRows to show popular tracks in the artist page. // Add the query parameter trackRows to show popular tracks in the artist page.
val addQueryParameterIndex = buildQueryParametersFingerprint.stringMatches!!.first().index - 1 buildQueryParametersFingerprint.apply {
buildQueryParametersFingerprint.method.replaceInstruction(addQueryParameterIndex, "nop") val addQueryParameterConditionIndex = method.indexOfFirstInstructionReversedOrThrow(
stringMatches!!.first().index, Opcode.IF_EQZ
)
method.replaceInstruction(addQueryParameterConditionIndex, "nop")
}
if (IS_SPOTIFY_LEGACY_APP_TARGET) {
return@execute Logger.getLogger(this::class.java.name).warning(
"Patching a legacy Spotify version. Patch functionality may be limited."
)
}
// Enable choosing a specific song/artist via Google Assistant.
contextFromJsonFingerprint.method.apply {
val insertIndex = contextFromJsonFingerprint.patternMatch!!.startIndex
// Both the URI and URL need to be modified.
val registerUrl = getInstruction<FiveRegisterInstruction>(insertIndex).registerC
val registerUri = getInstruction<FiveRegisterInstruction>(insertIndex + 2).registerD
val extensionMethodDescriptor = "$EXTENSION_CLASS_DESCRIPTOR->" +
"removeStationString(Ljava/lang/String;)Ljava/lang/String;"
addInstructions(
insertIndex,
"""
invoke-static { v$registerUrl }, $extensionMethodDescriptor
move-result-object v$registerUrl
invoke-static { v$registerUri }, $extensionMethodDescriptor
move-result-object v$registerUri
"""
)
}
// Disable forced shuffle when asking for an album/playlist via Google Assistant.
readPlayerOptionOverridesFingerprint.method.apply {
val shufflingContextCallIndex = indexOfFirstInstructionOrThrow {
getReference<MethodReference>()?.name == "shufflingContext"
}
val registerBool = getInstruction<FiveRegisterInstruction>(shufflingContextCallIndex).registerD
addInstruction(
shufflingContextCallIndex,
"sget-object v$registerBool, Ljava/lang/Boolean;->FALSE:Ljava/lang/Boolean;"
)
}
// Disable the "Spotify Premium" upsell experiment in context menus. // Disable the "Spotify Premium" upsell experiment in context menus.
with(contextMenuExperimentsFingerprint) { contextMenuExperimentsFingerprint.apply {
val moveIsEnabledIndex = stringMatches!!.first().index + 2 val moveIsEnabledIndex = method.indexOfFirstInstructionOrThrow(
stringMatches!!.first().index, Opcode.MOVE_RESULT
)
val isUpsellEnabledRegister = method.getInstruction<OneRegisterInstruction>(moveIsEnabledIndex).registerA val isUpsellEnabledRegister = method.getInstruction<OneRegisterInstruction>(moveIsEnabledIndex).registerA
method.replaceInstruction(moveIsEnabledIndex, "const/4 v$isUpsellEnabledRegister, 0") method.replaceInstruction(moveIsEnabledIndex, "const/4 v$isUpsellEnabledRegister, 0")
} }
// Make featureTypeCase_ accessible so we can check the home section type in the extension.
homeSectionFingerprint.classDef.fields.first { it.name == "featureTypeCase_" }.apply {
// Add public flag and remove private.
accessFlags = accessFlags.or(AccessFlags.PUBLIC.value).and(AccessFlags.PRIVATE.value.inv())
}
val protobufListClassName = with(protobufListsFingerprint.originalMethod) {
val emptyProtobufListGetIndex = indexOfFirstInstructionOrThrow(Opcode.SGET_OBJECT)
getInstruction(emptyProtobufListGetIndex).getReference<FieldReference>()!!.definingClass
}
val protobufListRemoveFingerprint = fingerprint {
custom { method, classDef ->
method.name == "remove" && classDef.type == protobufListClassName
}
}
// Need to allow mutation of the list so the home ads sections can be removed.
// Protobuffer list has an 'isMutable' boolean parameter that sets the mutability.
// Forcing that always on breaks unrelated code in strange ways.
// Instead, remove the method call that checks if the list is unmodifiable.
protobufListRemoveFingerprint.method.apply {
val invokeThrowUnmodifiableIndex = indexOfFirstInstructionOrThrow {
val reference = getReference<MethodReference>()
opcode == Opcode.INVOKE_VIRTUAL &&
reference?.returnType == "V" && reference.parameterTypes.isEmpty()
}
// Remove the method call that throws an exception if the list is not mutable.
removeInstruction(invokeThrowUnmodifiableIndex)
}
// Remove ads sections from home.
homeStructureFingerprint.method.apply {
val getSectionsIndex = indexOfFirstInstructionOrThrow(Opcode.IGET_OBJECT)
val sectionsRegister = getInstruction<TwoRegisterInstruction>(getSectionsIndex).registerA
addInstruction(
getSectionsIndex + 1,
"invoke-static { v$sectionsRegister }, " +
"$EXTENSION_CLASS_DESCRIPTOR->removeHomeSections(Ljava/util/List;)V"
)
}
} }
} }

View File

@ -0,0 +1,11 @@
package app.revanced.patches.spotify.misc.check
import app.revanced.patches.shared.misc.checks.checkEnvironmentPatch
import app.revanced.patches.spotify.shared.mainActivityOnCreateFingerprint
import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
internal val checkEnvironmentPatch = checkEnvironmentPatch(
mainActivityOnCreateFingerprint = mainActivityOnCreateFingerprint,
extensionPatch = sharedExtensionPatch,
"com.spotify.music",
)

View File

@ -1,5 +1,21 @@
package app.revanced.patches.spotify.misc.extension package app.revanced.patches.spotify.misc.extension
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
import app.revanced.patches.spotify.shared.SPOTIFY_MAIN_ACTIVITY_LEGACY
val sharedExtensionPatch = sharedExtensionPatch("spotify", spotifyMainActivityOnCreate) /**
* If patching a legacy 8.x target. This may also be set if patching slightly older/newer app targets,
* but the only legacy target of interest is 8.6.98.900 as it's the last version that
* supports Spotify integration on Kenwood/Pioneer car stereos.
*/
internal var IS_SPOTIFY_LEGACY_APP_TARGET = false
val sharedExtensionPatch = bytecodePatch {
dependsOn(sharedExtensionPatch("spotify", mainActivityOnCreateHook))
execute {
IS_SPOTIFY_LEGACY_APP_TARGET = mainActivityOnCreateHook.fingerprint
.originalClassDef.type == SPOTIFY_MAIN_ACTIVITY_LEGACY
}
}

View File

@ -1,10 +1,6 @@
package app.revanced.patches.spotify.misc.extension package app.revanced.patches.spotify.misc.extension
import app.revanced.patches.shared.misc.extension.extensionHook import app.revanced.patches.shared.misc.extension.extensionHook
import app.revanced.patches.spotify.shared.mainActivityOnCreateFingerprint
internal val spotifyMainActivityOnCreate = extensionHook { internal val mainActivityOnCreateHook = extensionHook(fingerprint = mainActivityOnCreateFingerprint)
custom { method, classDef ->
classDef.type == "Lcom/spotify/music/SpotifyMainActivity;" &&
method.name == "onCreate"
}
}

View File

@ -2,4 +2,8 @@ package app.revanced.patches.spotify.misc.fix
import app.revanced.patcher.fingerprint import app.revanced.patcher.fingerprint
internal val getAppSignatureFingerprint = fingerprint { strings("Failed to get the application signatures") } internal val getPackageInfoFingerprint = fingerprint {
strings(
"Failed to get the application signatures"
)
}

View File

@ -0,0 +1,63 @@
package app.revanced.patches.spotify.misc.fix
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.findInstructionIndicesReversedOrThrow
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
@Suppress("unused")
val spoofPackageInfoPatch = bytecodePatch(
name = "Spoof package info",
description = "Spoofs the package info of the app to fix various functions of the app.",
) {
compatibleWith("com.spotify.music")
execute {
getPackageInfoFingerprint.method.apply {
// region Spoof signature.
val failedToGetSignaturesStringIndex =
getPackageInfoFingerprint.stringMatches!!.first().index
val concatSignaturesIndex = indexOfFirstInstructionReversedOrThrow(
failedToGetSignaturesStringIndex,
Opcode.MOVE_RESULT_OBJECT,
)
val signatureRegister = getInstruction<OneRegisterInstruction>(concatSignaturesIndex).registerA
val expectedSignature = "d6a6dced4a85f24204bf9505ccc1fce114cadb32"
replaceInstruction(concatSignaturesIndex, "const-string v$signatureRegister, \"$expectedSignature\"")
// endregion
// region Spoof installer name.
val expectedInstallerName = "com.android.vending"
findInstructionIndicesReversedOrThrow {
val reference = getReference<MethodReference>()
reference?.name == "getInstallerPackageName" || reference?.name == "getInstallingPackageName"
}.forEach { index ->
val returnObjectIndex = index + 1
val installerPackageNameRegister = getInstruction<OneRegisterInstruction>(
returnObjectIndex
).registerA
addInstruction(
returnObjectIndex + 1,
"const-string v$installerPackageNameRegister, \"$expectedInstallerName\""
)
}
// endregion
}
}
}

View File

@ -1,33 +1,13 @@
package app.revanced.patches.spotify.misc.fix package app.revanced.patches.spotify.misc.fix
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@Deprecated("Superseded by spoofPackageInfoPatch", ReplaceWith("spoofPackageInfoPatch"))
@Suppress("unused") @Suppress("unused")
val spoofSignaturePatch = bytecodePatch( val spoofSignaturePatch = bytecodePatch(
name = "Spoof signature", description = "Spoofs the signature of the app fix various functions of the app.",
description = "Spoofs the signature of the app to fix various functions of the app.",
) { ) {
compatibleWith("com.spotify.music") compatibleWith("com.spotify.music")
execute { dependsOn(spoofPackageInfoPatch)
getAppSignatureFingerprint.method.apply {
val failedToGetSignaturesStringMatch = getAppSignatureFingerprint.stringMatches!!.first()
val concatSignaturesIndex = indexOfFirstInstructionReversedOrThrow(
failedToGetSignaturesStringMatch.index,
Opcode.MOVE_RESULT_OBJECT,
)
val register = getInstruction<OneRegisterInstruction>(concatSignaturesIndex).registerA
val expectedSignature = "d6a6dced4a85f24204bf9505ccc1fce114cadb32"
replaceInstruction(concatSignaturesIndex, "const-string v$register, \"$expectedSignature\"")
}
}
} }

View File

@ -0,0 +1,17 @@
package app.revanced.patches.spotify.shared
import app.revanced.patcher.fingerprint
private const val SPOTIFY_MAIN_ACTIVITY = "Lcom/spotify/music/SpotifyMainActivity;"
/**
* Main activity of target 8.6.98.900.
*/
internal const val SPOTIFY_MAIN_ACTIVITY_LEGACY = "Lcom/spotify/music/MainActivity;"
internal val mainActivityOnCreateFingerprint = fingerprint {
custom { method, classDef ->
method.name == "onCreate" && (classDef.type == SPOTIFY_MAIN_ACTIVITY
|| classDef.type == SPOTIFY_MAIN_ACTIVITY_LEGACY)
}
}

View File

@ -12,7 +12,7 @@ import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
val disableSubscriptionSuggestionsPatch = bytecodePatch( val disableSubscriptionSuggestionsPatch = bytecodePatch(
name = "Disable subscription suggestions", name = "Disable subscription suggestions",
) { ) {
compatibleWith("com.strava"("320.12")) compatibleWith("com.strava")
execute { execute {
val helperMethodName = "getModulesIfNotUpselling" val helperMethodName = "getModulesIfNotUpselling"

View File

@ -12,7 +12,7 @@ val dynamicColorPatch = resourcePatch(
) { ) {
compatibleWith( compatibleWith(
"com.twitter.android"( "com.twitter.android"(
"10.84.0-release.0", "10.86.0-release.0",
"10.60.0-release.0", "10.60.0-release.0",
"10.48.0-release.0" "10.48.0-release.0"
) )

View File

@ -13,8 +13,8 @@ fun hookPatch(
compatibleWith( compatibleWith(
"com.twitter.android"( "com.twitter.android"(
// 10.85+ uses Pairip and requires additional changes to work. // Only v10.85 uses Pairip and requires additional changes to work.
"10.84.0-release.0", "10.86.0-release.0",
// Confirmed to not show reply ads. Slightly newer versions may also work. // Confirmed to not show reply ads. Slightly newer versions may also work.
"10.60.0-release.0", "10.60.0-release.0",
"10.48.0-release.0" "10.48.0-release.0"

View File

@ -39,7 +39,7 @@ val changeLinkSharingDomainPatch = bytecodePatch(
compatibleWith( compatibleWith(
"com.twitter.android"( "com.twitter.android"(
"10.84.0-release.0", "10.86.0-release.0",
"10.60.0-release.0", "10.60.0-release.0",
"10.48.0-release.0" "10.48.0-release.0"
) )

View File

@ -10,7 +10,7 @@ val sanitizeSharingLinksPatch = bytecodePatch(
) { ) {
compatibleWith( compatibleWith(
"com.twitter.android"( "com.twitter.android"(
"10.84.0-release.0", "10.86.0-release.0",
"10.60.0-release.0", "10.60.0-release.0",
"10.48.0-release.0" "10.48.0-release.0"
) )

View File

@ -84,7 +84,8 @@ val hideAdsPatch = bytecodePatch(
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",
"20.07.39", "20.07.39",
), "20.12.46",
)
) )
execute { execute {

View File

@ -31,7 +31,8 @@ val hideGetPremiumPatch = bytecodePatch(
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",
"20.07.39", "20.07.39",
), "20.12.46",
)
) )
execute { execute {

View File

@ -29,7 +29,8 @@ val videoAdsPatch = bytecodePatch(
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",
"20.07.39", "20.07.39",
), "20.12.46",
)
) )
execute { execute {

View File

@ -59,7 +59,8 @@ val copyVideoUrlPatch = bytecodePatch(
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",
"20.07.39", "20.07.39",
), "20.12.46",
)
) )
execute { execute {

View File

@ -30,7 +30,8 @@ val removeViewerDiscretionDialogPatch = bytecodePatch(
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",
"20.07.39", "20.07.39",
), "20.12.46",
)
) )
val extensionMethodDescriptor = val extensionMethodDescriptor =

View File

@ -74,7 +74,8 @@ val downloadsPatch = bytecodePatch(
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",
"20.07.39", "20.07.39",
), "20.12.46",
)
) )
execute { execute {

View File

@ -10,10 +10,15 @@ import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.util.findFreeRegister
import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c
import com.android.tools.smali.dexlib2.iface.reference.MethodReference import com.android.tools.smali.dexlib2.iface.reference.MethodReference
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/SeekbarTappingPatch;"
val enableSeekbarTappingPatch = bytecodePatch( val enableSeekbarTappingPatch = bytecodePatch(
description = "Adds an option to enable tap to seek on the seekbar of the video player.", description = "Adds an option to enable tap to seek on the seekbar of the video player.",
) { ) {
@ -31,38 +36,36 @@ val enableSeekbarTappingPatch = bytecodePatch(
) )
// Find the required methods to tap the seekbar. // Find the required methods to tap the seekbar.
val patternMatch = onTouchEventHandlerFingerprint.patternMatch!! val seekbarTappingMethods = onTouchEventHandlerFingerprint.let {
fun getMethodReference(index: Int) = it.method.getInstruction<ReferenceInstruction>(index)
fun getReference(index: Int) = onTouchEventHandlerFingerprint.method.getInstruction<ReferenceInstruction>(index)
.reference as MethodReference .reference as MethodReference
val seekbarTappingMethods = buildMap { listOf(
put("N", getReference(patternMatch.startIndex)) getMethodReference(it.patternMatch!!.startIndex),
put("O", getReference(patternMatch.endIndex)) getMethodReference(it.patternMatch!!.endIndex)
)
} }
val insertIndex = seekbarTappingFingerprint.patternMatch!!.endIndex - 1
seekbarTappingFingerprint.method.apply { seekbarTappingFingerprint.method.apply {
val thisInstanceRegister = getInstruction<Instruction35c>(insertIndex - 1).registerC val pointIndex = indexOfNewPointInstruction(this)
val invokeIndex = indexOfFirstInstructionOrThrow(pointIndex, Opcode.INVOKE_VIRTUAL)
val insertIndex = invokeIndex + 1
val freeRegister = 0 val thisInstanceRegister = getInstruction<FiveRegisterInstruction>(invokeIndex).registerC
val xAxisRegister = 2 val xAxisRegister = this.getInstruction<FiveRegisterInstruction>(pointIndex).registerD
val freeRegister = findFreeRegister(insertIndex, thisInstanceRegister, xAxisRegister)
val oMethod = seekbarTappingMethods["O"]!! val oMethod = seekbarTappingMethods[0]
val nMethod = seekbarTappingMethods["N"]!! val nMethod = seekbarTappingMethods[1]
fun MethodReference.toInvokeInstructionString() =
"invoke-virtual { v$thisInstanceRegister, v$xAxisRegister }, $this"
addInstructionsWithLabels( addInstructionsWithLabels(
insertIndex, insertIndex,
""" """
invoke-static { }, Lapp/revanced/extension/youtube/patches/SeekbarTappingPatch;->seekbarTappingEnabled()Z invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->seekbarTappingEnabled()Z
move-result v$freeRegister move-result v$freeRegister
if-eqz v$freeRegister, :disabled if-eqz v$freeRegister, :disabled
${oMethod.toInvokeInstructionString()} invoke-virtual { v$thisInstanceRegister, v$xAxisRegister }, $oMethod
${nMethod.toInvokeInstructionString()} invoke-virtual { v$thisInstanceRegister, v$xAxisRegister }, $nMethod
""", """,
ExternalLabel("disabled", getInstruction(insertIndex)), ExternalLabel("disabled", getInstruction(insertIndex)),
) )

View File

@ -1,11 +1,15 @@
package app.revanced.patches.youtube.interaction.seekbar package app.revanced.patches.youtube.interaction.seekbar
import app.revanced.patcher.fingerprint import app.revanced.patcher.fingerprint
import app.revanced.util.containsLiteralInstruction
import app.revanced.util.getReference import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction import app.revanced.util.indexOfFirstInstruction
import app.revanced.util.indexOfFirstInstructionReversed
import app.revanced.util.literal import app.revanced.util.literal
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.iface.reference.StringReference import com.android.tools.smali.dexlib2.iface.reference.StringReference
internal val swipingUpGestureParentFingerprint = fingerprint { internal val swipingUpGestureParentFingerprint = fingerprint {
@ -101,14 +105,17 @@ internal val seekbarTappingFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("Z") returns("Z")
parameters("L") parameters("L")
opcodes( custom { method, _ ->
Opcode.IPUT_OBJECT, method.name == "onTouchEvent"
Opcode.INVOKE_VIRTUAL, && method.containsLiteralInstruction(Integer.MAX_VALUE.toLong())
// Insert seekbar tapping instructions here. && indexOfNewPointInstruction(method) >= 0
Opcode.RETURN, }
Opcode.INVOKE_VIRTUAL, }
)
literal { Integer.MAX_VALUE.toLong() } internal fun indexOfNewPointInstruction(method: Method) = method.indexOfFirstInstructionReversed {
val reference = getReference<MethodReference>()
reference?.definingClass == "Landroid/graphics/Point;"
&& reference.name == "<init>"
} }
internal val slideToSeekFingerprint = fingerprint { internal val slideToSeekFingerprint = fingerprint {

View File

@ -26,6 +26,7 @@ val seekbarPatch = bytecodePatch(
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",
"20.07.39", "20.07.39",
"20.12.46",
) )
) )
} }

View File

@ -6,6 +6,7 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMu
import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.InputType import app.revanced.patches.shared.misc.settings.preference.InputType
import app.revanced.patches.shared.misc.settings.preference.ListPreference
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.shared.misc.settings.preference.TextPreference import app.revanced.patches.shared.misc.settings.preference.TextPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
@ -42,11 +43,16 @@ private val swipeControlsResourcePatch = resourcePatch {
SwitchPreference("revanced_swipe_haptic_feedback"), SwitchPreference("revanced_swipe_haptic_feedback"),
SwitchPreference("revanced_swipe_save_and_restore_brightness"), SwitchPreference("revanced_swipe_save_and_restore_brightness"),
SwitchPreference("revanced_swipe_lowest_value_enable_auto_brightness"), SwitchPreference("revanced_swipe_lowest_value_enable_auto_brightness"),
SwitchPreference("revanced_swipe_show_circular_overlay"), ListPreference(
SwitchPreference("revanced_swipe_overlay_minimal_style"), "revanced_swipe_overlay_style",
summaryKey = null,
),
TextPreference("revanced_swipe_overlay_background_opacity", inputType = InputType.NUMBER), TextPreference("revanced_swipe_overlay_background_opacity", inputType = InputType.NUMBER),
TextPreference("revanced_swipe_overlay_progress_color", inputType = InputType.TEXT_CAP_CHARACTERS),
TextPreference("revanced_swipe_text_overlay_size", inputType = InputType.NUMBER),
TextPreference("revanced_swipe_overlay_timeout", inputType = InputType.NUMBER), TextPreference("revanced_swipe_overlay_timeout", inputType = InputType.NUMBER),
TextPreference("revanced_swipe_threshold", inputType = InputType.NUMBER), TextPreference("revanced_swipe_threshold", inputType = InputType.NUMBER),
TextPreference("revanced_swipe_volume_sensitivity", inputType = InputType.NUMBER),
) )
copyResources( copyResources(
@ -86,7 +92,8 @@ val swipeControlsPatch = bytecodePatch(
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",
"20.07.39", "20.07.39",
), "20.12.46",
)
) )
execute { execute {
@ -117,7 +124,7 @@ val swipeControlsPatch = bytecodePatch(
// region patch to enable/disable swipe to change video. // region patch to enable/disable swipe to change video.
if (is_19_43_or_greater) { if (is_19_43_or_greater) {
swipeChangeVideoFingerprint.method.insertFeatureFlagBooleanOverride( swipeChangeVideoFingerprint.method.insertLiteralOverride(
SWIPE_CHANGE_VIDEO_FEATURE_FLAG, SWIPE_CHANGE_VIDEO_FEATURE_FLAG,
"$EXTENSION_CLASS_DESCRIPTOR->allowSwipeChangeVideo(Z)Z" "$EXTENSION_CLASS_DESCRIPTOR->allowSwipeChangeVideo(Z)Z"
) )

View File

@ -8,7 +8,9 @@ import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.shared.subtitleButtonControllerFingerprint
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/DisableAutoCaptionsPatch;"
val autoCaptionsPatch = bytecodePatch( val autoCaptionsPatch = bytecodePatch(
name = "Disable auto captions", name = "Disable auto captions",
@ -28,42 +30,41 @@ val autoCaptionsPatch = bytecodePatch(
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",
"20.07.39", "20.07.39",
), "20.12.46",
)
) )
execute { execute {
addResources("youtube", "layout.autocaptions.autoCaptionsPatch") addResources("youtube", "layout.autocaptions.autoCaptionsPatch")
PreferenceScreen.PLAYER.addPreferences( PreferenceScreen.PLAYER.addPreferences(
SwitchPreference("revanced_auto_captions"), SwitchPreference("revanced_disable_auto_captions"),
)
subtitleTrackFingerprint.method.addInstructions(
0,
"""
invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->disableAutoCaptions()Z
move-result v0
if-eqz v0, :auto_captions_enabled
const/4 v0, 0x1
return v0
:auto_captions_enabled
nop
"""
) )
mapOf( mapOf(
startVideoInformerFingerprint to 0, startVideoInformerFingerprint to 0,
subtitleButtonControllerFingerprint to 1, storyboardRendererDecoderRecommendedLevelFingerprint to 1
).forEach { (fingerprint, enabled) -> ).forEach { (fingerprint, enabled) ->
fingerprint.method.addInstructions( fingerprint.method.addInstructions(
0, 0,
""" """
const/4 v0, 0x$enabled const/4 v0, 0x$enabled
sput-boolean v0, Lapp/revanced/extension/youtube/patches/DisableAutoCaptionsPatch;->captionsButtonDisabled:Z invoke-static { v0 }, $EXTENSION_CLASS_DESCRIPTOR->setCaptionsButtonStatus(Z)V
""",
)
}
subtitleTrackFingerprint.method.addInstructions(
0,
""" """
invoke-static {}, Lapp/revanced/extension/youtube/patches/DisableAutoCaptionsPatch;->autoCaptionsEnabled()Z
move-result v0
if-eqz v0, :auto_captions_enabled
sget-boolean v0, Lapp/revanced/extension/youtube/patches/DisableAutoCaptionsPatch;->captionsButtonDisabled:Z
if-nez v0, :auto_captions_enabled
const/4 v0, 0x1
return v0
:auto_captions_enabled
nop
""",
) )
} }
} }
}

View File

@ -14,6 +14,13 @@ internal val startVideoInformerFingerprint = fingerprint {
strings("pc") strings("pc")
} }
internal val storyboardRendererDecoderRecommendedLevelFingerprint = fingerprint {
returns("V")
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
parameters("L")
strings("#-1#")
}
internal val subtitleTrackFingerprint = fingerprint { internal val subtitleTrackFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("Z") returns("Z")
@ -28,6 +35,6 @@ internal val subtitleTrackFingerprint = fingerprint {
) )
strings("DISABLE_CAPTIONS_OPTION") strings("DISABLE_CAPTIONS_OPTION")
custom { _, classDef -> custom { _, classDef ->
classDef.endsWith("SubtitleTrack;") classDef.endsWith("/SubtitleTrack;")
} }
} }

View File

@ -49,7 +49,8 @@ val customBrandingPatch = resourcePatch(
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",
"20.07.39", "20.07.39",
), "20.12.46",
)
) )
val appName by stringOption( val appName by stringOption(

View File

@ -47,6 +47,7 @@ val changeHeaderPatch = resourcePatch(
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",
"20.07.39", "20.07.39",
"20.12.46",
) )
) )

View File

@ -28,7 +28,8 @@ val hideButtonsPatch = resourcePatch(
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",
"20.07.39", "20.07.39",
), "20.12.46",
)
) )
execute { execute {
@ -45,10 +46,11 @@ val hideButtonsPatch = resourcePatch(
SwitchPreference("revanced_hide_remix_button"), SwitchPreference("revanced_hide_remix_button"),
SwitchPreference("revanced_hide_download_button"), SwitchPreference("revanced_hide_download_button"),
SwitchPreference("revanced_hide_thanks_button"), SwitchPreference("revanced_hide_thanks_button"),
SwitchPreference("revanced_hide_ask_button"),
SwitchPreference("revanced_hide_clip_button"), SwitchPreference("revanced_hide_clip_button"),
SwitchPreference("revanced_hide_playlist_button"), SwitchPreference("revanced_hide_playlist_button"),
), )
), )
) )
addLithoFilter("Lapp/revanced/extension/youtube/patches/components/ButtonsFilter;") addLithoFilter("Lapp/revanced/extension/youtube/patches/components/ButtonsFilter;")

View File

@ -18,7 +18,7 @@ import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.util.getReference import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.insertFeatureFlagBooleanOverride import app.revanced.util.insertLiteralOverride
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference import com.android.tools.smali.dexlib2.iface.reference.MethodReference
@ -46,7 +46,8 @@ val navigationButtonsPatch = bytecodePatch(
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",
"20.07.39", "20.07.39",
), "20.12.46",
)
) )
execute { execute {
@ -119,17 +120,17 @@ val navigationButtonsPatch = bytecodePatch(
// Force on/off translucent effect on status bar and navigation buttons. // Force on/off translucent effect on status bar and navigation buttons.
if (is_19_25_or_greater) { if (is_19_25_or_greater) {
translucentNavigationStatusBarFeatureFlagFingerprint.method.insertFeatureFlagBooleanOverride( translucentNavigationStatusBarFeatureFlagFingerprint.method.insertLiteralOverride(
TRANSLUCENT_NAVIGATION_STATUS_BAR_FEATURE_FLAG, TRANSLUCENT_NAVIGATION_STATUS_BAR_FEATURE_FLAG,
"$EXTENSION_CLASS_DESCRIPTOR->useTranslucentNavigationStatusBar(Z)Z", "$EXTENSION_CLASS_DESCRIPTOR->useTranslucentNavigationStatusBar(Z)Z",
) )
translucentNavigationButtonsFeatureFlagFingerprint.method.insertFeatureFlagBooleanOverride( translucentNavigationButtonsFeatureFlagFingerprint.method.insertLiteralOverride(
TRANSLUCENT_NAVIGATION_BUTTONS_FEATURE_FLAG, TRANSLUCENT_NAVIGATION_BUTTONS_FEATURE_FLAG,
"$EXTENSION_CLASS_DESCRIPTOR->useTranslucentNavigationButtons(Z)Z", "$EXTENSION_CLASS_DESCRIPTOR->useTranslucentNavigationButtons(Z)Z",
) )
translucentNavigationButtonsSystemFeatureFlagFingerprint.method.insertFeatureFlagBooleanOverride( translucentNavigationButtonsSystemFeatureFlagFingerprint.method.insertLiteralOverride(
TRANSLUCENT_NAVIGATION_BUTTONS_SYSTEM_FEATURE_FLAG, TRANSLUCENT_NAVIGATION_BUTTONS_SYSTEM_FEATURE_FLAG,
"$EXTENSION_CLASS_DESCRIPTOR->useTranslucentNavigationButtons(Z)Z", "$EXTENSION_CLASS_DESCRIPTOR->useTranslucentNavigationButtons(Z)Z",
) )

View File

@ -60,7 +60,8 @@ val hidePlayerOverlayButtonsPatch = bytecodePatch(
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",
"20.07.39", "20.07.39",
), "20.12.46",
)
) )
execute { execute {

View File

@ -39,7 +39,8 @@ val changeFormFactorPatch = bytecodePatch(
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",
"20.07.39", "20.07.39",
), "20.12.46",
)
) )
execute { execute {

View File

@ -65,7 +65,8 @@ val hideEndscreenCardsPatch = bytecodePatch(
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",
"20.07.39", "20.07.39",
), "20.12.46",
)
) )
execute { execute {

View File

@ -37,7 +37,8 @@ val hideEndScreenSuggestedVideoPatch = bytecodePatch(
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",
"20.07.39", "20.07.39",
), "20.12.46",
)
) )
execute { execute {

View File

@ -35,7 +35,8 @@ val disableFullscreenAmbientModePatch = bytecodePatch(
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",
"20.07.39", "20.07.39",
), "20.12.46",
)
) )
execute { execute {

View File

@ -1,6 +1,7 @@
package app.revanced.patches.youtube.layout.hide.general package app.revanced.patches.youtube.layout.hide.general
import app.revanced.patcher.fingerprint import app.revanced.patcher.fingerprint
import app.revanced.patches.youtube.layout.searchbar.wideSearchbarLayoutFingerprint
import app.revanced.util.literal import app.revanced.util.literal
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
@ -16,9 +17,22 @@ internal val hideShowMoreButtonFingerprint = fingerprint {
} }
/** /**
* 20.07+ * 20.12+
*/ */
internal val parseElementFromBufferFingerprint = fingerprint { internal val parseElementFromBufferFingerprint = fingerprint {
parameters("L", "L", "[B", "L", "L")
opcodes(
Opcode.IGET_OBJECT,
Opcode.INVOKE_INTERFACE,
Opcode.MOVE_RESULT_OBJECT,
)
strings("Failed to parse Element") // String is a partial match.
}
/**
* 20.07+
*/
internal val parseElementFromBufferLegacy2007Fingerprint = fingerprint {
parameters("L", "L", "[B", "L", "L") parameters("L", "L", "[B", "L", "L")
opcodes( opcodes(
Opcode.IGET_OBJECT, Opcode.IGET_OBJECT,
@ -29,7 +43,10 @@ internal val parseElementFromBufferFingerprint = fingerprint {
strings("Failed to parse Element") // String is a partial match. strings("Failed to parse Element") // String is a partial match.
} }
internal val parseElementFromBufferLegacyFingerprint = fingerprint { /**
* 19.01 - 20.06
*/
internal val parseElementFromBufferLegacy1901Fingerprint = fingerprint {
parameters("L", "L", "[B", "L", "L") parameters("L", "L", "[B", "L", "L")
opcodes( opcodes(
Opcode.IGET_OBJECT, Opcode.IGET_OBJECT,
@ -51,6 +68,9 @@ internal val showWatermarkFingerprint = fingerprint {
parameters("L", "L") parameters("L", "L")
} }
/**
* Matches same method as [wideSearchbarLayoutFingerprint].
*/
internal val yoodlesImageViewFingerprint = fingerprint { internal val yoodlesImageViewFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("Landroid/view/View;") returns("Landroid/view/View;")

View File

@ -21,6 +21,7 @@ import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
import app.revanced.patches.youtube.misc.navigation.navigationBarHookPatch import app.revanced.patches.youtube.misc.navigation.navigationBarHookPatch
import app.revanced.patches.youtube.misc.playservice.is_20_07_or_greater import app.revanced.patches.youtube.misc.playservice.is_20_07_or_greater
import app.revanced.patches.youtube.misc.playservice.is_20_09_or_greater
import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.util.findFreeRegister import app.revanced.util.findFreeRegister
@ -43,14 +44,12 @@ var crowdfundingBoxId = -1L
private set private set
var youTubeLogo = -1L var youTubeLogo = -1L
private set private set
var filterBarHeightId = -1L var filterBarHeightId = -1L
private set private set
var relatedChipCloudMarginId = -1L var relatedChipCloudMarginId = -1L
private set private set
var barContainerHeightId = -1L var barContainerHeightId = -1L
private set private set
var fabButtonId = -1L var fabButtonId = -1L
private set private set
@ -130,11 +129,10 @@ val hideLayoutComponentsPatch = bytecodePatch(
"19.25.37", "19.25.37",
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38",
"19.46.42",
"19.47.53", "19.47.53",
"20.07.39", "20.07.39",
), "20.12.46",
)
) )
execute { execute {
@ -249,8 +247,9 @@ val hideLayoutComponentsPatch = bytecodePatch(
// region Mix playlists // region Mix playlists
(if (is_20_07_or_greater) parseElementFromBufferFingerprint (if (is_20_09_or_greater) parseElementFromBufferFingerprint
else parseElementFromBufferLegacyFingerprint).let { else if (is_20_07_or_greater) parseElementFromBufferLegacy2007Fingerprint
else parseElementFromBufferLegacy1901Fingerprint).let {
it.method.apply { it.method.apply {
val byteArrayParameter = "p3" val byteArrayParameter = "p3"
val startIndex = it.patternMatch!!.startIndex val startIndex = it.patternMatch!!.startIndex

View File

@ -63,7 +63,8 @@ val hideInfoCardsPatch = bytecodePatch(
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",
"20.07.39", "20.07.39",
), "20.12.46",
)
) )
execute { execute {

View File

@ -30,7 +30,8 @@ val hidePlayerFlyoutMenuPatch = bytecodePatch(
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",
"20.07.39", "20.07.39",
), "20.12.46",
)
) )
execute { execute {
@ -51,7 +52,10 @@ val hidePlayerFlyoutMenuPatch = bytecodePatch(
SwitchPreference("revanced_hide_player_flyout_speed"), SwitchPreference("revanced_hide_player_flyout_speed"),
SwitchPreference("revanced_hide_player_flyout_lock_screen"), SwitchPreference("revanced_hide_player_flyout_lock_screen"),
SwitchPreference("revanced_hide_player_flyout_more_info"), SwitchPreference("revanced_hide_player_flyout_more_info"),
SwitchPreference("revanced_hide_player_flyout_audio_track"), SwitchPreference(
key = "revanced_hide_player_flyout_audio_track",
tag = "app.revanced.extension.youtube.settings.preference.HideAudioFlyoutMenuPreference"
),
SwitchPreference("revanced_hide_player_flyout_watch_in_vr"), SwitchPreference("revanced_hide_player_flyout_watch_in_vr"),
SwitchPreference("revanced_hide_player_flyout_sleep_timer"), SwitchPreference("revanced_hide_player_flyout_sleep_timer"),
SwitchPreference("revanced_hide_player_flyout_video_quality_footer"), SwitchPreference("revanced_hide_player_flyout_video_quality_footer"),

View File

@ -35,7 +35,8 @@ val disableRollingNumberAnimationPatch = bytecodePatch(
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",
"20.07.39", "20.07.39",
), "20.12.46",
)
) )
execute { execute {

View File

@ -178,7 +178,8 @@ val hideShortsComponentsPatch = bytecodePatch(
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",
"20.07.39", "20.07.39",
), "20.12.46",
)
) )
hideShortsAppShortcutOption() hideShortsAppShortcutOption()

View File

@ -1,24 +1,16 @@
package app.revanced.patches.youtube.layout.hide.time package app.revanced.patches.youtube.layout.hide.time
import app.revanced.patcher.fingerprint
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import app.revanced.patcher.fingerprint
internal val timeCounterFingerprint = fingerprint( internal val timeCounterFingerprint = fingerprint {
fuzzyPatternScanThreshold = 1,
) {
returns("V")
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("V")
parameters() parameters()
opcodes( opcodes(
Opcode.SUB_LONG_2ADDR, Opcode.SUB_LONG_2ADDR,
Opcode.IGET_WIDE, Opcode.IGET_WIDE,
Opcode.SUB_LONG_2ADDR,
Opcode.IGET_OBJECT,
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT_OBJECT,
Opcode.IGET_WIDE,
Opcode.IGET_WIDE,
Opcode.SUB_LONG_2ADDR Opcode.SUB_LONG_2ADDR
) )
} }

View File

@ -27,7 +27,8 @@ val hideTimestampPatch = bytecodePatch(
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",
"20.07.39", "20.07.39",
), "20.12.46",
)
) )
execute { execute {

View File

@ -30,6 +30,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.NarrowLiteralInstructio
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.iface.reference.TypeReference import com.android.tools.smali.dexlib2.iface.reference.TypeReference
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
@ -168,14 +169,12 @@ val miniplayerPatch = bytecodePatch(
// 19.30.39 // Modern 3 is less broken when double tap expand is enabled, but cannot swipe to expand when double tap is off. // 19.30.39 // Modern 3 is less broken when double tap expand is enabled, but cannot swipe to expand when double tap is off.
// 19.31.36 // All Modern 1 buttons are missing. Unusable. // 19.31.36 // All Modern 1 buttons are missing. Unusable.
// 19.32.36 // 19.32+ and beyond all work without issues. // 19.32.36 // 19.32+ and beyond all work without issues.
// 19.33.35
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38",
"19.46.42",
"19.47.53", "19.47.53",
"20.07.39", "20.07.39",
), "20.12.46",
)
) )
execute { execute {
@ -281,7 +280,7 @@ val miniplayerPatch = bytecodePatch(
fun Fingerprint.insertMiniplayerFeatureFlagBooleanOverride( fun Fingerprint.insertMiniplayerFeatureFlagBooleanOverride(
literal: Long, literal: Long,
extensionMethod: String, extensionMethod: String,
) = method.insertFeatureFlagBooleanOverride( ) = method.insertLiteralOverride(
literal, literal,
"$EXTENSION_CLASS_DESCRIPTOR->$extensionMethod(Z)Z" "$EXTENSION_CLASS_DESCRIPTOR->$extensionMethod(Z)Z"
) )
@ -349,7 +348,12 @@ val miniplayerPatch = bytecodePatch(
// endregion // endregion
// region Legacy tablet miniplayer hooks. // region Legacy tablet miniplayer hooks.
val appNameStringIndex = miniplayerOverrideFingerprint.stringMatches!!.first().index + 2 val appNameStringIndex = miniplayerOverrideFingerprint.let {
it.method.indexOfFirstInstructionOrThrow(it.stringMatches!!.first().index) {
val reference = getReference<MethodReference>()
reference?.parameterTypes?.firstOrNull() == "Landroid/content/Context;"
}
}
navigate(miniplayerOverrideFingerprint.originalMethod).to(appNameStringIndex).stop().apply { navigate(miniplayerOverrideFingerprint.originalMethod).to(appNameStringIndex).stop().apply {
findReturnIndicesReversed().forEach { index -> insertLegacyTabletMiniplayerOverride(index) } findReturnIndicesReversed().forEach { index -> insertLegacyTabletMiniplayerOverride(index) }
} }

View File

@ -27,7 +27,8 @@ val playerPopupPanelsPatch = bytecodePatch(
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",
"20.07.39", "20.07.39",
), "20.12.46",
)
) )
execute { execute {

View File

@ -18,7 +18,8 @@ val playerControlsBackgroundPatch = resourcePatch(
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",
"20.07.39", "20.07.39",
), "20.12.46",
)
) )
execute { execute {

View File

@ -27,6 +27,7 @@ internal val exitFullscreenPatch = bytecodePatch(
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",
"20.07.39", "20.07.39",
"20.12.46",
) )
) )

View File

@ -5,7 +5,7 @@ import app.revanced.patches.youtube.layout.shortsplayer.openShortsInRegularPlaye
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.playservice.is_19_46_or_greater import app.revanced.patches.youtube.misc.playservice.is_19_46_or_greater
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
import app.revanced.util.insertFeatureFlagBooleanOverride import app.revanced.util.insertLiteralOverride
internal const val EXTENSION_CLASS_DESCRIPTOR = internal const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/OpenVideosFullscreenHookPatch;" "Lapp/revanced/extension/youtube/patches/OpenVideosFullscreenHookPatch;"
@ -24,7 +24,7 @@ internal val openVideosFullscreenHookPatch = bytecodePatch {
return@execute return@execute
} }
openVideosFullscreenPortraitFingerprint.method.insertFeatureFlagBooleanOverride( openVideosFullscreenPortraitFingerprint.method.insertLiteralOverride(
OPEN_VIDEOS_FULLSCREEN_PORTRAIT_FEATURE_FLAG, OPEN_VIDEOS_FULLSCREEN_PORTRAIT_FEATURE_FLAG,
"$EXTENSION_CLASS_DESCRIPTOR->openVideoFullscreenPortrait(Z)Z" "$EXTENSION_CLASS_DESCRIPTOR->openVideoFullscreenPortrait(Z)Z"
) )

View File

@ -25,9 +25,9 @@ val openVideosFullscreenPatch = bytecodePatch(
compatibleWith( compatibleWith(
"com.google.android.youtube"( "com.google.android.youtube"(
"19.46.42",
"19.47.53", "19.47.53",
"20.07.39", "20.07.39",
"20.12.46",
) )
) )

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